Files
rtems/cpukit/libmisc/untar/untar.c
Chris Johns b0f08c83e2 libmisc/untar: Set the perms to the value in the tar file.
This patch parses the mode field in the tar header and sets the
directory or file to the mode value in the header.

Closes #2768.
2016-08-09 17:22:26 +10:00

703 lines
16 KiB
C

/**
* @file
* @brief Untar an Image
* @ingroup libmisc_untar_img Untar Image
* FIXME:
* 1. Symbolic links are not created.
* 2. Untar_FromMemory uses FILE *fp.
* 3. How to determine end of archive?
*/
/*
* Written by: Jake Janovetz <janovetz@tempest.ece.uiuc.edu>
*
* Copyright 2016 Chris Johns <chrisj@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.
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include <stdbool.h>
#include <sys/param.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <rtems/untar.h>
#include <rtems/bspIo.h>
/*
* TAR file format:
* Offset Length Contents
* 0 100 bytes File name ('\0' terminated, 99 maxmum length)
* 100 8 bytes File mode (in octal ascii)
* 108 8 bytes User ID (in octal ascii)
* 116 8 bytes Group ID (in octal ascii)
* 124 12 bytes File size (s) (in octal ascii)
* 136 12 bytes Modify time (in octal ascii)
* 148 8 bytes Header checksum (in octal ascii)
* 156 1 bytes Link flag
* 157 100 bytes Linkname ('\0' terminated, 99 maxmum length)
* 257 8 bytes Magic PAX ("ustar\0" + 2 bytes padding)
* 257 8 bytes Magic GNU tar ("ustar \0")
* 265 32 bytes User name ('\0' terminated, 31 maxmum length)
* 297 32 bytes Group name ('\0' terminated, 31 maxmum length)
* 329 8 bytes Major device ID (in octal ascii)
* 337 8 bytes Minor device ID (in octal ascii)
* 345 155 bytes Prefix
* 512 (s+p)bytes File contents (s+p) := (((s) + 511) & ~511),
* round up to 512 bytes
*
* Checksum:
* int i, sum;
* char* header = tar_header_pointer;
* 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,
size_t len
)
{
size_t i;
unsigned long num;
num = 0;
for (i=0; i < len; i++) {
if ((octascii[i] < '0') || (octascii[i] > '9')) {
continue;
}
num = num * 8 + ((unsigned long)(octascii[i] - '0'));
}
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 *mode,
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';
*mode = strtoul(&bufr[100], NULL, 8);
*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 (s:%i,m:%04o)\n",
fname, (int) *file_size, (int) *mode);
*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, *mode);
}
}
}
}
if (r < 0) {
Print_Error(printer, "mkdir", fname);
retval = UNTAR_FAIL;
}
}
}
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_Print(
void *tar_buf,
size_t size,
const rtems_printer *printer
)
{
int fd;
const char *tar_ptr = (const char *)tar_buf;
const char *bufr;
char fname[100];
int retval = UNTAR_SUCCESSFUL;
unsigned long ptr;
unsigned long nblocks = 0;
unsigned long file_size = 0;
unsigned long mode = 0;
unsigned char linkflag = 0;
rtems_printf(printer, "untar: memory at %p (%zu)\n", tar_buf, size);
ptr = 0;
while (true) {
if (ptr + 512 > size) {
retval = UNTAR_SUCCESSFUL;
break;
}
/* Read the header */
bufr = &tar_ptr[ptr];
ptr += 512;
retval = Untar_ProcessHeader(bufr, fname, &mode, &file_size,
&nblocks, &linkflag, printer);
if (retval != UNTAR_SUCCESSFUL)
break;
if (linkflag == REGTYPE) {
if ((fd = open(fname, O_TRUNC | O_CREAT | O_WRONLY, mode)) == -1) {
Print_Error(printer, "open", fname);
ptr += 512 * nblocks;
} else {
unsigned long sizeToGo = file_size;
ssize_t len;
ssize_t i;
ssize_t n;
/*
* 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));
n = write(fd, &tar_ptr[ptr], len);
if (n != len) {
Print_Error(printer, "write", fname);
retval = UNTAR_FAIL;
break;
}
ptr += 512;
sizeToGo -= n;
}
close(fd);
}
}
}
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);
}
/*
* 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_Print(
const char *tar_name,
const rtems_printer *printer
)
{
int fd;
char *bufr;
ssize_t n;
char fname[100];
int retval;
unsigned long i;
unsigned long nblocks = 0;
unsigned long file_size = 0;
unsigned long mode = 0;
unsigned char linkflag = 0;
retval = UNTAR_SUCCESSFUL;
if ((fd = open(tar_name, O_RDONLY)) < 0) {
return UNTAR_FAIL;
}
bufr = (char *)malloc(512);
if (bufr == NULL) {
close(fd);
return(UNTAR_FAIL);
}
while (1) {
/* Read the header */
/* If the header read fails, we just consider it the end of the tarfile. */
if ((n = read(fd, bufr, 512)) != 512) {
break;
}
retval = Untar_ProcessHeader(bufr, fname, &mode, &file_size,
&nblocks, &linkflag, printer);
if (retval != UNTAR_SUCCESSFUL)
break;
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.
*/
if ((out_fd = creat(fname, mode)) == -1) {
(void) lseek(fd, SEEK_CUR, 512UL * nblocks);
} else {
for (i = 0; i < nblocks; i++) {
n = read(fd, bufr, 512);
n = MIN(n, file_size - (i * 512UL));
(void) write(out_fd, bufr, n);
}
close(out_fd);
}
}
}
free(bufr);
close(fd);
return retval;
}
void Untar_ChunkContext_Init(Untar_ChunkContext *context)
{
context->state = UNTAR_CHUNK_HEADER;
context->done_bytes = 0;
context->out_fd = -1;
}
int Untar_FromChunk_Print(
Untar_ChunkContext *context,
void *chunk,
size_t chunk_size,
const rtems_printer* printer
)
{
char *buf;
size_t done;
size_t todo;
size_t remaining;
size_t consume;
int retval;
unsigned char linkflag;
buf = chunk;
done = 0;
todo = chunk_size;
while (todo > 0) {
switch (context->state) {
case UNTAR_CHUNK_HEADER:
remaining = 512 - context->done_bytes;
consume = MIN(remaining, todo);
memcpy(&context->header[context->done_bytes], &buf[done], consume);
context->done_bytes += consume;
if (context->done_bytes == 512) {
retval = Untar_ProcessHeader(
&context->header[0],
&context->fname[0],
&context->mode,
&context->todo_bytes,
&context->todo_blocks,
&linkflag,
printer
);
if (retval != UNTAR_SUCCESSFUL) {
context->state = UNTAR_CHUNK_ERROR;
return retval;
}
if (linkflag == REGTYPE) {
context->out_fd = creat(&context->fname[0], context->mode);
if (context->out_fd >= 0) {
context->state = UNTAR_CHUNK_WRITE;
context->done_bytes = 0;
} else {
context->state = UNTAR_CHUNK_SKIP;
context->todo_bytes = 512 * context->todo_blocks;
context->done_bytes = 0;
}
} else {
context->done_bytes = 0;
}
}
break;
case UNTAR_CHUNK_SKIP:
remaining = context->todo_bytes - context->done_bytes;
consume = MIN(remaining, todo);
context->done_bytes += consume;
if (context->done_bytes == context->todo_bytes) {
context->state = UNTAR_CHUNK_HEADER;
context->done_bytes = 0;
}
break;
case UNTAR_CHUNK_WRITE:
remaining = context->todo_bytes - context->done_bytes;
consume = MIN(remaining, todo);
write(context->out_fd, &buf[done], consume);
context->done_bytes += consume;
if (context->done_bytes == context->todo_bytes) {
close(context->out_fd);
context->out_fd = -1;
context->state = UNTAR_CHUNK_SKIP;
context->todo_bytes = 512 * context->todo_blocks - context->todo_bytes;
context->done_bytes = 0;
}
break;
default:
return UNTAR_FAIL;
}
done += consume;
todo -= consume;
}
return UNTAR_SUCCESSFUL;
}
/*
* 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);
}
/*
* 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.
*/
int
_rtems_tar_header_checksum(
const char *bufr
)
{
int i, sum;
sum = 0;
for (i=0; i<512; i++) {
if ((i >= 148) && (i < 156))
sum += 0xff & ' ';
else
sum += 0xff & bufr[i];
}
return(sum);
}