Subject: [PATCH 1/1] Added new recursive feature to the shred command

From: TheJostler <[email-masked]>
Date: Sun, 12 Jan 2025 18:50:24 +0000 (UTC)

From: Josjuar Lister <[email-masked]>

---
 src/shred.c | 111 +++++++++++++++++++++++++++++++++++++++++++++-------
 1 file changed, 97 insertions(+), 14 deletions(-)

diff --git a/src/shred.c b/src/shred.c
index 03a260243..c824fc495 100644
--- a/src/shred.c
+++ b/src/shred.c
@@ -128,6 +128,7 @@ static enum remove_method const remove_methods[] =
 
 struct Options
 {
+  bool recursive;	/* -r flag: shred files and directories recursively */  
   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 */
@@ -179,6 +180,7 @@ If FILE is -, shred standard output.\n\
       emit_mandatory_arg_note ();
 
       printf (_("\
+  -r, --recursive  shred files and directories recursively\n\
   -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\n\
@@ -1035,6 +1037,7 @@ incname (char *name, size_t len)
 static bool
 wipename (char *oldname, char const *qoldname, struct Options const *flags)
 {
+  printf("wipename\n");
   char *newname = xstrdup (oldname);
   char *base = last_component (newname);
   char *dir = dir_name (newname);
@@ -1079,13 +1082,28 @@ wipename (char *oldname, char const *qoldname, struct Options const *flags)
           }
       }
 
-  if (unlink (oldname) != 0)
+  struct stat statbuf;
+  if (stat(oldname, &statbuf) == 0 && S_ISDIR(statbuf.st_mode))
     {
-      error (0, errno, _("%s: failed to remove"), qoldname);
-      ok = false;
+      if (rmdir(oldname) != 0)
+        {
+          error(0, errno, _("%s: failed to remove directory"), qoldname);
+          ok = false;
+        }
+      else if (flags->verbose)
+        error(0, 0, _("%s: removed directory"), qoldname);
     }
-  else if (flags->verbose)
-    error (0, 0, _("%s: removed"), qoldname);
+    else
+    {
+      if (unlink (oldname) != 0)
+      {
+        error (0, errno, _("%s: failed to remove"), qoldname);
+        ok = false;
+      }
+      else if (flags->verbose)
+        error (0, 0, _("%s: removed"), qoldname);
+    }
+
   if (0 <= dir_fd)
     {
       if (dosync (dir_fd, qdir) != 0)
@@ -1138,11 +1156,73 @@ wipefile (char *name, char const *qname,
       error (0, errno, _("%s: failed to close"), qname);
       ok = false;
     }
+  printf("remove_file: %u, Ok: %i\n", flags->remove_file, ok);
   if (ok && flags->remove_file)
     ok = wipename (name, qname, flags);
   return ok;
 }
 
+/* Wrapper for handling files simplifies the recursive function */
+static bool 
+handle_file (char *name, struct randint_source *s, struct Options const *flags, bool ok)
+{
+  char *qname = xstrdup (quotef (name));
+  if (STREQ (name, "-"))
+    {
+      ok &= wipefd (STDOUT_FILENO, qname, s, flags);
+    }
+  else
+    {
+      /* Plain filename - Note that this overwrites *argv! */
+      ok &= wipefile (name, qname, s, flags);
+    }
+  free (qname);
+  return ok;
+}
+
+
+/* Directory wiping */
+static bool
+wipe_directory (char *name, struct randint_source *s, struct Options const *flags, bool ok)
+{
+  DIR *dir;
+  struct dirent *entry;
+  struct stat statbuf;
+  char path[PATH_MAX];
+
+  if (!(dir = opendir(name)))
+    {
+      error (0, errno, _("cannot open directory %s"), name);
+      return false;
+    }
+
+  while ((entry = readdir(dir)) != nullptr)
+    {
+      if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0)
+        continue;
+
+      snprintf(path, sizeof(path), "%s/%s", name, entry->d_name);
+      if (lstat(path, &statbuf) == -1)
+        {
+          error (0, errno, _("cannot stat %s"), path);
+          continue;
+        }
+
+      if (S_ISDIR(statbuf.st_mode))
+        {
+          if (!wipe_directory(path, s, flags, ok))
+            return false;
+        }
+      else
+        {
+          handle_file(path, s, flags, ok);
+        }
+    }
+  if (flags->remove_file)
+    ok &= wipename(name, name, flags);
+  closedir(dir);
+  return ok;
+}
 
 /* Buffers for random data.  */
 static struct randint_source *randint_source;
@@ -1168,7 +1248,6 @@ main (int argc, char **argv)
   int i;
   char const *random_source = nullptr;
 
-  initialize_main (&argc, &argv);
   set_program_name (argv[0]);
   setlocale (LC_ALL, "");
   bindtextdomain (PACKAGE, LOCALEDIR);
@@ -1179,10 +1258,14 @@ main (int argc, char **argv)
   flags.n_iterations = DEFAULT_PASSES;
   flags.size = -1;
 
-  while ((c = getopt_long (argc, argv, "fn:s:uvxz", long_opts, nullptr)) != -1)
+  while ((c = getopt_long (argc, argv, "rfn:s:uvxz", long_opts, nullptr)) != -1)
     {
       switch (c)
         {
+        case 'r':
+          flags.recursive = true;
+          flags.remove_file = remove_wipesync;
+          break;
         case 'f':
           flags.force = true;
           break;
@@ -1249,21 +1332,21 @@ main (int argc, char **argv)
            quotef (random_source ? random_source : "getrandom"));
   atexit (clear_random_data);
 
+  struct stat statbuf;
   for (i = 0; i < n_files; i++)
     {
-      char *qname = xstrdup (quotef (file[i]));
-      if (STREQ (file[i], "-"))
+      if (stat(file[i], &statbuf) == 0 && S_ISDIR(statbuf.st_mode))
         {
-          ok &= wipefd (STDOUT_FILENO, qname, randint_source, &flags);
+          if (! ok){
+            continue;
+          }
+          ok &= wipe_directory (file[i], randint_source, &flags, ok);
         }
       else
         {
-          /* Plain filename - Note that this overwrites *argv! */
-          ok &= wipefile (file[i], qname, randint_source, &flags);
+          ok &= handle_file (file[i], randint_source, &flags, ok);
         }
-      free (qname);
     }
-
   return ok ? EXIT_SUCCESS : EXIT_FAILURE;
 }
 /*
-- 
2.39.5