Seite 1 von 2

Gelöst! Wie "echte Verzeichnisse" von "Links" unterscheiden?

Verfasst: 14.05.2025 08:50
von EmmJott
Guude Morsche aus Hessen!

Folgendes Problem:ExplorerTreeGadget zeigt u. a. im User-Verzeichnis das Verzeichnis "Startmenü" an. Mit ExamineDirectory finde ich darin keine Einträge. Meine laienhafte Vermutung: Richtig so, denn das ist gar kein Verzeichnis, sondern ein Link zu einem Verzeichnis, nämlich User\Appdata\Microsoft\usw. Leider zeigt aber auch Filesize mit "-2", dass das ein Verzeichnis ist.

Kann man mit Purebasic "echte Verzeichnisse" von "Link auf Verzeichnis" unterscheiden und wenn ja, wie findet man das Zielverzeichnis?

Nachtrag: Auf DOS-Ebene geht das mit "dir /al" - irgendwie muss es also gehen.

Re: Wie "echte Verzeichnisse" von "Links" unterscheiden?

Verfasst: 14.05.2025 18:28
von Bisonte
das sind dann die sogenannten "Systemverzeichnisse"... es gibt hier im Forum diverse Möglichkeiten die richtigen Pfade zu ermitteln... wie z.B. im englischen Forum https://www.purebasic.fr/english/viewtopic.php?t=82092 weiter unten von Jan2004 oder das was ich grad nicht wiederfinde was TassyJim dort meint.

Re: Wie "echte Verzeichnisse" von "Links" unterscheiden?

Verfasst: 14.05.2025 22:09
von EmmJott
Hi,

vielen Dank für den Hinweis! Ja, das geht wohl in diese Richtung. Schaue mir gerade das hier an:
https://www.purebasic.fr/english/viewto ... 70#p604570

Das würde schon mal meine 2. Frage beantworten, nämlich die nach dem Zielverzeichnis. Habe aber noch das Problem: Wie unterscheide ich ein "echtes Verzeichnis" (mir fehlt der Fachbegriff) von einem Link, für den das Zielverzeichnis erst noch ermittelt werden muss (mittels Code von Jan2004).

Re: Wie "echte Verzeichnisse" von "Links" unterscheiden?

Verfasst: 15.05.2025 05:48
von Bisonte
EmmJott hat geschrieben: 14.05.2025 22:09 ... Habe aber noch das Problem: Wie unterscheide ich ein "echtes Verzeichnis" (mir fehlt der Fachbegriff) von einem Link, für den das Zielverzeichnis erst noch ermittelt werden muss (mittels Code von Jan2004).
Ich wüsste nicht, dass es dafür eine Möglichkeit gibt, es per Programm zu "erkennen".
Die Frage hat sich mir nie gestellt, eher die Frage, wie krieg ich den Pfad raus...

Im Grunde kannst du davon ausgehen, das ALLES, was von Windows kommt, mit solchen "relativen" Pfaden arbeitet,
Window kann ja schliesslich auf jedem Laufwerk installiert werden... nicht nur C:

Re: Wie "echte Verzeichnisse" von "Links" unterscheiden?

Verfasst: 15.05.2025 11:47
von EmmJott
Bin da ja vielleicht auf dem Holzweg, habe aber mal mit den Konstanten aus
https://learn.microsoft.com/de-de/windo ... -constants
rumgespielt.

Bei den Verzeichnissen, die offenbar nur Symlinks sind und auf ein Zielverzeichnis verweisen, tauchen die Attribute
#FILE_ATTRIBUTE_REPARSE_POINT und #FILE_ATTRIBUTE_NOT_CONTENT_INDEXED auf. Hiermit ermittelt:

Code: Alles auswählen


Enumeration  
  #parent
  #parentstatusbar
  #parentmenu
  #parentfont_1
  #escape 
  #etg
EndEnumeration

;- Konstanten

#FILE_ATTRIBUTE_READONLY = $00000001                    ;#PB_FileSystem_ReadOnly
#FILE_ATTRIBUTE_HIDDEN = $00000002                      ;#PB_FileSystem_Hidden
#FILE_ATTRIBUTE_SYSTEM = $00000004                      ;#PB_FileSystem_System

