]> git.ipfire.org Git - thirdparty/libarchive.git/commitdiff
Add the ability to be able to recognize mtree format without a signature "#mtree"
authorMichihiro NAKAJIMA <ggcueroad@gmail.com>
Wed, 9 Mar 2011 17:12:29 +0000 (12:12 -0500)
committerMichihiro NAKAJIMA <ggcueroad@gmail.com>
Wed, 9 Mar 2011 17:12:29 +0000 (12:12 -0500)
since mtree utility still does not record the signature.

SVN-Revision: 2999

Makefile.am
libarchive/archive_read_support_format_mtree.c
libarchive/test/test_read_format_mtree.c
libarchive/test/test_read_format_mtree_nomagic.mtree.uu [new file with mode: 0644]

index 49ddc5f514fc7b84a7f9c144b8a24feea3491379..6b28b5460ca1c358ca2488eccb3f1eab0de26dbd 100644 (file)
@@ -440,6 +440,7 @@ libarchive_test_EXTRA_DIST=\
        libarchive/test/test_read_format_lha_lh7.lzh.uu                 \
        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_raw.data.Z.uu                  \
        libarchive/test/test_read_format_raw.data.uu                    \
        libarchive/test/test_read_format_tar_empty_filename.tar.uu      \
index e24aa86926883b5e3e48340594036377b61075c6..27dadface93e5dbfcb4a66c54b02e1d6fa3e84da 100644 (file)
@@ -1,6 +1,7 @@
 /*-
  * Copyright (c) 2003-2007 Tim Kientzle
  * Copyright (c) 2008 Joerg Sonnenberger
+ * Copyright (c) 2011 Michihiro NAKAJIMA
  * All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
@@ -186,20 +187,377 @@ cleanup(struct archive_read *a)
        return (ARCHIVE_OK);
 }
 
+static ssize_t
+get_line_size(const char *b, ssize_t avail, ssize_t *nlsize)
+{
+       ssize_t len;
+
+       len = 0;
+       while (len < avail) {
+               switch (*b) {
+               case '\0':/* Non-ascii character or control character. */
+                       if (nlsize != NULL)
+                               *nlsize = 0;
+                       return (-1);
+               case '\r':
+                       if (avail-len > 1 && b[1] == '\n') {
+                               if (nlsize != NULL)
+                                       *nlsize = 2;
+                               return (len+2);
+                       }
+                       /* FALL THROUGH */
+               case '\n':
+                       if (nlsize != NULL)
+                               *nlsize = 1;
+                       return (len+1);
+               default:
+                       b++;
+                       len++;
+                       break;
+               }
+       }
+       if (nlsize != NULL)
+               *nlsize = 0;
+       return (avail);
+}
+
+static ssize_t
+next_line(struct archive_read *a,
+    const char **b, ssize_t *avail, ssize_t *ravail, ssize_t *nl)
+{
+       ssize_t len;
+       int quit;
+       
+       quit = 0;
+       if (*avail == 0) {
+               *nl = 0;
+               len = 0;
+       } else
+               len = get_line_size(*b, *avail, nl);
+       /*
+        * Read bytes more while it does not reach the end of line.
+        */
+       while (*nl == 0 && len == *avail && !quit) {
+               ssize_t diff = *ravail - *avail;
+
+               *b = __archive_read_ahead(a, 160 + *ravail, avail);
+               if (*b == NULL) {
+                       if (*ravail >= *avail)
+                               return (0);
+                       /* Reading bytes reaches the end of file. */
+                       *b = __archive_read_ahead(a, *avail, avail);
+                       quit = 1;
+               }
+               *ravail = *avail;
+               *b += diff;
+               *avail -= diff;
+               len = get_line_size(*b, *avail, nl);
+       }
+       return (len);
+}
+
+/*
+ * Compare characters with a mtree keyword.
+ * Returns the length of a mtree keyword if matched.
+ * Returns 0 if not matched.
+ */
+int
+bid_keycmp(const char *p, const char *key, ssize_t len)
+{
+       int match_len = 0;
+
+       while (len > 0 && *p && *key) {
+               if (*p == *key) {
+                       --len;
+                       ++p;
+                       ++key;
+                       ++match_len;
+                       continue;
+               }
+               return (0);/* Not match */
+       }
+       if (*key != '\0')
+               return (0);/* Not match */
+
+       /* A following character should be specified characters */
+       if (p[0] == '=' || p[0] == ' ' || p[0] == '\t' ||
+           p[0] == '\n' || p[0] == '\r' ||
+          (p[0] == '\\' && (p[1] == '\n' || p[1] == '\r')))
+               return (match_len);
+       return (0);/* Not match */
+}
+
+/*
+ * Test whether the characters 'p' has is mtree keyword.
+ * Returns the length of a detected keyword.
+ * Returns 0 if any keywords were not found.
+ */
+static ssize_t
+bid_keyword(const char *p,  ssize_t len)
+{
+       static const char *keys_c[] = {
+               "content", "contents", "cksum", NULL
+       };
+       static const char *keys_df[] = {
+               "device", "flags", NULL
+       };
+       static const char *keys_g[] = {
+               "gid", "gname", NULL
+       };
+       static const char *keys_il[] = {
+               "ignore", "link", NULL
+       };
+       static const char *keys_m[] = {
+               "md5", "md5digest", "mode", NULL
+       };
+       static const char *keys_no[] = {
+               "nlink", "optional", NULL
+       };
+       static const char *keys_r[] = {
+               "rmd160", "rmd160digest", NULL
+       };
+       static const char *keys_s[] = {
+               "sha1", "sha1digest",
+               "sha256", "sha256digest",
+               "sha384", "sha384digest",
+               "sha512", "sha512digest",
+               "size", NULL
+       };
+       static const char *keys_t[] = {
+               "tags", "time", "type", NULL
+       };
+       static const char *keys_u[] = {
+               "uid", "uname", NULL
+       };
+       const char **keys;
+       int i;
+
+       switch (*p) {
+       case 'c': keys = keys_c; break;
+       case 'd': case 'f': keys = keys_df; break;
+       case 'g': keys = keys_g; break;
+       case 'i': case 'l': keys = keys_il; break;
+       case 'm': keys = keys_m; break;
+       case 'n': case 'o': keys = keys_no; break;
+       case 'r': keys = keys_r; break;
+       case 's': keys = keys_s; break;
+       case 't': keys = keys_t; break;
+       case 'u': keys = keys_u; break;
+       default: return (0);/* Unknown key */
+       }
+
+       for (i = 0; keys[i] != NULL; i++) {
+               int l = bid_keycmp(p, keys[i], len);
+               if (l > 0)
+                       return (l);
+       }
+       return (0);/* Unkown key */
+}
+
+/*
+ * Test whether there is a set of mtree keywords.
+ * Returns the number of keyword.
+ * Returns -1 if we got incorrect sequence.
+ * This function expects a set of "<space characters>keyword=value".
+ * When "unset" is specified, expects a set of "<space characters>keyword".
+ */
+static int
+bid_keyword_list(const char *p,  ssize_t len, int unset)
+{
+       int l;
+       int keycnt = 0;
+
+       while (len > 0 && *p) {
+               int blank = 0;
+
+               /* Test whether there are blank characters in the line. */
+               while (len >0 && (*p == ' ' || *p == '\t')) {
+                       ++p;
+                       --len;
+                       blank = 1;
+               }
+               if (*p == '\n' || *p == '\r')
+                       break;
+               if (p[0] == '\\' && (p[1] == '\n' || p[1] == '\r'))
+                       break;
+               if (!blank) /* No blank character. */
+                       return (-1);
+
+               if (unset) {
+                       l = bid_keycmp(p, "all", len);
+                       if (l > 0)
+                               return (1);
+               }
+               /* Test whether there is a correct key in the line. */
+               l = bid_keyword(p, len);
+               if (l == 0)
+                       return (-1);/* Unknown keyword was found. */
+               p += l;
+               len -= l;
+               keycnt++;
+
+               /* Skip value */
+               if (*p == '=') {
+                       int value = 0;
+                       ++p;
+                       --len;
+                       while (len > 0 && *p != ' ' && *p != '\t') {
+                               ++p;
+                               --len;
+                               value = 1;
+                       }
+                       /* A keyword should have a its value unless
+                        * "/unset" operation. */ 
+                       if (!unset && value == 0)
+                               return (-1);
+               }
+       }
+       return (keycnt);
+}
+
+static int
+bid_entry(const char *p, ssize_t len)
+{
+       int f = 0;
+       static const unsigned char safe_char[256] = {
+               0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 00 - 0F */
+               0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 10 - 1F */
+               /* !"$%&'()*+,-./  EXCLUSION:( )(#) */
+               0, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 20 - 2F */
+               /* 0123456789:;<>?  EXCLUSION:(=) */
+               1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, /* 30 - 3F */
+               /* @ABCDEFGHIJKLMNO */
+               1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 40 - 4F */
+               /* PQRSTUVWXYZ[\]^_  */
+               1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 50 - 5F */
+               /* `abcdefghijklmno */
+               1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 60 - 6F */
+               /* pqrstuvwxyz{|}~ */
+               1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, /* 70 - 7F */
+               0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 80 - 8F */
+               0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 90 - 9F */
+               0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* A0 - AF */
+               0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* B0 - BF */
+               0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* C0 - CF */
+               0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* D0 - DF */
+               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 */
+       };
+
+       /*
+        * Skip the path-name which is quoted.
+        */
+       while (len > 0 && *p != ' ' && *p != '\t') {
+               if (!safe_char[*(const unsigned char *)p])
+                       return (-1);
+               ++p;
+               --len;
+               ++f;
+       }
+       /* If a path-name was not found, returns error. */
+       if (f == 0)
+               return (-1);
+
+       return (bid_keyword_list(p, len, 0));
+}
+
+#define MAX_BID_ENTRY  3
 
 static int
 mtree_bid(struct archive_read *a)
 {
        const char *signature = "#mtree";
        const char *p;
+       ssize_t avail, ravail;
+       ssize_t len, nl;
+       int detected_bytes = 0, entry_cnt = 0, multiline = 0;
 
        /* Now let's look at the actual header and see if it matches. */
-       p = __archive_read_ahead(a, strlen(signature), NULL);
+       p = __archive_read_ahead(a, strlen(signature), &avail);
        if (p == NULL)
                return (-1);
 
        if (strncmp(p, signature, strlen(signature)) == 0)
                return (8 * (int)strlen(signature));
+
+       /*
+        * There is not a mtree signature. Let's try to detect mtree format.
+        */
+       ravail = avail;
+       for (;;) {
+               len = next_line(a, &p, &avail, &ravail, &nl);
+               /* The terminal character of the line should be
+                * a new line character, '\r\n' or '\n'. */
+               if (len <= 0 || nl == 0)
+                       break;
+               if (!multiline) {
+                       /* Leading whitespace is never significant,
+                        * ignore it. */
+                       while (len > 0 && (*p == ' ' || *p == '\t')) {
+                               ++p;
+                               --avail;
+                               --len;
+                       }
+                       /* Skip comment or empty line. */ 
+                       if (p[0] == '#' || p[0] == '\n' || p[0] == '\r') {
+                               p += len;
+                               avail -= len;
+                               continue;
+                       }
+               } else {
+                       /* A continuance line; the terminal
+                        * character of previous line was '\' character. */
+                       if (bid_keyword_list(p, len, 0) <= 0)
+                               break;
+                       if (multiline == 1)
+                               detected_bytes += len;
+                       if (p[len-nl-1] != '\\') {
+                               if (multiline == 1 &&
+                                   ++entry_cnt >= MAX_BID_ENTRY)
+                                       break;
+                               multiline = 0;
+                       }
+                       p += len;
+                       avail -= len;
+                       continue;
+               }
+               if (p[0] != '/') {
+                       if (bid_entry(p, len) >= 0) {
+                               detected_bytes += len;
+                               if (p[len-nl-1] == '\\')
+                                       /* This line continues. */
+                                       multiline = 1;
+                               else {
+                                       /* We've got plenty of correct lines
+                                        * to assume that this file is a mtree
+                                        * format. */
+                                       if (++entry_cnt >= MAX_BID_ENTRY)
+                                               break;
+                               }
+                       } else
+                               break;
+               } else if (strncmp(p, "/set", 4) == 0) {
+                       if (bid_keyword_list(p+4, len-4, 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)
+                               break;
+                       /* This line continues. */
+                       if (p[len-nl-1] == '\\')
+                               multiline = 2;
+               } else
+                       break;
+
+               /* Test next line. */
+               p += len;
+               avail -= len;
+       }
+       if (entry_cnt >= MAX_BID_ENTRY || (entry_cnt > 0 && len == 0))
+               return (32);
+
        return (0);
 }
 
index 1ea45ed1ed8ff7e9396f7249efc963ed7c67b151..02c4be36e4906422f9991160437306311f8b22f5 100644 (file)
@@ -177,10 +177,91 @@ test_read_format_mtree3(void)
 }
 
 
