diff options
Diffstat (limited to 'libtar/block.c')
-rw-r--r-- | libtar/block.c | 496 |
1 files changed, 496 insertions, 0 deletions
diff --git a/libtar/block.c b/libtar/block.c new file mode 100644 index 000000000..6ed9e6000 --- /dev/null +++ b/libtar/block.c @@ -0,0 +1,496 @@ +/* +** Copyright 1998-2003 University of Illinois Board of Trustees +** Copyright 1998-2003 Mark D. Roth +** All rights reserved. +** +** block.c - libtar code to handle tar archive header blocks +** +** Mark D. Roth <roth@uiuc.edu> +** Campus Information Technologies and Educational Services +** University of Illinois at Urbana-Champaign +*/ + +#include <internal.h> +#include <stdio.h> +#include <errno.h> + +#ifdef STDC_HEADERS +# include <string.h> +# include <stdlib.h> +#endif + +#define BIT_ISSET(bitmask, bit) ((bitmask) & (bit)) + +// Used to identify selinux_context in extended ('x') +// metadata. From RedHat implementation. +#define SELINUX_TAG "RHT.security.selinux=" +#define SELINUX_TAG_LEN 21 + +/* read a header block */ +int +th_read_internal(TAR *t) +{ + int i; + int num_zero_blocks = 0; + +#ifdef DEBUG + printf("==> th_read_internal(TAR=\"%s\")\n", t->pathname); +#endif + + while ((i = tar_block_read(t, &(t->th_buf))) == T_BLOCKSIZE) + { + /* two all-zero blocks mark EOF */ + if (t->th_buf.name[0] == '\0') + { + num_zero_blocks++; + if (!BIT_ISSET(t->options, TAR_IGNORE_EOT) + && num_zero_blocks >= 2) + return 0; /* EOF */ + else + continue; + } + + /* verify magic and version */ + if (BIT_ISSET(t->options, TAR_CHECK_MAGIC) + && strncmp(t->th_buf.magic, TMAGIC, TMAGLEN - 1) != 0) + { +#ifdef DEBUG + puts("!!! unknown magic value in tar header"); +#endif + return -2; + } + + if (BIT_ISSET(t->options, TAR_CHECK_VERSION) + && strncmp(t->th_buf.version, TVERSION, TVERSLEN) != 0) + { +#ifdef DEBUG + puts("!!! unknown version value in tar header"); +#endif + return -2; + } + + /* check chksum */ + if (!BIT_ISSET(t->options, TAR_IGNORE_CRC) + && !th_crc_ok(t)) + { +#ifdef DEBUG + puts("!!! tar header checksum error"); +#endif + return -2; + } + + break; + } + +#ifdef DEBUG + printf("<== th_read_internal(): returning %d\n", i); +#endif + return i; +} + + +/* wrapper function for th_read_internal() to handle GNU extensions */ +int +th_read(TAR *t) +{ + int i, j; + size_t sz; + char *ptr; + +#ifdef DEBUG + printf("==> th_read(t=0x%lx)\n", t); +#endif + + if (t->th_buf.gnu_longname != NULL) + free(t->th_buf.gnu_longname); + if (t->th_buf.gnu_longlink != NULL) + free(t->th_buf.gnu_longlink); +#ifdef HAVE_SELINUX + if (t->th_buf.selinux_context != NULL) + free(t->th_buf.selinux_context); +#endif + + memset(&(t->th_buf), 0, sizeof(struct tar_header)); + + i = th_read_internal(t); + if (i == 0) + return 1; + else if (i != T_BLOCKSIZE) + { + if (i != -1) + errno = EINVAL; + return -1; + } + + /* check for GNU long link extention */ + if (TH_ISLONGLINK(t)) + { + sz = th_get_size(t); + j = (sz / T_BLOCKSIZE) + (sz % T_BLOCKSIZE ? 1 : 0); +#ifdef DEBUG + printf(" th_read(): GNU long linkname detected " + "(%ld bytes, %d blocks)\n", sz, j); +#endif + t->th_buf.gnu_longlink = (char *)malloc(j * T_BLOCKSIZE); + if (t->th_buf.gnu_longlink == NULL) + return -1; + + for (ptr = t->th_buf.gnu_longlink; j > 0; + j--, ptr += T_BLOCKSIZE) + { +#ifdef DEBUG + printf(" th_read(): reading long linkname " + "(%d blocks left, ptr == %ld)\n", j, ptr); +#endif + i = tar_block_read(t, ptr); + if (i != T_BLOCKSIZE) + { + if (i != -1) + errno = EINVAL; + return -1; + } +#ifdef DEBUG + printf(" th_read(): read block == \"%s\"\n", ptr); +#endif + } +#ifdef DEBUG + printf(" th_read(): t->th_buf.gnu_longlink == \"%s\"\n", + t->th_buf.gnu_longlink); +#endif + + i = th_read_internal(t); + if (i != T_BLOCKSIZE) + { + if (i != -1) + errno = EINVAL; + return -1; + } + } + + /* check for GNU long name extention */ + if (TH_ISLONGNAME(t)) + { + sz = th_get_size(t); + j = (sz / T_BLOCKSIZE) + (sz % T_BLOCKSIZE ? 1 : 0); +#ifdef DEBUG + printf(" th_read(): GNU long filename detected " + "(%ld bytes, %d blocks)\n", sz, j); +#endif + t->th_buf.gnu_longname = (char *)malloc(j * T_BLOCKSIZE); + if (t->th_buf.gnu_longname == NULL) + return -1; + + for (ptr = t->th_buf.gnu_longname; j > 0; + j--, ptr += T_BLOCKSIZE) + { +#ifdef DEBUG + printf(" th_read(): reading long filename " + "(%d blocks left, ptr == %ld)\n", j, ptr); +#endif + i = tar_block_read(t, ptr); + if (i != T_BLOCKSIZE) + { + if (i != -1) + errno = EINVAL; + return -1; + } +#ifdef DEBUG + printf(" th_read(): read block == \"%s\"\n", ptr); +#endif + } +#ifdef DEBUG + printf(" th_read(): t->th_buf.gnu_longname == \"%s\"\n", + t->th_buf.gnu_longname); +#endif + + i = th_read_internal(t); + if (i != T_BLOCKSIZE) + { + if (i != -1) + errno = EINVAL; + return -1; + } + } + +#ifdef HAVE_SELINUX + if(TH_ISEXTHEADER(t)) + { + sz = th_get_size(t); + + if(sz >= T_BLOCKSIZE) // Not supported + { +#ifdef DEBUG + printf(" th_read(): Extended header is too long!\n"); +#endif + } + else + { + char buf[T_BLOCKSIZE]; + i = tar_block_read(t, buf); + if (i != T_BLOCKSIZE) + { + if (i != -1) + errno = EINVAL; + return -1; + } + + // To be sure + buf[T_BLOCKSIZE-1] = 0; + + int len = strlen(buf); + char *start = strstr(buf, SELINUX_TAG); + if(start && start+SELINUX_TAG_LEN < buf+len) + { + start += SELINUX_TAG_LEN; + char *end = strchr(start, '\n'); + if(end) + { + t->th_buf.selinux_context = strndup(start, end-start); +#ifdef DEBUG + printf(" th_read(): SELinux context xattr detected: %s\n", t->th_buf.selinux_context); +#endif + } + } + } + + i = th_read_internal(t); + if (i != T_BLOCKSIZE) + { + if (i != -1) + errno = EINVAL; + return -1; + } + } +#endif + +#if 0 + /* + ** work-around for old archive files with broken typeflag fields + ** NOTE: I fixed this in the TH_IS*() macros instead + */ + + /* + ** (directories are signified with a trailing '/') + */ + if (t->th_buf.typeflag == AREGTYPE + && t->th_buf.name[strlen(t->th_buf.name) - 1] == '/') + t->th_buf.typeflag = DIRTYPE; + + /* + ** fallback to using mode bits + */ + if (t->th_buf.typeflag == AREGTYPE) + { + mode = (mode_t)oct_to_int(t->th_buf.mode); + + if (S_ISREG(mode)) + t->th_buf.typeflag = REGTYPE; + else if (S_ISDIR(mode)) + t->th_buf.typeflag = DIRTYPE; + else if (S_ISFIFO(mode)) + t->th_buf.typeflag = FIFOTYPE; + else if (S_ISCHR(mode)) + t->th_buf.typeflag = CHRTYPE; + else if (S_ISBLK(mode)) + t->th_buf.typeflag = BLKTYPE; + else if (S_ISLNK(mode)) + t->th_buf.typeflag = SYMTYPE; + } +#endif + + return 0; +} + + +/* write a header block */ +int +th_write(TAR *t) +{ + int i, j; + char type2; + size_t sz, sz2; + char *ptr; + char buf[T_BLOCKSIZE]; + +#ifdef DEBUG + printf("==> th_write(TAR=\"%s\")\n", t->pathname); + th_print(t); +#endif + + if ((t->options & TAR_GNU) && t->th_buf.gnu_longlink != NULL) + { +#ifdef DEBUG + printf("th_write(): using gnu_longlink (\"%s\")\n", + t->th_buf.gnu_longlink); +#endif + /* save old size and type */ + type2 = t->th_buf.typeflag; + sz2 = th_get_size(t); + + /* write out initial header block with fake size and type */ + t->th_buf.typeflag = GNU_LONGLINK_TYPE; + sz = strlen(t->th_buf.gnu_longlink); + th_set_size(t, sz); + th_finish(t); + i = tar_block_write(t, &(t->th_buf)); + if (i != T_BLOCKSIZE) + { + if (i != -1) + errno = EINVAL; + return -1; + } + + /* write out extra blocks containing long name */ + for (j = (sz / T_BLOCKSIZE) + (sz % T_BLOCKSIZE ? 1 : 0), + ptr = t->th_buf.gnu_longlink; j > 1; + j--, ptr += T_BLOCKSIZE) + { + i = tar_block_write(t, ptr); + if (i != T_BLOCKSIZE) + { + if (i != -1) + errno = EINVAL; + return -1; + } + } + memset(buf, 0, T_BLOCKSIZE); + strncpy(buf, ptr, T_BLOCKSIZE); + i = tar_block_write(t, &buf); + if (i != T_BLOCKSIZE) + { + if (i != -1) + errno = EINVAL; + return -1; + } + + /* reset type and size to original values */ + t->th_buf.typeflag = type2; + th_set_size(t, sz2); + } + + if ((t->options & TAR_GNU) && t->th_buf.gnu_longname != NULL) + { +#ifdef DEBUG + printf("th_write(): using gnu_longname (\"%s\")\n", + t->th_buf.gnu_longname); +#endif + /* save old size and type */ + type2 = t->th_buf.typeflag; + sz2 = th_get_size(t); + + /* write out initial header block with fake size and type */ + t->th_buf.typeflag = GNU_LONGNAME_TYPE; + sz = strlen(t->th_buf.gnu_longname); + th_set_size(t, sz); + th_finish(t); + i = tar_block_write(t, &(t->th_buf)); + if (i != T_BLOCKSIZE) + { + if (i != -1) + errno = EINVAL; + return -1; + } + + /* write out extra blocks containing long name */ + for (j = (sz / T_BLOCKSIZE) + (sz % T_BLOCKSIZE ? 1 : 0), + ptr = t->th_buf.gnu_longname; j > 1; + j--, ptr += T_BLOCKSIZE) + { + i = tar_block_write(t, ptr); + if (i != T_BLOCKSIZE) + { + if (i != -1) + errno = EINVAL; + return -1; + } + } + memset(buf, 0, T_BLOCKSIZE); + strncpy(buf, ptr, T_BLOCKSIZE); + i = tar_block_write(t, &buf); + if (i != T_BLOCKSIZE) + { + if (i != -1) + errno = EINVAL; + return -1; + } + + /* reset type and size to original values */ + t->th_buf.typeflag = type2; + th_set_size(t, sz2); + } + +#ifdef HAVE_SELINUX + if((t->options & TAR_STORE_SELINUX) && t->th_buf.selinux_context != NULL) + { +#ifdef DEBUG + printf("th_write(): using selinux_context (\"%s\")\n", + t->th_buf.selinux_context); +#endif + /* save old size and type */ + type2 = t->th_buf.typeflag; + sz2 = th_get_size(t); + + /* write out initial header block with fake size and type */ + t->th_buf.typeflag = TH_EXT_TYPE; + + /* setup size - EXT header has format "*size of this whole tag as ascii numbers* *space* *content* *newline* */ + // size newline + sz = SELINUX_TAG_LEN + strlen(t->th_buf.selinux_context) + 3 + 1; + + if(sz >= 100) // another ascci digit for size + ++sz; + + if(sz >= T_BLOCKSIZE) // impossible + { + errno = EINVAL; + return -1; + } + + th_set_size(t, sz); + th_finish(t); + i = tar_block_write(t, &(t->th_buf)); + if (i != T_BLOCKSIZE) + { + if (i != -1) + errno = EINVAL; + return -1; + } + + memset(buf, 0, T_BLOCKSIZE); + snprintf(buf, T_BLOCKSIZE, "%d "SELINUX_TAG"%s\n", sz, t->th_buf.selinux_context); + i = tar_block_write(t, &buf); + if (i != T_BLOCKSIZE) + { + if (i != -1) + errno = EINVAL; + return -1; + } + + /* reset type and size to original values */ + t->th_buf.typeflag = type2; + th_set_size(t, sz2); + } +#endif + + th_finish(t); + +#ifdef DEBUG + /* print tar header */ + th_print(t); +#endif + + i = tar_block_write(t, &(t->th_buf)); + if (i != T_BLOCKSIZE) + { + if (i != -1) + errno = EINVAL; + return -1; + } + +#ifdef DEBUG + puts("th_write(): returning 0"); +#endif + return 0; +} + + |