diff options
-rw-r--r-- | NEWS | 4 | ||||
-rw-r--r-- | doc/coreutils.texi | 5 | ||||
-rw-r--r-- | src/shred.c | 118 | ||||
-rwxr-xr-x | tests/misc/shred-exact.sh | 6 |
4 files changed, 77 insertions, 56 deletions
@@ -58,7 +58,9 @@ GNU coreutils NEWS -*- outline -*- in case the look-up within the chroot fails due to library conflicts etc. shred now supports multiple passes on GNU/Linux tape devices by rewinding - the tape before each pass. Also redundant writes to empty files are avoided. + the tape before each pass, avoids redundant writes to empty files, + uses direct I/O for all passes where possible, and attempts to clear + inode storage used for small files on some file systems. split avoids unnecessary input buffering, immediately writing input to output which is significant with --filter or when writing to fifos or stdout etc. diff --git a/doc/coreutils.texi b/doc/coreutils.texi index 75875d88c..6c49385e1 100644 --- a/doc/coreutils.texi +++ b/doc/coreutils.texi @@ -9634,8 +9634,9 @@ Display to standard error all status updates as sterilization proceeds. @opindex -x @opindex --exact By default, @command{shred} rounds the size of a regular file up to the next -multiple of the file system block size to fully erase the last block -of the file. +multiple of the file system block size to fully erase the slack space in +the last block of the file. This space may contain portions of the current +system memory on some systems for example. Use @option{--exact} to suppress that behavior. Thus, by default if you shred a 10-byte regular file on a system with 512-byte blocks, the resulting file will be 512 bytes long. With this option, diff --git a/src/shred.c b/src/shred.c index ed370513d..607c6be15 100644 --- a/src/shred.c +++ b/src/shred.c @@ -406,12 +406,12 @@ dorewind (int fd, struct stat const *st) } /* - * 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. */ @@ -428,10 +428,6 @@ dopass (int fd, struct stat const *st, char const *qname, off_t *sizep, size_t soff; /* Offset into buffer for next write */ ssize_t ssize; /* Return value from write */ - /* Do nothing for --size=0 or regular empty files. */ - if (size == 0) - return 0; - /* 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) @@ -448,12 +444,18 @@ dopass (int fd, struct stat const *st, char const *qname, off_t *sizep, char pass_string[PASS_NAME_SIZE]; /* Name of current pass */ bool write_error = false; bool other_error = false; - bool tried_without_directio = false; /* Printable previous offset into the file */ char previous_offset_buf[LONGEST_HUMAN_READABLE + 1]; 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 (! dorewind (fd, st)) { error (0, errno, _("%s: cannot rewind"), qname); @@ -517,11 +519,11 @@ dopass (int fd, struct stat const *st, char const *qname, off_t *sizep, 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 (k == 1 && !tried_without_directio && errno == EINVAL) + if (! try_without_directio && errno == EINVAL) { direct_mode (fd, false); ssize = 0; - tried_without_directio = true; + try_without_directio = true; continue; } error (0, errnum, _("%s: error writing at offset %s"), @@ -841,13 +843,14 @@ do_wipefd (int fd, char const *qname, struct randint_source *s, { 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; @@ -867,8 +870,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); @@ -879,23 +885,17 @@ do_wipefd (int fd, char const *qname, struct randint_source *s, if (S_ISREG (st.st_mode)) { size = st.st_size; - if (size < 0) - { - error (0, 0, _("%s: file has negative size"), qname); - return false; - } if (! flags->exact) { - /* Round up to the nearest blocksize to clear slack space. */ + /* 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; - if (! INT_ADD_OVERFLOW (size, size_incr)) - size += size_incr; - else - size = OFF_T_MAX; + size += MIN (size_incr, OFF_T_MAX - size); } } } @@ -913,53 +913,67 @@ do_wipefd (int fd, char const *qname, struct randint_source *s, } } } + 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, &st, qname, &size, passarray[i], rs, i + 1, n); - if (err) + off_t pass_size; + unsigned long int pn = n; + + if (i_size) { - if (err < 0) - { - memset (passarray, 0, flags->n_iterations * sizeof (int)); - free (passarray); - return false; - } - ok = false; + pass_size = i_size; + i_size = 0; + pn = 0; } - } - - memset (passarray, 0, flags->n_iterations * sizeof (int)); - free (passarray); + 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; - if (flags->zero_fill) - { - int err = dopass (fd, &st, qname, &size, 0, rs, - flags->n_iterations + 1, n); - if (err) + for (i = 0; i < flags->n_iterations + flags->zero_fill; i++) { - if (err < 0) - return false; - ok = false; + 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; } diff --git a/tests/misc/shred-exact.sh b/tests/misc/shred-exact.sh index 5f2848e1d..5434229c4 100755 --- a/tests/misc/shred-exact.sh +++ b/tests/misc/shred-exact.sh @@ -40,6 +40,10 @@ done # (i.e. we want to test failed writes not at the start). truncate -s1MiB file.slop || framework_failure_ truncate -s+1 file.slop || framework_failure_ -shred --exact -n1 file.slop || fail=1 +shred --exact -n2 file.slop || fail=1 + +# make sure direct I/O is handled appropriately at start of file +truncate -s1 file.slop || framework_failure_ +shred --exact -n2 file.slop || fail=1 Exit $fail |