Removendo uma pasta (aparentemente) infinitamente recursiva

45

De alguma forma, uma de nossas antigas caixas Server 2008 (não R2) desenvolveu uma pasta aparentemente infinitamente recursiva. Isso está funcionando com nossos backups, pois o agente de backup tenta recorrer à pasta e nunca mais retorna.

A estrutura de pastas é parecida com:

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

... e assim por diante. É como um daqueles conjunto de Mandelbrot que usamos para brincar nos anos 90.

Eu tentei:

  • Excluindo do Explorer. Sim, sou otimista.
  • RMDIR C:\Storage\Folder1 /Q/S - isso retorna The directory is not empty
  • ROBOCOPY C:\temp\EmptyDirectory C:\Storage\Folder1 /PURGE - isso passa pelas pastas por alguns minutos antes de o robocopy.exe travar.

Alguém pode sugerir uma maneira de acabar com essa pasta?

    
por KenD 12.02.2015 / 12:12

7 respostas

45

Obrigado a todos pelo conselho útil.

Indo além do território do StackOverflow, resolvi o problema ao criar esse trecho de código C #. Ele usa a biblioteca Delimon.Win32.IO que aborda especificamente os problemas de acesso a caminhos longos de arquivos.

Apenas para o caso de isso ajudar alguém, aqui está o código - ele passou dos 1600 níveis de recursão de que eu estava presa e levou cerca de 20 minutos para removê-los.

using System;
using Delimon.Win32.IO;

namespace ConsoleApplication1
{
    class Program
    {
        private static int level;
        static void Main(string[] args)
        {
            // Call the method to delete the directory structure
            RecursiveDelete(new DirectoryInfo(@"\server\c$\storage\folder1"));
        }

        // This deletes a particular folder, and recurses back to itself if it finds any subfolders
        public static void RecursiveDelete(DirectoryInfo Dir)
        {
            level++;
            Console.WriteLine("Now at level " +level);
            if (!Dir.Exists)
                return;

            // In any subdirectory ...
            foreach (var dir in Dir.GetDirectories())
            {
                // Call this method again, starting at the subdirectory
                RecursiveDelete(dir);
            }

            // Finally, delete the directory, and any files below it
            Dir.Delete(true);
            Console.WriteLine("Deleting directory at level " + level);
            level--;
        }
    }
}
    
por 12.02.2015 / 19:44
25

Pode ser um ponto de junção recursivo. Tal coisa pode ser criada com junction a um arquivo e utilitário de disco de Sysinternals .

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

E agora você pode ir infinitamente para baixo c: \ Hello \ Hello \ Hello .... (até que MAX_PATH seja alcançado, 260 caracteres para a maioria dos comandos, mas 32.767 caracteres para algumas funções da API do Windows).

Uma lista de diretórios mostra que é uma junção:

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    <DIR>          .
12/02/2015  08:18 AM    <DIR>          ..
12/02/2015  08:18 AM    <JUNCTION>     hello [\??\c:\hello]
               0 File(s)              0 bytes
               3 Dir(s)  461,591,506,944 bytes free

C:\>

Para excluir, use o utilitário de junção:

junction -d c:\Hello\Hello
    
por 12.02.2015 / 14:28
16

Não é uma resposta, mas não tenho representante suficiente para um comentário.

Uma vez eu consertei esse problema em um disco FAT16 de 500MB, então enorme, em um sistema MS-DOS. Eu usei o DOS para depurar manualmente e analisar a tabela de diretórios. Em seguida, inverti um bit para marcar o diretório recursivo como excluído. Minha cópia de Dettman e Wyatt 'DOS Programmers' Reference 'me mostrou o caminho.

Ainda estou orgulhosa disso. Eu ficaria surpreso e aterrorizado se houvesse qualquer ferramenta de propósito geral que tivesse tanto poder sobre os volumes FAT32 ou NTFS. A vida era mais simples naquela época.

    
por 13.02.2015 / 15:06
8

O Java também pode lidar com caminhos de arquivos longos. E também pode ser muito mais rápido. Esse código (que eu copiei da documentação da Java API) excluirá uma estrutura de diretórios profunda de nível 1600 em aproximadamente 1 segundo (no Windows 7, Java 8.0) e sem risco de estouro de pilha, pois não usa recursão de fato.

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<Path>() {
         @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"));
  }
}
    
por 14.02.2015 / 18:35
6

Você não precisa de nomes longos de caminho se você chdir no diretório e apenas usar caminhos relativos para rmdir .

Ou, se você tiver um shell POSIX instalado ou portar isso para o equivalente do DOS:

# untested code, didn't bother actually testing since the OP already solved the problem.

while [ -d Folder1 ]; do
    mv Folder1/Folder1/Folder1/Folder1  tmp # repeat more times to work in larger batches
    rm -r Folder1     # remove the first several levels remaining after moving the main tree out
    # then repeat to end up with the remaining big tree under the original name
    mv tmp/Folder1/Folder1/.../Folder1 Folder1 
    rm -r tmp
done

