[rtems commit] libmisc/untar: Support directory create and overwrites. Share the common code.

Chris Johns chrisj at rtems.org
Sat Jun 4 00:11:25 UTC 2016


Module:    rtems
Branch:    master
Commit:    d84e346b26017f021c1a7d5c8ad078c7264240ab
Changeset: http://git.rtems.org/rtems/commit/?id=d84e346b26017f021c1a7d5c8ad078c7264240ab

Author:    Chris Johns <chrisj at rtems.org>
Date:      Thu May 19 09:18:21 2016 +1000

libmisc/untar: Support directory create and overwrites. Share the common code.

Support creating directories for files with a path depth greater than 1. Some
tar files can have files with a path depth greater than 1 and no directory
entry in the tar file to create a directory.

Support overwriting existing files and directories failing in a similar
way to tar on common hosts. If a file is replaced with a file delete the
file and create a new file. If a directory replaces a file remove the file
and create the directory. If a file replaces a directory remove the directory,
and if the directory is not empty and cannot be removed report an error. If a
directory alreday exists do nothing leaving the contents untouched.

The untar code now shares the common header parsing and initial processing
with the actual writes still separate. No changes to the IMFS have been made.

Updates #2415.
Closes #2207.

---

 cpukit/libmisc/untar/untar.c     | 463 ++++++++++++++++++++++++++++-----------
 cpukit/libmisc/untar/untar.h     |   7 +-
 testsuites/libtests/tar01/init.c |   9 +-
 3 files changed, 349 insertions(+), 130 deletions(-)

