From 3ba777976f7e9c9796117a0b1c3fb5521e983d15 Mon Sep 17 00:00:00 2001 From: kientzle Date: Wed, 29 Dec 2004 23:26:18 +0000 Subject: [PATCH] Rewrite the code that hacks a short names to use in the regular ustar entry. The old code sometimes created a too-long name that overflowed the ustar fields and triggered an internal assertion failure. This version should be more robust. Thanks to: Michal Listos Fixes: bin/74385 MFC after: 15 days --- lib/libarchive/archive_write_set_format_pax.c | 216 +++++++++++++----- 1 file changed, 154 insertions(+), 62 deletions(-) diff --git a/lib/libarchive/archive_write_set_format_pax.c b/lib/libarchive/archive_write_set_format_pax.c index 94e608a07bb1..8b94b4e91b44 100644 --- a/lib/libarchive/archive_write_set_format_pax.c +++ b/lib/libarchive/archive_write_set_format_pax.c @@ -720,99 +720,191 @@ archive_write_pax_header(struct archive *a, /* * We need a valid name for the regular 'ustar' entry. This routine * tries to hack something more-or-less reasonable. + * + * The approach here tries to preserve leading dir names. We do so by + * breaking the full path into three sections: + * 1) "prefix" directory names, + * 2) "suffix" directory names, + * 3) filename. + * + * These three sections must satisfy the following requirements: + * * Parts 1 & 2 together form an initial portion of the dir name. + * * Part 3 forms an initial portion of the base filename. + * * The filename must be <= 90 chars to fit the ustar 'name' field while + * allowing room for the '/PaxHeader' dir element (see below) + * * Parts 2 & 3 together must be <= 90 chars to fit the ustar 'name' field + * while allowing room for the '/PaxHeader' dir element. + * * Part 1 must be <= 155 chars to fit the ustar 'prefix' field. + * * If the original name ends in a '/', the new name must also end in a '/' + * * Trailing '/.' sequences may be stripped. + * + * Note: Recall that the ustar format does not store the '/' separating + * parts 1 & 2, but does store the '/' separating parts 2 & 3. */ static char * build_ustar_entry_name(char *dest, const char *src) { - const char *basename, *break_point, *prefix; - int basename_length, dirname_length, prefix_length; + const char *prefix, *prefix_end; + const char *suffix, *suffix_end; + const char *filename, *filename_end; + char *p; + size_t s; + int need_slash = 0; /* Was there a trailing slash? */ + size_t suffix_length = 90; - prefix = src; - basename = strrchr(src, '/'); - if (basename == NULL) { - basename = src; - prefix_length = 0; - basename_length = strlen(basename); - if (basename_length > 100) - basename_length = 100; - } else { - basename_length = strlen(basename); - if (basename_length > 100) - basename_length = 100; - dirname_length = basename - src; - - break_point = - strchr(src + dirname_length + basename_length - 101, '/'); - prefix_length = break_point - prefix - 1; - while (prefix_length > 155) { - prefix = strchr(prefix, '/') + 1; /* Drop 1st dir. */ - prefix_length = break_point - prefix - 1; - } + /* Step 0: Initial checks. */ + s = strlen(src); + if (s < 100) { + strcpy(dest, src); + return (dest); } + /* Step 1: Locate filename and enforce the length restriction. */ + filename_end = src + s; + /* Remove trailing '/' chars and '/.' pairs. */ + for (;;) { + if (filename_end > src && filename_end[-1] == '/') { + filename_end --; + need_slash = 1; /* Remember to restore trailing '/'. */ + continue; + } + if (filename_end > src + 1 && filename_end[-1] == '.' + && filename_end[-2] == '/') { + filename_end -= 2; + continue; + } + break; + } + filename = filename_end - 1; + if (need_slash) + suffix_length--; + while ((filename > src) && (*filename != '/')) + filename --; + if ((*filename == '/') && (filename < filename_end - 1)) + filename ++; + if (filename_end > filename + suffix_length) + filename_end = filename + suffix_length; + + /* Step 2: Locate the "prefix" section of the dirname, including + * trailing '/'. */ + prefix = src; + prefix_end = prefix + 155; + if (prefix_end > filename) + prefix_end = filename; + while (prefix_end > prefix && *prefix_end != '/') + prefix_end--; + if ((prefix_end < filename) && (*prefix_end == '/')) + prefix_end++; + + /* Step 3: Locate the "suffix" section of the dirname, + * including trailing '/'. */ + suffix = prefix_end; + suffix_end = suffix + 89 - (filename_end - filename); + if (suffix_end > filename) + suffix_end = filename; + if (suffix_end < suffix) + suffix_end = suffix; + while (suffix_end > suffix && *suffix_end != '/') + suffix_end--; + if ((suffix_end < filename) && (*suffix_end == '/')) + suffix_end++; + + /* Step 4: Build the new name. */ /* The OpenBSD strlcpy function is safer, but less portable. */ /* Rather than maintain two versions, just use the strncpy version. */ - strncpy(dest, prefix, basename - prefix + basename_length); - dest[basename - prefix + basename_length] = '\0'; + p = dest; + if (prefix_end > prefix) { + strncpy(p, prefix, prefix_end - prefix); + p += prefix_end - prefix; + } + if (suffix_end > suffix) { + strncpy(p, suffix, suffix_end - suffix); + p += suffix_end - suffix; + } + strncpy(p, filename, filename_end - filename); + p += filename_end - filename; + if (need_slash) + *p++ = '/'; + *p++ = '\0'; return (dest); } /* * The ustar header for the pax extended attributes must have a - * reasonable name: SUSv3 suggests 'dirname'/PaxHeaders/'basename' + * reasonable name: SUSv3 suggests 'dirname'/PaxHeader/'filename' * * Joerg Schiling has argued that this is unnecessary because, in practice, * if the pax extended attributes get extracted as regular files, noone is * going to bother reading those attributes to manually restore them. - * This is a tempting argument, but I'm not entirely convinced. + * Based on this, 'star' uses /tmp/PaxHeader/'basename' as the ustar header + * name. This is a tempting argument, but I'm not entirely convinced. + * I'm also uncomfortable with the fact that "/tmp" is a Unix-ism. * - * Of course, adding "PaxHeaders/" might force the name to be too big. - * Here, I start from the (possibly already-trimmed) name used in the - * main ustar header and delete some additional early path elements to - * fit in the extra "PaxHeader/" part. + * The following routine implements the SUSv3 recommendation, and is + * much simpler because we do the initial processing with + * build_ustar_entry_name() above which results in something that is + * already short enough to accomodate the extra '/PaxHeader' + * addition. We just need to separate dir and filename portions and + * handle a few pathological cases. */ static char * -build_pax_attribute_name(const char *abbreviated, /* ustar-compat name */ +build_pax_attribute_name(const char *src, /* ustar-compat name */ struct archive_string *work) { - const char *basename, *break_point, *prefix; - int prefix_length, suffix_length; + const char *filename, *filename_end; - /* - * This is much simpler because I know that "abbreviated" is - * already small enough; I just need to determine if it needs - * any further trimming to fit the "PaxHeader/" portion. - */ + if (*src == '\0') { + archive_strcpy(work, "PaxHeader/blank"); + return (work->s); + } + if (*src == '.' && src[1] == '\0') { + archive_strcpy(work, "PaxHeader/dot"); + return (work->s); + } - /* Identify the final prefix and suffix portions. */ - prefix = abbreviated; /* First guess: prefix starts at beginning */ - if (strlen(abbreviated) > 100) { - break_point = strchr(prefix + strlen(prefix) - 101, '/'); - prefix_length = break_point - prefix - 1; - suffix_length = strlen(break_point + 1); - /* - * The next loop keeps trimming until "/PaxHeader/" can - * be added to either the prefix or the suffix. - */ - while (prefix_length > 144 && suffix_length > 89) { - prefix = strchr(prefix, '/') + 1; /* Drop 1st dir. */ - prefix_length = break_point - prefix - 1; + /* Prune unwanted final path elements. */ + filename_end = src + strlen(src); + for (;;) { + if (filename_end > src && filename_end[-1] == '/') { + filename_end --; + continue; } + if (filename_end > src + 1 && filename_end[-1] == '.' + && filename_end[-2] == '/') { + filename_end -= 2; + continue; + } + break; + } + while ((filename_end > src) && (filename_end[-1] == '/')) + filename_end --; + + /* Pathological case: Entire 'src' consists of '/' characters. */ + if (filename_end == src) { + archive_strcpy(work, "/PaxHeader/rootdir"); + return (work->s); } - archive_string_empty(work); - basename = strrchr(prefix, '/'); - if (basename == NULL) { - archive_strcpy(work, "PaxHeader/"); - archive_strcat(work, prefix); - } else { - basename++; - archive_strncpy(work, prefix, basename - prefix); - archive_strcat(work, "PaxHeader/"); - archive_strcat(work, basename); + /* Find the '/' before the filename portion. */ + filename = filename_end - 1; + while ((filename > src) && (*filename != '/')) + filename --; + if (*filename == '/') + filename ++; + + /* Pathological case: filename is '.' */ + if (filename_end == filename + 2 + && filename[0] == '/' && filename[1] == '.') { + archive_strncpy(work, src, filename - src); + archive_strcat(work, "PaxHeader/dot"); + return (work->s); } + /* Build the new name. */ + archive_strncpy(work, src, filename - src); + archive_strcat(work, "PaxHeader/"); + archive_strncat(work, filename, filename_end - filename); return (work->s); }