/* * fast-rm-rf.c - remove files and directories really fast but unsafely * Copyright 2011 Lars Wirzenius * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ /* * GNU Coreutils has an rm implementation that is not as fast as it should * be. It is, for example, slower than "find -delete", according to my * benchmarks: http://blog.liw.fi/posts/fileops-benchmark/ . * * This program is an attempt to make a program that does the equivalent * of "rm -rf" as quickly as possible for Linux. Some points: * * - use getdents instead of opendir/readdir/closedir * - further, use getdents with a fixed size buffer of reasonable size, * and remove as much as you get, then iterate if you didn't get * everything; this seems to be faster than trying to get everything * in one getdents call for very large directories * - don't use stat to see if something is a directory; use unlink(2) * and check errno for EISDIR instead * - avoid any other unnecessary system calls, too * * Screw portability for this program. * * Ideas for further improvement: * * - use threading: at least one for getdents, at least one for unlink */ #define _GNU_SOURCE #include #include #include #include #include #include #include #include #include #include #include #include /* There seems to be no header that declares this. */ struct linux_dirent { long d_ino; off_t d_off; unsigned short d_reclen; char d_name[]; }; /* The size of the buffer for getdents. */ #define MAXBUF (1024U * 1024U) /* We have no settings, but we want --help to work. */ static int help; static struct option options[] = { { "help", 0, &help, 1 }, { NULL }, }; static void usage(const char *argv0) { printf("usage: %s [--help] FILE...\n", argv0); } /* Remove a file or directory (recursively). */ static int fast_remove(const char *filename) { /* Most things are non-directories. Can we just remove it? */ if (unlink(filename) == 0) return 0; /* Did it fail because it's a directory? If not, abort. */ if (errno != EISDIR) { perror(filename); return -1; } /* Read some filenames in the directory, remove them, then repeat. */ size_t filename_len = strlen(filename); bool got_any; do { /* * Read filenames using getdents. It is not wrapped in a function, * so we have to call via syscall(2). */ char buf[MAXBUF]; int fd = open(filename, O_RDONLY); int n = syscall(SYS_getdents, fd, buf, MAXBUF); close(fd); if (n == -1) { perror(filename); return -1; } /* Iterate over what getdents returned. */ int ret = 0; got_any = false; struct linux_dirent *d; for (char *pos = buf; pos < buf + n; pos += d->d_reclen) { d = (void *) pos; /* Skip . and .. since we really can't remove them. */ if (strcmp(d->d_name, ".") == 0 || strcmp(d->d_name, "..") == 0) continue; got_any = true; /* Construct pathname to file in directory. */ size_t name_len = strlen(d->d_name); char child[filename_len + 1 + name_len + 1]; memcpy(child, filename, filename_len); child[filename_len] = '/'; memcpy(child + filename_len + 1, d->d_name, name_len); child[filename_len + 1 + name_len] = '\0'; /* Recurse. Remember failures, but remove everything we can. */ if (fast_remove(child) == -1) ret = -1; } /* Did we fail? */ if (ret != 0) return -1; } while (got_any); /* Remove the directory itself. */ if (rmdir(filename) == -1) { perror(filename); return -1; } return 0; } int main(int argc, char **argv) { while (getopt_long(argc, argv, "", options, NULL) != -1) continue; if (help) { usage(argv[0]); return 0; } int ret = 0; for (int i = optind; i < argc; ++i) { if (fast_remove(argv[i]) == -1) ret = 1; } return ret; }