diff --git a/cpukit/libmisc/untar/untar.c b/cpukit/libmisc/untar/untar.c
index e4b0aeb..f6f4f0c 100644
--- a/cpukit/libmisc/untar/untar.c
+++ b/cpukit/libmisc/untar/untar.c
@@ -13,7 +13,9 @@
 
 /*
  *  Written by: Jake Janovetz <janovetz at tempest.ece.uiuc.edu>
-
+ *
+ *  Copyright 2016 Chris Johns <chrisj at rtems.org>
+ *
  *  The license and distribution terms for this file may be
  *  found in the file LICENSE in this distribution or at
  *  http://www.rtems.org/license/LICENSE.
@@ -23,6 +25,7 @@
 #include "config.h"
 #endif
 
+#include <stdbool.h>
 #include <sys/param.h>
 #include <stdio.h>
 #include <string.h>
@@ -64,14 +67,16 @@
  *   sum = 0;
  *   for(i = 0; i < 512; i++)
  *       sum += 0xFF & header[i];
- */ 
+ */
 
 #define MAX_NAME_FIELD_SIZE      99
 
 /*
  * This converts octal ASCII number representations into an
  * unsigned long.  Only support 32-bit numbers for now.
- */ 
+ *
+ * warning: this code is referenced in the IMFS.
+ */
 unsigned long
 _rtems_octal2ulong(
   const char *octascii,
@@ -92,6 +97,244 @@ _rtems_octal2ulong(
 }
 
 /*
+ * Common error message formatter.
+ */
+static void
+Print_Error(const rtems_printer *printer, const char* message, const char* path)
+{
+  rtems_printf(printer, "untar: %s: %s: (%d) %s\n",
+               message, path, errno, strerror(errno));
+}
+
+/*
+ * Get the type of node on in the file system if present.
+ */
+static int
+Stat_Node(const char* path)
+{
+  struct stat sb;
+  if (stat(path, &sb) < 0)
+    return -1;
+  if (S_ISDIR(sb.st_mode))
+    return DIRTYPE;
+  return REGTYPE;
+}
+
+/*
+ * Make the directory path for a file if it does not exist.
+ */
+static int
+Make_Path(const rtems_printer *printer, const char* filename, bool end_is_dir)
+{
+  char* copy = strdup(filename);
+  char* path = copy;
+
+  /*
+   * Skip leading path separators.
+   */
+  while (*path == '/')
+    ++path;
+
+  /*
+   * Any path left?
+   */
+  if (*path != '\0') {
+    bool  path_end = false;
+    char* end = path;
+    int   r;
+
+    /*
+     * Split the path into directory components. Check the node and if a file
+     * and not the end of the path remove it and create a directory. If a
+     * directory and not the end of the path decend into the directory.
+     */
+    while (!path_end) {
+      while (*end != '\0' && *end != '/')
+        ++end;
+
+      /*
+       * Are we at the end of the path?
+       */
+      if (*end == '\0')
+        path_end = true;
+
+      /*
+       * Split the path.
+       */
+      *end = '\0';
+
+      /*
+       * Get the node's status, exists, error, directory or regular? Regular
+       * means not a directory.
+       */
+      r = Stat_Node(path);
+
+      /*
+       * If there are errors other than not existing we are finished.
+       */
+      if (r < 0 && errno != ENOENT) {
+        Print_Error(printer, "stat", path);
+        return -1;
+      }
+
+      /*
+       * If a file remove and create a directory if not the end.
+       */
+      if (r == REGTYPE) {
+        r = unlink(path);
+        if (r < 0) {
+          Print_Error(printer, "unlink", path);
+          free(copy);
+          return -1;
+        }
+        if (!path_end) {
+          r = mkdir(path, S_IRWXU | S_IRWXG | S_IRWXO);
+          if (r < 0) {
+            Print_Error(printer, "mkdir", path);
+            free(copy);
+            return -1;
+          }
+        }
+      }
+      else if (r < 0) {
+        /*
+         * Node does not exist which means the rest of the path will not exist.
+         */
+        while (!path_end) {
+          r = mkdir(path, S_IRWXU | S_IRWXG | S_IRWXO);
+          if (r < 0) {
+            Print_Error(printer, "mkdir", path);
+            free(copy);
+            return -1;
+          }
+          if (!path_end) {
+            *end = '/';
+            ++end;
+          }
+          while (*end != '\0' && *end != '/')
+            ++end;
+          if (*end == '\0')
+            path_end = true;
+        }
+      }
+      else if (path_end && r == DIRTYPE && !end_is_dir) {
+        /*
+         * We only handle a directory if at the end of the path and the end is
+         * a file. If we cannot remove the directory because it is not empty we
+         * raise an error. Otherwise this is a directory and we do nothing
+         * which lets us decend into it.
+         */
+        r = rmdir(path);
+        if (r < 0) {
+          Print_Error(printer, "rmdir", path);
+          free(copy);
+          return -1;
+        }
+      }
+
+      /*
+       * If not the end of the path put back the directory separator.
+       */
+      if (!path_end) {
+        *end = '/';
+        ++end;
+      }
+    }
+  }
+
+  free(copy);
+
+  return 0;
+}
+
+static int
+Untar_ProcessHeader(
+  const char          *bufr,
+  char                *fname,
+  unsigned long       *file_size,
+  unsigned long       *nblocks,
+  unsigned char       *linkflag,
+  const rtems_printer *printer
+)
+{
+  char           linkname[100];
+  int            sum;
+  int            hdr_chksum;
+  int            retval = UNTAR_SUCCESSFUL;
+
+  fname[0] = '\0';
+  *file_size = 0;
+  *nblocks = 0;
+  *linkflag = -1;
+
+  if (strncmp(&bufr[257], "ustar", 5)) {
+    return UNTAR_SUCCESSFUL;
+  }
+
+  /*
+   * Compute the TAR checksum and check with the value in the archive.  The
+   * checksum is computed over the entire header, but the checksum field is
+   * substituted with blanks.
+   */
+  hdr_chksum = _rtems_octal2ulong(&bufr[148], 8);
+  sum        = _rtems_tar_header_checksum(bufr);
+
+  if (sum != hdr_chksum) {
+    rtems_printf(printer, "untar: file header checksum error\n");
+    return UNTAR_INVALID_CHECKSUM;
+  }
+
+  strncpy(fname, bufr, MAX_NAME_FIELD_SIZE);
+  fname[MAX_NAME_FIELD_SIZE] = '\0';
+
+  *linkflag   = bufr[156];
+  *file_size = _rtems_octal2ulong(&bufr[124], 12);
+
+  /*
+   * We've decoded the header, now figure out what it contains and do something
+   * with it.
+   */
+  if (*linkflag == SYMTYPE) {
+    strncpy(linkname, &bufr[157], MAX_NAME_FIELD_SIZE);
+    linkname[MAX_NAME_FIELD_SIZE] = '\0';
+    rtems_printf(printer, "untar: symlink: %s -> %s\n", linkname, fname);
+    symlink(linkname, fname);
+  } else if (*linkflag == REGTYPE) {
+    rtems_printf(printer, "untar: file: %s (%i)\n", fname, (int) *file_size);
+    *nblocks = (((*file_size) + 511) & ~511) / 512;
+    if (Make_Path(printer, fname, false) < 0) {
+      retval  = UNTAR_FAIL;
+    }
+  } else if (*linkflag == DIRTYPE) {
+    int r;
+    rtems_printf(printer, "untar: dir: %s\n", fname);
+    if (Make_Path(printer, fname, true) < 0) {
+      retval  = UNTAR_FAIL;
+    }
+    r = mkdir(fname, S_IRWXU | S_IRWXG | S_IRWXO);
+    if (r < 0) {
+      if (errno == EEXIST) {
+        struct stat stat_buf;
+        if (stat(fname, &stat_buf) == 0) {
+          if (!S_ISDIR(stat_buf.st_mode)) {
+            r = unlink(fname);
+            if (r == 0) {
+              r = mkdir(fname, S_IRWXU | S_IRWXG | S_IRWXO);
+            }
+          }
+        }
+      }
+      if (r < 0) {
+        Print_Error(printer, "mkdir", fname);
+        retval = UNTAR_FAIL;
+      }
+    }
+  }
+
+  return retval;
+}
+
+/*
  * Function: Untar_FromMemory
  *
  * Description:
@@ -114,28 +357,26 @@ _rtems_octal2ulong(
  *
  */
 int
-Untar_FromMemory(
-  void   *tar_buf,
-  size_t  size
+Untar_FromMemory_Print(
+  void                *tar_buf,
+  size_t               size,
+  const rtems_printer *printer
 )
 {
   FILE           *fp;
   const char     *tar_ptr = (const char *)tar_buf;
   const char     *bufr;
-  size_t         n;
   char           fname[100];
-  char           linkname[100];
-  int            sum;
-  int            hdr_chksum;
-  int            retval;
+  int            retval = UNTAR_SUCCESSFUL;
   unsigned long  ptr;
-  unsigned long  i;
   unsigned long  nblocks;
   unsigned long  file_size;
   unsigned char  linkflag;
 
+  rtems_printf(printer, "untar: memory at %p (%zu)\n", tar_buf, size);
+
   ptr = 0;
-  while (1) {
+  while (true) {
     if (ptr + 512 > size) {
       retval = UNTAR_SUCCESSFUL;
       break;
@@ -144,56 +385,31 @@ Untar_FromMemory(
     /* Read the header */
     bufr = &tar_ptr[ptr];
     ptr += 512;
-    if (strncmp(&bufr[257], "ustar", 5)) {
-      retval = UNTAR_SUCCESSFUL;
-      break;
-    }
-
-    strncpy(fname, bufr, MAX_NAME_FIELD_SIZE);
-    fname[MAX_NAME_FIELD_SIZE] = '\0';
 
-    linkflag   = bufr[156];
-    file_size  = _rtems_octal2ulong(&bufr[124], 12);
+    retval = Untar_ProcessHeader(bufr, fname, &file_size, &nblocks, &linkflag, printer);
 
-    /*
-     * Compute the TAR checksum and check with the value in
-     * the archive.  The checksum is computed over the entire
-     * header, but the checksum field is substituted with blanks.
-     */
-    hdr_chksum = _rtems_octal2ulong(&bufr[148], 8);
-    sum = _rtems_tar_header_checksum(bufr);
-
-    if (sum != hdr_chksum) {
-      retval = UNTAR_INVALID_CHECKSUM;
+    if (retval != UNTAR_SUCCESSFUL)
       break;
-    }
 
-    /*
-     * We've decoded the header, now figure out what it contains and
-     * do something with it.
-     */
-    if (linkflag == SYMTYPE) {
-      strncpy(linkname, &bufr[157], MAX_NAME_FIELD_SIZE);
-      linkname[MAX_NAME_FIELD_SIZE] = '\0';
-      symlink(linkname, fname);
-    } else if (linkflag == REGTYPE) {
-      nblocks = (((file_size) + 511) & ~511) / 512;
+    if (linkflag == REGTYPE) {
       if ((fp = fopen(fname, "w")) == NULL) {
-        printk("Untar: failed to create file %s\n", fname);
+        Print_Error(printer, "open", fname);
         ptr += 512 * nblocks;
       } else {
         unsigned long sizeToGo = file_size;
-        size_t len;
+        size_t        len;
+        size_t        i;
+        size_t        n;
 
         /*
-         * Read out the data.  There are nblocks of data where nblocks
-         * is the file_size rounded to the nearest 512-byte boundary.
+         * Read out the data.  There are nblocks of data where nblocks is the
+         * file_size rounded to the nearest 512-byte boundary.
          */
-        for (i=0; i<nblocks; i++) {
-          len = ((sizeToGo < 512L)?(sizeToGo):(512L));
+        for (i = 0; i < nblocks; i++) {
+          len = ((sizeToGo < 512L) ? (sizeToGo) : (512L));
           n = fwrite(&tar_ptr[ptr], 1, len, fp);
           if (n != len) {
-            printk("untar: Error during write\n");
+            Print_Error(printer, "write", fname);
             retval  = UNTAR_FAIL;
             break;
           }
@@ -202,29 +418,42 @@ Untar_FromMemory(
         }
         fclose(fp);
       }
-    } else if (linkflag == DIRTYPE) {
-      if ( mkdir(fname, S_IRWXU | S_IRWXG | S_IRWXO) != 0 ) {
-        if (errno == EEXIST) {
-          struct stat stat_buf;
-          if ( stat(fname, &stat_buf) == 0 ) {
-            if (  S_ISDIR(stat_buf.st_mode) ) {
-              continue;
-            } else {
-              if ( unlink(fname) != -1 ) {
-                if ( mkdir(fname, S_IRWXU | S_IRWXG | S_IRWXO) == 0 )
-                  continue;
-              }
-            }
-          }
-        }
-        printk("Untar: failed to create directory %s\n", fname);
-        retval = UNTAR_FAIL;
-        break;
-      }
+
     }
   }
 
-  return(retval);
+  return retval;
+}
+
+/*
+ * Function: Untar_FromMemory
+ *
+ * Description:
+ *
+ *    This is a simple subroutine used to rip links, directories, and
+ *    files out of a block of memory.
+ *
+ *
+ * Inputs:
+ *
+ *    void *  tar_buf    - Pointer to TAR buffer.
+ *    size_t  size       - Length of TAR buffer.
+ *
+ *
+ * Output:
+ *
+ *    int - UNTAR_SUCCESSFUL (0)    on successful completion.
+ *          UNTAR_INVALID_CHECKSUM  for an invalid header checksum.
+ *          UNTAR_INVALID_HEADER    for an invalid header.
+ *
+ */
+int
+Untar_FromMemory(
+  void   *tar_buf,
+  size_t  size
+)
+{
+  return Untar_FromMemory_Print(tar_buf, size, false);
 }
 
 /*
@@ -246,21 +475,19 @@ Untar_FromMemory(
  *          UNTAR_INVALID_HEADER    for an invalid header.
  */
 int
-Untar_FromFile(
-  const char *tar_name
+Untar_FromFile_Print(
+  const char          *tar_name,
+  const rtems_printer *printer
 )
 {
   int            fd;
   char           *bufr;
   ssize_t        n;
   char           fname[100];
-  char           linkname[100];
-  int            sum;
-  int            hdr_chksum;
   int            retval;
   unsigned long  i;
   unsigned long  nblocks;
-  unsigned long  size;
+  unsigned long  file_size;
   unsigned char  linkflag;
 
   retval = UNTAR_SUCCESSFUL;
@@ -282,78 +509,62 @@ Untar_FromFile(
       break;
     }
 
-    if (strncmp(&bufr[257], "ustar", 5)) {
-      break;
-    }
-
-    strncpy(fname, bufr, MAX_NAME_FIELD_SIZE);
-    fname[MAX_NAME_FIELD_SIZE] = '\0';
-
-    linkflag   = bufr[156];
-    size       = _rtems_octal2ulong(&bufr[124], 12);
-
-    /*
-     * Compute the TAR checksum and check with the value in
-     * the archive.  The checksum is computed over the entire
-     * header, but the checksum field is substituted with blanks.
-     */
-    hdr_chksum = _rtems_octal2ulong(&bufr[148], 8);
-    sum = _rtems_tar_header_checksum(bufr);
+    retval = Untar_ProcessHeader(bufr, fname, &file_size, &nblocks, &linkflag, printer);
 
-    if (sum != hdr_chksum) {
-      retval = UNTAR_INVALID_CHECKSUM;
+    if (retval != UNTAR_SUCCESSFUL)
       break;
-    }
 
-    /*
-     * We've decoded the header, now figure out what it contains and
-     * do something with it.
-     */
-    if (linkflag == SYMTYPE) {
-      strncpy(linkname, &bufr[157], MAX_NAME_FIELD_SIZE);
-      linkname[MAX_NAME_FIELD_SIZE] = '\0';
-      symlink(linkname,fname);
-    } else if (linkflag == REGTYPE) {
+    if (linkflag == REGTYPE) {
       int out_fd;
 
       /*
        * Read out the data.  There are nblocks of data where nblocks
        * is the size rounded to the nearest 512-byte boundary.
        */
-      nblocks = (((size) + 511) & ~511) / 512;
 
       if ((out_fd = creat(fname, 0644)) == -1) {
-        (void) lseek(fd, SEEK_CUR, 512 * nblocks);
+        (void) lseek(fd, SEEK_CUR, 512UL * nblocks);
       } else {
-        for (i=0; i<nblocks; i++) {
+        for (i = 0; i < nblocks; i++) {
           n = read(fd, bufr, 512);
-          n = MIN(n, size - i*512);
+          n = MIN(n, file_size - (i * 512UL));
           (void) write(out_fd, bufr, n);
         }
         close(out_fd);
       }
-    } else if (linkflag == DIRTYPE) {
-      if ( mkdir(fname, S_IRWXU | S_IRWXG | S_IRWXO) != 0 ) {
-        if (errno == EEXIST) {
-          struct stat stat_buf;
-          if ( stat(fname, &stat_buf) == 0 ) {
-            if (  S_ISDIR(stat_buf.st_mode) ) {
-              continue;
-            } else {
-              if ( unlink(fname) != -1 ) {
-                if ( mkdir(fname, S_IRWXU | S_IRWXG | S_IRWXO) == 0 )
-                  continue;
-              }
-            }
-          }
-        }
-      }
     }
   }
+
   free(bufr);
   close(fd);
 
-  return(retval);
+  return retval;
+}
+
+/*
+ * Function: Untar_FromFile
+ *
+ * Description:
+ *
+ *    This is a simple subroutine used to rip links, directories, and
+ *    files out of a TAR file.
+ *
+ * Inputs:
+ *
+ *    const char *tar_name   - TAR filename.
+ *
+ * Output:
+ *
+ *    int - UNTAR_SUCCESSFUL (0)    on successful completion.
+ *          UNTAR_INVALID_CHECKSUM  for an invalid header checksum.
+ *          UNTAR_INVALID_HEADER    for an invalid header.
+ */
+int
+Untar_FromFile(
+  const char *tar_name
+)
+{
+  return Untar_FromFile_Print(tar_name, NULL);
 }
 
 /*
diff --git a/cpukit/libmisc/untar/untar.h b/cpukit/libmisc/untar/untar.h
index c1f71dc..d67c29e 100644
--- a/cpukit/libmisc/untar/untar.h
+++ b/cpukit/libmisc/untar/untar.h
@@ -2,7 +2,7 @@
  * @file
  *
  * @brief Untar an Image
- * 
+ *
  * This file defines the interface to methods which can untar an image.
  */
 
@@ -17,9 +17,12 @@
 #ifndef _RTEMS_UNTAR_H
 #define _RTEMS_UNTAR_H
 
+#include <stdbool.h>
 #include <stddef.h>
 #include <tar.h>
 
+#include <rtems/print.h>
+
 /**
  *  @defgroup libmisc_untar_img Untar Image
  *
@@ -37,7 +40,9 @@ extern "C" {
 
 
 int Untar_FromMemory(void *tar_buf, size_t size);
+int Untar_FromMemory_Print(void *tar_buf, size_t size, const rtems_printer* printer);
 int Untar_FromFile(const char *tar_name);
+int Untar_FromFile_Print(const char *tar_name, const rtems_printer* printer);
 
 /**************************************************************************
  * This converts octal ASCII number representations into an
diff --git a/testsuites/libtests/tar01/init.c b/testsuites/libtests/tar01/init.c
index 8ff107e..54760e9 100644
--- a/testsuites/libtests/tar01/init.c
+++ b/testsuites/libtests/tar01/init.c
@@ -44,9 +44,12 @@ void test_cat(
 void test_untar_from_memory(void)
 {
   rtems_status_code sc;
+  rtems_printer     printer;
+
+  rtems_print_printer_printf(&printer);
 
   printf("Untaring from memory - ");
-  sc = Untar_FromMemory((void *)TARFILE_START, TARFILE_SIZE);
+  sc = Untar_FromMemory_Print((void *)TARFILE_START, TARFILE_SIZE, &printer);
   if (sc != RTEMS_SUCCESSFUL) {
     printf ("error: untar failed: %s\n", rtems_status_text (sc));
     exit(1);
@@ -56,7 +59,7 @@ void test_untar_from_memory(void)
   /******************/
   printf( "========= /home/test_file =========\n" );
   test_cat( "/home/test_file", 0, 0 );
-  
+
   /******************/
   printf( "========= /symlink =========\n" );
   test_cat( "/symlink", 0, 0 );
@@ -97,7 +100,7 @@ void test_untar_from_file(void)
   /******************/
   printf( "========= /dest/home/test_file =========\n" );
   test_cat( "/dest/home/test_file", 0, 0 );
-  
+
   /******************/
   printf( "========= /dest/symlink =========\n" );
   test_cat( "/dest/symlink", 0, 0 );



More information about the vc mailing list