summaryrefslogtreecommitdiff
path: root/libarchive/libarchive-2.4.17/libarchive/archive_read_support_format_mtree.c
diff options
context:
space:
mode:
Diffstat (limited to 'libarchive/libarchive-2.4.17/libarchive/archive_read_support_format_mtree.c')
-rw-r--r--libarchive/libarchive-2.4.17/libarchive/archive_read_support_format_mtree.c711
1 files changed, 711 insertions, 0 deletions
diff --git a/libarchive/libarchive-2.4.17/libarchive/archive_read_support_format_mtree.c b/libarchive/libarchive-2.4.17/libarchive/archive_read_support_format_mtree.c
new file mode 100644
index 0000000..7db6db3
--- /dev/null
+++ b/libarchive/libarchive-2.4.17/libarchive/archive_read_support_format_mtree.c
@@ -0,0 +1,711 @@
+/*-
+ * Copyright (c) 2003-2007 Tim Kientzle
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "archive_platform.h"
+__FBSDID("$FreeBSD: src/lib/libarchive/archive_read_support_format_mtree.c,v 1.2 2008/02/19 06:07:10 kientzle Exp $");
+
+#ifdef HAVE_SYS_STAT_H
+#include <sys/stat.h>
+#endif
+#ifdef HAVE_ERRNO_H
+#include <errno.h>
+#endif
+#ifdef HAVE_FCNTL_H
+#include <fcntl.h>
+#endif
+#include <stddef.h>
+/* #include <stdint.h> */ /* See archive_platform.h */
+#ifdef HAVE_STDLIB_H
+#include <stdlib.h>
+#endif
+#ifdef HAVE_STRING_H
+#include <string.h>
+#endif
+
+#include "archive.h"
+#include "archive_entry.h"
+#include "archive_private.h"
+#include "archive_read_private.h"
+#include "archive_string.h"
+
+#ifndef O_BINARY
+#define O_BINARY 0
+#endif
+
+struct mtree_entry {
+ struct mtree_entry *next;
+ char *name;
+ char *option_start;
+ char *option_end;
+ char full;
+ char used;
+};
+
+struct mtree {
+ struct archive_string line;
+ size_t buffsize;
+ char *buff;
+ off_t offset;
+ int fd;
+ int filetype;
+ int archive_format;
+ const char *archive_format_name;
+ struct mtree_entry *entries;
+ struct mtree_entry *this_entry;
+ struct archive_string current_dir;
+ struct archive_string contents_name;
+};
+
+static int cleanup(struct archive_read *);
+static int mtree_bid(struct archive_read *);
+static void parse_escapes(char *, struct mtree_entry *);
+static int parse_setting(struct archive_read *, struct mtree *,
+ struct archive_entry *, char *, char *);
+static int read_data(struct archive_read *a,
+ const void **buff, size_t *size, off_t *offset);
+static ssize_t readline(struct archive_read *, struct mtree *, char **, ssize_t);
+static int skip(struct archive_read *a);
+static int read_header(struct archive_read *,
+ struct archive_entry *);
+static int64_t mtree_atol10(char **);
+static int64_t mtree_atol8(char **);
+
+int
+archive_read_support_format_mtree(struct archive *_a)
+{
+ struct archive_read *a = (struct archive_read *)_a;
+ struct mtree *mtree;
+ int r;
+
+ mtree = (struct mtree *)malloc(sizeof(*mtree));
+ if (mtree == NULL) {
+ archive_set_error(&a->archive, ENOMEM,
+ "Can't allocate mtree data");
+ return (ARCHIVE_FATAL);
+ }
+ memset(mtree, 0, sizeof(*mtree));
+ mtree->fd = -1;
+
+ r = __archive_read_register_format(a, mtree,
+ mtree_bid, read_header, read_data, skip, cleanup);
+
+ if (r != ARCHIVE_OK)
+ free(mtree);
+ return (ARCHIVE_OK);
+}
+
+static int
+cleanup(struct archive_read *a)
+{
+ struct mtree *mtree;
+ struct mtree_entry *p, *q;
+
+ mtree = (struct mtree *)(a->format->data);
+ p = mtree->entries;
+ while (p != NULL) {
+ q = p->next;
+ free(p->name);
+ /*
+ * Note: option_start, option_end are pointers into
+ * the block that p->name points to. So we should
+ * not try to free them!
+ */
+ free(p);
+ p = q;
+ }
+ archive_string_free(&mtree->line);
+ archive_string_free(&mtree->current_dir);
+ archive_string_free(&mtree->contents_name);
+ free(mtree->buff);
+ free(mtree);
+ (a->format->data) = NULL;
+ return (ARCHIVE_OK);
+}
+
+
+static int
+mtree_bid(struct archive_read *a)
+{
+ struct mtree *mtree;
+ ssize_t bytes_read;
+ const void *h;
+ const char *signature = "#mtree";
+ const char *p;
+ int bid;
+
+ mtree = (struct mtree *)(a->format->data);
+
+ /* Now let's look at the actual header and see if it matches. */
+ bytes_read = (a->decompressor->read_ahead)(a, &h, strlen(signature));
+
+ if (bytes_read <= 0)
+ return (bytes_read);
+
+ p = h;
+ bid = 0;
+ while (bytes_read > 0 && *signature != '\0') {
+ if (*p != *signature)
+ return (bid = 0);
+ bid += 8;
+ p++;
+ signature++;
+ bytes_read--;
+ }
+ return (bid);
+}
+
+/*
+ * The extended mtree format permits multiple lines specifying
+ * attributes for each file. Practically speaking, that means we have
+ * to read the entire mtree file into memory up front.
+ */
+static int
+read_mtree(struct archive_read *a, struct mtree *mtree)
+{
+ ssize_t len;
+ char *p;
+ struct mtree_entry *mentry;
+ struct mtree_entry *last_mentry = NULL;
+
+ mtree->archive_format = ARCHIVE_FORMAT_MTREE_V1;
+ mtree->archive_format_name = "mtree";
+
+ for (;;) {
+ len = readline(a, mtree, &p, 256);
+ if (len == 0) {
+ mtree->this_entry = mtree->entries;
+ return (ARCHIVE_OK);
+ }
+ if (len < 0)
+ return (len);
+ /* Leading whitespace is never significant, ignore it. */
+ while (*p == ' ' || *p == '\t') {
+ ++p;
+ --len;
+ }
+ /* Skip content lines and blank lines. */
+ if (*p == '#')
+ continue;
+ if (*p == '\r' || *p == '\n' || *p == '\0')
+ continue;
+ mentry = malloc(sizeof(*mentry));
+ if (mentry == NULL) {
+ archive_set_error(&a->archive, ENOMEM,
+ "Can't allocate memory");
+ return (ARCHIVE_FATAL);
+ }
+ memset(mentry, 0, sizeof(*mentry));
+ /* Add this entry to list. */
+ if (last_mentry == NULL) {
+ last_mentry = mtree->entries = mentry;
+ } else {
+ last_mentry->next = mentry;
+ }
+ last_mentry = mentry;
+
+ /* Copy line over onto heap. */
+ mentry->name = malloc(len + 1);
+ if (mentry->name == NULL) {
+ free(mentry);
+ archive_set_error(&a->archive, ENOMEM,
+ "Can't allocate memory");
+ return (ARCHIVE_FATAL);
+ }
+ strcpy(mentry->name, p);
+ mentry->option_end = mentry->name + len;
+ /* Find end of name. */
+ p = mentry->name;
+ while (*p != ' ' && *p != '\n' && *p != '\0')
+ ++p;
+ *p++ = '\0';
+ parse_escapes(mentry->name, mentry);
+ /* Find start of options and record it. */
+ while (p < mentry->option_end && (*p == ' ' || *p == '\t'))
+ ++p;
+ mentry->option_start = p;
+ /* Null terminate each separate option. */
+ while (++p < mentry->option_end)
+ if (*p == ' ' || *p == '\t' || *p == '\n')
+ *p = '\0';
+ }
+}
+
+static int
+read_header(struct archive_read *a, struct archive_entry *entry)
+{
+ struct stat st;
+ struct mtree *mtree;
+ struct mtree_entry *mentry, *mentry2;
+ char *p, *q;
+ int r = ARCHIVE_OK, r1;
+
+ mtree = (struct mtree *)(a->format->data);
+
+ if (mtree->fd >= 0) {
+ close(mtree->fd);
+ mtree->fd = -1;
+ }
+
+ if (mtree->entries == NULL) {
+ r = read_mtree(a, mtree);
+ if (r != ARCHIVE_OK)
+ return (r);
+ }
+
+ a->archive.archive_format = mtree->archive_format;
+ a->archive.archive_format_name = mtree->archive_format_name;
+
+ for (;;) {
+ mentry = mtree->this_entry;
+ if (mentry == NULL) {
+ mtree->this_entry = NULL;
+ return (ARCHIVE_EOF);
+ }
+ mtree->this_entry = mentry->next;
+ if (mentry->used)
+ continue;
+ mentry->used = 1;
+ if (strcmp(mentry->name, "..") == 0) {
+ if (archive_strlen(&mtree->current_dir) > 0) {
+ /* Roll back current path. */
+ p = mtree->current_dir.s
+ + mtree->current_dir.length - 1;
+ while (p >= mtree->current_dir.s && *p != '/')
+ --p;
+ if (p >= mtree->current_dir.s)
+ --p;
+ mtree->current_dir.length
+ = p - mtree->current_dir.s + 1;
+ }
+ continue;
+ }
+
+ mtree->filetype = AE_IFREG;
+
+ /* Parse options. */
+ p = mentry->option_start;
+ while (p < mentry->option_end) {
+ q = p + strlen(p);
+ r1 = parse_setting(a, mtree, entry, p, q);
+ if (r1 != ARCHIVE_OK)
+ r = r1;
+ p = q + 1;
+ }
+
+ if (mentry->full) {
+ archive_entry_copy_pathname(entry, mentry->name);
+ /*
+ * "Full" entries are allowed to have multiple
+ * lines and those lines aren't required to be
+ * adjacent. We don't support multiple lines
+ * for "relative" entries nor do we make any
+ * attempt to merge data from separate
+ * "relative" and "full" entries. (Merging
+ * "relative" and "full" entries would require
+ * dealing with pathname canonicalization,
+ * which is a very tricky subject.)
+ */
+ mentry2 = mentry->next;
+ while (mentry2 != NULL) {
+ if (mentry2->full
+ && !mentry2->used
+ && strcmp(mentry->name, mentry2->name) == 0) {
+ /*
+ * Add those options as well;
+ * later lines override
+ * earlier ones.
+ */
+ p = mentry2->option_start;
+ while (p < mentry2->option_end) {
+ q = p + strlen(p);
+ r1 = parse_setting(a, mtree, entry, p, q);
+ if (r1 != ARCHIVE_OK)
+ r = r1;
+ p = q + 1;
+ }
+ mentry2->used = 1;
+ }
+ mentry2 = mentry2->next;
+ }
+ } else {
+ /*
+ * Relative entries require us to construct
+ * the full path and possibly update the
+ * current directory.
+ */
+ size_t n = archive_strlen(&mtree->current_dir);
+ if (n > 0)
+ archive_strcat(&mtree->current_dir, "/");
+ archive_strcat(&mtree->current_dir, mentry->name);
+ archive_entry_copy_pathname(entry, mtree->current_dir.s);
+ if (archive_entry_filetype(entry) != AE_IFDIR)
+ mtree->current_dir.length = n;
+ }
+
+ /*
+ * Try to open and stat the file to get the real size.
+ * It would be nice to avoid this here so that getting
+ * a listing of an mtree wouldn't require opening
+ * every referenced contents file. But then we
+ * wouldn't know the actual contents size, so I don't
+ * see a really viable way around this. (Also, we may
+ * want to someday pull other unspecified info from
+ * the contents file on disk.)
+ */
+ if (archive_strlen(&mtree->contents_name) > 0) {
+ mtree->fd = open(mtree->contents_name.s,
+ O_RDONLY | O_BINARY);
+ if (mtree->fd < 0) {
+ archive_set_error(&a->archive, errno,
+ "Can't open content=\"%s\"",
+ mtree->contents_name.s);
+ r = ARCHIVE_WARN;
+ }
+ } else {
+ /* If the specified path opens, use it. */
+ mtree->fd = open(mtree->current_dir.s,
+ O_RDONLY | O_BINARY);
+ /* But don't fail if it's not there. */
+ }
+
+ /*
+ * If there is a contents file on disk, use that size;
+ * otherwise leave it as-is (it might have been set from
+ * the mtree size= keyword).
+ */
+ if (mtree->fd >= 0) {
+ fstat(mtree->fd, &st);
+ archive_entry_set_size(entry, st.st_size);
+ }
+
+ return r;
+ }
+}
+
+static int
+parse_setting(struct archive_read *a, struct mtree *mtree, struct archive_entry *entry, char *key, char *end)
+{
+ char *val;
+
+
+ if (end == key)
+ return (ARCHIVE_OK);
+ if (*key == '\0')
+ return (ARCHIVE_OK);
+
+ val = strchr(key, '=');
+ if (val == NULL) {
+ archive_set_error(&a->archive, ARCHIVE_ERRNO_FILE_FORMAT,
+ "Malformed attribute \"%s\" (%d)", key, key[0]);
+ return (ARCHIVE_WARN);
+ }
+
+ *val = '\0';
+ ++val;
+
+ switch (key[0]) {
+ case 'c':
+ if (strcmp(key, "content") == 0) {
+ parse_escapes(val, NULL);
+ archive_strcpy(&mtree->contents_name, val);
+ break;
+ }
+ case 'g':
+ if (strcmp(key, "gid") == 0) {
+ archive_entry_set_gid(entry, mtree_atol10(&val));
+ break;
+ }
+ if (strcmp(key, "gname") == 0) {
+ archive_entry_copy_gname(entry, val);
+ break;
+ }
+ case 'm':
+ if (strcmp(key, "mode") == 0) {
+ if (val[0] == '0') {
+ archive_entry_set_perm(entry,
+ mtree_atol8(&val));
+ } else
+ archive_set_error(&a->archive,
+ ARCHIVE_ERRNO_FILE_FORMAT,
+ "Symbolic mode \"%s\" unsupported", val);
+ break;
+ }
+ case 't':
+ if (strcmp(key, "type") == 0) {
+ switch (val[0]) {
+ case 'b':
+ if (strcmp(val, "block") == 0) {
+ mtree->filetype = AE_IFBLK;
+ break;
+ }
+ case 'c':
+ if (strcmp(val, "char") == 0) {
+ mtree->filetype = AE_IFCHR;
+ break;
+ }
+ case 'd':
+ if (strcmp(val, "dir") == 0) {
+ mtree->filetype = AE_IFDIR;
+ break;
+ }
+ case 'f':
+ if (strcmp(val, "fifo") == 0) {
+ mtree->filetype = AE_IFIFO;
+ break;
+ }
+ if (strcmp(val, "file") == 0) {
+ mtree->filetype = AE_IFREG;
+ break;
+ }
+ case 'l':
+ if (strcmp(val, "link") == 0) {
+ mtree->filetype = AE_IFLNK;
+ break;
+ }
+ default:
+ archive_set_error(&a->archive,
+ ARCHIVE_ERRNO_FILE_FORMAT,
+ "Unrecognized file type \"%s\"", val);
+ return (ARCHIVE_WARN);
+ }
+ archive_entry_set_filetype(entry, mtree->filetype);
+ break;
+ }
+ if (strcmp(key, "time") == 0) {
+ archive_entry_set_mtime(entry, mtree_atol10(&val), 0);
+ break;
+ }
+ case 'u':
+ if (strcmp(key, "uid") == 0) {
+ archive_entry_set_uid(entry, mtree_atol10(&val));
+ break;
+ }
+ if (strcmp(key, "uname") == 0) {
+ archive_entry_copy_uname(entry, val);
+ break;
+ }
+ default:
+ archive_set_error(&a->archive, ARCHIVE_ERRNO_FILE_FORMAT,
+ "Unrecognized key %s=%s", key, val);
+ return (ARCHIVE_WARN);
+ }
+ return (ARCHIVE_OK);
+}
+
+static int
+read_data(struct archive_read *a, const void **buff, size_t *size, off_t *offset)
+{
+ ssize_t bytes_read;
+ struct mtree *mtree;
+
+ mtree = (struct mtree *)(a->format->data);
+ if (mtree->fd < 0) {
+ *buff = NULL;
+ *offset = 0;
+ *size = 0;
+ return (ARCHIVE_EOF);
+ }
+ if (mtree->buff == NULL) {
+ mtree->buffsize = 64 * 1024;
+ mtree->buff = malloc(mtree->buffsize);
+ if (mtree->buff == NULL) {
+ archive_set_error(&a->archive, ENOMEM,
+ "Can't allocate memory");
+ }
+ }
+
+ *buff = mtree->buff;
+ *offset = mtree->offset;
+ bytes_read = read(mtree->fd, mtree->buff, mtree->buffsize);
+ if (bytes_read < 0) {
+ archive_set_error(&a->archive, errno, "Can't read");
+ return (ARCHIVE_WARN);
+ }
+ if (bytes_read == 0) {
+ *size = 0;
+ return (ARCHIVE_EOF);
+ }
+ mtree->offset += bytes_read;
+ *size = (size_t)bytes_read;
+ return (ARCHIVE_OK);
+}
+
+/* Skip does nothing except possibly close the contents file. */
+static int
+skip(struct archive_read *a)
+{
+ struct mtree *mtree;
+
+ mtree = (struct mtree *)(a->format->data);
+ if (mtree->fd >= 0) {
+ close(mtree->fd);
+ mtree->fd = -1;
+ }
+ return (ARCHIVE_OK);
+}
+
+/*
+ * Since parsing octal escapes always makes strings shorter,
+ * we can always do this conversion in-place.
+ */
+static void
+parse_escapes(char *src, struct mtree_entry *mentry)
+{
+ char *dest = src;
+ char c;
+
+ while (*src != '\0') {
+ c = *src++;
+ if (c == '/' && mentry != NULL)
+ mentry->full = 1;
+ if (c == '\\') {
+ if (src[0] >= '0' && src[0] <= '3'
+ && src[1] >= '0' && src[1] <= '7'
+ && src[2] >= '0' && src[2] <= '7') {
+ c = (src[0] - '0') << 6;
+ c |= (src[1] - '0') << 3;
+ c |= (src[2] - '0');
+ src += 3;
+ }
+ }
+ *dest++ = c;
+ }
+ *dest = '\0';
+}
+
+/*
+ * Note that this implementation does not (and should not!) obey
+ * locale settings; you cannot simply substitute strtol here, since
+ * it does obey locale.
+ */
+static int64_t
+mtree_atol8(char **p)
+{
+ int64_t l, limit, last_digit_limit;
+ int digit, base;
+
+ base = 8;
+ limit = INT64_MAX / base;
+ last_digit_limit = INT64_MAX % base;
+
+ l = 0;
+ digit = **p - '0';
+ while (digit >= 0 && digit < base) {
+ if (l>limit || (l == limit && digit > last_digit_limit)) {
+ l = INT64_MAX; /* Truncate on overflow. */
+ break;
+ }
+ l = (l * base) + digit;
+ digit = *++(*p) - '0';
+ }
+ return (l);
+}
+
+/*
+ * Note that this implementation does not (and should not!) obey
+ * locale settings; you cannot simply substitute strtol here, since
+ * it does obey locale.
+ */
+static int64_t
+mtree_atol10(char **p)
+{
+ int64_t l, limit, last_digit_limit;
+ int base, digit, sign;
+
+ base = 10;
+ limit = INT64_MAX / base;
+ last_digit_limit = INT64_MAX % base;
+
+ if (**p == '-') {
+ sign = -1;
+ ++(*p);
+ } else
+ sign = 1;
+
+ l = 0;
+ digit = **p - '0';
+ while (digit >= 0 && digit < base) {
+ if (l > limit || (l == limit && digit > last_digit_limit)) {
+ l = UINT64_MAX; /* Truncate on overflow. */
+ break;
+ }
+ l = (l * base) + digit;
+ digit = *++(*p) - '0';
+ }
+ return (sign < 0) ? -l : l;
+}
+
+/*
+ * Returns length of line (including trailing newline)
+ * or negative on error. 'start' argument is updated to
+ * point to first character of line.
+ */
+static ssize_t
+readline(struct archive_read *a, struct mtree *mtree, char **start, ssize_t limit)
+{
+ ssize_t bytes_read;
+ ssize_t total_size = 0;
+ const void *t;
+ const char *s;
+ void *p;
+
+ /* Accumulate line in a line buffer. */
+ for (;;) {
+ /* Read some more. */
+ bytes_read = (a->decompressor->read_ahead)(a, &t, 1);
+ if (bytes_read == 0)
+ return (0);
+ if (bytes_read < 0)
+ return (ARCHIVE_FATAL);
+ s = t; /* Start of line? */
+ p = memchr(t, '\n', bytes_read);
+ /* If we found '\n', trim the read. */
+ if (p != NULL) {
+ bytes_read = 1 + ((const char *)p) - s;
+ }
+ if (total_size + bytes_read + 1 > limit) {
+ archive_set_error(&a->archive,
+ ARCHIVE_ERRNO_FILE_FORMAT,
+ "Line too long");
+ return (ARCHIVE_FATAL);
+ }
+ if (archive_string_ensure(&mtree->line,
+ total_size + bytes_read + 1) == NULL) {
+ archive_set_error(&a->archive, ENOMEM,
+ "Can't allocate working buffer");
+ return (ARCHIVE_FATAL);
+ }
+ memcpy(mtree->line.s + total_size, t, bytes_read);
+ (a->decompressor->consume)(a, bytes_read);
+ total_size += bytes_read;
+ /* Null terminate. */
+ mtree->line.s[total_size] = '\0';
+ /* If we found '\n', clean up and return. */
+ if (p != NULL) {
+ *start = mtree->line.s;
+ return (total_size);
+ }
+ }
+}