Removing an (apparently) infinitely recursive folder
Solution 1:
Thanks to everyone for the useful advice.
Straying well into StackOverflow territory, I've solved the problem by knocking up this snippet of C# code. It uses the Delimon.Win32.IO library that specifically addresses issues accessing long file paths.
Just in case this can help someone else out, here's the code - it got through the ~1600 levels of recursion I'd somehow been stuck with and took around 20 minutes to remove them all.
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--;
}
}
}
Solution 2:
Could be a recursive junction point. Such a thing can be created with junction
a file and disk utility from Sysinternals.
mkdir c:\Hello
junction c:\Hello\Hello c:\Hello
And you can now go endlessly down c:\Hello\Hello\Hello.... (well until MAX_PATH is reached, 260 characters for most commands but 32,767 characters for some Windows API functions).
A directory list shows that it is a junction:
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:\>
To delete use the junction utility:
junction -d c:\Hello\Hello
Solution 3:
Not an answer, but I don't have enough rep for a comment.
I once fixed this problem on a then-huge 500MB FAT16 disc on an MS-DOS system. I used DOS debug to manually dump and parse through the directory table. I then flipped one bit to mark the recursive directory as deleted. My copy of Dettman and Wyatt 'DOS Programmers' Reference' showed me the way.
I am still inordinately proud of this. I would be amazed and terrified if there is any general-purpose tool that has such power over FAT32 or NTFS volumes. Life was simpler back then.
Solution 4:
Java can also deal with long file paths. And it can do it a lot faster too. This code (which I copied from the Java API documentation) will delete a 1600 level deep directory structure in about 1 second (under Windows 7, Java 8.0) and with no risk of stack overflow since it doesn't actually use recursion.
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"));
}
}
Solution 5:
You don't need long pathnames if you chdir
into the directory and just use relative paths to rmdir
.
Or, if you have a POSIX shell installed, or port this to the DOS equivalent:
# 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
(Using a shell variable to track where you renamed it for the loop condition is the other alternative to unrolling the loop like I did there.)
This avoids the CPU overhead of KenD's solution, which forces the OS to traverse the tree from the top to the n
th level every time a new level is added, checking permissions etc. So it has sum(1, n) = n * (n-1) / 2 = O(n^2)
time complexity. Solutions that pare off a chunk from the start of the chain should be O(n)
, unless Windows needs to traverse a tree when renaming its parent directory. (Linux/Unix doesn't.) Solutions that chdir
all the way down to the bottom of the tree and use relative paths from there, removing directories as they chdir
back up, should also be O(n)
, assuming the OS doesn't need to check all your parent directories every system call, when you do things while CDed somewhere.
find Folder1 -depth -execdir rmdir {} +
will run rmdir while CDed to the deepest directory. Or actually, find's -delete
option works on directories, and implies -depth
. So find Folder1 -delete
should do the exact same thing, but faster. Yeah, GNU find on Linux descends by scanning a directory, CDing to subdirectories with relative paths, then rmdir
with a relative path, then chdir("..")
. It doesn't rescan directories while ascending, so it would consume O(n)
RAM.
That was really an approximation: strace
shows it ACTUALLY uses unlinkat(AT_FDCWD, "tmp", AT_REMOVEDIR)
, open("..", O_DIRECTORY|...)
, and fchdir(the fd from opening the directory)
, with a bunch of fstat
calls mixed in, too. But the effect is the same if the directory tree isn't getting modified while find is running.
edit: Just for kicks, I tried this on GNU/Linux (Ubuntu 14.10, on a 2.4GHz first-gen Core2Duo CPU, on an XFS filesystem on a WD 2.5TB Green Power drive (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 creates a directory and any missing path components).
Yes, really 0.05 seconds for 2k rmdir ops. xfs is quite good at batching metadata operations together in the journal, since they fixed meta data ops being slow like 10 years ago.
On ext4, create took 0m0.279s, delete with find still took 0m0.074s.