45 votes

Suppression d'un dossier (apparemment) récursif à l'infini

De quelque manière, l'une de nos anciennes boîtes Server 2008 (pas R2) a développé un dossier qui semble se récursif à l'infini. Cela perturbe nos sauvegardes, car l'agent de sauvegarde tente de descendre dans le dossier et ne revient jamais.

La structure du dossier ressemble à quelque chose comme :

C:\Storage\Folder1
C:\Storage\Folder1\Folder1
C:\Storage\Folder1\Folder1\Folder1
C:\Storage\Folder1\Folder1\Folder1\Folder1

... et ainsi de suite. C'est comme l'un de ces ensembles de Mandelbrot avec lesquels nous jouions tous dans les années 90.

J'ai essayé :

  • Le supprimer depuis l'Explorateur. Oui, je suis un optimiste.
  • RMDIR C:\Storage\Folder1 /Q/S - cela renvoie Le répertoire n'est pas vide
  • ROBOCOPY C:\temp\EmptyDirectory C:\Storage\Folder1 /PURGE - cela parcourt les dossiers pendant quelques minutes avant que robocopy.exe ne plante.

Est-ce que quelqu'un peut suggérer un moyen d'éliminer ce dossier une bonne fois pour toutes ?

0 votes

Avez-vous essayé rmdir sans l'option silencieuse? rmdir C:\storage\folder1 /s

0 votes

Oui, même résultat désolé.

1 votes

Je vais essayer /MIR à la place : ROBOCOPY /MIR C:\temp\EmptyDirectory C:\Storage\Folder1 il peut également être utile d'exécuter un chkdsk juste pour rire.

45voto

jaffo Points 1

Merci à tous pour les conseils utiles.

S'aventurant bien dans le territoire de StackOverflow, j'ai résolu le problème en concoctant ce extrait de code C#. Il utilise la bibliothèque Delimon.Win32.IO qui traite spécifiquement des problèmes d'accès aux longs chemins de fichiers.

Juste au cas où cela puisse aider quelqu'un d'autre, voici le code - il a réussi à passer les ~1600 niveaux de récursion sur lesquels j'étais bloqué et a pris environ 20 minutes pour tous les supprimer.

using System;
using Delimon.Win32.IO;

namespace ConsoleApplication1
{
    class Program
    {
        private static int level;
        static void Main(string[] args)
        {
            // Appeler la méthode pour supprimer la structure du répertoire
            RecursiveDelete(new DirectoryInfo(@"\\serveur\\c$\\stockage\\dossier1"));
        }

        // Cela supprime un dossier particulier et se réappelle s'il trouve des sous-dossiers
        public static void RecursiveDelete(DirectoryInfo Dir)
        {
            level++;
            Console.WriteLine("Maintenant au niveau " +level);
            if (!Dir.Exists)
                return;

            // Dans tout sous-répertoire ...
            foreach (var dir in Dir.GetDirectories())
            {
                // Appeler à nouveau cette méthode, en commençant par le sous-répertoire
                RecursiveDelete(dir);
            }

            // Enfin, supprimer le répertoire et les fichiers en dessous
            Dir.Delete(true);
            Console.WriteLine("Suppression du répertoire au niveau " + level);
            level--;
        }
    }
}

0 votes

Y a-t-il une raison de ne pas simplement utiliser Directory.Delete(..., true) ?

2 votes

J'ai essayé, même en utilisant la version Delimon de .Delete (au lieu de la version normale System.IO), et bien qu'il n'ait pas lancé d'exception, il ne semblait pas faire grand-chose. Certainement, la récursion en utilisant la méthode ci-dessus a pris du temps, et .Delete n'a mâché que quelques secondes. Peut-être a-t-il effacé quelques répertoires puis abandonné?

4 votes

Avez-vous déjà compris comment cela s'était produit en premier lieu? On dirait que c'est la faute d'un programme utilisateur vraiment mal écrit.

25voto

Brian Points 3366

Peut être un point de jonction récursif. Une telle chose peut être créée avec junction, un utilitaire de fichiers et de disque de Sysinternals.

mkdir c:\Hello
junction c:\Hello\Hello c:\Hello

Et vous pouvez maintenant descendre sans fin dans c:\Hello\Hello\Hello.... (enfin jusqu'à ce que MAX_PATH soit atteint, 260 caractères pour la plupart des commandes mais 32,767 caractères pour certaines fonctions de l'API Windows).

Une liste de répertoire montre qu'il s'agit d'un point de jonction :

