/*
* 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;
}