From 6623b1fe4df1e2fceb27eff286a86cf36809b2bc Mon Sep 17 00:00:00 2001 From: David Oberhollenzer Date: Thu, 4 Jul 2019 12:43:57 +0200 Subject: libtar: add support for xattr extensions Signed-off-by: David Oberhollenzer --- include/tar.h | 8 +++++++ lib/Makemodule.am | 3 ++- lib/tar/base64.c | 33 ++++++++++++++++++++++++++ lib/tar/cleanup.c | 12 ++++++++++ lib/tar/internal.h | 6 +++++ lib/tar/read_header.c | 52 ++++++++++++++++++++++++++++++++++++++++- lib/tar/urldecode.c | 31 +++++++++++++++++++++++++ tests/Makemodule.am | 12 +++++++++- tests/tar_xattr_bsd.c | 60 ++++++++++++++++++++++++++++++++++++++++++++++++ tests/tar_xattr_schily.c | 60 ++++++++++++++++++++++++++++++++++++++++++++++++ 10 files changed, 274 insertions(+), 3 deletions(-) create mode 100644 lib/tar/base64.c create mode 100644 lib/tar/urldecode.c create mode 100644 tests/tar_xattr_bsd.c create mode 100644 tests/tar_xattr_schily.c diff --git a/include/tar.h b/include/tar.h index fd701d9..424be80 100644 --- a/include/tar.h +++ b/include/tar.h @@ -56,6 +56,13 @@ typedef struct { char padding[7]; } gnu_sparse_t; +typedef struct tar_xattr_t { + struct tar_xattr_t *next; + char *key; + char *value; + char data[]; +} tar_xattr_t; + typedef struct { struct stat sb; char *name; @@ -64,6 +71,7 @@ typedef struct { uint64_t actual_size; uint64_t record_size; bool unknown_record; + tar_xattr_t *xattr; } tar_header_decoded_t; #define TAR_TYPE_FILE '0' diff --git a/lib/Makemodule.am b/lib/Makemodule.am index 23ce70d..865d99a 100644 --- a/lib/Makemodule.am +++ b/lib/Makemodule.am @@ -10,7 +10,8 @@ libfstree_a_CPPFLAGS = $(AM_CPPFLAGS) libtar_a_SOURCES = lib/tar/read_header.c lib/tar/write_header.c lib/tar/skip.c libtar_a_SOURCES += lib/tar/number.c lib/tar/checksum.c lib/tar/cleanup.c libtar_a_SOURCES += lib/tar/read_sparse_map.c lib/tar/read_sparse_map_old.c -libtar_a_SOURCES += lib/tar/internal.h include/tar.h +libtar_a_SOURCES += lib/tar/base64.c lib/tar/urldecode.c lib/tar/internal.h +libtar_a_SOURCES += include/tar.h libtar_a_CFLAGS = $(AM_CFLAGS) libtar_a_CPPFLAGS = $(AM_CPPFLAGS) diff --git a/lib/tar/base64.c b/lib/tar/base64.c new file mode 100644 index 0000000..313e9f4 --- /dev/null +++ b/lib/tar/base64.c @@ -0,0 +1,33 @@ +/* SPDX-License-Identifier: GPL-3.0-or-later */ +#include "internal.h" + +static uint8_t convert(char in) +{ + if (isupper(in)) + return in - 'A'; + if (islower(in)) + return in - 'a' + 26; + if (isdigit(in)) + return in - '0' + 52; + if (in == '+') + return 62; + if (in == '/' || in == '-') + return 63; + return 0; +} + +void base64_decode(uint8_t *out, const char *in) +{ + char temp[4]; + + while (*in != '\0' && *in != '=') { + temp[0] = *in == '\0' ? 0 : convert(*(in++)); + temp[1] = *in == '\0' ? 0 : convert(*(in++)); + temp[2] = *in == '\0' ? 0 : convert(*(in++)); + temp[3] = *in == '\0' ? 0 : convert(*(in++)); + + *(out++) = ((temp[0] << 2) & 0xFC) | ((temp[1] >> 4) & 0x03); + *(out++) = ((temp[1] << 4) & 0xF0) | ((temp[2] >> 2) & 0x0F); + *(out++) = ((temp[2] << 6) & 0xC0) | ( temp[3] & 0x3F); + } +} diff --git a/lib/tar/cleanup.c b/lib/tar/cleanup.c index c4d1734..a34c28b 100644 --- a/lib/tar/cleanup.c +++ b/lib/tar/cleanup.c @@ -12,8 +12,20 @@ void free_sparse_list(sparse_map_t *sparse) } } +void free_xattr_list(tar_xattr_t *list) +{ + tar_xattr_t *old; + + while (list != NULL) { + old = list; + list = list->next; + free(old); + } +} + void clear_header(tar_header_decoded_t *hdr) { + free_xattr_list(hdr->xattr); free_sparse_list(hdr->sparse); free(hdr->name); free(hdr->link_target); diff --git a/lib/tar/internal.h b/lib/tar/internal.h index 3c0e27f..04a97d3 100644 --- a/lib/tar/internal.h +++ b/lib/tar/internal.h @@ -50,4 +50,10 @@ sparse_map_t *read_gnu_old_sparse(int fd, tar_header_t *hdr); void free_sparse_list(sparse_map_t *sparse); +void free_xattr_list(tar_xattr_t *list); + +void base64_decode(uint8_t *out, const char *in); + +void urldecode(char *str); + #endif /* INTERNAL_H */ diff --git a/lib/tar/read_header.c b/lib/tar/read_header.c index db40d7d..94c4a69 100644 --- a/lib/tar/read_header.c +++ b/lib/tar/read_header.c @@ -60,12 +60,29 @@ fail: return NULL; } +static tar_xattr_t *mkxattr(const char *key, size_t keylen, + const char *value, size_t valuelen) +{ + tar_xattr_t *xattr; + + xattr = calloc(1, sizeof(*xattr) + keylen + 1 + valuelen + 1); + if (xattr == NULL) + return NULL; + + xattr->key = xattr->data; + xattr->value = xattr->data + keylen + 1; + memcpy(xattr->key, key, keylen); + memcpy(xattr->value, value, valuelen); + return xattr; +} + static int read_pax_header(int fd, uint64_t entsize, unsigned int *set_by_pax, tar_header_decoded_t *out) { sparse_map_t *sparse_last = NULL, *sparse; uint64_t field, offset = 0, num_bytes = 0; - char *buffer, *line; + char *buffer, *line, *key, *ptr, *value; + tar_xattr_t *xattr; uint64_t i; buffer = record_to_memory(fd, entsize); @@ -183,6 +200,39 @@ static int read_pax_header(int fd, uint64_t entsize, unsigned int *set_by_pax, sparse_last->next = sparse; sparse_last = sparse; } + } else if (!strncmp(line, "SCHILY.xattr.", 13)) { + key = line + 13; + + ptr = strrchr(key, '='); + if (ptr == NULL || ptr == key) + continue; + + value = ptr + 1; + + xattr = mkxattr(key, ptr - key, value, strlen(value)); + if (xattr == NULL) + goto fail_errno; + + xattr->next = out->xattr; + out->xattr = xattr; + } else if (!strncmp(line, "LIBARCHIVE.xattr.", 17)) { + key = line + 17; + + ptr = strrchr(key, '='); + if (ptr == NULL || ptr == key) + continue; + + value = ptr + 1; + + xattr = mkxattr(key, ptr - key, value, strlen(value)); + if (xattr == NULL) + goto fail_errno; + + urldecode(xattr->key); + base64_decode((uint8_t *)xattr->value, value); + + xattr->next = out->xattr; + out->xattr = xattr; } } diff --git a/lib/tar/urldecode.c b/lib/tar/urldecode.c new file mode 100644 index 0000000..ac03f10 --- /dev/null +++ b/lib/tar/urldecode.c @@ -0,0 +1,31 @@ +/* SPDX-License-Identifier: GPL-3.0-or-later */ +#include "internal.h" + +static int xdigit(int x) +{ + if (isupper(x)) + return x - 'A' + 0x0A; + if (islower(x)) + return x - 'a' + 0x0A; + return x - '0'; +} + +void urldecode(char *str) +{ + unsigned char *out = (unsigned char *)str; + char *in = str; + int x; + + while (*in != '\0') { + x = *(in++); + + if (x == '%' && isxdigit(in[0]) && isxdigit(in[1])) { + x = xdigit(*(in++)) << 4; + x |= xdigit(*(in++)); + } + + *(out++) = x; + } + + *out = '\0'; +} diff --git a/tests/Makemodule.am b/tests/Makemodule.am index 2c8344a..ad6532c 100644 --- a/tests/Makemodule.am +++ b/tests/Makemodule.am @@ -60,18 +60,28 @@ test_tar_sparse_gnu2_LDADD = libtar.a libutil.a test_tar_sparse_gnu2_CPPFLAGS = $(AM_CPPFLAGS) test_tar_sparse_gnu2_CPPFLAGS += -DTESTPATH=$(top_srcdir)/tests/tar +test_tar_xattr_bsd_SOURCES = tests/tar_xattr_bsd.c +test_tar_xattr_bsd_LDADD = libtar.a libutil.a +test_tar_xattr_bsd_CPPFLAGS = $(AM_CPPFLAGS) -DTESTPATH=$(top_srcdir)/tests/tar + +test_tar_xattr_schily_SOURCES = tests/tar_xattr_schily.c +test_tar_xattr_schily_LDADD = libtar.a libutil.a +test_tar_xattr_schily_CPPFLAGS = $(AM_CPPFLAGS) +test_tar_xattr_schily_CPPFLAGS += -DTESTPATH=$(top_srcdir)/tests/tar + check_PROGRAMS += test_canonicalize_name test_mknode_simple test_mknode_slink check_PROGRAMS += test_mknode_reg test_mknode_dir test_gen_inode_table check_PROGRAMS += test_add_by_path test_get_path test_fstree_sort check_PROGRAMS += test_fstree_from_file test_fstree_init test_fstree_xattr check_PROGRAMS += test_tar_ustar test_tar_pax test_tar_gnu test_tar_sparse_gnu check_PROGRAMS += test_tar_sparse_gnu1 test_tar_sparse_gnu2 +check_PROGRAMS += test_tar_xattr_bsd test_tar_xattr_schily TESTS += test_canonicalize_name test_mknode_simple test_mknode_slink TESTS += test_mknode_reg test_mknode_dir test_gen_inode_table TESTS += test_add_by_path test_get_path test_fstree_sort test_fstree_from_file TESTS += test_fstree_init test_fstree_xattr test_tar_ustar test_tar_pax TESTS += test_tar_gnu test_tar_sparse_gnu test_tar_sparse_gnu1 -TESTS += test_tar_sparse_gnu2 +TESTS += test_tar_sparse_gnu2 test_tar_xattr_bsd test_tar_xattr_schily EXTRA_DIST += $(top_srcdir)/tests/tar diff --git a/tests/tar_xattr_bsd.c b/tests/tar_xattr_bsd.c new file mode 100644 index 0000000..cfd7ad7 --- /dev/null +++ b/tests/tar_xattr_bsd.c @@ -0,0 +1,60 @@ +/* SPDX-License-Identifier: GPL-3.0-or-later */ +#include "util.h" +#include "tar.h" + +#include +#include +#include +#include +#include +#include + +#define STR(x) #x +#define STRVALUE(x) STR(x) + +#define TEST_PATH STRVALUE(TESTPATH) + +static int open_read(const char *path) +{ + int fd = open(path, O_RDONLY); + + if (fd < 0) { + perror(path); + exit(EXIT_FAILURE); + } + + return fd; +} + +int main(void) +{ + tar_header_decoded_t hdr; + char buffer[6]; + int fd; + + assert(chdir(TEST_PATH) == 0); + + fd = open_read("xattr/xattr-libarchive.tar"); + assert(read_header(fd, &hdr) == 0); + assert(hdr.sb.st_mode == (S_IFREG | 0644)); + assert(hdr.sb.st_uid == 01750); + assert(hdr.sb.st_gid == 01750); + assert(hdr.sb.st_size == 5); + assert(hdr.sb.st_mtime == 1543094477); + assert(hdr.sb.st_atime == 1543094642); + assert(hdr.sb.st_ctime == 1543094606); + assert(strcmp(hdr.name, "input.txt") == 0); + assert(!hdr.unknown_record); + assert(read_retry(fd, buffer, 5) == 5); + buffer[5] = '\0'; + assert(strcmp(buffer, "test\n") == 0); + + assert(hdr.xattr != NULL); + assert(strcmp(hdr.xattr->key, "user.mime_type") == 0); + assert(strcmp(hdr.xattr->value, "text/plain") == 0); + assert(hdr.xattr->next == NULL); + + clear_header(&hdr); + close(fd); + return EXIT_SUCCESS; +} diff --git a/tests/tar_xattr_schily.c b/tests/tar_xattr_schily.c new file mode 100644 index 0000000..481e6ea --- /dev/null +++ b/tests/tar_xattr_schily.c @@ -0,0 +1,60 @@ +/* SPDX-License-Identifier: GPL-3.0-or-later */ +#include "util.h" +#include "tar.h" + +#include +#include +#include +#include +#include +#include + +#define STR(x) #x +#define STRVALUE(x) STR(x) + +#define TEST_PATH STRVALUE(TESTPATH) + +static int open_read(const char *path) +{ + int fd = open(path, O_RDONLY); + + if (fd < 0) { + perror(path); + exit(EXIT_FAILURE); + } + + return fd; +} + +int main(void) +{ + tar_header_decoded_t hdr; + char buffer[6]; + int fd; + + assert(chdir(TEST_PATH) == 0); + + fd = open_read("xattr/xattr-schily.tar"); + assert(read_header(fd, &hdr) == 0); + assert(hdr.sb.st_mode == (S_IFREG | 0644)); + assert(hdr.sb.st_uid == 01750); + assert(hdr.sb.st_gid == 01750); + assert(hdr.sb.st_size == 5); + assert(hdr.sb.st_mtime == 1543094477); + assert(hdr.sb.st_atime == 1543094642); + assert(hdr.sb.st_ctime == 1543094606); + assert(strcmp(hdr.name, "input.txt") == 0); + assert(!hdr.unknown_record); + assert(read_retry(fd, buffer, 5) == 5); + buffer[5] = '\0'; + assert(strcmp(buffer, "test\n") == 0); + + assert(hdr.xattr != NULL); + assert(strcmp(hdr.xattr->key, "user.mime_type") == 0); + assert(strcmp(hdr.xattr->value, "text/plain") == 0); + assert(hdr.xattr->next == NULL); + + clear_header(&hdr); + close(fd); + return EXIT_SUCCESS; +} -- cgit v1.2.3