Hi,

I'm not sure if this was discussed before, but I've got tired from having to
work around the lack of native recursion within `shred`. So, attached is a patch
to add recursion, including documentation.

Let me know if there's anything that needs to be modified further. I've compiled
and tested shred, so far everything works perfectly.

Have a great day,
-- 
Amr Ali
From 481932b48ffd4e9c0e97f934a64ea54b039666f1 Mon Sep 17 00:00:00 2001
From: Amr Ali <[email protected]>
Date: Fri, 9 Dec 2011 18:48:40 +0200
Subject: [PATCH 1/2] shred: add recursion operations

---
 src/shred.c |  155 +++++++++++++++++++++++++++++++++++++++++++++++++++-------
 1 files changed, 136 insertions(+), 19 deletions(-)

diff --git a/src/shred.c b/src/shred.c
index a2365b0..820b86d 100644
--- a/src/shred.c
+++ b/src/shred.c
@@ -22,7 +22,6 @@
    - use consistent non-capitalization in error messages
    - add standard GNU copyleft comment
 
-  - Add -r/-R/--recursive
   - Add -i/--interactive
   - Reserve -d
   - Add -L
@@ -97,10 +96,12 @@
 #include "error.h"
 #include "fcntl--.h"
 #include "human.h"
+#include "quote.h"
 #include "quotearg.h"		/* For quotearg_colon */
 #include "randint.h"
 #include "randread.h"
 #include "stat-size.h"
+#include "xfts.h"
 
 /* Default number of times to overwrite.  */
 enum { DEFAULT_PASSES = 3 };
@@ -121,6 +122,7 @@ struct Options
   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 */
+  bool recursive;	/* -r flag: go through directories recursively */
   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 */
@@ -141,6 +143,7 @@ static struct option const long_opts[] =
   {"size", required_argument, NULL, 's'},
   {"random-source", required_argument, NULL, RANDOM_SOURCE_OPTION},
   {"remove", no_argument, NULL, 'u'},
+  {"recursive", no_argument, NULL, 'r'},
   {"verbose", no_argument, NULL, 'v'},
   {"zero", no_argument, NULL, 'z'},
   {GETOPT_HELP_OPTION_DECL},
