📄 shred.c
字号:
n = 0; break; } randpasses += k; n -= k; } else if ((size_t) k <= n) { /* Full block of patterns */ memcpy (d, p, k * sizeof (int)); p += k; d += k; n -= k; } else if (n < 2 || 3 * n < (size_t) k) { /* Finish with random */ randpasses += n; break; } else { /* Pad out with k of the n available */ do { if (n == (size_t) k-- || irand_mod (&r, k) < n) { *d++ = *p; n--; } p++; } while (n); break; } } top = num - randpasses; /* Top of initialized data */ /* assert (d == dest+top); */ /* * We now have fixed patterns in the dest buffer up to * "top", and we need to scramble them, with "randpasses" * random passes evenly spaced among them. * * We want one at the beginning, one at the end, and * evenly spaced in between. To do this, we basically * use Bresenham's line draw (a.k.a DDA) algorithm * to draw a line with slope (randpasses-1)/(num-1). * (We use a positive accumulator and count down to * do this.) * * So for each desired output value, we do the following: * - If it should be a random pass, copy the pass type * to top++, out of the way of the other passes, and * set the current pass to -1 (random). * - If it should be a normal pattern pass, choose an * entry at random between here and top-1 (inclusive) * and swap the current entry with that one. */ randpasses--; /* To speed up later math */ accum = randpasses; /* Bresenham DDA accumulator */ for (n = 0; n < num; n++) { if (accum <= randpasses) { accum += num - 1; dest[top++] = dest[n]; dest[n] = -1; } else { swap = n + irand_mod (&r, top - n - 1); k = dest[n]; dest[n] = dest[swap]; dest[swap] = k; } accum -= randpasses; } /* assert (top == num); */ memset (&r, 0, sizeof r); /* Wipe this on general principles */}/* * The core routine to actually do the work. This overwrites the first * size bytes of the given fd. Returns -1 on error, 0 on success. */static intdo_wipefd (int fd, char const *qname, struct isaac_state *s, struct Options const *flags){ size_t i; struct stat st; off_t size; /* Size to write, size to read */ unsigned long n; /* Number of passes for printing purposes */ int *passarray; n = 0; /* dopass takes n -- 0 to mean "don't print progress" */ if (flags->verbose) n = flags->n_iterations + ((flags->zero_fill) != 0); if (fstat (fd, &st)) { error (0, errno, "%s: fstat", qname); return -1; } /* If we know that we can't possibly shred the file, give up now. Otherwise, we may go into a infinite loop writing data before we find that we can't rewind the device. */ if ((S_ISCHR (st.st_mode) && isatty (fd)) || S_ISFIFO (st.st_mode) || S_ISSOCK (st.st_mode)) { error (0, 0, _("%s: invalid file type"), qname); return -1; } /* Allocate pass array */ passarray = xmalloc (flags->n_iterations * sizeof (int)); size = flags->size; if (size == -1) { /* Accept a length of zero only if it's a regular file. For any other type of file, try to get the size another way. */ if (S_ISREG (st.st_mode)) { size = st.st_size; if (size < 0) { error (0, 0, _("%s: file has negative size"), qname); return -1; } } else { size = lseek (fd, (off_t) 0, SEEK_END); if (size <= 0) { /* We are unable to determine the length, up front. Let dopass do that as part of its first iteration. */ size = -1; } } if (0 <= size && !(flags->exact)) { size += ST_BLKSIZE (st) - 1 - (size - 1) % ST_BLKSIZE (st); /* If in rounding up, we've just overflowed, use the maximum. */ if (size < 0) size = TYPE_MAXIMUM (off_t); } } /* Schedule the passes in random order. */ genpattern (passarray, flags->n_iterations, s); /* Do the work */ for (i = 0; i < flags->n_iterations; i++) { if (dopass (fd, qname, &size, passarray[i], s, i + 1, n) < 0) { memset (passarray, 0, flags->n_iterations * sizeof (int)); free (passarray); return -1; } } memset (passarray, 0, flags->n_iterations * sizeof (int)); free (passarray); if (flags->zero_fill) if (dopass (fd, qname, &size, 0, s, flags->n_iterations + 1, n) < 0) return -1; /* Okay, now deallocate the data. The effect of ftruncate on non-regular files is unspecified, so don't worry about any errors reported for them. */ if (flags->remove_file && ftruncate (fd, (off_t) 0) != 0 && S_ISREG (st.st_mode)) { error (0, errno, _("%s: error truncating"), qname); return -1; } return 0;}/* A wrapper with a little more checking for fds on the command line */static intwipefd (int fd, char const *qname, struct isaac_state *s, struct Options const *flags){ int fd_flags = fcntl (fd, F_GETFL); if (fd_flags < 0) { error (0, errno, "%s: fcntl", qname); return -1; } if (fd_flags & O_APPEND) { error (0, 0, _("%s: cannot shred append-only file descriptor"), qname); return -1; } return do_wipefd (fd, qname, s, flags);}/* --- Name-wiping code --- *//* Characters allowed in a file name - a safe universal set. */static char const nameset[] ="0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_+=%@#.";/* * This increments the name, considering it as a big-endian base-N number * with the digits taken from nameset. Characters not in the nameset * are considered to come before nameset[0]. * * It's not obvious, but this will explode if name[0..len-1] contains * any 0 bytes. * * This returns the carry (1 on overflow). */static intincname (char *name, unsigned len){ char const *p; if (!len) return 1; p = strchr (nameset, name[--len]); /* If the character is not found, replace it with a 0 digit */ if (!p) { name[len] = nameset[0]; return 0; } /* If this character has a successor, use it */ if (p[1]) { name[len] = p[1]; return 0; } /* Otherwise, set this digit to 0 and increment the prefix */ name[len] = nameset[0]; return incname (name, len);}/* * Repeatedly rename a file with shorter and shorter names, * to obliterate all traces of the file name on any system that * adds a trailing delimiter to on-disk file names and reuses * the same directory slot. Finally, unlink it. * The passed-in filename is modified in place to the new filename. * (Which is unlinked if this function succeeds, but is still present if * it fails for some reason.) * * The main loop is written carefully to not get stuck if all possible * names of a given length are occupied. It counts down the length from * the original to 0. While the length is non-zero, it tries to find an * unused file name of the given length. It continues until either the * name is available and the rename succeeds, or it runs out of names * to try (incname wraps and returns 1). Finally, it unlinks the file. * * The unlink is Unix-specific, as ANSI-standard remove has more * portability problems with C libraries making it "safe". rename * is ANSI-standard. * * To force the directory data out, we try to open the directory and * invoke fdatasync on it. This is rather non-standard, so we don't * insist that it works, just fall back to a global sync in that case. * This is fairly significantly Unix-specific. Of course, on any * filesystem with synchronous metadata updates, this is unnecessary. */static intwipename (char *oldname, char const *qoldname, struct Options const *flags){ char *newname, *base; /* Base points to filename part of newname */ unsigned len; int first = 1; int err; int dir_fd; /* Try to open directory to sync *it* */ newname = xstrdup (oldname); if (flags->verbose) error (0, 0, _("%s: removing"), qoldname); /* Find the file name portion */ base = strrchr (newname, '/'); /* Temporary hackery to get a directory fd */ if (base) { *base = '\0'; dir_fd = open (newname, O_RDONLY | O_NOCTTY); *base = '/'; } else { dir_fd = open (".", O_RDONLY | O_NOCTTY); } base = base ? base + 1 : newname; len = strlen (base); while (len) { memset (base, nameset[0], len); base[len] = 0; do { struct stat st; if (lstat (newname, &st) < 0) { if (rename (oldname, newname) == 0) { if (dir_fd < 0 || (fdatasync (dir_fd) < 0 && fsync (dir_fd) < 0)) sync (); /* Force directory out */ if (flags->verbose) { /* * People seem to understand this better than talking * about renaming oldname. newname doesn't need * quoting because we picked it. oldname needs to * be quoted only the first time. */ char const *old = (first ? qoldname : oldname); error (0, 0, _("%s: renamed to %s"), old, newname); first = 0; } memcpy (oldname + (base - newname), base, len + 1); break; } else { /* The rename failed: give up on this length. */ break; } } else { /* newname exists, so increment BASE so we use another */ } } while (!incname (base, len)); len--; } free (newname); err = unlink (oldname); if (dir_fd < 0 || (fdatasync (dir_fd) < 0 && fsync (dir_fd) < 0)) sync (); close (dir_fd); if (!err && flags->verbose) error (0, 0, _("%s: removed"), qoldname); return err;}/* * Finally, the function that actually takes a filename and grinds * it into hamburger. * * FIXME * Detail to note: since we do not restore errno to EACCES after * a failed chmod, we end up printing the error code from the chmod. * This is actually the error that stopped us from proceeding, so * it's arguably the right one, and in practice it'll be either EACCES * again or EPERM, which both give similar error messages. * Does anyone disagree? */static intwipefile (char *name, char const *qname, struct isaac_state *s, struct Options const *flags){ int err, fd; fd = open (name, O_WRONLY | O_NOCTTY); if (fd < 0) { if (errno == EACCES && flags->force) { if (chmod (name, S_IWUSR) >= 0) /* 0200, user-write-only */ fd = open (name, O_WRONLY | O_NOCTTY); } else if ((errno == ENOENT || errno == ENOTDIR) && strncmp (name, "/dev/fd/", 8) == 0) { /* We accept /dev/fd/# even if the OS doesn't support it */ int errnum = errno; unsigned long num; char *p; errno = 0; num = strtoul (name + 8, &p, 10); /* If it's completely decimal with no leading zeros... */ if (errno == 0 && !*p && num <= INT_MAX && (('1' <= name[8] && name[8] <= '9') || (name[8] == '0' && !name[9]))) { return wipefd ((int) num, qname, s, flags); } errno = errnum; } } if (fd < 0) { error (0, errno, "%s", qname); return -1; } err = do_wipefd (fd, qname, s, flags); if (close (fd) != 0) { error (0, 0, "%s: close", qname); err = -1; } if (err == 0 && flags->remove_file) { err = wipename (name, qname, flags); if (err < 0) error (0, 0, _("%s: cannot remove"), qname); } return err;}intmain (int argc, char **argv){ struct isaac_state s; int err = 0; struct Options flags; char **file; int n_files; int c; int i; program_name = argv[0]; setlocale (LC_ALL, ""); bindtextdomain (PACKAGE, LOCALEDIR); textdomain (PACKAGE); atexit (close_stdout); isaac_seed (&s); memset (&flags, 0, sizeof flags); flags.n_iterations = DEFAULT_PASSES; flags.size = -1; while ((c = getopt_long (argc, argv, "fn:s:uvxz", long_opts, NULL)) != -1) { switch (c) { case 0: break; case 'f': flags.force = 1; break; case 'n': { uintmax_t tmp; if (xstrtoumax (optarg, NULL, 10, &tmp, NULL) != LONGINT_OK || (word32) tmp != tmp || ((size_t) (tmp * sizeof (int)) / sizeof (int) != tmp)) { error (1, 0, _("%s: invalid number of passes"), quotearg_colon (optarg)); } flags.n_iterations = (size_t) tmp; } break; case 'u': flags.remove_file = 1; break; case 's': { uintmax_t tmp; if (xstrtoumax (optarg, NULL, 0, &tmp, "cbBkKMGTPEZY0") != LONGINT_OK) { error (1, 0, _("%s: invalid file size"), quotearg_colon (optarg)); } flags.size = tmp; } break; case 'v': flags.verbose = 1; break; case 'x': flags.exact = 1; break; case 'z': flags.zero_fill = 1; break; case_GETOPT_HELP_CHAR; case_GETOPT_VERSION_CHAR (PROGRAM_NAME, AUTHORS); default: usage (1); } } file = argv + optind; n_files = argc - optind; if (n_files == 0) { error (0, 0, _("missing file argument")); usage (1); } for (i = 0; i < n_files; i++) { char *qname = xstrdup (quotearg_colon (file[i])); if (STREQ (file[i], "-")) { if (wipefd (STDOUT_FILENO, qname, &s, &flags) < 0) err = 1; } else { /* Plain filename - Note that this overwrites *argv! */ if (wipefile (file[i], qname, &s, &flags) < 0) err = 1; } free (qname); } /* Just on general principles, wipe s. */ memset (&s, 0, sizeof s); exit (err);}/* * vim:sw=2:sts=2: */
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -