summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLars Wirzenius <liw@liw.fi>2019-11-02 15:21:09 +0200
committerLars Wirzenius <liw@liw.fi>2019-11-02 15:21:09 +0200
commitb579e9a5aa7bf5dbeb20e2fedb6eb743b305a79d (patch)
tree70fb6b8d8d18161d91e0a21c2321211177378260
downloadfast-rm-rf-b579e9a5aa7bf5dbeb20e2fedb6eb743b305a79d.tar.gz
Add old codeHEADmaster
-rw-r--r--Makefile14
-rw-r--r--fast-rm-rf.c173
2 files changed, 187 insertions, 0 deletions
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..4fed9f8
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,14 @@
+CFLAGS = --std=c99 -Wall -O2 -g
+
+all: fast-rm-rf
+
+fast-rm-rf: fast-rm-rf.c
+
+clean:
+ rm -f fast-rm-rf
+ rm -rf test.data
+
+check: all
+ genbackupdata --create=1m test.data
+ ./fast-rm-rf test.data
+ test ! -e test.data
diff --git a/fast-rm-rf.c b/fast-rm-rf.c
new file mode 100644
index 0000000..c8665a8
--- /dev/null
+++ b/fast-rm-rf.c
@@ -0,0 +1,173 @@
+/*
+ * 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 <http://www.gnu.org/licenses/>.
+ */
+
+/*
+ * 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 <dirent.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/syscall.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <string.h>
+#include <stddef.h>
+#include <stdbool.h>
+#include <getopt.h>
+
+
+/* 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;
+}