Add DFS metadata classification concept

This commit is contained in:
Meik
2026-03-13 17:14:57 +01:00
parent 4850fdb80d
commit e7fc76bf5a

View File

@@ -0,0 +1,396 @@
# LIAM NTFS / DFS Metadaten-Klassifikation - konkretes Umsetzungskonzept
## Ziel
Dieses Dokument beschreibt die konkrete Zielumsetzung fuer die robuste Klassifikation von UNC-Pfaden im LIAM-NTFS-Provider.
Rahmenbedingungen:
- keine Erweiterung der bestehenden Provider-Config
- der Code darf nicht voraussetzen, auf dem DFS- oder SMB-Server selbst zu laufen
- DFS-Strukturen koennen unterschiedlich aufgebaut sein
- es muss sauber unterschieden werden zwischen:
- klassischem SMB-Share
- DFS-Namespace-Root
- DFS-Link
- echtem Folder
Die bestehende Config liefert dafuer nur den Einstiegspunkt und die Zugriffsrechte:
- `RootPath`
- `Domain`
- `Credential`
Die eigentliche Typbestimmung muss daher zur Laufzeit ueber Metadaten des adressierten UNC-Pfads erfolgen.
## Kurzfazit
Die robuste Loesung ist:
1. UNC-Pfad normalisieren
2. DFS-Metadaten fuer den Pfad und relevante Praefixe abfragen
3. wenn kein DFS-Treffer vorliegt, SMB-Share-Metadaten abfragen
4. alles unterhalb einer erkannten fachlichen Grenze als Folder behandeln
Die aktuelle Segment-Heuristik wird damit ersetzt, nicht nur verbessert.
## Fachregeln
Es gelten folgende semantische Regeln:
- `\\server\\namespace` ist ein `DfsNamespaceRoot`, wenn der Pfad als DFS-Namespace-Root aufgeloest werden kann.
- `\\server\\namespace\\link` ist ein `DfsLink`, wenn der Pfad als DFS-Link aufgeloest werden kann.
- `\\server\\share` ist ein `ClassicShare`, wenn der Share auf dem Server publiziert ist und kein DFS-Treffer vorliegt.
- jeder Pfad unterhalb eines `DfsLink` ist ein `Folder`
- jeder Pfad unterhalb eines `ClassicShare` ist ein `Folder`
- jeder Pfad unterhalb eines `DfsNamespaceRoot`, aber oberhalb eines `DfsLink`, ist nur dann `Folder`, wenn er kein DFS-Link ist und fachlich wirklich Dateisystemstruktur repraesentiert; fuer den Normalfall sind direkte Kinder eines Namespace-Roots als DFS-Links zu erwarten
## Verwendbare Laufzeit-Metadaten
### 1. DFS-Metadaten
Primaere Quelle fuer DFS:
- Win32 DFS API, bevorzugt `NetDfsGetInfo`
Damit kann fuer einen UNC-Pfad geprueft werden:
- ist der Pfad ein DFS-Objekt?
- handelt es sich um Root oder Link?
- welche Targets sind hinterlegt?
Wichtig:
- diese Abfrage funktioniert remote gegen den adressierten Namespace
- sie benoetigt keine lokale Ausfuehrung auf dem DFS-Server
- sie ist fachlich deutlich verlaesslicher als `Directory.Exists(...)`
### 2. SMB-Share-Metadaten
Primaere Quelle fuer klassische Shares:
- `NetShareEnum`
- optional ergaenzend `NetShareGetInfo`
Das ist fuer LIAM bereits teilweise vorhanden:
- `cNetworkConnection.EnumNetShares(server)`
Damit kann geprueft werden:
- ob `\\server\\share` ein echter publizierter SMB-Share ist
- welche sichtbaren Disk-Shares auf dem Host existieren
### 3. Dateisystem-Metadaten
Nur fuer Folder:
- `Directory.Exists`
- `DirectoryInfo`
Diese Daten duerfen nicht fuer die Unterscheidung zwischen DFS und Share benutzt werden, sondern nur nachdem bereits eine fachliche Grenze erkannt wurde.
## Nicht ausreichende Datenquellen
Folgende Informationen reichen fuer die Typbestimmung nicht aus:
- Anzahl der UNC-Segmente
- `RootPath` alleine
- `Directory.Exists`
- ACLs
- Naming Conventions
- Custom Tags
- Group Strategy
Diese Daten koennen ergaenzend helfen, liefern aber keine robuste Aussage ueber DFS vs. SMB.
## Zielmodell
Internes Modell:
- `ServerRoot`
- `ClassicShare`
- `DfsNamespaceRoot`
- `DfsLink`
- `Folder`
- `Unknown`
Externe LIAM-Abbildung:
- `ClassicShare` -> `NtfsShare`
- `DfsNamespaceRoot` -> `DfsNamespaceRoot`
- `DfsLink` -> `NtfsShare`
- `Folder` -> `NtfsFolder`
Damit bleibt die fachliche Aussenwirkung stabil:
- DFS-Link bleibt nach aussen ein share-aehnliches Objekt
- DFS-Namespace-Root bleibt eigenstaendig sichtbar
## Konkreter Klassifikationsalgorithmus
### Schritt 1: Normalisierung
Jeder Eingabepfad wird zuerst vereinheitlicht:
- Slash zu Backslash
- doppelte Separatoren bereinigen
- UNC-Praefix sicherstellen
- Trailing Backslash entfernen
Beispiel:
- `\\SERVER\\share\\`
- `//server/share`
werden intern zu:
- `\\server\\share`
fuer Vergleiche kann case-insensitive gearbeitet werden.
### Schritt 2: DFS zuerst pruefen
Fuer den gesamten Pfad und seine relevanten Praefixe werden DFS-Metadaten geprueft.
Beispiele:
- bei `\\server\\namespace\\link\\folder`
- pruefe `\\server\\namespace`
- pruefe `\\server\\namespace\\link`
Entscheidungsregel:
- wenn der volle Pfad ein DFS-Namespace-Root ist -> `DfsNamespaceRoot`
- wenn der volle Pfad ein DFS-Link ist -> `DfsLink`
- wenn ein Praefix ein DFS-Link ist und der Gesamtpfad darunter liegt -> `Folder`
- wenn ein Praefix ein DFS-Namespace-Root ist, aber noch kein DFS-Link erkannt wurde, wird weiter geprueft
Wichtig:
- der tiefste erkannte DFS-Link gewinnt als fachliche Grenze
- die Share-Grenze ist dann genau dieser Link-Pfad
### Schritt 3: SMB-Share pruefen
Nur wenn kein DFS-Ergebnis vorliegt:
- ermittle sichtbare Shares des Servers per `NetShareEnum`
- pruefe, ob `\\server\\share` exakt ein publizierter Share ist
Entscheidungsregel:
- wenn der volle Pfad exakt einem Share entspricht -> `ClassicShare`
- wenn ein Praefix exakt einem Share entspricht und der Gesamtpfad darunter liegt -> `Folder`
### Schritt 4: Folder nur relativ zu einer erkannten Grenze
Ein Pfad ist nur dann sicher `Folder`, wenn bereits eine fachliche Grenze erkannt wurde:
- klassischer Share
- DFS-Link
Beispiele:
- `\\server\\share\\dept` -> `Folder`, wenn `\\server\\share` ein ClassicShare ist
- `\\server\\namespace\\link\\dept` -> `Folder`, wenn `\\server\\namespace\\link` ein DfsLink ist
### Schritt 5: Unknown statt falscher Sicherheit
Wenn weder DFS noch SMB sicher bestimmt werden koennen:
- liefere intern `Unknown`
- logge die Ursache
- triff keine stille Segment-basierten Fallback-Entscheidung
## Erforderliche Runtime-Abfragen
### DFSResolver
Neue interne Hilfskomponente:
- `TryGetDfsMetadata(path, out metadata)`
Rueckgabedaten:
- `Exists`
- `IsNamespaceRoot`
- `IsLink`
- `Path`
- `EntryPath`
- `Targets`
Empfohlene Implementierung:
- P/Invoke gegen `NetDfsGetInfo`
Sinnvolle Aufrufmuster:
- pruefe `\\server\\namespace`
- pruefe `\\server\\namespace\\link`
- cache Ergebnisse je Pfad
### ShareResolver
Bestehende Hilfskomponente weiterverwenden:
- `EnumNetShares(server)`
Ergaenzung:
- optional Hilfsmethode `IsPublishedShare(server, shareName)`
- Cache je Server
## Neue zentrale Datenstruktur
Empfohlene interne Rueckgabe:
```text
PathClassification
- NormalizedPath
- Kind
- BoundaryPath
- ParentBoundaryPath
- BackingType
- ResolvedFrom
- Diagnostics
```
Dabei bedeutet:
- `BoundaryPath`: fachliche Share-Grenze
- `ParentBoundaryPath`: Parent-Objekt fuer LIAM-ParentUID
- `BackingType`: `DFS` oder `SMB`
- `ResolvedFrom`: z. B. `DfsLinkPrefix`, `SharePrefix`, `FullPathDfsRoot`
## Verhalten fuer typische Faelle
### Fall A
Pfad:
- `\\SRVWSM001.imagoverum.com\\file_shares`
Erwartung:
- wenn DFS-Metadaten Root bestaetigen -> `DfsNamespaceRoot`
### Fall B
Pfad:
- `\\SRVWSM001.imagoverum.com\\file_shares\\share2`
Erwartung:
- wenn DFS-Metadaten Link bestaetigen -> intern `DfsLink`, extern `NtfsShare`
### Fall C
Pfad:
- `\\SRVWSM001.imagoverum.com\\file_shares\\share2\\test33`
Erwartung:
- wenn `\\...\\file_shares\\share2` als DFS-Link erkannt wurde -> `Folder`
## Anpassung von `getDataAreasAsync()`
Die Enumeration darf nicht mehr nur mit `RequestFoldersListAsync(RootPath, depth)` arbeiten.
Stattdessen:
- Root zuerst klassifizieren
- Kinder je nach Root-Typ enumerieren
Regeln:
- `ClassicShare`
- Kinder per Dateisystem-Verzeichnisliste
- `DfsNamespaceRoot`
- direkte Kinder primaer ueber DFS-Namespace-Inhaltsmetadaten
- nur diese direkten Kinder sind fachlich Share-aehnliche Eintraege
- `DfsLink`
- Kinder per Dateisystem-Verzeichnisliste unterhalb des Link-Ziels bzw. unter dem UNC-Linkpfad
- `Folder`
- Kinder per Dateisystem-Verzeichnisliste
Der aktuelle Fehler entsteht gerade dadurch, dass direkte Kinder eines DFS-Namespace-Roots wie normale Ordner enumeriert und spaeter wieder als Folder behandelt werden.
## Anpassung von `LoadDataArea()`
`LoadDataArea()` muss dieselbe zentrale Klassifikation benutzen wie `getDataAreasAsync()`.
Wichtig:
- keine eigene Kurzlogik
- keine Typentscheidung ueber Segmentzahl
- gleiche `PathClassification` fuer denselben Pfad wie im Listenpfad
## Logging
Fuer Diagnosefaelle sollten folgende Logeintraege vorhanden sein:
- welcher Pfad wird klassifiziert
- welcher DFS-Check wurde ausgefuehrt
- welcher Share-Check wurde ausgefuehrt
- welcher Praefix als Boundary erkannt wurde
- warum ein Pfad `Unknown` wurde
Beispiel:
- `Path '\\server\\namespace\\link\\team' classified as Folder via DFS link boundary '\\server\\namespace\\link'`
## Fehlerverhalten
Wenn DFS-Abfragen fehlschlagen:
- Fehler loggen
- weiter mit SMB-Pruefung
Wenn SMB-Abfragen fehlschlagen:
- Fehler loggen
- nicht automatisch Folder annehmen
Wenn beides fehlschlaegt:
- `Unknown`
Damit wird falsche Typvergabe vermieden.
## Performance
Noetige Caches:
- DFS-Metadaten-Cache pro Pfad
- Share-Liste pro Server
Empfehlung:
- innerhalb eines Provider-Laufs cachen
- keine globale Langzeitpersistenz
## Konkrete Implementierungsschritte
1. neue interne Resolver-Komponente fuer DFS-Metadaten einfuehren
2. bestehende Share-Abfrage in dedizierte Share-Resolver-Methode kapseln
3. `ClassifyPath()` auf Metadaten-basierte Entscheidung umbauen
4. `getDataAreasAsync()` root-typabhaengig enumerieren
5. `LoadDataArea()` auf dieselbe Klassifikation umstellen
6. JSON-Rueckgabe unveraendert lassen:
- `DfsNamespaceRoot` als eigener `DataAreaType`
- `DfsLink` nach aussen als `NtfsShare`
- `Folder` als `NtfsFolder`
## Entscheidung
Die empfohlene Umsetzung ohne Config-Erweiterung ist:
- `RootPath` aus der vorhandenen Config nur als Einstieg verwenden
- DFS-Metadaten zur Laufzeit ueber API abfragen
- SMB-Share-Metadaten zur Laufzeit ueber `NetShareEnum` abfragen
- Folder ausschliesslich relativ zu einer erkannten fachlichen Grenze ableiten
Nur so koennen klassische Shares, DFS-Namespaces, DFS-Links und echte Folder belastbar unterschieden werden, auch wenn LIAM nicht auf dem Zielserver selbst laeuft.