[PATCH] libmisc/untar: Support directory create and overwrites. Share the common code.
Chris Johns
chrisj at rtems.org
Wed May 18 23:34:26 UTC 2016
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 | 464 +++++++++++++++++++++++++++++++------------
cpukit/libmisc/untar/untar.h | 5 +-
2 files changed, 343 insertions(+), 126 deletions(-)
diff --git a/cpukit/libmisc/untar/untar.c b/cpukit/libmisc/untar/untar.c
index e4b0aeb..3cb4a31 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,246 @@ _rtems_octal2ulong(
}
/*
+ * Common error message formatter.
+ */
+static void
+Print_Error(const char* message, const char* path)
+{
+ fprintf(stderr, "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 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("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("unlink", path);
+ free(copy);
+ return -1;
+ }
+ if (!path_end) {
+ r = mkdir(path, S_IRWXU | S_IRWXG | S_IRWXO);
+ if (r < 0) {
+ Print_Error("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("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("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,
+ bool verbose
+)
+{
+ 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) {
+ fprintf(stderr, "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';
+ if (verbose)
+ printf("untar: symlink: %s -> %s\n", linkname, fname);
+ symlink(linkname, fname);
+ } else if (*linkflag == REGTYPE) {
+ if (verbose)
+ printf("untar: file: %s (%i)\n", fname, (int) *file_size);
+ *nblocks = (((*file_size) + 511) & ~511) / 512;
+ if (Make_Path(fname, false) < 0) {
+ retval = UNTAR_FAIL;
+ }
+ } else if (*linkflag == DIRTYPE) {
+ int r;
+ if (verbose)
+ printf("untar: dir: %s\n", fname);
+ if (Make_Path(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("mkdir", fname);
+ retval = UNTAR_FAIL;
+ }
+ }
+ }
+
+ return retval;
+}
+
+/*
* Function: Untar_FromMemory
*
* Description:
@@ -114,28 +359,27 @@ _rtems_octal2ulong(
*
*/
int
-Untar_FromMemory(
+Untar_FromMemory_Verbose(
void *tar_buf,
- size_t size
+ size_t size,
+ bool verbose
)
{
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;
+ if (verbose)
+ printf("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 +388,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);
- /*
- * 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, verbose);
- 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("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("write", fname);
retval = UNTAR_FAIL;
break;
}
@@ -202,29 +421,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_Verbose(tar_buf, size, false);
}
/*
@@ -246,21 +478,19 @@ Untar_FromMemory(
* UNTAR_INVALID_HEADER for an invalid header.
*/
int
-Untar_FromFile(
- const char *tar_name
+Untar_FromFile_Verbose(
+ const char *tar_name,
+ bool verbose
)
{
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 +512,62 @@ Untar_FromFile(
break;
}
- if (strncmp(&bufr[257], "ustar", 5)) {
- break;
- }
-
- strncpy(fname, bufr, MAX_NAME_FIELD_SIZE);
- fname[MAX_NAME_FIELD_SIZE] = '\0';
+ retval = Untar_ProcessHeader(bufr, fname, &file_size, &nblocks, &linkflag, verbose);
- 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);
-
- 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_Verbose(tar_name, false);
}
/*
diff --git a/cpukit/libmisc/untar/untar.h b/cpukit/libmisc/untar/untar.h
index c1f71dc..5ef299e 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,6 +17,7 @@
#ifndef _RTEMS_UNTAR_H
#define _RTEMS_UNTAR_H
+#include <stdbool.h>
#include <stddef.h>
#include <tar.h>
@@ -37,7 +38,9 @@ extern "C" {
int Untar_FromMemory(void *tar_buf, size_t size);
+int Untar_FromMemory_Verbose(void *tar_buf, size_t size, bool verbose);
int Untar_FromFile(const char *tar_name);
+int Untar_FromFile_Verbose(const char *tar_name, bool verbose);
/**************************************************************************
* This converts octal ASCII number representations into an
--
2.4.6
More information about the devel
mailing list