diff options
Diffstat (limited to 'src/shred.c')
-rw-r--r-- | src/shred.c | 1002 |
1 files changed, 557 insertions, 445 deletions
diff --git a/src/shred.c b/src/shred.c index 23a4944..ba93f80 100644 --- a/src/shred.c +++ b/src/shred.c @@ -1,12 +1,12 @@ /* shred.c - overwrite files and devices to make it harder to recover data - Copyright (C) 1999-2006 Free Software Foundation, Inc. + Copyright (C) 1999-2016 Free Software Foundation, Inc. Copyright (C) 1997, 1998, 1999 Colin Plumb. - This program is free software; you can redistribute it and/or modify + 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 2, or (at your option) - any later version. + 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 @@ -14,22 +14,10 @@ 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, write to the Free Software Foundation, - Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + along with this program. If not, see <http://www.gnu.org/licenses/>. Written by Colin Plumb. */ -/* TODO: - - use consistent non-capitalization in error messages - - add standard GNU copyleft comment - - - Add -r/-R/--recursive - - Add -i/--interactive - - Reserve -d - - Add -L - - Add an unlink-all option to emulate rm. - */ - /* * Do a more secure overwrite of given files or devices, to make it harder * for even very expensive hardware probing to recover the data. @@ -80,10 +68,10 @@ * drastically bad if told to attack a named pipe or socket? */ -/* The official name of this program (e.g., no `g' prefix). */ +/* The official name of this program (e.g., no 'g' prefix). */ #define PROGRAM_NAME "shred" -#define AUTHORS "Colin Plumb" +#define AUTHORS proper_name ("Colin Plumb") #include <config.h> @@ -92,21 +80,22 @@ #include <assert.h> #include <setjmp.h> #include <sys/types.h> +#ifdef __linux__ +# include <sys/mtio.h> +#endif #include "system.h" -#include "xstrtol.h" +#include "argmatch.h" +#include "xdectoint.h" #include "error.h" #include "fcntl--.h" -#include "getpagesize.h" #include "human.h" -#include "inttostr.h" -#include "quotearg.h" /* For quotearg_colon */ -#include "quote.h" /* For quotearg_colon */ #include "randint.h" #include "randread.h" +#include "stat-size.h" /* Default number of times to overwrite. */ -enum { DEFAULT_PASSES = 25 }; +enum { DEFAULT_PASSES = 3 }; /* How many seconds to wait before checking whether to output another verbose output line. */ @@ -118,12 +107,30 @@ enum { SECTOR_SIZE = 512 }; enum { SECTOR_MASK = SECTOR_SIZE - 1 }; verify (0 < SECTOR_SIZE && (SECTOR_SIZE & SECTOR_MASK) == 0); +enum remove_method +{ + remove_none = 0, /* the default: only wipe data. */ + remove_unlink, /* don't obfuscate name, just unlink. */ + remove_wipe, /* obfuscate name before unlink. */ + remove_wipesync /* obfuscate name, syncing each byte, before unlink. */ +}; + +static char const *const remove_args[] = +{ + "unlink", "wipe", "wipesync", NULL +}; + +static enum remove_method const remove_methods[] = +{ + remove_unlink, remove_wipe, remove_wipesync +}; + struct Options { bool force; /* -f flag: chmod files if necessary */ size_t n_iterations; /* -n flag: Number of iterations */ off_t size; /* -s flag: size of file */ - bool remove_file; /* -u flag: remove file after shredding */ + enum remove_method remove_file; /* -u flag: remove file after shredding */ bool verbose; /* -v flag: Print progress */ bool exact; /* -x flag: Do not round up file size */ bool zero_fill; /* -z flag: Add a final zero pass */ @@ -143,7 +150,7 @@ static struct option const long_opts[] = {"iterations", required_argument, NULL, 'n'}, {"size", required_argument, NULL, 's'}, {"random-source", required_argument, NULL, RANDOM_SOURCE_OPTION}, - {"remove", no_argument, NULL, 'u'}, + {"remove", optional_argument, NULL, 'u'}, {"verbose", no_argument, NULL, 'v'}, {"zero", no_argument, NULL, 'z'}, {GETOPT_HELP_OPTION_DECL}, @@ -151,34 +158,34 @@ static struct option const long_opts[] = {NULL, 0, NULL, 0} }; -/* Global variable for error printing purposes */ -char const *program_name; /* Initialized before any possible use */ - void usage (int status) { if (status != EXIT_SUCCESS) - fprintf (stderr, _("Try `%s --help' for more information.\n"), - program_name); + emit_try_help (); else { - printf (_("Usage: %s [OPTIONS] FILE [...]\n"), program_name); + printf (_("Usage: %s [OPTION]... FILE...\n"), program_name); fputs (_("\ Overwrite the specified FILE(s) repeatedly, in order to make it harder\n\ for even very expensive hardware probing to recover the data.\n\ -\n\ "), stdout); fputs (_("\ -Mandatory arguments to long options are mandatory for short options too.\n\ +\n\ +If FILE is -, shred standard output.\n\ "), stdout); + + emit_mandatory_arg_note (); + printf (_("\ -f, --force change permissions to allow writing if necessary\n\ - -n, --iterations=N Overwrite N times instead of the default (%d)\n\ - --random-source=FILE get random bytes from FILE (default /dev/urandom)\n\ + -n, --iterations=N overwrite N times instead of the default (%d)\n\ + --random-source=FILE get random bytes from FILE\n\ -s, --size=N shred this many bytes (suffixes like K, M, G accepted)\n\ "), DEFAULT_PASSES); fputs (_("\ - -u, --remove truncate and remove file after overwriting\n\ + -u truncate and remove file after overwriting\n\ + --remove[=HOW] like -u but give control on HOW to delete; See below\n\ -v, --verbose show progress\n\ -x, --exact do not round file sizes up to the next full block;\n\ this is the default for non-regular files\n\ @@ -188,12 +195,14 @@ Mandatory arguments to long options are mandatory for short options too.\n\ fputs (VERSION_OPTION_DESCRIPTION, stdout); fputs (_("\ \n\ -If FILE is -, shred standard output.\n\ -\n\ Delete FILE(s) if --remove (-u) is specified. The default is not to remove\n\ the files because it is common to operate on device files like /dev/hda,\n\ -and those files usually should not be removed. When operating on regular\n\ -files, most people use the --remove option.\n\ +and those files usually should not be removed.\n\ +The optional HOW parameter indicates how to remove a directory entry:\n\ +'unlink' => use a standard unlink call.\n\ +'wipe' => also first obfuscate bytes in the name.\n\ +'wipesync' => also sync each obfuscated byte to disk.\n\ +The default mode is 'wipesync', but note it can be expensive.\n\ \n\ "), stdout); fputs (_("\ @@ -236,11 +245,30 @@ In addition, file system backups and remote mirrors may contain copies\n\ of the file that cannot be removed, and that will allow a shredded file\n\ to be recovered later.\n\ "), stdout); - printf (_("\nReport bugs to <%s>.\n"), PACKAGE_BUGREPORT); + emit_ancillary_info (PROGRAM_NAME); } exit (status); } +/* + * Determine if pattern type is periodic or not. + */ +static bool +periodic_pattern (int type) +{ + if (type <= 0) + return false; + + unsigned char r[3]; + unsigned int bits = type & 0xfff; + + bits |= bits << 12; + r[0] = (bits >> 4) & 255; + r[1] = (bits >> 8) & 255; + r[2] = bits & 255; + + return (r[0] != r[1]) || (r[0] != r[2]); +} /* * Fill a buffer with a fixed pattern. @@ -283,6 +311,17 @@ passname (unsigned char const *data, char name[PASS_NAME_SIZE]) memcpy (name, "random", PASS_NAME_SIZE); } +/* Return true when it's ok to ignore an fsync or fdatasync + failure that set errno to ERRNO_VAL. */ +static bool +ignorable_sync_errno (int errno_val) +{ + return (errno_val == EINVAL + || errno_val == EBADF + /* HP-UX does this */ + || errno_val == EISDIR); +} + /* Request that all data for FD be transferred to the corresponding storage device. QNAME is the file name (quoted for colons). Report any errors found. Return 0 on success, -1 @@ -298,7 +337,7 @@ dosync (int fd, char const *qname) if (fdatasync (fd) == 0) return 0; err = errno; - if (err != EINVAL && err != EBADF) + if ( ! ignorable_sync_errno (err)) { error (0, err, _("%s: fdatasync failed"), qname); errno = err; @@ -309,7 +348,7 @@ dosync (int fd, char const *qname) if (fsync (fd) == 0) return 0; err = errno; - if (err != EINVAL && err != EBADF) + if ( ! ignorable_sync_errno (err)) { error (0, err, _("%s: fsync failed"), qname); errno = err; @@ -329,13 +368,13 @@ direct_mode (int fd, bool enable) { int fd_flags = fcntl (fd, F_GETFL); if (0 < fd_flags) - { - int new_flags = (enable - ? (fd_flags | O_DIRECT) - : (fd_flags & ~O_DIRECT)); - if (new_flags != fd_flags) - fcntl (fd, F_SETFL, new_flags); - } + { + int new_flags = (enable + ? (fd_flags | O_DIRECT) + : (fd_flags & ~O_DIRECT)); + if (new_flags != fd_flags) + fcntl (fd, F_SETFL, new_flags); + } } #if HAVE_DIRECTIO && defined DIRECTIO_ON && defined DIRECTIO_OFF @@ -345,59 +384,101 @@ direct_mode (int fd, bool enable) #endif } +/* Rewind FD; its status is ST. */ +static bool +dorewind (int fd, struct stat const *st) +{ + if (S_ISCHR (st->st_mode)) + { +#ifdef __linux__ + /* In the Linux kernel, lseek does not work on tape devices; it + returns a randomish value instead. Try the low-level tape + rewind operation first. */ + struct mtop op; + op.mt_op = MTREW; + op.mt_count = 1; + if (ioctl (fd, MTIOCTOP, &op) == 0) + return true; +#endif + } + off_t offset = lseek (fd, 0, SEEK_SET); + if (0 < offset) + errno = EINVAL; + return offset == 0; +} + +/* By convention, negative sizes represent unknown values. */ + +static bool +known (off_t size) +{ + return 0 <= size; +} + /* - * Do pass number k of n, writing "size" bytes of the given pattern "type" - * to the file descriptor fd. Qname, k and n are passed in only for verbose - * progress message purposes. If n == 0, no progress messages are printed. + * Do pass number K of N, writing *SIZEP bytes of the given pattern TYPE + * to the file descriptor FD. K and N are passed in only for verbose + * progress message purposes. If N == 0, no progress messages are printed. * - * If *sizep == -1, the size is unknown, and it will be filled in as soon - * as writing fails. + * If *SIZEP == -1, the size is unknown, and it will be filled in as soon + * as writing fails with ENOSPC. * * Return 1 on write error, -1 on other error, 0 on success. */ static int -dopass (int fd, char const *qname, off_t *sizep, int type, - struct randread_source *s, unsigned long int k, unsigned long int n) +dopass (int fd, struct stat const *st, char const *qname, off_t *sizep, + int type, struct randread_source *s, + unsigned long int k, unsigned long int n) { off_t size = *sizep; - off_t offset; /* Current file posiiton */ - time_t thresh IF_LINT (= 0); /* Time to maybe print next status update */ + off_t offset; /* Current file position */ + time_t thresh IF_LINT ( = 0); /* Time to maybe print next status update */ time_t now = 0; /* Current time */ size_t lim; /* Amount of data to try writing */ size_t soff; /* Offset into buffer for next write */ ssize_t ssize; /* Return value from write */ - /* Fill pattern buffer. Aligning it to a 32-bit boundary speeds up randread - in some cases. */ - typedef uint32_t fill_pattern_buffer[3 * 1024]; - union - { - fill_pattern_buffer buffer; - char c[sizeof (fill_pattern_buffer)]; - unsigned char u[sizeof (fill_pattern_buffer)]; - } r; - - off_t sizeof_r = sizeof r; + /* Fill pattern buffer. Aligning it to a page so we can do direct I/O. */ + size_t page_size = getpagesize (); +#define PERIODIC_OUTPUT_SIZE (60 * 1024) +#define NONPERIODIC_OUTPUT_SIZE (64 * 1024) + verify (PERIODIC_OUTPUT_SIZE % 3 == 0); + size_t output_size = periodic_pattern (type) + ? PERIODIC_OUTPUT_SIZE : NONPERIODIC_OUTPUT_SIZE; +#define PAGE_ALIGN_SLOP (page_size - 1) /* So directio works */ +#define FILLPATTERN_SIZE (((output_size + 2) / 3) * 3) /* Multiple of 3 */ +#define PATTERNBUF_SIZE (PAGE_ALIGN_SLOP + FILLPATTERN_SIZE) + void *fill_pattern_mem = xmalloc (PATTERNBUF_SIZE); + unsigned char *pbuf = ptr_align (fill_pattern_mem, page_size); + char pass_string[PASS_NAME_SIZE]; /* Name of current pass */ bool write_error = false; - bool first_write = true; + bool other_error = false; /* Printable previous offset into the file */ char previous_offset_buf[LONGEST_HUMAN_READABLE + 1]; - char const *previous_human_offset IF_LINT (= 0); + char const *previous_human_offset IF_LINT ( = 0); + + /* As a performance tweak, avoid direct I/O for small sizes, + as it's just a performance rather then security consideration, + and direct I/O can often be unsupported for small non aligned sizes. */ + bool try_without_directio = 0 < size && size < output_size; + if (! try_without_directio) + direct_mode (fd, true); - if (lseek (fd, 0, SEEK_SET) == -1) + if (! dorewind (fd, st)) { error (0, errno, _("%s: cannot rewind"), qname); - return -1; + other_error = true; + goto free_pattern_mem; } /* Constant fill patterns need only be set up once. */ if (type >= 0) { - lim = (0 <= size && size < sizeof_r ? size : sizeof r); - fillpattern (type, r.u, lim); - passname (r.u, pass_string); + lim = known (size) && size < FILLPATTERN_SIZE ? size : FILLPATTERN_SIZE; + fillpattern (type, pbuf, lim); + passname (pbuf, pass_string); } else { @@ -413,153 +494,169 @@ dopass (int fd, char const *qname, off_t *sizep, int type, } offset = 0; - for (;;) + while (true) { /* How much to write this time? */ - lim = sizeof r; - if (0 <= size && size - offset < sizeof_r) - { - if (size < offset) - break; - lim = size - offset; - if (!lim) - break; - } + lim = output_size; + if (known (size) && size - offset < output_size) + { + if (size < offset) + break; + lim = size - offset; + if (!lim) + break; + } if (type < 0) - randread (s, &r, lim); + randread (s, pbuf, lim); /* Loop to retry partial writes. */ - for (soff = 0; soff < lim; soff += ssize, first_write = false) - { - ssize = write (fd, r.c + soff, lim - soff); - if (ssize <= 0) - { - if (size < 0 && (ssize == 0 || errno == ENOSPC)) - { - /* Ah, we have found the end of the file */ - *sizep = size = offset + soff; - break; - } - else - { - int errnum = errno; - char buf[INT_BUFSIZE_BOUND (uintmax_t)]; - - /* If the first write of the first pass for a given file - has just failed with EINVAL, turn off direct mode I/O - and try again. This works around a bug in linux-2.4 - whereby opening with O_DIRECT would succeed for some - file system types (e.g., ext3), but any attempt to - access a file through the resulting descriptor would - fail with EINVAL. */ - if (k == 1 && first_write && errno == EINVAL) - { - direct_mode (fd, false); - ssize = 0; - continue; - } - error (0, errnum, _("%s: error writing at offset %s"), - qname, umaxtostr (offset + soff, buf)); - - /* 'shred' is often used on bad media, before throwing it - out. Thus, it shouldn't give up on bad blocks. This - code works because lim is always a multiple of - SECTOR_SIZE, except at the end. */ - verify (sizeof r % SECTOR_SIZE == 0); - if (errnum == EIO && 0 <= size && (soff | SECTOR_MASK) < lim) - { - size_t soff1 = (soff | SECTOR_MASK) + 1; - if (lseek (fd, offset + soff1, SEEK_SET) != -1) - { - /* Arrange to skip this block. */ - ssize = soff1 - soff; - write_error = true; - continue; - } - error (0, errno, _("%s: lseek failed"), qname); - } - return -1; - } - } - } + for (soff = 0; soff < lim; soff += ssize) + { + ssize = write (fd, pbuf + soff, lim - soff); + if (0 < ssize) + assume (ssize <= lim - soff); + else + { + if (! known (size) && (ssize == 0 || errno == ENOSPC)) + { + /* We have found the end of the file. */ + if (soff <= OFF_T_MAX - offset) + *sizep = size = offset + soff; + break; + } + else + { + int errnum = errno; + char buf[INT_BUFSIZE_BOUND (uintmax_t)]; + + /* Retry without direct I/O since this may not be supported + at all on some (file) systems, or with the current size. + I.e., a specified --size that is not aligned, or when + dealing with slop at the end of a file with --exact. */ + if (! try_without_directio && errno == EINVAL) + { + direct_mode (fd, false); + ssize = 0; + try_without_directio = true; + continue; + } + error (0, errnum, _("%s: error writing at offset %s"), + qname, umaxtostr (offset + soff, buf)); + + /* 'shred' is often used on bad media, before throwing it + out. Thus, it shouldn't give up on bad blocks. This + code works because lim is always a multiple of + SECTOR_SIZE, except at the end. This size constraint + also enables direct I/O on some (file) systems. */ + verify (PERIODIC_OUTPUT_SIZE % SECTOR_SIZE == 0); + verify (NONPERIODIC_OUTPUT_SIZE % SECTOR_SIZE == 0); + if (errnum == EIO && known (size) + && (soff | SECTOR_MASK) < lim) + { + size_t soff1 = (soff | SECTOR_MASK) + 1; + if (lseek (fd, offset + soff1, SEEK_SET) != -1) + { + /* Arrange to skip this block. */ + ssize = soff1 - soff; + write_error = true; + continue; + } + error (0, errno, _("%s: lseek failed"), qname); + } + other_error = true; + goto free_pattern_mem; + } + } + } /* Okay, we have written "soff" bytes. */ - if (offset + soff < offset) - { - error (0, 0, _("%s: file too large"), qname); - return -1; - } + if (OFF_T_MAX - offset < soff) + { + error (0, 0, _("%s: file too large"), qname); + other_error = true; + goto free_pattern_mem; + } offset += soff; + bool done = offset == size; + /* Time to print progress? */ - if (n - && ((offset == size && *previous_human_offset) - || thresh <= (now = time (NULL)))) - { - char offset_buf[LONGEST_HUMAN_READABLE + 1]; - char size_buf[LONGEST_HUMAN_READABLE + 1]; - int human_progress_opts = (human_autoscale | human_SI - | human_base_1024 | human_B); - char const *human_offset - = human_readable (offset, offset_buf, - human_floor | human_progress_opts, 1, 1); - - if (offset == size - || !STREQ (previous_human_offset, human_offset)) - { - if (size < 0) - error (0, 0, _("%s: pass %lu/%lu (%s)...%s"), - qname, k, n, pass_string, human_offset); - else - { - uintmax_t off = offset; - int percent = (size == 0 - ? 100 - : (off <= TYPE_MAXIMUM (uintmax_t) / 100 - ? off * 100 / size - : off / (size / 100))); - char const *human_size - = human_readable (size, size_buf, - human_ceiling | human_progress_opts, - 1, 1); - if (offset == size) - human_offset = human_size; - error (0, 0, _("%s: pass %lu/%lu (%s)...%s/%s %d%%"), - qname, k, n, pass_string, human_offset, human_size, - percent); - } - - strcpy (previous_offset_buf, human_offset); - previous_human_offset = previous_offset_buf; - thresh = now + VERBOSE_UPDATE; - - /* - * Force periodic syncs to keep displayed progress accurate - * FIXME: Should these be present even if -v is not enabled, - * to keep the buffer cache from filling with dirty pages? - * It's a common problem with programs that do lots of writes, - * like mkfs. - */ - if (dosync (fd, qname) != 0) - { - if (errno != EIO) - return -1; - write_error = true; - } - } - } + if (n && ((done && *previous_human_offset) + || thresh <= (now = time (NULL)))) + { + char offset_buf[LONGEST_HUMAN_READABLE + 1]; + char size_buf[LONGEST_HUMAN_READABLE + 1]; + int human_progress_opts = (human_autoscale | human_SI + | human_base_1024 | human_B); + char const *human_offset + = human_readable (offset, offset_buf, + human_floor | human_progress_opts, 1, 1); + + if (done || !STREQ (previous_human_offset, human_offset)) + { + if (! known (size)) + error (0, 0, _("%s: pass %lu/%lu (%s)...%s"), + qname, k, n, pass_string, human_offset); + else + { + uintmax_t off = offset; + int percent = (size == 0 + ? 100 + : (off <= TYPE_MAXIMUM (uintmax_t) / 100 + ? off * 100 / size + : off / (size / 100))); + char const *human_size + = human_readable (size, size_buf, + human_ceiling | human_progress_opts, + 1, 1); + if (done) + human_offset = human_size; + error (0, 0, _("%s: pass %lu/%lu (%s)...%s/%s %d%%"), + qname, k, n, pass_string, human_offset, human_size, + percent); + } + + strcpy (previous_offset_buf, human_offset); + previous_human_offset = previous_offset_buf; + thresh = now + VERBOSE_UPDATE; + + /* + * Force periodic syncs to keep displayed progress accurate + * FIXME: Should these be present even if -v is not enabled, + * to keep the buffer cache from filling with dirty pages? + * It's a common problem with programs that do lots of writes, + * like mkfs. + */ + if (dosync (fd, qname) != 0) + { + if (errno != EIO) + { + other_error = true; + goto free_pattern_mem; + } + write_error = true; + } + } + } } /* Force what we just wrote to hit the media. */ if (dosync (fd, qname) != 0) { if (errno != EIO) - return -1; + { + other_error = true; + goto free_pattern_mem; + } write_error = true; } - return write_error; +free_pattern_mem: + memset (pbuf, 0, FILLPATTERN_SIZE); + free (fill_pattern_mem); + + return other_error ? -1 : write_error; } /* @@ -627,7 +724,7 @@ static int const 12, 0x111, 0x222, 0x333, 0x444, 0x666, 0x777, 0x888, 0x999, 0xBBB, 0xCCC, 0xDDD, 0xEEE, /* 4-bit */ -1, /* 1 random pass */ - /* The following patterns have the frst bit per block flipped */ + /* The following patterns have the first bit per block flipped */ 8, 0x1000, 0x1249, 0x1492, 0x16DB, 0x1924, 0x1B6D, 0x1DB6, 0x1FFF, 14, 0x1111, 0x1222, 0x1333, 0x1444, 0x1555, 0x1666, 0x1777, 0x1888, 0x1999, 0x1AAA, 0x1BBB, 0x1CCC, 0x1DDD, 0x1EEE, @@ -660,51 +757,51 @@ genpattern (int *dest, size_t num, struct randint_source *s) d = dest; /* Destination for generated pass list */ n = num; /* Passes remaining to fill */ - for (;;) + while (true) { k = *p++; /* Block descriptor word */ if (!k) - { /* Loop back to the beginning */ - p = patterns; - } + { /* Loop back to the beginning */ + p = patterns; + } else if (k < 0) - { /* -k random passes */ - k = -k; - if ((size_t) k >= n) - { - randpasses += n; - n = 0; - break; - } - randpasses += k; - n -= k; - } + { /* -k random passes */ + k = -k; + if ((size_t) k >= n) + { + randpasses += n; + 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; - } + { /* 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; - } + { /* Finish with random */ + randpasses += n; + break; + } else - { /* Pad out with k of the n available */ - do - { - if (n == (size_t) k || randint_choose (s, k) < n) - { - *d++ = *p; - n--; - } - p++; - } - while (n); - break; - } + { /* Pad out with n of the k available */ + do + { + if (n == (size_t) k || randint_choose (s, k) < n) + { + *d++ = *p; + n--; + } + p++; + k--; + } + while (n); + break; + } } top = num - randpasses; /* Top of initialized data */ /* assert (d == dest+top); */ @@ -734,18 +831,18 @@ genpattern (int *dest, size_t num, struct randint_source *s) for (n = 0; n < num; n++) { if (accum <= randpasses) - { - accum += num - 1; - dest[top++] = dest[n]; - dest[n] = -1; - } + { + accum += num - 1; + dest[top++] = dest[n]; + dest[n] = -1; + } else - { - swap = n + randint_choose (s, top - n); - k = dest[n]; - dest[n] = dest[swap]; - dest[swap] = k; - } + { + swap = n + randint_choose (s, top - n); + k = dest[n]; + dest[n] = dest[swap]; + dest[swap] = k; + } accum -= randpasses; } /* assert (top == num); */ @@ -757,17 +854,18 @@ genpattern (int *dest, size_t num, struct randint_source *s) */ static bool do_wipefd (int fd, char const *qname, struct randint_source *s, - struct Options const *flags) + struct Options const *flags) { size_t i; struct stat st; - off_t size; /* Size to write, size to read */ - unsigned long int n; /* Number of passes for printing purposes */ + off_t size; /* Size to write, size to read */ + off_t i_size = 0; /* For small files, initial size to overwrite inode */ + unsigned long int n; /* Number of passes for printing purposes */ int *passarray; bool ok = true; struct randread_source *rs; - n = 0; /* dopass takes n -- 0 to mean "don't print progress" */ + n = 0; /* dopass takes n == 0 to mean "don't print progress" */ if (flags->verbose) n = flags->n_iterations + flags->zero_fill; @@ -778,7 +876,7 @@ do_wipefd (int fd, char const *qname, struct randint_source *s, } /* 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 + Otherwise, we may go into an 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) @@ -787,8 +885,11 @@ do_wipefd (int fd, char const *qname, struct randint_source *s, error (0, 0, _("%s: invalid file type"), qname); return false; } - - direct_mode (fd, true); + else if (S_ISREG (st.st_mode) && st.st_size < 0) + { + error (0, 0, _("%s: file has negative size"), qname); + return false; + } /* Allocate pass array */ passarray = xnmalloc (flags->n_iterations, sizeof *passarray); @@ -796,91 +897,105 @@ do_wipefd (int fd, char const *qname, struct randint_source *s, 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 false; - } - } + { + size = st.st_size; + + if (! flags->exact) + { + /* Round up to the nearest block size to clear slack space. */ + off_t remainder = size % ST_BLKSIZE (st); + if (size && size < ST_BLKSIZE (st)) + i_size = size; + if (remainder != 0) + { + off_t size_incr = ST_BLKSIZE (st) - remainder; + size += MIN (size_incr, OFF_T_MAX - size); + } + } + } else - { - size = lseek (fd, 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; - } - } - - /* Allow `rounding up' only for regular files. */ - if (0 <= size && !(flags->exact) && S_ISREG (st.st_mode)) - { - 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); - } + { + /* The behavior of lseek is unspecified, but in practice if + it returns a positive number that's the size of this + device. */ + size = lseek (fd, 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; + } + } } + else if (S_ISREG (st.st_mode) + && st.st_size < MIN (ST_BLKSIZE (st), size)) + i_size = st.st_size; /* Schedule the passes in random order. */ genpattern (passarray, flags->n_iterations, s); rs = randint_get_source (s); - /* Do the work */ - for (i = 0; i < flags->n_iterations; i++) + while (true) { - int err = dopass (fd, qname, &size, passarray[i], rs, i + 1, n); - if (err) - { - if (err < 0) - { - memset (passarray, 0, flags->n_iterations * sizeof (int)); - free (passarray); - return false; - } - ok = false; - } - } - - memset (passarray, 0, flags->n_iterations * sizeof (int)); - free (passarray); - - if (flags->zero_fill) - { - int err = dopass (fd, qname, &size, 0, rs, flags->n_iterations + 1, n); - if (err) - { - if (err < 0) - return false; - ok = false; - } + off_t pass_size; + unsigned long int pn = n; + + if (i_size) + { + pass_size = i_size; + i_size = 0; + pn = 0; + } + else if (size) + { + pass_size = size; + size = 0; + } + /* TODO: consider handling tail packing by + writing the tail padding as a separate pass, + (that would not rewind). */ + else + break; + + for (i = 0; i < flags->n_iterations + flags->zero_fill; i++) + { + int err = 0; + int type = i < flags->n_iterations ? passarray[i] : 0; + + err = dopass (fd, &st, qname, &pass_size, type, rs, i + 1, pn); + + if (err) + { + ok = false; + if (err < 0) + goto wipefd_out; + } + } } - /* Okay, now deallocate the data. The effect of ftruncate on + /* 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, 0) != 0 && S_ISREG (st.st_mode)) { error (0, errno, _("%s: error truncating"), qname); - return false; + ok = false; + goto wipefd_out; } +wipefd_out: + memset (passarray, 0, flags->n_iterations * sizeof (int)); + free (passarray); return ok; } /* A wrapper with a little more checking for fds on the command line */ static bool wipefd (int fd, char const *qname, struct randint_source *s, - struct Options const *flags) + struct Options const *flags) { int fd_flags = fcntl (fd, F_GETFL); @@ -904,9 +1019,9 @@ static char const nameset[] = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_."; /* Increment NAME (with LEN bytes). NAME must be a big-endian base N - number with the digits taken from nameset. Return true if - successful if not (because NAME already has the greatest possible - value. */ + number with the digits taken from nameset. Return true if successful. + Otherwise, (because NAME already has the greatest possible value) + return false. */ static bool incname (char *name, size_t len) @@ -915,12 +1030,16 @@ incname (char *name, size_t len) { char const *p = strchr (nameset, name[len]); + /* Given that NAME is composed of bytes from NAMESET, + P will never be NULL here. */ + assert (p); + /* If this character has a successor, use it. */ if (p[1]) - { - name[len] = p[1]; - return true; - } + { + name[len] = p[1]; + return true; + } /* Otherwise, set this digit to 0 and increment the prefix. */ name[len] = nameset[0]; @@ -931,8 +1050,8 @@ incname (char *name, size_t 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 + * to obliterate all traces of the file name (and length) 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 @@ -962,54 +1081,57 @@ wipename (char *oldname, char const *qoldname, struct Options const *flags) char *base = last_component (newname); size_t len = base_len (base); char *dir = dir_name (newname); - char *qdir = xstrdup (quotearg_colon (dir)); + char *qdir = xstrdup (quotef (dir)); bool first = true; bool ok = true; + int dir_fd = -1; - int dir_fd = open (dir, O_RDONLY | O_DIRECTORY | O_NOCTTY | O_NONBLOCK); + if (flags->remove_file == remove_wipesync) + dir_fd = open (dir, O_RDONLY | O_DIRECTORY | O_NOCTTY | O_NONBLOCK); if (flags->verbose) error (0, 0, _("%s: removing"), qoldname); - while (len) + while ((flags->remove_file != remove_unlink) && len) { memset (base, nameset[0], len); base[len] = 0; do - { - struct stat st; - if (lstat (newname, &st) < 0) - { - if (rename (oldname, newname) == 0) - { - if (0 <= dir_fd && dosync (dir_fd, qdir) != 0) - ok = false; - 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 = false; - } - 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 */ - } - } + { + struct stat st; + if (lstat (newname, &st) < 0) + { + if (rename (oldname, newname) == 0) + { + if (0 <= dir_fd && dosync (dir_fd, qdir) != 0) + ok = false; + 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 = false; + } + 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--; } @@ -1023,12 +1145,12 @@ wipename (char *oldname, char const *qoldname, struct Options const *flags) if (0 <= dir_fd) { if (dosync (dir_fd, qdir) != 0) - ok = false; + ok = false; if (close (dir_fd) != 0) - { - error (0, errno, _("%s: failed to close"), qdir); - ok = false; - } + { + error (0, errno, _("%s: failed to close"), qdir); + ok = false; + } } free (newname); free (dir); @@ -1050,7 +1172,7 @@ wipename (char *oldname, char const *qoldname, struct Options const *flags) */ static bool wipefile (char *name, char const *qname, - struct randint_source *s, struct Options const *flags) + struct randint_source *s, struct Options const *flags) { bool ok; int fd; @@ -1103,7 +1225,7 @@ main (int argc, char **argv) char const *random_source = NULL; initialize_main (&argc, &argv); - program_name = argv[0]; + set_program_name (argv[0]); setlocale (LC_ALL, ""); bindtextdomain (PACKAGE, LOCALEDIR); textdomain (PACKAGE); @@ -1116,66 +1238,56 @@ main (int argc, char **argv) while ((c = getopt_long (argc, argv, "fn:s:uvxz", long_opts, NULL)) != -1) { switch (c) - { - case 'f': - flags.force = true; - break; - - case 'n': - { - uintmax_t tmp; - if (xstrtoumax (optarg, NULL, 10, &tmp, NULL) != LONGINT_OK - || MIN (UINT32_MAX, SIZE_MAX / sizeof (int)) < tmp) - { - error (EXIT_FAILURE, 0, _("%s: invalid number of passes"), - quotearg_colon (optarg)); - } - flags.n_iterations = tmp; - } - break; - - case RANDOM_SOURCE_OPTION: - if (random_source && !STREQ (random_source, optarg)) - error (EXIT_FAILURE, 0, _("multiple random sources specified")); - random_source = optarg; - break; - - case 'u': - flags.remove_file = true; - break; - - case 's': - { - uintmax_t tmp; - if (xstrtoumax (optarg, NULL, 0, &tmp, "cbBkKMGTPEZY0") - != LONGINT_OK) - { - error (EXIT_FAILURE, 0, _("%s: invalid file size"), - quotearg_colon (optarg)); - } - flags.size = tmp; - } - break; - - case 'v': - flags.verbose = true; - break; - - case 'x': - flags.exact = true; - break; - - case 'z': - flags.zero_fill = true; - break; - - case_GETOPT_HELP_CHAR; - - case_GETOPT_VERSION_CHAR (PROGRAM_NAME, AUTHORS); - - default: - usage (EXIT_FAILURE); - } + { + case 'f': + flags.force = true; + break; + + case 'n': + flags.n_iterations = xdectoumax (optarg, 0, + MIN (ULONG_MAX, + SIZE_MAX / sizeof (int)), "", + _("invalid number of passes"), 0); + break; + + case RANDOM_SOURCE_OPTION: + if (random_source && !STREQ (random_source, optarg)) + error (EXIT_FAILURE, 0, _("multiple random sources specified")); + random_source = optarg; + break; + + case 'u': + if (optarg == NULL) + flags.remove_file = remove_wipesync; + else + flags.remove_file = XARGMATCH ("--remove", optarg, + remove_args, remove_methods); + break; + + case 's': + flags.size = xnumtoumax (optarg, 0, 0, OFF_T_MAX, "cbBkKMGTPEZY0", + _("invalid file size"), 0); + break; + + case 'v': + flags.verbose = true; + break; + + case 'x': + flags.exact = true; + break; + + case 'z': + flags.zero_fill = true; + break; + + case_GETOPT_HELP_CHAR; + + case_GETOPT_VERSION_CHAR (PROGRAM_NAME, AUTHORS); + + default: + usage (EXIT_FAILURE); + } } file = argv + optind; @@ -1189,25 +1301,25 @@ main (int argc, char **argv) randint_source = randint_all_new (random_source, SIZE_MAX); if (! randint_source) - error (EXIT_FAILURE, errno, "%s", quotearg_colon (random_source)); + error (EXIT_FAILURE, errno, "%s", quotef (random_source)); atexit (clear_random_data); for (i = 0; i < n_files; i++) { - char *qname = xstrdup (quotearg_colon (file[i])); + char *qname = xstrdup (quotef (file[i])); if (STREQ (file[i], "-")) - { - ok &= wipefd (STDOUT_FILENO, qname, randint_source, &flags); - } + { + ok &= wipefd (STDOUT_FILENO, qname, randint_source, &flags); + } else - { - /* Plain filename - Note that this overwrites *argv! */ - ok &= wipefile (file[i], qname, randint_source, &flags); - } + { + /* Plain filename - Note that this overwrites *argv! */ + ok &= wipefile (file[i], qname, randint_source, &flags); + } free (qname); } - exit (ok ? EXIT_SUCCESS : EXIT_FAILURE); + return ok ? EXIT_SUCCESS : EXIT_FAILURE; } /* * vim:sw=2:sts=2: |