(Usar uma variável de shell para rastrear onde você a renomeou para a condição de loop é a outra alternativa para desenrolar o loop como eu fiz lá.)

Isso evita a sobrecarga da CPU da solução do KenD, que força o sistema operacional a percorrer a árvore do topo para o nível n th sempre que um novo nível é adicionado, verificando permissões, etc. Portanto, ele tem sum(1, n) = n * (n-1) / 2 = O(n^2) complexidade do tempo . As soluções que cortam um pedaço do início da cadeia devem ser O(n) , a menos que o Windows precise atravessar uma árvore ao renomear seu diretório pai. (Linux / Unix não.) Soluções que chdir até o final da árvore e usam caminhos relativos a partir daí, removendo diretórios como chdir , também deve ser O(n) , assumindo o O SO não precisa verificar todos os seus diretórios pai em todas as chamadas do sistema, quando você faz coisas enquanto está em algum lugar no CD.

find Folder1 -depth -execdir rmdir {} + executará o rmdir enquanto estiver no CD com o diretório mais profundo. Ou, na verdade, a opção -delete do find funciona em diretórios e implica em -depth . Então find Folder1 -delete deve fazer exatamente a mesma coisa, mas mais rápido. Sim, o GNU find no Linux desce varrendo um diretório, CDing para subdiretórios com caminhos relativos, então rmdir com um caminho relativo, então chdir("..") . Ele não faz uma nova varredura de diretórios durante a subida, então consumiria O(n) RAM.

Isso foi realmente uma aproximação: strace mostra que ele realmente usa unlinkat(AT_FDCWD, "tmp", AT_REMOVEDIR) , open("..", O_DIRECTORY|...) e fchdir(the fd from opening the directory) , com um bocado de fstat chamadas misturadas também. Mas o efeito é o mesmo se a árvore de diretórios não for modificada enquanto a localização está sendo executada.

edit: Apenas por brincadeira, eu tentei isso no GNU / Linux (Ubuntu 14.10, em uma CPU Core2Duo de primeira geração de 2.4GHz, em um sistema de arquivos XFS em uma unidade WD Power Green de 2,5 TB (WD25EZRS)).

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

real    0m1.141s
user    0m0.005s
sys     0m0.052s

find annoyingfoldername/ | wc
   2000    2000 38019001  # 2k lines / 2k words / 38M characters of text


ll -R annoyingfoldername
... eventually
ls: cannot access ./annoyingfoldername/annoyingfoldername/annoyingfoldername/annoyingfoldername/annoyingfoldername/annoyingfoldername/annoyingfoldername/annoyingfoldername/annoyingfoldername/annoyingfoldername/annoyingfoldername/annoyingfoldername/annoyingfoldername/annoyingfoldername/annoyingfoldername/annoyingfoldername/annoyingfoldername/annoyingfoldername/annoyingfoldername/annoyingfoldername/annoyingfoldername/annoyingfoldername/annoyingfoldername/annoyingfoldername/annoyingfoldername/annoyingfoldername/annoyingfoldername/annoyingfoldername/annoyingfoldername/annoyingfoldername/annoyingfoldername/annoyingfoldername/annoyingfoldername/annoyingfoldername/annoyingfoldername/annoyingfoldername/annoyingfoldername/annoyingfoldername/annoyingfoldername/annoyingfoldername/annoyingfoldername/annoyingfoldername/annoyingfoldername/annoyingfoldername/annoyingfoldername/annoyingfoldername/annoyingfoldername/annoyingfoldername/annoyingfoldername/annoyingfoldername/annoyingfoldername/annoyingfoldername/annoyingfoldername/annoyingfoldername/annoyingfoldername/annoyingfoldername/annoyingfoldername/annoyingfoldername/annoyingfoldername/annoyingfoldername/annoyingfoldername/annoyingfoldername/annoyingfoldername/annoyingfoldername/annoyingfoldername/annoyingfoldername/annoyingfoldername/annoyingfoldername/annoyingfoldername/annoyingfoldername/annoyingfoldername/annoyingfoldername/annoyingfoldername/annoyingfoldername/annoyingfoldername/annoyingfoldername/annoyingfoldername/annoyingfoldername/annoyingfoldername/annoyingfoldername/annoyingfoldername/annoyingfoldername/annoyingfoldername/annoyingfoldername/annoyingfoldername/annoyingfoldername/annoyingfoldername/annoyingfoldername/annoyingfoldername/annoyingfoldername/annoyingfoldername/annoyingfoldername/annoyingfoldername/annoyingfoldername/annoyingfoldername/annoyingfoldername/annoyingfoldername/annoyingfoldername/annoyingfoldername/annoyingfoldername/annoyingfoldername/annoyingfoldername/annoyingfoldername/annoyingfoldername/annoyingfoldername/annoyingfoldername/annoyingfoldername/annoyingfoldername/annoyingfoldername/annoyingfoldername/annoyingfoldername/annoyingfoldername/annoyingfoldername/annoyingfoldername/annoyingfoldername/annoyingfoldername/annoyingfoldername/annoyingfoldername/annoyingfoldername/annoyingfoldername/annoyingfoldername/annoyingfoldername/annoyingfoldername/annoyingfoldername/annoyingfoldername/annoyingfoldername/annoyingfoldername/annoyingfoldername/annoyingfoldername/annoyingfoldername/annoyingfoldername/annoyingfoldername/annoyingfoldername/annoyingfoldername/annoyingfoldername/annoyingfoldername/annoyingfoldername/annoyingfoldername/annoyingfoldername/annoyingfoldername/annoyingfoldername/annoyingfoldername/annoyingfoldername/annoyingfoldername/annoyingfoldername/annoyingfoldername/annoyingfoldername/annoyingfoldername/annoyingfoldername/annoyingfoldername/annoyingfoldername/annoyingfoldername/annoyingfoldername/annoyingfoldername/annoyingfoldername/annoyingfoldername/annoyingfoldername/annoyingfoldername/annoyingfoldername/annoyingfoldername/annoyingfoldername/annoyingfoldername/annoyingfoldername/annoyingfoldername/annoyingfoldername/annoyingfoldername/annoyingfoldername/annoyingfoldername/annoyingfoldername/annoyingfoldername/annoyingfoldername/annoyingfoldername/annoyingfoldername/annoyingfoldername/annoyingfoldername/annoyingfoldername/annoyingfoldername/annoyingfoldername/annoyingfoldername/annoyingfoldername/annoyingfoldername/annoyingfoldername/annoyingfoldername/annoyingfoldername/annoyingfoldername/annoyingfoldername/annoyingfoldername/annoyingfoldername/annoyingfoldername/annoyingfoldername/annoyingfoldername/annoyingfoldername/annoyingfoldername/annoyingfoldername/annoyingfoldername/annoyingfoldername/annoyingfoldername/annoyingfoldername/annoyingfoldername/annoyingfoldername/annoyingfoldername/annoyingfoldername/annoyingfoldername/annoyingfoldername/annoyingfoldername/annoyingfoldername/annoyingfoldername/annoyingfoldername/annoyingfoldername/annoyingfoldername/annoyingfoldername/annoyingfoldername/annoyingfoldername/annoyingfoldername/annoyingfoldername/annoyingfoldername: File name too long
total 0
?????????? ? ? ? ?            ? annoyingfoldername

time find annoyingfoldername -delete

real    0m0.054s
user    0m0.004s
sys     0m0.049s

# about the same for normal rm -r,
# which also didn't fail due to long path names

(mkdir -p cria um diretório e qualquer componente do caminho ausente).

Sim, realmente 0,05 segundos para 2k rmdir ops. O xfs é muito bom em reunir as operações de metadados juntas no periódico, uma vez que elas consertaram as operações de metadados como 10 anos atrás.

No ext4, create levou 0m0.279s, delete com find ainda levou 0m0.074s.

    
por 15.02.2015 / 06:40
0

Eu me deparei com o mesmo problema com uma pasta de pastas com mais de 5000 diretórios que alguns aplicativos Java fizeram e escrevi um programa que ajudará a remover essa pasta. Todo o código-fonte está neste link:

link

Ele removeu tudo depois de um tempo, mas conseguiu fazer o trabalho, espero que ajude as pessoas que (como eu) enfrentam o mesmo problema frustrante

    
por 08.08.2015 / 20:35
0

Eu também tive isso, em um sistema autônomo do Windows 10 embora. C: \ Usuário \ Nome \ Repetir \ Repetir \ Repetir \ Repetir \ Repetir \ Repetir \ Repetir aparentemente ao infinito.

Eu poderia navegar usando o Windows ou o Prompt de Comando para o 50º e não mais. Não consegui eliminá-lo nem clique nele, etc.

C é minha linguagem, então eventualmente eu escrevi um programa com um loop de chamadas do sistema, que se repetem até falharem. Você poderia fazer isso em qualquer idioma, mesmo em lote DOS. Fiz um diretório chamado tmp e movi Repeat \ Repeat, deletei a pasta Repeat agora vazia e movi tmp \ Repeat de volta para a pasta atual. De novo e de novo!

 while (times<2000)
 {
  ChkSystem("move Repeat\Repeat tmp");
  ChkSystem("rd Repeat");
  ChkSystem("move tmp\Repeat Repeat");
  ++times;
  printf("Removed %d nested so far.\n", times);
 }

O ChkSystem apenas executa uma chamada ao sistema () e verifica o valor de retorno, parando se ele falhar.

Importante, falhou várias vezes. Eu pensei que talvez o meu programa não estivesse funcionando, ou que fosse infinitamente longo, afinal. No entanto, eu já tive isso antes com as chamadas do sistema, com as coisas não sendo sincronizadas, então executei o programa novamente e continuei de onde parei, portanto não pense imediatamente que seu programa não está funcionando. Então, no total, depois de executá-lo cerca de 20 vezes, apagou todos eles. No total, foram originalmente cerca de 1280 pastas. Não faço ideia do que causou isso. Louco.

    
por 31.03.2017 / 18:24