#FILE_ATTRIBUTE_DIRECTORY = $00000010
#FILE_ATTRIBUTE_ARCHIVE = $00000020                     ;#PB_FileSystem_Archive
#FILE_ATTRIBUTE_DEVICE = $00000040
#FILE_ATTRIBUTE_NORMAL = $00000080                      ;#PB_FileSystem_Normal
#FILE_ATTRIBUTE_TEMPORARY = $00000100
#FILE_ATTRIBUTE_SPARSE_FILE = $00000200
#FILE_ATTRIBUTE_REPARSE_POINT = $00000400
#FILE_ATTRIBUTE_COMPRESSED = $00000800                  ;#PB_FileSystem_Compressed
#FILE_ATTRIBUTE_OFFLINE = $00001000
#FILE_ATTRIBUTE_NOT_CONTENT_INDEXED = $00002000
#FILE_ATTRIBUTE_ENCRYPTED = $00004000
#FILE_ATTRIBUTE_INTEGRITY_STREAM = $00008000
#FILE_ATTRIBUTE_VIRTUAL = $00010000
#FILE_ATTRIBUTE_NO_SCRUB_DATA = $00020000
#FILE_ATTRIBUTE_EA = $00040000
#FILE_ATTRIBUTE_RECALL_ON_OPEN = $00040000             ;gleicher Wert wie #FILE_ATTRIBUTE_EA ?
#FILE_ATTRIBUTE_PINNED = $00080000
#FILE_ATTRIBUTE_UNPINNED = $00100000

#FILE_ATTRIBUTE_RECALL_ON_DATA_ACCESS = $00400000

Procedure checkDirAttr(dir$)
  Value = GetFileAttributes(dir$) 
  Debug dir$
  If Value = -1 
    Debug "Fehler beim Lesen der Attribute!"
  Else     
    If Value & #FILE_ATTRIBUTE_READONLY : Debug "#FILE_ATTRIBUTE_READONLY" : EndIf
    If Value & #FILE_ATTRIBUTE_HIDDEN : Debug "#FILE_ATTRIBUTE_HIDDEN" : EndIf
    If Value & #FILE_ATTRIBUTE_SYSTEM : Debug "#FILE_ATTRIBUTE_SYSTEM" : EndIf
    
    If Value & #FILE_ATTRIBUTE_DIRECTORY : Debug "#FILE_ATTRIBUTE_DIRECTORY" : EndIf
    If Value & #FILE_ATTRIBUTE_ARCHIVE : Debug "#FILE_ATTRIBUTE_ARCHIVE" : EndIf
    If Value & #FILE_ATTRIBUTE_DEVICE : Debug "#FILE_ATTRIBUTE_DEVICE" : EndIf
    If Value & #FILE_ATTRIBUTE_NORMAL : Debug "#FILE_ATTRIBUTE_NORMAL" : EndIf
    If Value & #FILE_ATTRIBUTE_TEMPORARY : Debug "#FILE_ATTRIBUTE_TEMPORARY" : EndIf
    If Value & #FILE_ATTRIBUTE_SPARSE_FILE : Debug "#FILE_ATTRIBUTE_SPARSE_FILE" : EndIf
    If Value & #FILE_ATTRIBUTE_REPARSE_POINT : Debug "#FILE_ATTRIBUTE_REPARSE_POINT" : EndIf
    If Value & #FILE_ATTRIBUTE_COMPRESSED : Debug "#FILE_ATTRIBUTE_COMPRESSED" : EndIf
    If Value & #FILE_ATTRIBUTE_OFFLINE : Debug "#FILE_ATTRIBUTE_OFFLINE" : EndIf
    If Value & #FILE_ATTRIBUTE_NOT_CONTENT_INDEXED : Debug "#FILE_ATTRIBUTE_NOT_CONTENT_INDEXED" : EndIf
    If Value & #FILE_ATTRIBUTE_ENCRYPTED : Debug "#FILE_ATTRIBUTE_ENCRYPTED" : EndIf
    If Value & #FILE_ATTRIBUTE_INTEGRITY_STREAM : Debug "#FILE_ATTRIBUTE_INTEGRITY_STREAM" : EndIf
    If Value & #FILE_ATTRIBUTE_VIRTUAL : Debug "#FILE_ATTRIBUTE_VIRTUAL" : EndIf
    If Value & #FILE_ATTRIBUTE_NO_SCRUB_DATA : Debug "#FILE_ATTRIBUTE_NO_SCRUB_DATA" : EndIf
    If Value & #FILE_ATTRIBUTE_EA : Debug "#FILE_ATTRIBUTE_EA" : EndIf
    If Value & #FILE_ATTRIBUTE_RECALL_ON_OPEN : Debug "#FILE_ATTRIBUTE_RECALL_ON_OPEN" : EndIf
    If Value & #FILE_ATTRIBUTE_PINNED : Debug "#FILE_ATTRIBUTE_PINNED" : EndIf
    If Value & #FILE_ATTRIBUTE_UNPINNED : Debug "#FILE_ATTRIBUTE_UNPINNED" : EndIf
    
    If Value & #FILE_ATTRIBUTE_RECALL_ON_DATA_ACCESS : Debug "#FILE_ATTRIBUTE_RECALL_ON_DATA_ACCESS" : EndIf     
  EndIf
  Debug "----------------------"
  Debug " "   
  ; Werte aus: https://learn.microsoft.com/de-de/windows/win32/fileio/file-attribute-constants
