[Gelöst] std::filesystem_error bei path > MAX_PATH



  • @hustbaer sagte in [Gelöst] std::filesystem_error bei path > MAX_PATH:

    @Finnegan
    Was der Standard vorschreiben sollte und was nicht ist eine Sache.
    Was die Implementierung machen sollte mMn. eine andere.

    Ich finde es halt doof, dass es auf Windows mit standard C++ nicht möglich ist bestimmte Verzeichnisse aufzulisten -- obwohl es mit anderen Programmen sehr wohl möglich ist auf diese Verzeichnisse/Files zuzugreifen. Fälle wo man in das Limit läuft sind auch gar nicht so selten.

    Ja, das geht mir auch schon seit ewig gehörig auf die Nerven - ich hab oft mit tiefen Verzeichnisstrukturen zu tun habe die zur Verarbeitung nochmal ein paar extra Verzeichnisse tief ausgepackt werden. Dazu noch das Locking von Dateien, wo man sich doch oft fragt welcher Prozess denn da noch welche Datei offen hat, dass Löschen nicht wirklich atomar bezüglich Metadaten ist (VMS-Relikt: Dateien werden erstmal nur zum Löschen vorgemerkt und dann meistens nur sehr kurze Zeit später tatsächlich gelöscht - manchmal dauerts aber auch länger), die furchtbar langsame Handhabung von kleinen Dateien und die nicht sonderlich flotte Erzeugung von neuen Prozessen. Ich bin für Dinge, wo das alles besonders stark einschlägt (z.B. die eigene GCC-Toolchain bauen) aus diesen Gründen sogar dazu übergegangen, das nur noch in einem Linux-Container zu machen. Das ist ohne Scheiss 10 mal so schnell bei wesentlich weniger Kopfschmerzen 😉

    Ich hab grad das GetFileAttributesExW mal mit einem zu langen Verzeichnisnamen getestet. Das schlägt bei mir selbst mit LongPathsEnabled = 1 sowohl unter MinGW-w64 als auch mit Visual Studio fehl. Ich hatte eigentlich gedacht, da bräuchte es kein \\?\-Voodoo, wegen "These are the file management functions that no longer have MAX_PATH restrictions if you opt-in to long path behavior", aber @HarteWare hatte das ja auch schon angedeutet. Das ist schon übel und erklärt, warum lange Pfade immer noch so viele Probleme machen. Anscheinend muss jedes Programm, das die ordentlich unterstützen will, selbst in dieses Pfadformat konvertieren - mit allen Sonderfällen bezüglich relativen Pfaden - sehe ich das richtig?

    Wenn das stimmt, dann sehe das Problem vor allem darin, dass MS hier so ein Geraffel als Lösung implementiert hat anstatt das bei LongPathsEnabled = 1 völlig transparent für sämtliche Dateisystem-Operationen zu machen - inklusive des Lowelevel-I/O der C-Runtime (_open, _wstat64, etc.). Immerhin werden die langen Pfade laut Doku für jeden neuen Prozess aktiviert. Wenn man dann ein inkompatibles Programm verwendet, das z.B. blind in einen wchar_t[MAX_PATH] kopiert, ist man selbst schuld.

    Und Holla die Waldfee, mein etwas angestaubtes Visual Studio (2022, 17.2.0) wirft beim Testprogramm von @HarteWare ebenfalls:

    recursive_directory_iterator::operator++: Das System kann den angegebenen Pfad nicht finden.
    

    ... sobald der Iterator beim zu langen Pfad ankommt.

    Sieht so aus, als bekommt das nichtmal MS ordentlich hin 😄

    Bei anderen Dingen die theoretisch auf Windows möglich sind wird es dann etwas schwieriger. Z.B. kannst du auf Windows normalerweise keine Files erzeugen die "reservierte" Namen wie "CON" oder "COM1" haben. Mit dem \\?\ Prefix geht es aber. Das Erzeugen von solchen Files über die C++ Standard Library zu erlauben fände ich z.B. nicht so gut.

    Wenn man wirklich will, dass solche Dinge "einfach funktionieren" dann sollte man das Problem möglichst an der Wurzel anpacken. Mein Problem, das in der C++-Standardbibliothek zu implementieren ist, dass eben nur neu gebaute C++-Programme davon profitieren. Und das erstreckt sich nicht nur über std::filesystem - auch Funktionen wie fstream::open sollten mit diesen Pfaden umgehen können.

    Die beste Lösung hat m.E. MS verbaut, da böte sich vielleicht am ehesten an, den Support dafür transparent in einer Lowlevel-Abstraktion zu implementieren. Die libc++ implementiert z.B. sowas in die Richtung - POSIX-Funktionen wie stat, open, close. Da könnte man sowas z.B. einbauen. Noch besser vielleicht, wenn man das in MinGW einbaut. GCC und andere Programme könnten dann auf eine Reihe windows-spezifischer #ifdefs verzichten und einfach diese Funktionen verwenden - löst natürlich das Problem nicht für Programme, die diese #ifdefs immer noch drin haben und ist daher auch keine wirklich gute Lösung.

    Man könnte auch so verwegen sein und das in MinGW direkt in GetFileAttributesExW, _wstat64 et al. einbauen - so wie es MS hätte machen sollen. Das hat aber einiges an Potential, ordentlich schief zu gehen und widerspricht wohl auch dem Anspruch von MinGW eine API zur Verfügung zu stellen, die sich exakt wie die von MS verhält. Wäre aber m.E. die einzige Möglichkeit mit der dann alle Programme "einfach so" funktionieren - zumindest die neu mit MinGW kompilierten 🙂

    P.S.:
    @SeppJ sagte in [Gelöst] std::filesystem_error bei path > MAX_PATH:

    @Tyrdal sagte in [Gelöst] std::filesystem_error bei path > MAX_PATH:

    @Swordfish Wie nimmt man den Python, um in C++ Plattformunabhängig mit Dateien zu arbeiten?

    system("python script.py")
    

    Hat das mal jemand ausprobiert? Nach dem was ich bisher gesehen habe, würde es mich nicht wundern, wenn auch Python das Problem hat 🙂

    Edit: Ne, sieht gut aus:

    >>> import os
    >>> os.stat("D:\\Temp\\test-fs\\very-long-path-subdirectory1\\very-long-path-subdirectory2\\very-long-path-subdirectory3\\very-long-path-subdirectory4\\very-long-path-subdirectory5\\very-long-path-subdirectory6\\very-long-path-subdirectory7\\very-long-path-subdirectory8\\very-long-path-subdirectory9\\very-long-path-subdirectory10\\very-long-path-subdirectory11\\")
    os.stat_result(st_mode=16895, st_ino=2251799813742350, st_dev=1077506507, st_nlink=1, st_uid=0, st_gid=0, st_size=0, st_atime=1682013939, st_mtime=1682013838, st_ctime=1682013838)
    

    ... wär mal interessant zu sehen, wie die das machen.



  • Weitere Beobachtungen mit Datei

    D:\Temp\test-fs\very-long-path-subdirectory1\very-long-path-subdirectory2\very-long-path-subdirectory3\very-long-path-subdirectory4\very-long-path-subdirectory5\very-long-path-subdirectory6\very-long-path-subdirectory7\very-long-path-subdirectory8\very-long-path-subdirectory9\very-long-path-subdirectory10\very-long-path-subdirectory11\test.txt

    Explorer -> Rechtsklick "test.txt" -> Öffnen:
    "Explorer.EXE: Der Verzeichnisname ist ungültig."

    Explorer -> Rechtsklick "test.txt" -> Edit with Nodepad++: nichts passiert.

    Explorer -> Rechtsklick Ordner-Hintergrund von "long-path-subdirectory11" -> Neu: Keine Optionen, um neue Dateien im Ordner zu erstellen (im Gegensatz zu Ordnern mit nicht-überlangem Pfad). Lediglich eine "Ordner"-Option mit Admin-Symbol davor. Nichts passiert, wenn ich das klicke.

    Die MSYS2-Bash-Shell scheint das alles nicht zu interessieren und funktioniert wie erwartet:

    …ery-long-path-subdirectory8/very-long-path-subdirectory9/very-long-path-subdirectory10/very-long-path-subdirectory11
    $ echo TEST > test.txt
    $ cat test.txt
    TEST
    

    Die machen in MSYS2 auch intern viel Pfadübersetzungs-Zeug, z.B. Dinge wie /c/verzeichnis in Programm-Argumenten transparent nach C:\verzeichnis zu übersetzen, was überraschend gut funktioniert. Es wundert mich also eher weniger, dass MSYS2 damit klar kommt.

    wsl.exe scheint auch so seine Probleme zu haben:

    PS > cd \\?\D:\Temp\test-fs\very-long-path-subdirectory1\very-long-path-subdirectory2\very-long-path-subdirectory3\very-long-path-subdirectory4\very-long-path-subdirectory5\very-long-path-subdirectory6\very-long-path-subdirectory7\very-long-path-subdirectory8\very-long-path-subdirectory9\very-long-path-subdirectory10\very-long-path-subdirectory11
    PS > wsl
    Fehler beim Ausführen des Programms "wsl.exe": Der Verzeichnisname ist ungültig
    

    Innerhalb des WSL-Linux wie erwartet keine Probleme:

    > cd /mnt/d/Temp/test-fs/very-long-path-subdirectory1/very-long-path-subdirectory2/very-long-path-subdirectory3/very-long-path-subdirectory4/very-long-path-subdirectory5/very-long-path-subdirectory6/very-long-path-subdirectory7/very-long-path-subdirectory8/very-long-path-subdirectory9/very-long-path-subdirectory10/very-long-path-subdirectory11/
    > cat test.txt
    TEST
    

    ... was ein Chaos 😁



  • @Finnegan sagte in [Gelöst] std::filesystem_error bei path > MAX_PATH:

    Anscheinend muss jedes Programm, das die ordentlich unterstützen will, selbst in dieses Pfadformat konvertieren - mit allen Sonderfällen bezüglich relativen Pfaden - sehe ich das richtig?

    Relative Pfade kann man mit GetFullPathNameW erschlagen. Das scheint auch mit langen Pfaden zu funktionieren ohne dass man das blöde \\?\ vorn anhängt. Das Ersetzen von / durch \ übernimmt auch GetFullPathNameW.

    Mit "verbotenen" Namen ala CON, COM1 etc. kommt bei GetFullPathNameW aber u.U. Blödsinn raus - bzw. halt nicht unbedingt das was man möchte. Wenn's mittem im Pfad vorkommt, dann bleiben die verbotenen Namen erhalten. Sobald allerdings das letzte Pfad-Segment ein verbotener Name ist (oder auch ein verbotener Name gefolgt von einer Extension), dann bekommt man als Ergebnis den "Device-Path" der dem verbotenen Namen entspricht. Also z.B. aus C:\Foo\Bar\CON.txt wird \\.\CON.

    Den Fall sollte man vermutlich also noch selbst abdecken. Wie man den dann behandelt kann man sich aussuchen. Entweder man bastelt einen Workaround für den Fall, z.B. indem man den verbotenen Namen in einen nicht verbotenen ändert, dann GetFullPathNameW aufruft und dann den letzten Teil des Ergebnisses wieder auf den verbotenen Namen zurückändert. Oder indem man solche Pfade einfach verbietet - z.B. indem man prüft ob das Ergebnis mit \\.\ anfängt.

    Wenn das stimmt, dann sehe das Problem vor allem darin, dass MS hier so ein Geraffel als Lösung implementiert hat anstatt das bei LongPathsEnabled = 1 völlig transparent für sämtliche Dateisystem-Operationen zu machen - inklusive des Lowelevel-I/O der C-Runtime (_open, _wstat64, etc.). Immerhin werden die langen Pfade laut Doku für jeden neuen Prozess aktiviert. Wenn man dann ein inkompatibles Programm verwendet, das z.B. blind in einen wchar_t[MAX_PATH] kopiert, ist man selbst schuld.

    Tja. Pfuh. Wobei es mir wenig bringen würde wenn das über die Registry super funktionieren würde. Unsere Programme müssen auf allen möglichen Systemen laufen, und wir können nicht von unseren Benutzern verlangen dass sie Einstellungen in der Registry ändern. (Und automatisch ändern dürfen wir sie schon gar nicht.)

    Und Holla die Waldfee, mein etwas angestaubtes Visual Studio (2022, 17.2.0) wirft beim Testprogramm von @HarteWare ebenfalls:

    recursive_directory_iterator::operator++: Das System kann den angegebenen Pfad nicht finden.
    

    ... sobald der Iterator beim zu langen Pfad ankommt.

    Sieht so aus, als bekommt das nichtmal MS ordentlich hin 😄

    Ja, richtig. Ich dachte das wäre aus der Verlauf des Threads mittlerweile klar geworden 🙂

    ...

    Wie gesagt: ich fände es gut wenn zumindest Support für lange Pfade transparent in der MSVC Standard Library (bzw. jeder Standard Library für Windows) verbaut wäre. Und damit meine ich natürlich nicht nur std::filesystem sondern auch den "libc" Teil (open, fopen, _wfopen etc.).



  • @hustbaer sagte in [Gelöst] std::filesystem_error bei path > MAX_PATH:

    Tja. Pfuh. Wobei es mir wenig bringen würde wenn das über die Registry super funktionieren würde. Unsere Programme müssen auf allen möglichen Systemen laufen, und wir können nicht von unseren Benutzern verlangen dass sie Einstellungen in der Registry ändern. (Und automatisch ändern dürfen wir sie schon gar nicht.)

    Aus MS-Doku:

    The registry key Computer\HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\FileSystem\LongPathsEnabled (Type: REG_DWORD) must exist and be set to 1. The key's value will be cached by the system (per process) after the first call to an affected Win32 file or directory function (see below for the list of functions).

    Das klingt so, als hätte man auch eine Funktion zur Verfügung stellen können, die beim Programmstart aufgerufen wird und das Flag für den Prozess setzt. Müsste man wahrscheinlich in den Startup-Code einbauen (wo vermutlich das erste mal eine betroffene Dateisystem-Funktion aufgerufen wird) und das dann über eine Compiler-Option aktivieren - ähnlich wie z.B. das GCC-Flag -mcrtdll für die Auswahl der MSVCRT/UCRT Runtime oder die Flags, mit denen man auswählt, ob Konsolen- oder GUI-Programm (/SUBSYSTEM:console/-mconsole). Das wäre jedenfalls sinnvoller ausrollbar als der Registry-Schlüssel. Dann braucht man auch noch weniger auf irgendwelche Kompatibilitätsprobleme zu achten, wenn die Entwickler des Programms das schon explizit angeben.

    Vor allem muss die Voraussetzung fallen, dass es nur mit \\?\-Pfaden funktioniert. Das erachte ich als größte Hürde. Besonders für auf Windows portierte Programme und Bibliotheken. Ist ja schön und gut, wenn man das im eigenen Code richtig macht - nur was ist mit den 20 gelinkten Libs, die ebenfalls mit Pfaden hantieren? 🤯

    Und Holla die Waldfee, mein etwas angestaubtes Visual Studio (2022, 17.2.0) wirft beim Testprogramm von @HarteWare ebenfalls:

    recursive_directory_iterator::operator++: Das System kann den angegebenen Pfad nicht finden.
    

    ... sobald der Iterator beim zu langen Pfad ankommt.

    Sieht so aus, als bekommt das nichtmal MS ordentlich hin 😄

    Ja, richtig. Ich dachte das wäre aus der Verlauf des Threads mittlerweile klar geworden 🙂

    Ich muss zu meiner Schade gestehen, dass ich dazu neige, längere Threads eher querzulesen und mir dabei gerne etwas entgeht. Das ist nicht das erste mal, dass mir erst später etwas auffällt, das eigentlich schon geklärt war ... immer etwas peinlich 😁



  • @Finnegan Es wäre natürlich für viele Anwendungen super wenn man das irgendwie im Programm selbst aktivieren könnte. Also z.B. per API-call, Linker-Flag oder Manifest.

    Uns würde das allerdings nur wenig bringen, da ein grosser Teil unseres Codes im Kontext von fremden Prozessen läuft. Und in denen dürfen wir keinen globalen State des Prozesses ändern, selbst wenn wir es können.



  • @hustbaer sagte in [Gelöst] std::filesystem_error bei path > MAX_PATH:

    Uns würde das allerdings nur wenig bringen, da ein grosser Teil unseres Codes im Kontext von fremden Prozessen läuft. Und in denen dürfen wir keinen globalen State des Prozesses ändern, selbst wenn wir es können.

    Schon klar dass der Code, der den Prozess aufmacht ihn auch exklusiv konfiguriert. Da kann man dann wohl nichts machen... ausser die Dateisystemzugriffe in einem weiteren Prozess zu machen, den ihr selbst startet und mit dem euer Code dann kommuniziert 😁



  • Da kann man dann wohl nichts machen... ausser die Dateisystemzugriffe in einem weiteren Prozess zu machen, den ihr selbst startet und mit dem euer Code dann kommuniziert 😁

    🙂

    Was wir machen werden damit wir endlich keine Probleme mehr mit langen Pfaden haben ist die Pfade selbst zu "konvertieren", per GetFullPathNameW + Check auf verbotene Namen + voranstellen von \\?\ .



  • @hustbaer sagte in [Gelöst] std::filesystem_error bei path > MAX_PATH:

    Was wir machen werden damit wir endlich keine Probleme mehr mit langen Pfaden haben ist die Pfade selbst zu "konvertieren", per GetFullPathNameW + Check auf verbotene Namen + voranstellen von \\?\ .

    Darf ich mal ne ganz doofe Frage stellen, die mir wieder peinlich werden könnte? ... was macht eigentlich LongPathsEnabled, wenn es auch so mit \\?\-Pfaden funktionieren soll?

    Mein Eindruck beim Testen war, dass \\?\ auch dann gebraucht wird, wenn man das aktiv hat. Oder hat mir da vielleicht irgendwas dazwischen gefunkt? GetFileAttributesExW hat bei mir auch mit LongPathsEnabled = 1 einen Fehler zurückgegeben, wenn das\\?\ fehlte.



  • @Finnegan Ich habe das nicht probiert, sondern gerade nur in der Doku gefunden. Angeblich muss man Long Path Awareness noch im Application Manifest setzen:

     <ws2:longPathAware>true</ws2:longPathAware>
    

    Edit: Das kann aber ja eigentlich nicht die Lösung für PS sein oO



  • @Schlangenmensch sagte in [Gelöst] std::filesystem_error bei path > MAX_PATH:

    @Finnegan Ich habe das nicht probiert, sondern gerade nur in der Doku gefunden. Angeblich muss man Long Path Awareness noch im Application Manifest setzen:

     <ws2:longPathAware>true</ws2:longPathAware>
    

    Hah! Das ist ja wie bei den Flugzeugbauern: Mehrfach redundante Sicherheitssysteme stellen sicher, dass nicht am Ende noch jemand mit dem OS produktiv arbeiten kann 😁

    Mal ehrlich, das mit dem Manifest hätte echt gereicht - aber schön, dass es scheinbar auch ohne das Registry-Flag klappt - so bleibt immerhin eine gut kompatible Lösung.

    Ich bin ja bei Dateisystem-Zugriffen eher ein Fan davon, mit Handles zu arbeiten statt mit Pfaden und String-Operationen zu hantieren. Unter Linux geht das mit open() und openat(). Hat den Vorteil, dass die Operationen atomar sind, man Dateisystem-Races vermeidet und ein Handle offen hält, wodurch man den Zugriff auf das Verzeichnis nicht verliert wenn es gelöscht oder verschoben wird. Einem damit implementierten Directory Iterator könnte man im laufenden Betrieb das Wurzelverzeichnis verschieben und der würde immer noch korrekt durchlaufen. Ist ein praktisches Verhalten für Tools, die lange Zeit in einem Verzeichnisbaum arbeiten und man sich währenddessen überlegt, den umzubenennen oder an eine sinnvollere Stelle zu packen.


Anmelden zum Antworten