From 78307be86b71d852d037af703f5212f0b93474a1 Mon Sep 17 00:00:00 2001 From: Michihiro NAKAJIMA Date: Thu, 20 Sep 2012 11:34:30 +0900 Subject: [PATCH] Add support for a mtree form that NetBSD's mtree -D generates, which places the path as last parameter. --- Makefile.am | 2 + .../archive_read_support_format_mtree.c | 163 ++++++++++++++---- libarchive/test/test_read_format_mtree.c | 143 +++++++++++++++ .../test_read_format_mtree_nomagic2.mtree.uu | 10 ++ .../test_read_format_mtree_nomagic3.mtree.uu | 10 ++ 5 files changed, 296 insertions(+), 32 deletions(-) create mode 100644 libarchive/test/test_read_format_mtree_nomagic2.mtree.uu create mode 100644 libarchive/test/test_read_format_mtree_nomagic3.mtree.uu diff --git a/Makefile.am b/Makefile.am index 93413b2e7..83cf780bf 100644 --- a/Makefile.am +++ b/Makefile.am @@ -558,6 +558,8 @@ libarchive_test_EXTRA_DIST=\ libarchive/test/test_read_format_lha_withjunk.lzh.uu \ libarchive/test/test_read_format_mtree.mtree.uu \ libarchive/test/test_read_format_mtree_nomagic.mtree.uu \ + libarchive/test/test_read_format_mtree_nomagic2.mtree.uu \ + libarchive/test/test_read_format_mtree_nomagic3.mtree.uu \ libarchive/test/test_read_format_rar.rar.uu \ libarchive/test/test_read_format_rar_binary_data.rar.uu \ libarchive/test/test_read_format_rar_compress_best.rar.uu \ diff --git a/libarchive/archive_read_support_format_mtree.c b/libarchive/archive_read_support_format_mtree.c index 6fb576291..4f0ffbd9a 100644 --- a/libarchive/archive_read_support_format_mtree.c +++ b/libarchive/archive_read_support_format_mtree.c @@ -1,7 +1,7 @@ /*- * Copyright (c) 2003-2007 Tim Kientzle * Copyright (c) 2008 Joerg Sonnenberger - * Copyright (c) 2011 Michihiro NAKAJIMA + * Copyright (c) 2011-2012 Michihiro NAKAJIMA * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -103,6 +103,7 @@ struct mtree { static int bid_keycmp(const char *, const char *, ssize_t); static int cleanup(struct archive_read *); +static int detect_form(struct archive_read *, int *); static int mtree_bid(struct archive_read *, int); static int parse_file(struct archive_read *, struct archive_entry *, struct mtree *, struct mtree_entry *, int *); @@ -419,7 +420,7 @@ bid_keyword(const char *p, ssize_t len) * When "unset" is specified, expects a set of "keyword". */ static int -bid_keyword_list(const char *p, ssize_t len, int unset) +bid_keyword_list(const char *p, ssize_t len, int unset, int last_is_path) { int l; int keycnt = 0; @@ -437,8 +438,10 @@ bid_keyword_list(const char *p, ssize_t len, int unset) break; if (p[0] == '\\' && (p[1] == '\n' || p[1] == '\r')) break; - if (!blank) /* No blank character. */ + if (!blank && !last_is_path) /* No blank character. */ return (-1); + if (last_is_path && len == 0) + return (keycnt); if (unset) { l = bid_keycmp(p, "all", len); @@ -473,7 +476,7 @@ bid_keyword_list(const char *p, ssize_t len, int unset) } static int -bid_entry(const char *p, ssize_t len) +bid_entry(const char *p, ssize_t len, ssize_t nl, int *last_is_path) { int f = 0; static const unsigned char safe_char[256] = { @@ -500,22 +503,49 @@ bid_entry(const char *p, ssize_t len) 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* E0 - EF */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* F0 - FF */ }; + ssize_t ll = len; + const char *pp = p; + *last_is_path = 0; /* * Skip the path-name which is quoted. */ - while (len > 0 && *p != ' ' && *p != '\t') { - if (!safe_char[*(const unsigned char *)p]) - return (-1); - ++p; - --len; + while (ll > 0 && *pp != ' ' && *pp != '\t') { + if (!safe_char[*(const unsigned char *)pp]) { + f = 0; + break; + } + ++pp; + --ll; ++f; } - /* If a path-name was not found, returns error. */ - if (f == 0) - return (-1); + /* If a path-name was not found at the first, try to check + * a mtree format ``NetBSD's mtree -D'' creates, which + * places the path-name at the last. */ + if (f == 0) { + const char *pb = p + len - nl; + int name_len = 0; + + /* Do not accept multi lines for form D. */ + if (pb-2 >= p && + pb[-1] == '\\' && (pb[-2] == ' ' || pb[-2] == '\t')) + return (-1); + if (pb-1 >= p && pb[-1] == '\\') + return (-1); - return (bid_keyword_list(p, len, 0)); + while (p <= --pb && *pb != ' ' && *pb != '\t') { + if (!safe_char[*(const unsigned char *)pb]) + return (-1); + name_len++; + } + if (name_len == 0) + return (-1); + ll = len - nl - name_len; + pp = p; + *last_is_path = 1; + } + + return (bid_keyword_list(pp, ll, 0, *last_is_path)); } #define MAX_BID_ENTRY 3 @@ -525,14 +555,11 @@ mtree_bid(struct archive_read *a, int best_bid) { const char *signature = "#mtree"; const char *p; - ssize_t avail, ravail; - ssize_t len, nl; - int detected_bytes = 0, entry_cnt = 0, multiline = 0; (void)best_bid; /* UNUSED */ /* Now let's look at the actual header and see if it matches. */ - p = __archive_read_ahead(a, strlen(signature), &avail); + p = __archive_read_ahead(a, strlen(signature), NULL); if (p == NULL) return (-1); @@ -542,6 +569,24 @@ mtree_bid(struct archive_read *a, int best_bid) /* * There is not a mtree signature. Let's try to detect mtree format. */ + return (detect_form(a, NULL)); +} + +static int +detect_form(struct archive_read *a, int *is_form_d) +{ + const char *p; + ssize_t avail, ravail; + ssize_t len, nl; + int detected_bytes = 0, entry_cnt = 0, multiline = 0; + int form_D = 0;/* The archive is generated by `NetBSD mtree -D' + * (In this source we call it `form D') . */ + + if (is_form_d != NULL) + *is_form_d = 0; + p = __archive_read_ahead(a, 1, &avail); + if (p == NULL) + return (-1); ravail = avail; for (;;) { len = next_line(a, &p, &avail, &ravail, &nl); @@ -566,7 +611,7 @@ mtree_bid(struct archive_read *a, int best_bid) } else { /* A continuance line; the terminal * character of previous line was '\' character. */ - if (bid_keyword_list(p, len, 0) <= 0) + if (bid_keyword_list(p, len, 0, 0) <= 0) break; if (multiline == 1) detected_bytes += len; @@ -581,9 +626,25 @@ mtree_bid(struct archive_read *a, int best_bid) continue; } if (p[0] != '/') { - if (bid_entry(p, len) >= 0) { + int last_is_path, keywords; + + keywords = bid_entry(p, len, nl, &last_is_path); + if (keywords >= 0) { detected_bytes += len; - if (p[len-nl-1] == '\\') + if (form_D == 0) { + if (last_is_path) + form_D = 1; + else if (keywords > 0) + /* This line is not `form D'. */ + form_D = -1; + } else if (form_D == 1) { + if (!last_is_path && keywords > 0) + /* This this is not `form D' + * and We cannot accept mixed + * format. */ + break; + } + if (!last_is_path && p[len-nl-1] == '\\') /* This line continues. */ multiline = 1; else { @@ -596,13 +657,13 @@ mtree_bid(struct archive_read *a, int best_bid) } else break; } else if (strncmp(p, "/set", 4) == 0) { - if (bid_keyword_list(p+4, len-4, 0) <= 0) + if (bid_keyword_list(p+4, len-4, 0, 0) <= 0) break; /* This line continues. */ if (p[len-nl-1] == '\\') multiline = 2; } else if (strncmp(p, "/unset", 6) == 0) { - if (bid_keyword_list(p+6, len-6, 1) <= 0) + if (bid_keyword_list(p+6, len-6, 1, 0) <= 0) break; /* This line continues. */ if (p[len-nl-1] == '\\') @@ -614,8 +675,13 @@ mtree_bid(struct archive_read *a, int best_bid) p += len; avail -= len; } - if (entry_cnt >= MAX_BID_ENTRY || (entry_cnt > 0 && len == 0)) + if (entry_cnt >= MAX_BID_ENTRY || (entry_cnt > 0 && len == 0)) { + if (is_form_d != NULL) { + if (form_D == 1) + *is_form_d = 1; + } return (32); + } return (0); } @@ -739,12 +805,12 @@ process_global_unset(struct archive_read *a, static int process_add_entry(struct archive_read *a, struct mtree *mtree, - struct mtree_option **global, const char *line, - struct mtree_entry **last_entry) + struct mtree_option **global, const char *line, ssize_t line_len, + struct mtree_entry **last_entry, int is_form_d) { struct mtree_entry *entry; struct mtree_option *iter; - const char *next, *eq; + const char *next, *eq, *name, *end; size_t len; int r; @@ -765,17 +831,46 @@ process_add_entry(struct archive_read *a, struct mtree *mtree, (*last_entry)->next = entry; *last_entry = entry; - len = strcspn(line, " \t\r\n"); + if (is_form_d) { + /* + * This form places the file name as last parameter. + */ + name = line + line_len -1; + while (line_len > 0) { + if (*name != '\r' && *name != '\n' && + *name != '\t' && *name != ' ') + break; + name--; + line_len--; + } + len = 0; + while (line_len > 0) { + if (*name == '\r' || *name == '\n' || + *name == '\t' || *name == ' ') { + name++; + break; + } + name--; + line_len--; + len++; + } + end = name; + } else { + len = strcspn(line, " \t\r\n"); + name = line; + line += len; + end = line + line_len; + } + if ((entry->name = malloc(len + 1)) == NULL) { archive_set_error(&a->archive, errno, "Can't allocate memory"); return (ARCHIVE_FATAL); } - memcpy(entry->name, line, len); + memcpy(entry->name, name, len); entry->name[len] = '\0'; parse_escapes(entry->name, entry); - line += len; for (iter = *global; iter != NULL; iter = iter->next) { r = add_option(a, &entry->options, iter->value, strlen(iter->value)); @@ -787,6 +882,8 @@ process_add_entry(struct archive_read *a, struct mtree *mtree, next = line + strspn(line, " \t\r\n"); if (*next == '\0') return (ARCHIVE_OK); + if (next >= end) + return (ARCHIVE_OK); line = next; next = line + strcspn(line, " \t\r\n"); eq = strchr(line, '='); @@ -811,7 +908,7 @@ read_mtree(struct archive_read *a, struct mtree *mtree) char *p; struct mtree_option *global; struct mtree_entry *last_entry; - int r; + int r, is_form_d; mtree->archive_format = ARCHIVE_FORMAT_MTREE; mtree->archive_format_name = "mtree"; @@ -819,6 +916,8 @@ read_mtree(struct archive_read *a, struct mtree *mtree) global = NULL; last_entry = NULL; + (void)detect_form(a, &is_form_d); + for (counter = 1; ; ++counter) { len = readline(a, mtree, &p, 65536); if (len == 0) { @@ -841,8 +940,8 @@ read_mtree(struct archive_read *a, struct mtree *mtree) if (*p == '\r' || *p == '\n' || *p == '\0') continue; if (*p != '/') { - r = process_add_entry(a, mtree, &global, p, - &last_entry); + r = process_add_entry(a, mtree, &global, p, len, + &last_entry, is_form_d); } else if (strncmp(p, "/set", 4) == 0) { if (p[4] != ' ' && p[4] != '\t') break; diff --git a/libarchive/test/test_read_format_mtree.c b/libarchive/test/test_read_format_mtree.c index a5d7febe1..67466cd62 100644 --- a/libarchive/test/test_read_format_mtree.c +++ b/libarchive/test/test_read_format_mtree.c @@ -1,5 +1,6 @@ /*- * Copyright (c) 2003-2007 Tim Kientzle + * Copyright (c) 2011-2012 Michihiro NAKAJIMA * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -340,6 +341,146 @@ test_read_format_mtree5(void) assertChdir(".."); } +/* + * Test for a format that NetBSD mtree -C generates. + */ +static void +test_read_format_mtree6(void) +{ + const char reffile[] = "test_read_format_mtree_nomagic2.mtree"; + char buff[16]; + struct archive_entry *ae; + struct archive *a; + FILE *f; + + assertMakeDir("mtree6", 0777); + assertChdir("mtree6"); + + extract_reference_file(reffile); + + assert((a = archive_read_new()) != NULL); + assertEqualIntA(a, ARCHIVE_OK, + archive_read_support_filter_all(a)); + assertEqualIntA(a, ARCHIVE_OK, + archive_read_support_format_all(a)); + assertEqualIntA(a, ARCHIVE_OK, archive_read_open_file(a, reffile, 11)); + + /* + * Read "file", whose data is available on disk. + */ + f = fopen("file", "wb"); + assert(f != NULL); + assertEqualInt(3, fwrite("hi\n", 1, 3, f)); + fclose(f); + assertEqualIntA(a, ARCHIVE_OK, archive_read_next_header(a, &ae)); + assertEqualInt(archive_format(a), ARCHIVE_FORMAT_MTREE); + assertEqualString(archive_entry_pathname(ae), "./file"); + assertEqualInt(archive_entry_uid(ae), 18); + assertEqualInt(AE_IFREG, archive_entry_filetype(ae)); + assertEqualInt(archive_entry_mode(ae), AE_IFREG | 0123); + assertEqualInt(archive_entry_size(ae), 3); + assertEqualInt(3, archive_read_data(a, buff, 3)); + assertEqualMem(buff, "hi\n", 3); + + assertEqualIntA(a, ARCHIVE_OK, archive_read_next_header(a, &ae)); + assertEqualString(archive_entry_pathname(ae), "./dir"); + assertEqualInt(archive_entry_mode(ae), AE_IFDIR | 0755); + + assertEqualIntA(a, ARCHIVE_OK, archive_read_next_header(a, &ae)); + assertEqualString(archive_entry_pathname(ae), "./dir/file with space"); + assertEqualInt(archive_entry_uid(ae), 18); + assertEqualInt(archive_entry_mode(ae), AE_IFREG | 0644); + + assertEqualIntA(a, ARCHIVE_OK, archive_read_next_header(a, &ae)); + assertEqualString(archive_entry_pathname(ae), "./file with space"); + assertEqualInt(archive_entry_mode(ae), AE_IFREG | 0644); + + assertEqualIntA(a, ARCHIVE_OK, archive_read_next_header(a, &ae)); + assertEqualString(archive_entry_pathname(ae), "./dir2"); + assertEqualInt(archive_entry_mode(ae), AE_IFDIR | 0755); + + assertEqualIntA(a, ARCHIVE_OK, archive_read_next_header(a, &ae)); + assertEqualString(archive_entry_pathname(ae), "./dir2/dir3a"); + assertEqualInt(archive_entry_mode(ae), AE_IFDIR | 0755); + + assertEqualIntA(a, ARCHIVE_EOF, archive_read_next_header(a, &ae)); + assertEqualInt(6, archive_file_count(a)); + assertEqualInt(ARCHIVE_OK, archive_read_close(a)); + assertEqualInt(ARCHIVE_OK, archive_read_free(a)); + + assertChdir(".."); +} + +/* + * Test for a format that NetBSD mtree -D generates. + */ +static void +test_read_format_mtree7(void) +{ + const char reffile[] = "test_read_format_mtree_nomagic3.mtree"; + char buff[16]; + struct archive_entry *ae; + struct archive *a; + FILE *f; + + assertMakeDir("mtree7", 0777); + assertChdir("mtree7"); + + extract_reference_file(reffile); + + assert((a = archive_read_new()) != NULL); + assertEqualIntA(a, ARCHIVE_OK, + archive_read_support_filter_all(a)); + assertEqualIntA(a, ARCHIVE_OK, + archive_read_support_format_all(a)); + assertEqualIntA(a, ARCHIVE_OK, archive_read_open_file(a, reffile, 11)); + + /* + * Read "file", whose data is available on disk. + */ + f = fopen("file", "wb"); + assert(f != NULL); + assertEqualInt(3, fwrite("hi\n", 1, 3, f)); + fclose(f); + assertEqualIntA(a, ARCHIVE_OK, archive_read_next_header(a, &ae)); + assertEqualInt(archive_format(a), ARCHIVE_FORMAT_MTREE); + assertEqualString(archive_entry_pathname(ae), "./file"); + assertEqualInt(archive_entry_uid(ae), 18); + assertEqualInt(AE_IFREG, archive_entry_filetype(ae)); + assertEqualInt(archive_entry_mode(ae), AE_IFREG | 0123); + assertEqualInt(archive_entry_size(ae), 3); + assertEqualInt(3, archive_read_data(a, buff, 3)); + assertEqualMem(buff, "hi\n", 3); + + assertEqualIntA(a, ARCHIVE_OK, archive_read_next_header(a, &ae)); + assertEqualString(archive_entry_pathname(ae), "./dir"); + assertEqualInt(archive_entry_mode(ae), AE_IFDIR | 0755); + + assertEqualIntA(a, ARCHIVE_OK, archive_read_next_header(a, &ae)); + assertEqualString(archive_entry_pathname(ae), "./dir/file with space"); + assertEqualInt(archive_entry_uid(ae), 18); + assertEqualInt(archive_entry_mode(ae), AE_IFREG | 0644); + + assertEqualIntA(a, ARCHIVE_OK, archive_read_next_header(a, &ae)); + assertEqualString(archive_entry_pathname(ae), "./file with space"); + assertEqualInt(archive_entry_mode(ae), AE_IFREG | 0644); + + assertEqualIntA(a, ARCHIVE_OK, archive_read_next_header(a, &ae)); + assertEqualString(archive_entry_pathname(ae), "./dir2"); + assertEqualInt(archive_entry_mode(ae), AE_IFDIR | 0755); + + assertEqualIntA(a, ARCHIVE_OK, archive_read_next_header(a, &ae)); + assertEqualString(archive_entry_pathname(ae), "./dir2/dir3a"); + assertEqualInt(archive_entry_mode(ae), AE_IFDIR | 0755); + + assertEqualIntA(a, ARCHIVE_EOF, archive_read_next_header(a, &ae)); + assertEqualInt(6, archive_file_count(a)); + assertEqualInt(ARCHIVE_OK, archive_read_close(a)); + assertEqualInt(ARCHIVE_OK, archive_read_free(a)); + + assertChdir(".."); +} + DEFINE_TEST(test_read_format_mtree) { test_read_format_mtree1(); @@ -347,4 +488,6 @@ DEFINE_TEST(test_read_format_mtree) test_read_format_mtree3(); test_read_format_mtree4(); test_read_format_mtree5(); + test_read_format_mtree6(); + test_read_format_mtree7(); } diff --git a/libarchive/test/test_read_format_mtree_nomagic2.mtree.uu b/libarchive/test/test_read_format_mtree_nomagic2.mtree.uu new file mode 100644 index 000000000..f27fb2b9c --- /dev/null +++ b/libarchive/test/test_read_format_mtree_nomagic2.mtree.uu @@ -0,0 +1,10 @@ +begin 644 test_read_format_mtree_nomagic2.mtree +M(PHC(%1H:7,@9F]R;2!IF4],PHN +M+V1I7!E/61I7!E/61I7!E/61I