From owner-svn-src-stable@FreeBSD.ORG Mon Dec 8 17:18:37 2008 Return-Path: Delivered-To: svn-src-stable@freebsd.org Received: from mx1.freebsd.org (mx1.freebsd.org [IPv6:2001:4f8:fff6::34]) by hub.freebsd.org (Postfix) with ESMTP id C08821065670; Mon, 8 Dec 2008 17:18:37 +0000 (UTC) (envelope-from kientzle@FreeBSD.org) Received: from svn.freebsd.org (svn.freebsd.org [IPv6:2001:4f8:fff6::2c]) by mx1.freebsd.org (Postfix) with ESMTP id ACF1F8FC18; Mon, 8 Dec 2008 17:18:37 +0000 (UTC) (envelope-from kientzle@FreeBSD.org) Received: from svn.freebsd.org (localhost [127.0.0.1]) by svn.freebsd.org (8.14.3/8.14.3) with ESMTP id mB8HIbOr066047; Mon, 8 Dec 2008 17:18:37 GMT (envelope-from kientzle@svn.freebsd.org) Received: (from kientzle@localhost) by svn.freebsd.org (8.14.3/8.14.3/Submit) id mB8HIbwf066044; Mon, 8 Dec 2008 17:18:37 GMT (envelope-from kientzle@svn.freebsd.org) Message-Id: <200812081718.mB8HIbwf066044@svn.freebsd.org> From: Tim Kientzle Date: Mon, 8 Dec 2008 17:18:37 +0000 (UTC) To: src-committers@freebsd.org, svn-src-all@freebsd.org, svn-src-stable@freebsd.org, svn-src-stable-7@freebsd.org X-SVN-Group: stable-7 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Cc: Subject: svn commit: r185770 - stable/7/lib/libarchive X-BeenThere: svn-src-stable@freebsd.org X-Mailman-Version: 2.1.5 Precedence: list List-Id: SVN commit messages for all the -stable branches of the src tree List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , X-List-Received-Date: Mon, 08 Dec 2008 17:18:37 -0000 Author: kientzle Date: Mon Dec 8 17:18:37 2008 New Revision: 185770 URL: http://svn.freebsd.org/changeset/base/185770 Log: MFC r185667,r185680,r185681: Overhaul the Rockridge option parsing and beef up the ISO9660 bidder. This makes the ISO reader a lot more robust when reading malformed input. Approved by: re Modified: stable/7/lib/libarchive/ (props changed) stable/7/lib/libarchive/archive_read_support_format_iso9660.c stable/7/lib/libarchive/archive_string.c stable/7/lib/libarchive/archive_string.h Modified: stable/7/lib/libarchive/archive_read_support_format_iso9660.c ============================================================================== --- stable/7/lib/libarchive/archive_read_support_format_iso9660.c Mon Dec 8 17:12:40 2008 (r185769) +++ stable/7/lib/libarchive/archive_read_support_format_iso9660.c Mon Dec 8 17:18:37 2008 (r185770) @@ -138,6 +138,15 @@ __FBSDID("$FreeBSD$"); #define PVD_reserved4_size 1 #define PVD_application_data_offset (PVD_reserved4_offset + PVD_reserved4_size) #define PVD_application_data_size 512 +#define PVD_reserved5_offset (PVD_application_data_offset + PVD_application_data_size) +#define PVD_reserved5_size (2048 - PVD_reserved5_offset) + +/* TODO: It would make future maintenance easier to just hardcode the + * above values. In particular, ECMA119 states the offsets as part of + * the standard. That would eliminate the need for the following check.*/ +#if PVD_reserved5_offset != 1395 +#error PVD offset and size definitions are wrong. +#endif /* Structure of an on-disk directory record. */ /* Note: ISO9660 stores each multi-byte integer twice, once in @@ -178,17 +187,20 @@ struct file_info { uint64_t size; /* File size in bytes. */ uint64_t ce_offset; /* Offset of CE */ uint64_t ce_size; /* Size of CE */ + time_t birthtime; /* File created time. */ time_t mtime; /* File last modified time. */ time_t atime; /* File last accessed time. */ - time_t ctime; /* File creation time. */ + time_t ctime; /* File attribute change time. */ uint64_t rdev; /* Device number */ mode_t mode; uid_t uid; gid_t gid; ino_t inode; int nlinks; - char *name; /* Null-terminated filename. */ + struct archive_string name; /* Pathname */ + char name_continues; /* Non-zero if name continues */ struct archive_string symlink; + char symlink_continues; /* Non-zero if link continues */ }; @@ -210,6 +222,7 @@ struct iso9660 { uint64_t current_position; ssize_t logical_block_size; + uint64_t volume_size; /* Total size of volume in bytes. */ off_t entry_sparse_offset; int64_t entry_bytes_remaining; @@ -224,7 +237,9 @@ static int archive_read_format_iso9660_r static int archive_read_format_iso9660_read_header(struct archive_read *, struct archive_entry *); static const char *build_pathname(struct archive_string *, struct file_info *); +#if DEBUG static void dump_isodirrec(FILE *, const unsigned char *isodirrec); +#endif static time_t time_from_tm(struct tm *); static time_t isodate17(const unsigned char *); static time_t isodate7(const unsigned char *); @@ -238,6 +253,12 @@ static struct file_info * static void parse_rockridge(struct iso9660 *iso9660, struct file_info *file, const unsigned char *start, const unsigned char *end); +static void parse_rockridge_NM1(struct file_info *, + const unsigned char *, int); +static void parse_rockridge_SL1(struct file_info *, + const unsigned char *, int); +static void parse_rockridge_TF1(struct file_info *, + const unsigned char *, int); static void release_file(struct iso9660 *, struct file_info *); static unsigned toi(const void *p, int n); @@ -314,13 +335,61 @@ static int isPVD(struct iso9660 *iso9660, const unsigned char *h) { struct file_info *file; + int i; - if (h[0] != 1) + /* Type of the Primary Volume Descriptor must be 1. */ + if (h[PVD_type_offset] != 1) return (0); - if (memcmp(h+1, "CD001", 5) != 0) + + /* ID must be "CD001" */ + if (memcmp(h + PVD_id_offset, "CD001", 5) != 0) + return (0); + + /* PVD version must be 1. */ + if (h[PVD_version_offset] != 1) return (0); + /* Reserved field must be 0. */ + if (h[PVD_reserved1_offset] != 0) + return (0); + + /* Reserved field must be 0. */ + for (i = 0; i < PVD_reserved2_size; ++i) + if (h[PVD_reserved2_offset + i] != 0) + return (0); + + /* Reserved field must be 0. */ + for (i = 0; i < PVD_reserved3_size; ++i) + if (h[PVD_reserved3_offset + i] != 0) + return (0); + + /* Logical block size must be > 0. */ + /* I've looked at Ecma 119 and can't find any stronger + * restriction on this field. */ iso9660->logical_block_size = toi(h + PVD_logical_block_size_offset, 2); + if (iso9660->logical_block_size <= 0) + return (0); + + iso9660->volume_size = iso9660->logical_block_size + * (uint64_t)toi(h + PVD_volume_space_size_offset, 4); + + /* File structure version must be 1 for ISO9660/ECMA119. */ + if (h[PVD_file_structure_version_offset] != 1) + return (0); + + + /* Reserved field must be 0. */ + for (i = 0; i < PVD_reserved4_size; ++i) + if (h[PVD_reserved4_offset + i] != 0) + return (0); + + /* Reserved field must be 0. */ + for (i = 0; i < PVD_reserved5_size; ++i) + if (h[PVD_reserved5_offset + i] != 0) + return (0); + + /* XXX TODO: Check other values for sanity; reject more + * malformed PVDs. XXX */ /* Store the root directory in the pending list. */ file = parse_file_info(iso9660, NULL, h + PVD_root_directory_record_offset); @@ -352,12 +421,22 @@ archive_read_format_iso9660_read_header( iso9660->entry_bytes_remaining = file->size; iso9660->entry_sparse_offset = 0; /* Offset for sparse-file-aware clients. */ + if (file->offset + file->size > iso9660->volume_size) { + archive_set_error(&a->archive, ARCHIVE_ERRNO_MISC, + "File is beyond end-of-media: %s", file->name); + iso9660->entry_bytes_remaining = 0; + iso9660->entry_sparse_offset = 0; + release_file(iso9660, file); + return (ARCHIVE_WARN); + } + /* Set up the entry structure with information about this entry. */ archive_entry_set_mode(entry, file->mode); archive_entry_set_uid(entry, file->uid); archive_entry_set_gid(entry, file->gid); archive_entry_set_nlink(entry, file->nlinks); archive_entry_set_ino(entry, file->inode); + /* archive_entry_set_birthtime(entry, file->birthtime, 0); */ archive_entry_set_mtime(entry, file->mtime, 0); archive_entry_set_ctime(entry, file->ctime, 0); archive_entry_set_atime(entry, file->atime, 0); @@ -535,13 +614,7 @@ parse_file_info(struct iso9660 *iso9660, file->mtime = isodate7(isodirrec + DR_date_offset); file->ctime = file->atime = file->mtime; name_len = (size_t)*(const unsigned char *)(isodirrec + DR_name_len_offset); - file->name = (char *)malloc(name_len + 1); - if (file->name == NULL) { - free(file); - return (NULL); - } - memcpy(file->name, isodirrec + DR_name_offset, name_len); - file->name[name_len] = '\0'; + archive_strncpy(&file->name, isodirrec + DR_name_offset, name_len); flags = *(isodirrec + DR_flags_offset); if (flags & 0x02) file->mode = AE_IFDIR | 0700; @@ -561,6 +634,7 @@ parse_file_info(struct iso9660 *iso9660, parse_rockridge(iso9660, file, rr_start, rr_end); } +#if DEBUG /* DEBUGGING: Warn about attributes I don't yet fully support. */ if ((flags & ~0x02) != 0) { fprintf(stderr, "\n ** Unrecognized flag: "); @@ -583,7 +657,7 @@ parse_file_info(struct iso9660 *iso9660, dump_isodirrec(stderr, isodirrec); fprintf(stderr, "\n"); } - +#endif return (file); } @@ -623,6 +697,7 @@ parse_rockridge(struct iso9660 *iso9660, while (p + 4 < end /* Enough space for another entry. */ && p[0] >= 'A' && p[0] <= 'Z' /* Sanity-check 1st char of name. */ && p[1] >= 'A' && p[1] <= 'Z' /* Sanity-check 2nd char of name. */ + && p[2] >= 4 /* Sanity-check length. */ && p + p[2] <= end) { /* Sanity-check length. */ const unsigned char *data = p + 4; int data_length = p[2] - 4; @@ -635,61 +710,54 @@ parse_rockridge(struct iso9660 *iso9660, */ switch(p[0]) { case 'C': - if (p[0] == 'C' && p[1] == 'E' && version == 1) { - /* - * CE extension comprises: - * 8 byte sector containing extension - * 8 byte offset w/in above sector - * 8 byte length of continuation - */ - file->ce_offset = toi(data, 4) - * iso9660->logical_block_size - + toi(data + 8, 4); - file->ce_size = toi(data + 16, 4); + if (p[0] == 'C' && p[1] == 'E') { + if (version == 1 && data_length == 24) { + /* + * CE extension comprises: + * 8 byte sector containing extension + * 8 byte offset w/in above sector + * 8 byte length of continuation + */ + file->ce_offset = (uint64_t)toi(data, 4) + * iso9660->logical_block_size + + toi(data + 8, 4); + file->ce_size = toi(data + 16, 4); + /* If the result is rediculous, + * ignore it. */ + if (file->ce_offset + file->ce_size + > iso9660->volume_size) { + file->ce_offset = 0; + file->ce_size = 0; + } + } break; } /* FALLTHROUGH */ case 'N': - if (p[0] == 'N' && p[1] == 'M' && version == 1 - && *data == 0) { - /* NM extension with flag byte == 0 */ - /* - * NM extension comprises: - * one byte flag - * rest is long name - */ - /* TODO: Obey flags. */ - char *old_name = file->name; - - data++; /* Skip flag byte. */ - data_length--; - file->name = (char *)malloc(data_length + 1); - if (file->name != NULL) { - free(old_name); - memcpy(file->name, data, data_length); - file->name[data_length] = '\0'; - } else - file->name = old_name; + if (p[0] == 'N' && p[1] == 'M') { + if (version == 1) + parse_rockridge_NM1(file, + data, data_length); break; } /* FALLTHROUGH */ case 'P': - if (p[0] == 'P' && p[1] == 'D' && version == 1) { + if (p[0] == 'P' && p[1] == 'D') { /* * PD extension is padding; * contents are always ignored. */ break; } - if (p[0] == 'P' && p[1] == 'N' && version == 1) { - if (data_length == 16) { + if (p[0] == 'P' && p[1] == 'N') { + if (version == 1 && data_length == 16) { file->rdev = toi(data,4); file->rdev <<= 32; file->rdev |= toi(data + 8, 4); } break; } - if (p[0] == 'P' && p[1] == 'X' && version == 1) { + if (p[0] == 'P' && p[1] == 'X') { /* * PX extension comprises: * 8 bytes for mode, @@ -698,12 +766,22 @@ parse_rockridge(struct iso9660 *iso9660, * 8 bytes for gid, * 8 bytes for inode. */ - if (data_length == 32) { - file->mode = toi(data, 4); - file->nlinks = toi(data + 8, 4); - file->uid = toi(data + 16, 4); - file->gid = toi(data + 24, 4); - file->inode = toi(data + 32, 4); + if (version == 1) { + if (data_length >= 8) + file->mode + = toi(data, 4); + if (data_length >= 16) + file->nlinks + = toi(data + 8, 4); + if (data_length >= 24) + file->uid + = toi(data + 16, 4); + if (data_length >= 32) + file->gid + = toi(data + 24, 4); + if (data_length >= 40) + file->inode + = toi(data + 32, 4); } break; } @@ -720,56 +798,14 @@ parse_rockridge(struct iso9660 *iso9660, } /* FALLTHROUGH */ case 'S': - if (p[0] == 'S' && p[1] == 'L' && version == 1 - && *data == 0) { - int cont = 1; - /* SL extension with flags == 0 */ - /* TODO: handle non-zero flag values. */ - data++; /* Skip flag byte. */ - data_length--; - while (data_length > 0) { - unsigned char flag = *data++; - unsigned char nlen = *data++; - data_length -= 2; - - if (cont == 0) - archive_strcat(&file->symlink, "/"); - cont = 0; - - switch(flag) { - case 0x01: /* Continue */ - archive_strncat(&file->symlink, - (const char *)data, nlen); - cont = 1; - break; - case 0x02: /* Current */ - archive_strcat(&file->symlink, "."); - break; - case 0x04: /* Parent */ - archive_strcat(&file->symlink, ".."); - break; - case 0x08: /* Root */ - case 0x10: /* Volume root */ - archive_string_empty(&file->symlink); - break; - case 0x20: /* Hostname */ - archive_strcat(&file->symlink, "hostname"); - break; - case 0: - archive_strncat(&file->symlink, - (const char *)data, nlen); - break; - default: - /* TODO: issue a warning ? */ - break; - } - data += nlen; - data_length -= nlen; - } + if (p[0] == 'S' && p[1] == 'L') { + if (version == 1) + parse_rockridge_SL1(file, + data, data_length); break; } if (p[0] == 'S' && p[1] == 'P' - && version == 1 && data_length == 7 + && version == 1 && data_length == 3 && data[0] == (unsigned char)'\xbe' && data[1] == (unsigned char)'\xef') { /* @@ -805,66 +841,27 @@ parse_rockridge(struct iso9660 *iso9660, return; } case 'T': - if (p[0] == 'T' && p[1] == 'F' && version == 1) { - char flag = data[0]; - /* - * TF extension comprises: - * one byte flag - * create time (optional) - * modify time (optional) - * access time (optional) - * attribute time (optional) - * Time format and presence of fields - * is controlled by flag bits. - */ - data++; - if (flag & 0x80) { - /* Use 17-byte time format. */ - if (flag & 1) /* Create time. */ - data += 17; - if (flag & 2) { /* Modify time. */ - file->mtime = isodate17(data); - data += 17; - } - if (flag & 4) { /* Access time. */ - file->atime = isodate17(data); - data += 17; - } - if (flag & 8) { /* Attribute time. */ - file->ctime = isodate17(data); - data += 17; - } - } else { - /* Use 7-byte time format. */ - if (flag & 1) /* Create time. */ - data += 7; - if (flag & 2) { /* Modify time. */ - file->mtime = isodate7(data); - data += 7; - } - if (flag & 4) { /* Access time. */ - file->atime = isodate7(data); - data += 7; - } - if (flag & 8) { /* Attribute time. */ - file->ctime = isodate7(data); - data += 7; - } - } + if (p[0] == 'T' && p[1] == 'F') { + if (version == 1) + parse_rockridge_TF1(file, + data, data_length); break; } /* FALLTHROUGH */ default: /* The FALLTHROUGHs above leave us here for * any unsupported extension. */ +#if DEBUG { const unsigned char *t; - fprintf(stderr, "\nUnsupported RRIP extension for %s\n", file->name); + fprintf(stderr, "\nUnsupported RRIP extension for %s\n", file->name.s); fprintf(stderr, " %c%c(%d):", p[0], p[1], data_length); for (t = data; t < data + data_length && t < data + 16; t++) fprintf(stderr, " %02x", *t); fprintf(stderr, "\n"); } +#endif + break; } @@ -874,14 +871,222 @@ parse_rockridge(struct iso9660 *iso9660, } static void +parse_rockridge_NM1(struct file_info *file, const unsigned char *data, + int data_length) +{ + if (!file->name_continues) + archive_string_empty(&file->name); + file->name_continues = 0; + if (data_length < 1) + return; + /* + * NM version 1 extension comprises: + * 1 byte flag, value is one of: + * = 0: remainder is name + * = 1: remainder is name, next NM entry continues name + * = 2: "." + * = 4: ".." + * = 32: Implementation specific + * All other values are reserved. + */ + switch(data[0]) { + case 0: + if (data_length < 2) + return; + archive_strncat(&file->name, data + 1, data_length - 1); + break; + case 1: + if (data_length < 2) + return; + archive_strncat(&file->name, data + 1, data_length - 1); + file->name_continues = 1; + break; + case 2: + archive_strcat(&file->name, "."); + break; + case 4: + archive_strcat(&file->name, ".."); + break; + default: + return; + } + +} + +static void +parse_rockridge_TF1(struct file_info *file, const unsigned char *data, + int data_length) +{ + char flag; + /* + * TF extension comprises: + * one byte flag + * create time (optional) + * modify time (optional) + * access time (optional) + * attribute time (optional) + * Time format and presence of fields + * is controlled by flag bits. + */ + if (data_length < 1) + return; + flag = data[0]; + ++data; + --data_length; + if (flag & 0x80) { + /* Use 17-byte time format. */ + if ((flag & 1) && data_length >= 17) { + /* Create time. */ + file->birthtime = isodate17(data); + data += 17; + data_length -= 17; + } + if ((flag & 2) && data_length >= 17) { + /* Modify time. */ + file->mtime = isodate17(data); + data += 17; + data_length -= 17; + } + if ((flag & 4) && data_length >= 17) { + /* Access time. */ + file->atime = isodate17(data); + data += 17; + data_length -= 17; + } + if ((flag & 8) && data_length >= 17) { + /* Attribute change time. */ + file->ctime = isodate17(data); + data += 17; + data_length -= 17; + } + } else { + /* Use 7-byte time format. */ + if ((flag & 1) && data_length >= 7) { + /* Create time. */ + file->birthtime = isodate17(data); + data += 7; + data_length -= 7; + } + if ((flag & 2) && data_length >= 7) { + /* Modify time. */ + file->mtime = isodate7(data); + data += 7; + data_length -= 7; + } + if ((flag & 4) && data_length >= 7) { + /* Access time. */ + file->atime = isodate7(data); + data += 7; + data_length -= 7; + } + if ((flag & 8) && data_length >= 7) { + /* Attribute change time. */ + file->ctime = isodate7(data); + data += 7; + data_length -= 7; + } + } +} + +static void +parse_rockridge_SL1(struct file_info *file, const unsigned char *data, + int data_length) +{ + int component_continues = 1; + + if (!file->symlink_continues) + archive_string_empty(&file->symlink); + else + archive_strcat(&file->symlink, "/"); + file->symlink_continues = 0; + + /* + * Defined flag values: + * 0: This is the last SL record for this symbolic link + * 1: this symbolic link field continues in next SL entry + * All other values are reserved. + */ + if (data_length < 1) + return; + switch(*data) { + case 0: + break; + case 1: + file->symlink_continues = 1; + break; + default: + return; + } + ++data; /* Skip flag byte. */ + --data_length; + + /* + * SL extension body stores "components". + * Basically, this is a complicated way of storing + * a POSIX path. It also interferes with using + * symlinks for storing non-path data. + * + * Each component is 2 bytes (flag and length) + * possibly followed by name data. + */ + while (data_length >= 2) { + unsigned char flag = *data++; + unsigned char nlen = *data++; + data_length -= 2; + + if (!component_continues) + archive_strcat(&file->symlink, "/"); + component_continues = 0; + + switch(flag) { + case 0: /* Usual case, this is text. */ + if (data_length < nlen) + return; + archive_strncat(&file->symlink, + (const char *)data, nlen); + break; + case 0x01: /* Text continues in next component. */ + if (data_length < nlen) + return; + archive_strncat(&file->symlink, + (const char *)data, nlen); + component_continues = 1; + break; + case 0x02: /* Current dir. */ + archive_strcat(&file->symlink, "."); + break; + case 0x04: /* Parent dir. */ + archive_strcat(&file->symlink, ".."); + break; + case 0x08: /* Root of filesystem. */ + archive_string_empty(&file->symlink); + archive_strcat(&file->symlink, "/"); + break; + case 0x10: /* Undefined (historically "volume root" */ + archive_string_empty(&file->symlink); + archive_strcat(&file->symlink, "ROOT"); + break; + case 0x20: /* Undefined (historically "hostname") */ + archive_strcat(&file->symlink, "hostname"); + break; + default: + /* TODO: issue a warning ? */ + return; + } + data += nlen; + data_length -= nlen; + } +} + + +static void release_file(struct iso9660 *iso9660, struct file_info *file) { struct file_info *parent; if (file->refcount == 0) { parent = file->parent; - if (file->name) - free(file->name); + archive_string_free(&file->name); archive_string_free(&file->symlink); free(file); if (parent != NULL) { @@ -906,7 +1111,9 @@ next_entry_seek(struct archive_read *a, /* CE area precedes actual file data? Ignore it. */ if (file->ce_offset > file->offset) { -fprintf(stderr, " *** Discarding CE data.\n"); +#if DEBUG + fprintf(stderr, " *** Discarding CE data.\n"); +#endif file->ce_offset = 0; file->ce_size = 0; } @@ -1071,17 +1278,18 @@ time_from_tm(struct tm *t) static const char * build_pathname(struct archive_string *as, struct file_info *file) { - if (file->parent != NULL && file->parent->name[0] != '\0') { + if (file->parent != NULL && archive_strlen(&file->parent->name) > 0) { build_pathname(as, file->parent); archive_strcat(as, "/"); } - if (file->name[0] == '\0') + if (archive_strlen(&file->name) == 0) archive_strcat(as, "."); else - archive_strcat(as, file->name); + archive_string_concat(as, &file->name); return (as->s); } +#if DEBUG static void dump_isodirrec(FILE *out, const unsigned char *isodirrec) { @@ -1106,3 +1314,4 @@ dump_isodirrec(FILE *out, const unsigned fprintf(out, " `%.*s'", toi(isodirrec + DR_name_len_offset, DR_name_len_size), isodirrec + DR_name_offset); } +#endif Modified: stable/7/lib/libarchive/archive_string.c ============================================================================== --- stable/7/lib/libarchive/archive_string.c Mon Dec 8 17:12:40 2008 (r185769) +++ stable/7/lib/libarchive/archive_string.c Mon Dec 8 17:18:37 2008 (r185770) @@ -70,6 +70,18 @@ __archive_string_copy(struct archive_str } void +__archive_string_concat(struct archive_string *dest, struct archive_string *src) +{ + if (src->length > 0) { + if (__archive_string_ensure(dest, dest->length + src->length + 1) == NULL) + __archive_errx(1, "Out of memory"); + memcpy(dest->s + dest->length, src->s, src->length); + dest->length += src->length; + dest->s[dest->length] = 0; + } +} + +void __archive_string_free(struct archive_string *as) { as->length = 0; Modified: stable/7/lib/libarchive/archive_string.h ============================================================================== --- stable/7/lib/libarchive/archive_string.h Mon Dec 8 17:12:40 2008 (r185769) +++ stable/7/lib/libarchive/archive_string.h Mon Dec 8 17:18:37 2008 (r185770) @@ -92,6 +92,12 @@ __archive_string_copy(struct archive_str #define archive_string_copy(dest, src) \ __archive_string_copy(dest, src) +/* Concatenate one archive_string to another */ +void +__archive_string_concat(struct archive_string *dest, struct archive_string *src); +#define archive_string_concat(dest, src) \ + __archive_string_concat(dest, src) + /* Ensure that the underlying buffer is at least as large as the request. */ struct archive_string * __archive_string_ensure(struct archive_string *, size_t);