+static void
+test_read_format_mtree4(void)
+{
+       const char reffile[] = "test_read_format_mtree_nomagic.mtree";
+       char buff[16];
+       struct archive_entry *ae;
+       struct archive *a;
+       FILE *f;
+
+       assertMakeDir("mtree4", 0777);
+       assertChdir("mtree4");
+
+       extract_reference_file(reffile);
+
+       assert((a = archive_read_new()) != NULL);
+       assertEqualIntA(a, ARCHIVE_OK,
+           archive_read_support_compression_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(AE_IFDIR, archive_entry_filetype(ae));
+
+       assertEqualIntA(a, ARCHIVE_OK, archive_read_next_header(a, &ae));
+       assertEqualString(archive_entry_pathname(ae), "dir/file with space");
+
+       assertEqualIntA(a, ARCHIVE_OK, archive_read_next_header(a, &ae));
+       assertEqualString(archive_entry_pathname(ae), "file with space");
+
+       assertEqualIntA(a, ARCHIVE_OK, archive_read_next_header(a, &ae));
+       assertEqualString(archive_entry_pathname(ae), "dir2");
+
+       assertEqualIntA(a, ARCHIVE_OK, archive_read_next_header(a, &ae));
+       assertEqualString(archive_entry_pathname(ae), "dir2/dir3a");
+
+       assertEqualIntA(a, ARCHIVE_OK, archive_read_next_header(a, &ae));
+       assertEqualString(archive_entry_pathname(ae), "dir2/dir3a/indir3a");
+
+       assertEqualIntA(a, ARCHIVE_OK, archive_read_next_header(a, &ae));
+       assertEqualString(archive_entry_pathname(ae), "dir2/fullindir2");
+       assertEqualInt(archive_entry_mode(ae), AE_IFREG | 0644);
+
+       assertEqualIntA(a, ARCHIVE_OK, archive_read_next_header(a, &ae));
+       assertEqualString(archive_entry_pathname(ae), "dir2/indir2");
+
+       assertEqualIntA(a, ARCHIVE_OK, archive_read_next_header(a, &ae));
+       assertEqualString(archive_entry_pathname(ae), "dir2/dir3b");
+
+       assertEqualIntA(a, ARCHIVE_OK, archive_read_next_header(a, &ae));
+       assertEqualString(archive_entry_pathname(ae), "dir2/dir3b/indir3b");
+
+       assertEqualIntA(a, ARCHIVE_OK, archive_read_next_header(a, &ae));
+       assertEqualString(archive_entry_pathname(ae), "notindir");
+
+       assertEqualIntA(a, ARCHIVE_EOF, archive_read_next_header(a, &ae));
+       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();
        test_read_format_mtree2();
        test_read_format_mtree3();
+       test_read_format_mtree4();
 }
diff --git a/libarchive/test/test_read_format_mtree_nomagic.mtree.uu b/libarchive/test/test_read_format_mtree_nomagic.mtree.uu
new file mode 100644 (file)
index 0000000..be08910
--- /dev/null
@@ -0,0 +1,11 @@
+begin 644 test_read_format_mtree_nomagic.mtree
+M9FEL92!T>7!E/69I;&4@=6ED/3$X(&UO9&4],#$R,R!S:7IE/3,*9&ER('1Y
+M<&4]9&ER"B!F:6QE7#`T,'=I=&A<,#0P<W!A8V4@='EP93UF:6QE('5I9#TQ
+M.`H@+BX*9FEL95PP-#!W:71H7#`T,'-P86-E('1Y<&4]9FEL90ID:7(R('1Y
+M<&4]9&ER"B!D:7(S82!T>7!E/61I<@H@(&EN9&ER,V$@='EP93UF:6QE"F1I
+M<C(O9G5L;&EN9&ER,B!T>7!E/69I;&4@;6]D93TP-S<W"B`@+BX*(&EN9&ER
+M,B!T>7!E/69I;&4*(&1I<C-B('1Y<&4]9&ER"B`@:6YD:7(S8B!T>7!E/69I
+M;&4*("`N+@H@+BX*;F]T:6YD:7(@='EP93UF:6QE"F1I<C(O9G5L;&EN9&ER
+,,B!M;V1E/3`V-#0*
+`
+end