C:\>dir c:\hello
 Volume in drive C is DR1
 Volume Serial Number is 993E-B99C

 Directory of c:\hello

12/02/2015  08:18 AM    

Pour supprimer, utilisez l'utilitaire de point de jonction :

junction -d c:\Hello\Hello

4 votes

Malheureusement, un DIR me montre simplement des répertoires ordinaires - aucune trace de jonctions, je le crains

2 votes

Pouvez-vous faire une vérification rapide avec junction -s C:\Storage\Folder1 ?

3 votes

Aucun point de reparse trouvé :(

17voto

Oscar Carballal Points 1239

Pas une réponse, mais je n'ai pas assez de réputation pour un commentaire.

J'ai une fois résolu ce problème sur un disque FAT16 alors énorme de 500 Mo sur un système MS-DOS. J'ai utilisé DOS debug pour faire une extraction manuelle et analyser le tableau de répertoire. J'ai alors basculé un bit pour marquer le répertoire récursif comme supprimé. Mon exemplaire de la référence DOS 'Programmers' de Dettman et Wyatt m'a montré le chemin.

Je suis toujours excessivement fier de cela. Je serais étonné et terrifié s'il y avait un outil généraliste ayant un tel pouvoir sur les volumes FAT32 ou NTFS. La vie était plus simple à l'époque.

8 votes

Je dirais que vous êtes justement fier de cela.

3 votes

+1 a un peu de réputation pour moi. Belle solution.

3 votes

Vous pouvez maintenant supprimer la première phrase de votre réponse.

8voto

SpiderPig Points 191

Java peut également gérer des chemins de fichiers longs. Et il peut le faire beaucoup plus rapidement aussi. Ce code (que j'ai copié de la documentation de l'API Java) supprimera une structure de répertoire de 1600 niveaux de profondeur en environ 1 seconde (sous Windows 7, Java 8.0) et sans risque de débordement de la pile car il n'utilise pas réellement de récursion.

import java.nio.file.*;
import java.nio.file.attribute.*;
import java.io.*;

public class DeleteDir {

  static void deleteDirRecur(Path dir) throws IOException {
    Files.walkFileTree(dir, new SimpleFileVisitor() {
         @Override
         public FileVisitResult visitFile(Path file, BasicFileAttributes attrs)
             throws IOException
         {
             Files.delete(file);
             return FileVisitResult.CONTINUE;
         }
         @Override
         public FileVisitResult postVisitDirectory(Path dir, IOException e)
             throws IOException
         {
             if (e == null) {
                 Files.delete(dir);
                 return FileVisitResult.CONTINUE;
             } else {
                 throw e;
             }
         }
     });
  }

  public static void main(String[] args) throws IOException {
    deleteDirRecur(Paths.get("C:/Storage/Folder1"));
  }
}

3 votes

Je te déteste. Tu m'as obligé à voter pour une réponse qui implique l'utilisation de Java, et maintenant je me sens tout sale. J'ai besoin d'une douche.

0 votes

Mes excuses. J'espère que vous guérirez finalement de votre traumatisme. Mais est-ce qu'un langage développé par Microsoft (c#) est vraiment beaucoup mieux?

5 votes

Je suis principalement un adepte de Windows ces jours-ci, donc oui, oui, c'est le cas. Je pourrais aussi être amer et partial contre Java en raison de devoir maintenir 5 versions spécifiques et différentes de JRE sur près de 1 000 clients dans l'environnement de mon employeur, dont l'une remonte à 2009, en raison de la merde... euh, logiciel malveillant... euh, suites logicielles "d'entreprise" que nous utilisons pour des applications critiques pour l'activité.

6voto

MauMen Points 531

Vous n'avez pas besoin de longs chemins d'accès si vous chdir dans le répertoire et utilisez simplement des chemins relatifs vers rmdir.

Ou, si vous avez un shell POSIX installé, ou si vous portez ceci à l'équivalent DOS :

# code non testé, je n'ai pas pris la peine de tester puisque l'OP a déjà résolu le problème.

while [ -d Folder1 ]; do
    mv Folder1/Folder1/Folder1/Folder1  tmp # répète plus de fois pour travailler en plus gros lots
    rm -r Folder1     # enlever les premiers niveaux restants après avoir déplacé l'arborescence principale
    # puis répéter pour finir avec l'arbre restant sous le nom d'origine
    mv tmp/Folder1/Folder1/.../Folder1 Folder1 
    rm -r tmp
done

(Utiliser une variable d'environnement pour suivre où vous l'avez renommée pour la condition de la boucle est l'autre alternative à dérouler la boucle comme je l'ai fait là.)

Cela évite les surdébits CPU de la solution de KenD, qui force le système d'exploitation à parcourir l'arbre du haut au niveau n à chaque fois qu'un nouveau niveau est ajouté, vérifiant les permissions etc. Donc cela a une complexité temporelle de sum(1, n) = n * (n-1) / 2 = O(n^2). Les solutions qui suppriment un morceau du début de la chaîne devraient être O(n), sauf si Windows doit parcourir un arbre lors du renommage de son répertoire parent. (Linux/Unix non plus.) Les solutions qui font chdir jusqu'au bas de l'arbre et utilisent des chemins relatifs à partir de là, en supprimant les répertoires pendant qu'ils remontent, devraient également être O(n), en supposant que le système d'exploitation n'ait pas besoin de vérifier tous vos répertoires parent à chaque appel système, lorsque vous effectuez des tâches tout en étant changeé quelque part.

find Folder1 -depth -execdir rmdir {} + exécutera rmdir tout en étant changé dans le répertoire le plus profond. Ou en fait, l'option -delete de find fonctionne sur les répertoires, et implique -depth. Donc find Folder1 -delete devrait faire exactement la même chose, mais plus rapidement. Oui, GNU find sur Linux descend en balayant un répertoire, en se changeant en sous-répertoires avec des chemins relatifs, puis rmdir avec un chemin relatif, puis chdir(".."). Il ne rescane pas les répertoires en montant, donc il consommerait O(n) de RAM.

C'était vraiment une approximation : strace montre qu'il utilise ACTUELLEMENT unlinkat(AT_FDCWD, "tmp", AT_REMOVEDIR), open("..", O_DIRECTORY|...), et fchdir(le fd de l'ouverture du répertoire), avec un tas d'appels de fstat mélangés, aussi. Mais l'effet est le même si l'arborescence de répertoires n'est pas modifiée pendant que find fonctionne.

modifier : Juste pour rire, j'ai essayé cela sur GNU/Linux (Ubuntu 14.10, sur un CPU Core2Duo de première génération de 2,4GHz, sur un système de fichiers XFS sur un disque WD 2.5TB Green Power (WD25EZRS)).

time mkdir -p $(perl -e 'print "annoyingfoldername/" x 2000, "\n"')

réel    0m1.141s
utilisateur    0m0.005s
système     0m0.052s

find annoyingfoldername/ | wc
   2000    2000 38019001  # 2k lignes / 2k mots / 38M caractères de texte

ll -R annoyingfoldername
... eventually
ls: ne peut accéder à ./annoyingfoldername/annoyingfoldername/annoyingfoldername/annoyingfoldername/annoyingfoldername/annoyingfoldername/annoyingfoldername/annoyingfoldername/annoyingfoldername/annoyingfoldername/annoyingfoldername/annoyingfoldername/annoyingfoldername/annoyingfoldername/annoyingfoldername/annoyingfoldername/annoyingfoldername/annoyingfoldername/annoyingfoldername/annoyingfoldername/annoyingfoldername/annoyingfoldername/annoyingfold...

time find annoyingfoldername -delete

réel    0m0.054s
utilisateur    0m0.004s
système     0m0.049s

# environ la même chose pour rm -r normal,
# qui n'a pas échoué non plus en raison de noms de chemin trop longs

(mkdir -p crée un répertoire et tous les composants de chemin manquants).

Oui, vraiment 0,05 secondes pour 2k opérations rmdir. xfs est très bon pour regrouper ensemble les opérations de métadonnées dans le journal, puisqu'ils ont corrigé les opérations de métadonnées lentes il y a environ 10 ans.

Sur ext4, la création a pris 0m0.279s, la suppression avec find a quand même pris 0m0.074s.

0 votes

A été curieux et l'a essayé sur Linux. Il s'avère que les outils GNU standard sont tous compatibles avec les chemins longs, car ils descendent dans l'arborescence au lieu d'essayer de faire des appels système avec des chemins très longs. Même mkdir est bien lorsque vous lui passez un chemin de 38k sur la ligne de commande!

SistemesEz.com

SystemesEZ est une communauté de sysadmins où vous pouvez résoudre vos problèmes et vos doutes. Vous pouvez consulter les questions des autres sysadmins, poser vos propres questions ou résoudre celles des autres.

Powered by:

X