@@ -173,6 +176,7 @@ Mandatory arguments to long options are mandatory for short options too.\n\
 "), DEFAULT_PASSES);
       fputs (_("\
   -u, --remove   truncate and remove file after overwriting\n\
+  -r, --recursive  go through directories recursively\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\
@@ -1068,6 +1072,8 @@ wipefile (char *name, char const *qname,
       && (errno == EACCES && flags->force)
       && chmod (name, S_IWUSR) == 0)
     fd = open (name, O_WRONLY | O_NOCTTY | O_BINARY);
+  if (fd < 0 && (errno == EISDIR)) /* Directories should be transparent */
+    return true;
   if (fd < 0)
     {
       error (0, errno, _("%s: failed to open for writing"), qname);
@@ -1098,6 +1104,126 @@ clear_random_data (void)
   randint_all_free (randint_source);
 }
 
+/* Wipe FILE.
+   Return true if successful.  This function is called
+   once for every file system object that fts encounters.  */
+static bool
+process_file (FTS *fts, FTSENT *ent, struct randint_source *s,
+              struct Options const *flags)
+{
+  char const *file_full_name = ent->fts_path;
+  char const *file = ent->fts_accpath;
+  const struct stat *file_stats = ent->fts_statp;
+  bool ok = true;
+
+  switch (ent->fts_info)
+    {
+    case FTS_DP:
+      return true;
+
+    case FTS_NS:
+      if (! flags->verbose)
+        error (0, ent->fts_errno, _("cannot access %s"),
+               quote (file_full_name));
+      ok = false;
+      break;
+
+    case FTS_ERR:
+      if (! flags->verbose)
+        error (0, ent->fts_errno, "%s", quote (file_full_name));
+      ok = false;
+      break;
+
+    case FTS_DNR:
+      if (! flags->verbose)
+        error (0, ent->fts_errno, _("cannot read directory %s"),
+               quote (file_full_name));
+      ok = false;
+      break;
+
+    case FTS_SLNONE:
+      if (! flags->verbose)
+        error (0, 0, _("cannot operate on dangling symlink %s"),
+               quote (file_full_name));
+      ok = false;
+      break;
+
+    case FTS_DC:		/* directory that causes cycles */
+      if (cycle_warning_required (fts, ent))
+        {
+          emit_cycle_warning (file_full_name);
+          return false;
+        }
+      break;
+
+    default:
+      break;
+    }
+
+  if (ok)
+    {
+      char *qname = xstrdup (quotearg_colon (file_full_name));
+
+      if (STREQ (file, "-"))
+      {
+        ok = wipefd (STDOUT_FILENO, qname, s, flags);
+      }
+      else
+      {
+        /* Plain filename */
+        char *fname = xstrdup (file_full_name);
+        ok = wipefile (fname, qname, s, flags);
+        free (fname);
+      }
+
+      free (qname);
+    }
+
+  if (! flags->recursive)
+    fts_set (fts, ent, FTS_SKIP);
+
+  return ok;
+}
+
+/* Recursively wipe the specified FILES (the last entry
+   of which is NULL).  BIT_FLAGS controls how fts works.
+   Return true if successful.  */
+static bool
+process_files (char **files, int bit_flags, struct randint_source *s,
+               struct Options const *flags)
+{
+  bool ok = true;
+
+  FTS *fts = xfts_open (files, bit_flags, NULL);
+
+  while (1)
+    {
+      FTSENT *ent;
+
+      ent = fts_read (fts);
+      if (ent == NULL)
+        {
+          if (errno != 0)
+            {
+              /* XXX: try to give a better message  */
+              if (! flags->verbose)
+                error (0, errno, _("fts_read failed"));
+              ok = false;
+            }
+          break;
+        }
+
+      ok &= process_file (fts, ent, s, flags);
+    }
+
+  if (fts_close (fts) != 0)
+    {
+      error (0, errno, _("fts_close failed"));
+      ok = false;
+    }
+
+  return ok;
+}
 
 int
 main (int argc, char **argv)
@@ -1105,7 +1231,6 @@ main (int argc, char **argv)
   bool ok = true;
   struct Options flags = { 0, };
   char **file;
-  int n_files;
   int c;
   int i;
   char const *random_source = NULL;
@@ -1121,7 +1246,7 @@ main (int argc, char **argv)
   flags.n_iterations = DEFAULT_PASSES;
   flags.size = -1;
 
-  while ((c = getopt_long (argc, argv, "fn:s:uvxz", long_opts, NULL)) != -1)
+  while ((c = getopt_long (argc, argv, "fn:s:urvxz", long_opts, NULL)) != -1)
     {
       switch (c)
         {
@@ -1152,6 +1277,10 @@ main (int argc, char **argv)
           flags.remove_file = true;
           break;
 
+        case 'r':
+          flags.recursive = true;
+          break;
+
         case 's':
           {
             uintmax_t tmp;
@@ -1187,9 +1316,8 @@ main (int argc, char **argv)
     }
 
   file = argv + optind;
-  n_files = argc - optind;
 
-  if (n_files == 0)
+  if (argc - optind == 0)
     {
       error (0, 0, _("missing file operand"));
       usage (EXIT_FAILURE);
@@ -1200,20 +1328,9 @@ main (int argc, char **argv)
     error (EXIT_FAILURE, errno, "%s", quotearg_colon (random_source));
   atexit (clear_random_data);
 
-  for (i = 0; i < n_files; i++)
-    {
-      char *qname = xstrdup (quotearg_colon (file[i]));
-      if (STREQ (file[i], "-"))
-        {
-          ok &= wipefd (STDOUT_FILENO, qname, randint_source, &flags);
-        }
-      else
-        {
-          /* Plain filename - Note that this overwrites *argv! */
-          ok &= wipefile (file[i], qname, randint_source, &flags);
-        }
-      free (qname);
-    }
+  ok &= process_files (file,
+                       FTS_COMFOLLOW | FTS_PHYSICAL | FTS_DEFER_STAT,
+                       randint_source, &flags);
 
   exit (ok ? EXIT_SUCCESS : EXIT_FAILURE);
 }
-- 
1.7.5.4


From 4bb9ba4a92adea5d77885445c82a17b967fb6f31 Mon Sep 17 00:00:00 2001
From: Amr Ali <[email protected]>
Date: Fri, 9 Dec 2011 18:49:03 +0200
Subject: [PATCH 2/2] doc: add entries for shred's -r/--recursive

---
 doc/coreutils.texi |    7 +++++++
 1 files changed, 7 insertions(+), 0 deletions(-)

diff --git a/doc/coreutils.texi b/doc/coreutils.texi
index 369fad2..edeba0a 100644
--- a/doc/coreutils.texi
+++ b/doc/coreutils.texi
@@ -9001,6 +9001,13 @@ the whole file.  @var{bytes} can be followed by a size specification like
 After shredding a file, truncate it (if possible) and then remove it.
 If a file has multiple links, only the named links will be removed.
 
+@item -r
+@item --recursive
+@opindex -r
+@opindex --recursive
+@cindex shred files recursively
+Recurse through directories and shred files within.
+
 @item -v
 @itemx --verbose
 @opindex -v
-- 
1.7.5.4

Attachment: signature.asc
Description: OpenPGP digital signature

Reply via email to