From d84e346b26017f021c1a7d5c8ad078c7264240ab Mon Sep 17 00:00:00 2001 From: Chris Johns Date: Thu, 19 May 2016 09:18:21 +1000 Subject: 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 +- 2 files changed, 343 insertions(+), 127 deletions(-) (limited to 'cpukit/libmisc/untar') diff --git a/cpukit/libmisc/untar/untar.c b/cpukit/libmisc/untar/untar.c index e4b0aeb968..f6f4f0c837 100644 --- a/cpukit/libmisc/untar/untar.c +++ b/cpukit/libmisc/untar/untar.c @@ -13,7 +13,9 @@ /* * Written by: Jake Janovetz - + * + * Copyright 2016 Chris Johns + * * 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 #include #include #include @@ -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, @@ -91,6 +96,244 @@ _rtems_octal2ulong( return(num); } +/* + * 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 * @@ -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 #include #include +#include + /** * @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 -- cgit v1.2.3