EndProcedure

;GetParentWinMetrics
Global winwidth, winheight
Procedure GetParentWinMetrics()
  winwidth = WindowWidth(#parent)
  winheight = WindowHeight(#parent)
  If IsMenu(#parentmenu) : winheight - MenuHeight() : EndIf
  If IsStatusBar(#parentstatusbar) : winheight - StatusBarHeight(#parentstatusbar) : EndIf  
EndProcedure

Procedure UpdateGadgets()
  GetParentWinMetrics()
  ResizeGadget(#etg,0,0,winwidth,winheight)
EndProcedure

Procedure UpdateWindow()
  dx = WindowWidth(#parent)
  dy = WindowHeight(#parent)
  If dx % 2 : dx + 1 : EndIf
  If dy % 2 : dy + 1 : EndIf
  ResizeWindow(#parent,#PB_Ignore,#PB_Ignore,dx,dy)  
  UpdateGadgets()
EndProcedure

;- openwindow
#parentflags = #PB_Window_SystemMenu | #PB_Window_SizeGadget | #PB_Window_MaximizeGadget | #PB_Window_MinimizeGadget | #PB_Window_ScreenCentered
OpenWindow(#parent, 0, 0, 1000, 700, "", #parentflags)
LoadFont(#parentfont_1, "Calibri", 11) : SetGadgetFont(#PB_Default, FontID(#parentfont_1))

;- Shortcuts
AddKeyboardShortcut(0, #PB_Shortcut_Escape, #escape)

;- Statusbar
If CreateStatusBar(#parentstatusbar, WindowID(#parent))
  AddStatusBarField(#PB_Ignore)  
EndIf

If IsMenu(#parentmenu)
  WindowBounds(#parent, 220, MenuHeight(), #PB_Ignore, #PB_Ignore)
Else
  WindowBounds(#parent, 220, 0, #PB_Ignore, #PB_Ignore)
EndIf

;- Programmcode -----------------------------------
;winwidth, winheight
GetParentWinMetrics()

ExplorerTreeGadget(#etg,0,0,winwidth,winheight,GetUserDirectory(#PB_Directory_Documents))



; BindEvents
BindEvent(#PB_Event_SizeWindow, @UpdateWindow(), #parent)

;- Eventschleife

Repeat
  Event = WindowEvent()
  Gadget = EventGadget()
  
  Select Event
    Case #PB_Event_CloseWindow
      Select EventWindow()
        Case #parent
          Break
      EndSelect
  EndSelect
  
  Select Gadget
    Case #etg
      Select EventType()
        Case #PB_EventType_Change
          checkDirAttr(GetGadgetText(Gadget))
        Case #PB_EventType_LeftClick
        Case #PB_EventType_LeftDoubleClick
        Case #PB_EventType_RightClick              
        Case #PB_EventType_RightDoubleClick
      EndSelect
      
  EndSelect
  
ForEver

End
Meine Frage: Bin ich da komplett auf der falschen Fährte oder kann jemand bestätigen, dass mit #FILE_ATTRIBUTE_REPARSE_POINT und / oder #FILE_ATTRIBUTE_NOT_CONTENT_INDEXED ein Symlink sicher erkannt und eine Unterscheidung zu einem "echten " Verzeichnis sicher funktioniert? Reicht eines der beiden Attribute (welches?) oder müssen beide vorhanden sein?

Re: Wie "echte Verzeichnisse" von "Links" unterscheiden?

Verfasst: 15.05.2025 12:22
von HeX0R
Laut MSDN ist #FILE_ATTRIBUTE_REPARSE_POINT zumindest ein Teil der Lösung.

Re: Wie "echte Verzeichnisse" von "Links" unterscheiden?

Verfasst: 15.05.2025 12:50
von EmmJott
Noch kurz, bevor ich mich auf's Fahrrad schwinge, um das schöne Wetter auszunutzen:

Ja, sieht so aus, dass mit dem Attribut #FILE_ATTRIBUTE_REPARSE_POINT ein "echtes Verzeichnis" von einem Link zu unterscheiden ist. Um das eigentliche Zielverzeichnis zu finden, gibt es zwei Wege:
a) eine Procedure zu entwickeln, die mittels API-Funktionen und Strukturen und sonstigen Betriebssysteminterna den Pfad ausspuckt - bei Weitem zu hoch für mich
b) FSUTIL verwenden, z. B. so:

Code: Alles auswählen

fsutil reparsepoint query "C:\Users\{username}\Startmenü"
ohne abschließenden "\".

Wenn mir jemand so eine elegante procedure schreiben könnte, wäre traumhaft, ansonsten würde ich mit runprogram eine Temp-Datei erzeugen, die entsprechende Zeile auslesen und die Temp-Datei wieder löschen.

Re: Wie "echte Verzeichnisse" von "Links" unterscheiden?

Verfasst: 15.05.2025 13:21
von DePe
Es gibt hier einen alten Code von freak der passen könnte:
https://www.purebasic.fr/english/viewto ... 2&t=370284

Edit: Der Code funktioniert ohne Änderung unter Windows 7 mit PB v6.21b9 32/64.

Peter

Re: Wie "echte Verzeichnisse" von "Links" unterscheiden?

Verfasst: 15.05.2025 14:25
von Axolotl
@DePe, der Link scheint nicht zu funktionieren.....
@EmmJott, du kannst mit RunProgram() das Ergebnis von "dir /a [C:\...]" auswerten. Bevor man sich mit API quält. Anbei noch ein Blog-Beitrag für eine Lösung in C. (Sorry, aber ich habe keine Zeit (Lust) das umzuschreiben.)

Weitere Suche ergab das hier. Nicht getestet.

Re: Wie "echte Verzeichnisse" von "Links" unterscheiden?

Verfasst: 15.05.2025 14:31
von DePe
Hallo Axolotl,

vielleicht funktioniert der Link so:
https://www.purebasic.fr/english/viewtopic.php?p=282633

Peter

Anbei der unveränderte Code von freak:

Code: Alles auswählen

; WinIoCtl.h
;
#FILE_DEVICE_FILE_SYSTEM         = $00000009
#METHOD_BUFFERED                 = 0
#FILE_ANY_ACCESS                 = 0
#FILE_SPECIAL_ACCESS             = (#FILE_ANY_ACCESS)

Macro CTL_CODE( DeviceType, Function, Method, Access )
  (((DeviceType) << 16) | ((Access) << 14) | ((Function) << 2) | (Method))
EndMacro

#FSCTL_SET_REPARSE_POINT         = CTL_CODE(#FILE_DEVICE_FILE_SYSTEM, 41, #METHOD_BUFFERED, #FILE_SPECIAL_ACCESS)
#FSCTL_GET_REPARSE_POINT         = CTL_CODE(#FILE_DEVICE_FILE_SYSTEM, 42, #METHOD_BUFFERED, #FILE_ANY_ACCESS)
#FSCTL_DELETE_REPARSE_POINT      = CTL_CODE(#FILE_DEVICE_FILE_SYSTEM, 43, #METHOD_BUFFERED, #FILE_SPECIAL_ACCESS)

; Winbase.h
;
#FILE_FLAG_OPEN_REPARSE_POINT    = $00200000

; WinNT.h
;
#IO_REPARSE_TAG_MOUNT_POINT  = $A0000003       
#IO_REPARSE_TAG_HSM          = $C0000004       
#IO_REPARSE_TAG_HSM2         = $80000006       
#IO_REPARSE_TAG_SIS          = $80000007       
#IO_REPARSE_TAG_DFS          = $8000000A       
#IO_REPARSE_TAG_SYMLINK      = $A000000C       
#IO_REPARSE_TAG_DFSR         = $80000012


; From Windows Driver Kit.
; http://msdn.microsoft.com/en-us/library/ms791514.aspx
;
Structure SymbolicLinkReparseBuffer
  SubstituteNameOffset.w
  SubstituteNameLength.w
  PrintNameOffset.w
  PrintNameLength.w
  Flags.l
  PathBuffer.w[1]
EndStructure

Structure MountPointReparseBuffer
  SubstituteNameOffset.w
  SubstituteNameLength.w
  PrintNameOffset.w
  PrintNameLength.w
  PathBuffer.w[1]
EndStructure

Structure GenericReparseBuffer
  DataBuffer.b[1]
EndStructure

Structure REPARSE_DATA_BUFFER
  ReparseTag.l
  ReparseDataLength.w
  Reserved.w
  StructureUnion
    SymbolicLinkReparseBuffer.SymbolicLinkReparseBuffer
    MountPointReparseBuffer.MountPointReparseBuffer
    GenericReparseBuffer.GenericReparseBuffer
  EndStructureUnion
EndStructure




; Tries to follow a directory link on Windows Vista (should also work for files)
;
; - If the directory is no link, the result is the original directory
; - If the target cannot be read, the result is ""
;
Procedure.s GetDirectoryTarget(Directory$)
  Protected TokenHandle, BufferSize, hDirectory, BytesReturned.l
  Protected Privileges.TOKEN_PRIVILEGES
  Protected *Buffer.REPARSE_DATA_BUFFER
  Protected Result$ = ""

  ; Check if the directory is a reparse point (link or mount point)
  ;
  If GetFileAttributes_(@Directory$) & #FILE_ATTRIBUTE_REPARSE_POINT

    ; The backup privilege is required to open a directory for io queries
    ; So try to set it on our process token. (usually it should be set already)
    ;
    If OpenProcessToken_(GetCurrentProcess_(), #TOKEN_ADJUST_PRIVILEGES, @TokenHandle)
      Privileges\PrivilegeCount = 1
      Privileges\Privileges[0]\Attributes = #SE_PRIVILEGE_ENABLED
    
      If LookupPrivilegeValue_(#Null, @"SeBackupPrivilege", @Privileges\Privileges[0]\Luid)       
        AdjustTokenPrivileges_(TokenHandle, #False, @Privileges, SizeOf(TOKEN_PRIVILEGES), #Null, #Null)
      EndIf    
      CloseHandle_(TokenHandle)
    EndIf
    
    ; Open the directory
    ;   Have to pass 0 as access right (not #GENERIC_READ), as it fails otherwise
    ;   http://www.codeproject.com/KB/vista/Windows_Vista.aspx
    ;
    hDirectory = CreateFile_(@Directory$, 0, #FILE_SHARE_READ|#FILE_SHARE_WRITE, #Null, #OPEN_EXISTING, #FILE_FLAG_OPEN_REPARSE_POINT | #FILE_FLAG_BACKUP_SEMANTICS, #Null)
    If hDirectory <> #INVALID_HANDLE_VALUE
    
      ; Allocate a buffer for the io query. 1000 bytes should be enough for the real path (in unicode)
      ;
      BufferSize = SizeOf(REPARSE_DATA_BUFFER) + 1000
      *Buffer = AllocateMemory(BufferSize)
      
      If *Buffer
        
        ; Query the directory for reparse point information
        ;
        If DeviceIoControl_(hDirectory, #FSCTL_GET_REPARSE_POINT, #Null, 0, *Buffer, BufferSize, @BytesReturned, #Null) <> 0
        
          ; Check the kind of reparse point (device drivers can create their own tags, so this is important)
          ; The "& $FFFFFFFF" is for 64bit, as the tags are negative when interpreted as quads
          ;
          If *Buffer\ReparseTag & $FFFFFFFF = #IO_REPARSE_TAG_MOUNT_POINT

            ; Read the result. The offset and length are in bytes. PeekS needs length in characters
            ;
            Result$ = PeekS(@*Buffer\MountPointReparseBuffer\PathBuffer[0] + *Buffer\MountPointReparseBuffer\SubstituteNameOffset, *Buffer\MountPointReparseBuffer\SubstituteNameLength / 2, #PB_Unicode)

          ElseIf *Buffer\ReparseTag & $FFFFFFFF = #IO_REPARSE_TAG_SYMLINK
          
            Result$ = PeekS(@*Buffer\SymbolicLinkReparseBuffer\PathBuffer[0] + *Buffer\SymbolicLinkReparseBuffer\SubstituteNameOffset, *Buffer\SymbolicLinkReparseBuffer\SubstituteNameLength / 2, #PB_Unicode)
            
          EndIf
        
        EndIf     
      
        FreeMemory(*Buffer)
      EndIf
    
      CloseHandle_(hDirectory)
    EndIf
  
  Else
  
    ; It is not a reparse point, so return the original path
    ;
    Result$ = Directory$
    
  EndIf
  
  ; Since the result is a unicode directory name, it can have the "\??\" prefix which allows a length of 32767 characters.
  ;
  If Left(Result$, 4) = "\??\"
    Result$ = Right(Result$, Len(Result$)-4)
  EndIf

  ProcedureReturn Result$
EndProcedure



; ----------------------------------------------------------------------------


Directory$ = "C:\Documents and Settings\"

Debug "Testing: " + Directory$
Debug "Target: " + GetDirectoryTarget(Directory$)