32 votes

Que se passe-t-il en interne lorsqu'un chemin de fichier dépasse approximativement 32767 caractères dans Windows?

Sur Windows (à partir de 2000), un chemin de fichier peut avoir au maximum environ 32767 caractères de longueur. Cette limitation existe en raison de la manipulation interne avec UNICODE_STRING dans l'API native (également du côté du noyau, dans les pilotes, etc). Jusque-là tout va bien. Je connais la théorie derrière cette partie.

La raison de la limite est que les membres Length et MaximumLength de UNICODE_STRING comptent le nombre d'octets dans le Buffer, mais sont eux-mêmes des entiers non signés sur 16 bits.

Je sais aussi pourquoi la limite est une approximation plutôt qu'une limite fixe. Cela est principalement dû à la façon dont votre nom de fichier (par exemple \\.\C:\boot.ini) est résolu dans sa forme native (par exemple \??\C:\boot.ini) puis dans quelque chose qui est précédé du nom du périphérique de volume réel suivi du chemin relatif à ce volume, par exemple \Device\HarddiskVolume2\boot.ini.

En outre, dans l'Explorateur Windows, le symptôme connu lorsque la limite ("ANSI") MAX_PATH est atteinte est de faire semblant que le fichier ou le dossier n'existe pas dans certaines versions de Windows (il est possible que cela ait été corrigé à un moment donné).

Mais qu'arrive-t-il respectivement au gestionnaire d'objets, au gestionnaire d'E/S et au pilote de système de fichiers lorsque j'appelle CreateFile() avec un chemin ressemblant à \\.\C:\...\nom_fichier.ext et que tout le chemin n'excède pas la limite, mais l'atteint, dans mon appel à CreateFile() de kernel32.dll et est ensuite étendu? ...

Ni les SDK ni les WDK ne semblent être particulièrement bavards sur le sujet. Ou ai-je regardé dans les mauvaises sections?

38voto

Igor Skochinsky Points 13292

Parce que je suis paresseux, je n'ai pas écrit de programme de test mais je l'ai testé en utilisant l'excellent Far Manager qui gère des choses comme les longs chemins (plus longs que MAX_PATH) ou des noms de fichiers spéciaux (con, prn, etc) très bien.

J'ai créé une chaîne de exactement 255 caractères ("12345678901234...012345") et j'ai commencé à créer des répertoires imbriqués. Heureusement, la fonction "Créer un répertoire" de Far prend une chaîne séparée par des barres obliques pour signifier "créer des répertoires imbriqués" donc j'ai pu le faire en quelques étapes en préparant une chaîne dans l'éditeur interne avec un peu de copier-coller.

Le chemin le plus long que j'ai pu créer était de 32739 caractères, en comptant à partir de "C:\" (c'est-à-dire qu'il n'inclut pas "\\?\" ajouté par Far). L'erreur que j'obtiens en essayant de créer un répertoire ou un fichier avec juste un caractère supplémentaire est "Le nom de fichier ou d'extension est trop long.". Si j'essaie d'entrer dans ce répertoire, j'obtiens la même erreur.

MODIFIER : j'ai passé du temps dans le débogueur et voici ce qui se passe au niveau de l'API Win32 :

  1. J'essaie de créer un fichier avec un caractère au-dessus de la limite.
  2. Far appelle CreateFileW avec la chaîne "\\?\C:\123[...]012345" qui fait 32744 caractères de large (sans compter le zéro terminal).
  3. CreateFileW effectue quelques vérifications supplémentaires, convertit la chaîne null-terminée en UNICODE_STRING (Longueur=65488, Longueur maximale=65490) et prépare une structure OBJECT_ATTRIBUTES.
  4. CreateFileW appelle ensuite NtCreateFile dans ntdll.dll, qui n'est qu'un wrapper autour de l'instruction syscall.
  5. NtCreateFile renvoie 0xC0000106 (STATUS_NAME_TOO_LONG).
  6. Cette valeur de statut est ensuite convertie (en utilisant RtlNtStatusToDosError) en l'erreur Win32 206 (ERROR_FILENAME_EXCED_RANGE).

Je ne me suis pas ennuyé à vérifier ce qui se passe dans le noyau, mais je suppose que je pourrais jeter un coup d'œil aussi.

MODIFIER 2 : j'ai exécuté WinObj et j'ai découvert que sur mon système, C: est un lien symbolique vers \Device\HarddiskVolume1. Cette chaîne fait 23 caractères. Si nous remplaçons le \C: dans la chaîne passée à NtCreateFile par cela, nous obtenons 32744 - 3 + 23 = 32764 caractères. Avec le zéro terminal, cela nécessite 65530 octets. Toujours en dessous de la limite (0xFFFF=65535) donc je suppose qu'il y a quelque chose en plus ajouté, comme un nom de session ou d'espace de noms.

MODIFIER 3 : après avoir passé par le noyau :

  1. NtCreateFile appelle IopCreateFile
  2. IopCreateFile appelle ObOpenObjectByName
  3. ObOpenObjectByName appelle ObpLookupObjectName
  4. ObpLookupObjectName vérifie ObpDosDevicesShortNamePrefix ("\??\") -> succès
  5. il saute le préfixe et divise la partie restante en "C:" et "\1234..."
  6. il résout le "C:" avec un appel à ObpLookupDirectoryEntry
  7. ensuite, il appelle ObpParseSymbolicLink en lui passant l'entrée de répertoire recherchée (_OBJECT_SYMBOLIC_LINK avec LinkTarget == "\Device\HarddiskVolume1" et DosDeviceDriveIndex == 3) et la partie restante du nom.
  8. Il fait ensuite quelque chose comme ceci (fidèlement reproduit par ReactOS) :

    CheminCible = &SymlinkObject->LinkTarget;
    LongueurTemp = CheminCible->Longueur;
    LongueurTotale = LongueurTemp + LongueurRestante->Longueur;
    si (LongueurUtilisée > 0xFFF0)
        return STATUS_NAME_TOO_LONG;

    Dans notre cas, 46 + 65476 = 65522 (0xfff2) ce qui est juste au-dessus de la limite.

    Voilà, mystère résolu (j'espère !).

P.S. tout a été testé sous Windows 7 x64 SP1.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X