]> git.ipfire.org Git - thirdparty/libarchive.git/commitdiff
IFC
authorTim Kientzle <kientzle@gmail.com>
Thu, 4 Dec 2008 22:08:00 +0000 (17:08 -0500)
committerTim Kientzle <kientzle@gmail.com>
Thu, 4 Dec 2008 22:08:00 +0000 (17:08 -0500)
SVN-Revision: 264

cpio/cpio.c
cpio/cpio.h
cpio/test/test_option_tv.stdout.uu
tar/config_freebsd.h
tar/tree.c
tar/tree.h
tar/util.c
tar/write.c

index 5d79f8e203b3f974c64913bce1a7155117332011..87932aa2cd6409e47f6b828701ca69aa317e2db6 100644 (file)
@@ -26,7 +26,7 @@
 
 
 #include "cpio_platform.h"
-__FBSDID("$FreeBSD: src/usr.bin/cpio/cpio.c,v 1.13 2008/09/04 05:20:46 kientzle Exp $");
+__FBSDID("$FreeBSD: src/usr.bin/cpio/cpio.c,v 1.14 2008/11/29 20:22:02 kientzle Exp $");
 
 #include <sys/types.h>
 #include <archive.h>
@@ -41,6 +41,12 @@ __FBSDID("$FreeBSD: src/usr.bin/cpio/cpio.c,v 1.13 2008/09/04 05:20:46 kientzle
 #ifdef HAVE_FCNTL_H
 #include <fcntl.h>
 #endif
+#ifdef HAVE_GRP_H
+#include <grp.h>
+#endif
+#ifdef HAVE_PWD_H
+#include <pwd.h>
+#endif
 #ifdef HAVE_STDARG_H
 #include <stdarg.h>
 #endif
@@ -58,11 +64,32 @@ __FBSDID("$FreeBSD: src/usr.bin/cpio/cpio.c,v 1.13 2008/09/04 05:20:46 kientzle
 #include "cpio.h"
 #include "matching.h"
 
+/* Fixed size of uname/gname caches. */
+#define        name_cache_size 101
+
+struct name_cache {
+       int     probes;
+       int     hits;
+       size_t  size;
+       struct {
+               id_t id;
+               char *name;
+       } cache[name_cache_size];
+};
+
 static int     copy_data(struct archive *, struct archive *);
 static const char *cpio_rename(const char *name);
 static int     entry_to_archive(struct cpio *, struct archive_entry *);
 static int     file_to_archive(struct cpio *, const char *);
+static void    free_cache(struct name_cache *cache);
+static void    list_item_verbose(struct cpio *, struct archive_entry *);
 static void    long_help(void);
+static const char *lookup_gname(struct cpio *, gid_t gid);
+static int     lookup_gname_helper(struct cpio *,
+                   const char **name, id_t gid);
+static const char *lookup_uname(struct cpio *, uid_t uid);
+static int     lookup_uname_helper(struct cpio *,
+                   const char **name, id_t uid);
 static void    mode_in(struct cpio *);
 static void    mode_list(struct cpio *);
 static void    mode_out(struct cpio *);
@@ -268,6 +295,8 @@ main(int argc, char *argv[])
                    "Must specify at least one of -i, -o, or -p");
        }
 
+       free_cache(cpio->gname_cache);
+       free_cache(cpio->uname_cache);
        return (0);
 }
 
@@ -798,18 +827,9 @@ mode_list(struct cpio *cpio)
                }
                if (excluded(cpio, archive_entry_pathname(entry)))
                        continue;
-               if (cpio->verbose) {
-                       /* TODO: uname/gname lookups */
-                       /* TODO: Clean this up. */
-                       fprintf(stdout,
-                           "%s%3d %8s%8s " CPIO_FILESIZE_PRINTF " %s\n",
-                           archive_entry_strmode(entry),
-                           archive_entry_nlink(entry),
-                           archive_entry_uname(entry),
-                           archive_entry_gname(entry),
-                           (CPIO_FILESIZE_TYPE)archive_entry_size(entry),
-                           archive_entry_pathname(entry));
-               } else
+               if (cpio->verbose)
+                       list_item_verbose(cpio, entry);
+               else
                        fprintf(stdout, "%s\n", archive_entry_pathname(entry));
        }
        r = archive_read_close(a);
@@ -825,6 +845,73 @@ mode_list(struct cpio *cpio)
        exit(0);
 }
 
+/*
+ * Display information about the current file.
+ *
+ * The format here roughly duplicates the output of 'ls -l'.
+ * This is based on SUSv2, where 'tar tv' is documented as
+ * listing additional information in an "unspecified format,"
+ * and 'pax -l' is documented as using the same format as 'ls -l'.
+ */
+static void
+list_item_verbose(struct cpio *cpio, struct archive_entry *entry)
+{
+       char                     size[32];
+       char                     date[32];
+       const char              *uname, *gname;
+       FILE                    *out = stdout;
+       const struct stat       *st;
+       const char              *fmt;
+       time_t                   tim;
+       static time_t            now;
+
+       st = archive_entry_stat(entry);
+
+       if (!now)
+               time(&now);
+
+       /* Use uname if it's present, else uid. */
+       uname = archive_entry_uname(entry);
+       if (uname == NULL)
+               uname = lookup_uname(cpio, archive_entry_uid(entry));
+
+       /* Use gname if it's present, else gid. */
+       gname = archive_entry_gname(entry);
+       if (gname == NULL)
+               gname = lookup_gname(cpio, archive_entry_gid(entry));
+
+       /* Print device number or file size. */
+       if (S_ISCHR(st->st_mode) || S_ISBLK(st->st_mode)) {
+               snprintf(size, sizeof(size), "%lu,%lu",
+                   (unsigned long)major(st->st_rdev),
+                   (unsigned long)minor(st->st_rdev)); /* ls(1) also casts here. */
+       } else {
+               snprintf(size, sizeof(size), CPIO_FILESIZE_PRINTF,
+                   (CPIO_FILESIZE_TYPE)st->st_size);
+       }
+
+       /* Format the time using 'ls -l' conventions. */
+       tim = (time_t)st->st_mtime;
+       if (abs(tim - now) > (365/2)*86400)
+               fmt = cpio->day_first ? "%e %b  %Y" : "%b %e  %Y";
+       else
+               fmt = cpio->day_first ? "%e %b %H:%M" : "%b %e %H:%M";
+       strftime(date, sizeof(date), fmt, localtime(&tim));
+
+       fprintf(out, "%s%3d %-8s %-8s %8s %12s %s",
+           archive_entry_strmode(entry),
+           archive_entry_nlink(entry),
+           uname, gname, size, date,
+           archive_entry_pathname(entry));
+
+       /* Extra information for links. */
+       if (archive_entry_hardlink(entry)) /* Hard link */
+               fprintf(out, " link to %s", archive_entry_hardlink(entry));
+       else if (archive_entry_symlink(entry)) /* Symbolic link */
+               fprintf(out, " -> %s", archive_entry_symlink(entry));
+       fprintf(out, "\n");
+}
+
 static void
 mode_pass(struct cpio *cpio, const char *destdir)
 {
@@ -1033,3 +1120,123 @@ process_lines_free(struct line_reader *lr)
        free(lr->pathname);
        free(lr);
 }
+
+static void
+free_cache(struct name_cache *cache)
+{
+       size_t i;
+
+       if (cache != NULL) {
+               for (i = 0; i < cache->size; i++)
+                       free(cache->cache[i].name);
+               free(cache);
+       }
+}
+
+/*
+ * Lookup uname/gname from uid/gid, return NULL if no match.
+ */
+static const char *
+lookup_name(struct cpio *cpio, struct name_cache **name_cache_variable,
+    int (*lookup_fn)(struct cpio *, const char **, id_t), id_t id)
+{
+       char asnum[16];
+       struct name_cache       *cache;
+       const char *name;
+       int slot;
+
+
+       if (*name_cache_variable == NULL) {
+               *name_cache_variable = malloc(sizeof(struct name_cache));
+               if (*name_cache_variable == NULL)
+                       cpio_errc(1, ENOMEM, "No more memory");
+               memset(*name_cache_variable, 0, sizeof(struct name_cache));
+               (*name_cache_variable)->size = name_cache_size;
+       }
+
+       cache = *name_cache_variable;
+       cache->probes++;
+
+       slot = id % cache->size;
+       if (cache->cache[slot].name != NULL) {
+               if (cache->cache[slot].id == id) {
+                       cache->hits++;
+                       return (cache->cache[slot].name);
+               }
+               free(cache->cache[slot].name);
+               cache->cache[slot].name = NULL;
+       }
+
+       if (lookup_fn(cpio, &name, id) == 0) {
+               if (name == NULL || name[0] == '\0') {
+                       /* If lookup failed, format it as a number. */
+                       snprintf(asnum, sizeof(asnum), "%u", (unsigned)id);
+                       name = asnum;
+               }
+               cache->cache[slot].name = strdup(name);
+               if (cache->cache[slot].name != NULL) {
+                       cache->cache[slot].id = id;
+                       return (cache->cache[slot].name);
+               }
+               /*
+                * Conveniently, NULL marks an empty slot, so
+                * if the strdup() fails, we've just failed to
+                * cache it.  No recovery necessary.
+                */
+       }
+       return (NULL);
+}
+
+static const char *
+lookup_uname(struct cpio *cpio, uid_t uid)
+{
+       return (lookup_name(cpio, &cpio->uname_cache,
+                   &lookup_uname_helper, (id_t)uid));
+}
+
+static int
+lookup_uname_helper(struct cpio *cpio, const char **name, id_t id)
+{
+       struct passwd   *pwent;
+
+       (void)cpio; /* UNUSED */
+
+       errno = 0;
+       pwent = getpwuid((uid_t)id);
+       if (pwent == NULL) {
+               *name = NULL;
+               if (errno != 0)
+                       cpio_warnc(errno, "getpwuid(%d) failed", id);
+               return (errno);
+       }
+
+       *name = pwent->pw_name;
+       return (0);
+}
+
+static const char *
+lookup_gname(struct cpio *cpio, gid_t gid)
+{
+       return (lookup_name(cpio, &cpio->gname_cache,
+                   &lookup_gname_helper, (id_t)gid));
+}
+
+static int
+lookup_gname_helper(struct cpio *cpio, const char **name, id_t id)
+{
+       struct group    *grent;
+
+       (void)cpio; /* UNUSED */
+
+       errno = 0;
+       grent = getgrgid((gid_t)id);
+       if (grent == NULL) {
+               *name = NULL;
+               if (errno != 0)
+                       cpio_warnc(errno, "getgrgid(%d) failed", id);
+               return (errno);
+       }
+
+       *name = grent->gr_name;
+       return (0);
+}
index 40b04cdde59ff838473fda2eba9b62a8ce3ed5f2..665a937ca3750331feecc980fb0365466e43d053 100644 (file)
@@ -22,7 +22,7 @@
  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  *
- * $FreeBSD: src/usr.bin/cpio/cpio.h,v 1.4 2008/08/04 01:25:48 cperciva Exp $
+ * $FreeBSD: src/usr.bin/cpio/cpio.h,v 1.5 2008/11/29 20:22:02 kientzle Exp $
  */
 
 #ifndef CPIO_H_INCLUDED
@@ -68,6 +68,7 @@ struct cpio {
        char             *pass_destpath;
        int               uid_override;
        int               gid_override;
+       int               day_first; /* true if locale prefers day/mon */
 
        /* If >= 0, then close this when done. */
        int               fd;
@@ -79,6 +80,9 @@ struct cpio {
        int               return_value; /* Value returned by main() */
        struct archive_entry_linkresolver *linkresolver;
 
+       struct name_cache *uname_cache;
+       struct name_cache *gname_cache;
+
        /* Work data. */
        struct matching  *matching;
        char             *buff;
index baead3ee324dde34064ada694e5cc3ae68590f3b..e28888ac4747f09997022717cc7132b0ee6d18f0 100644 (file)
@@ -1,5 +1,6 @@
-$FreeBSD$
+$FreeBSD: src/usr.bin/cpio/test/test_option_tv.stdout.uu,v 1.2 2008/11/29 20:22:02 kientzle Exp $
 begin 644 test_option_tv.stdout
-G+7)W+7(M+7(M+2`@(#$@("`H;G5L;"D@("AN=6QL*2`P(&9I;&4*
+M+7)W+7(M+7(M+2`@(#$@=&EM("`@("`@=&EM("`@("`@("`@("`@(#`@1&5C
+/(#,Q("`Q.38Y(&9I;&4*
 `
 end
index ee6f8a184a7b2b31cf35e8ccff1529ce56c4328f..16f2b395629b3cbc244375c4b8db345a2d27a587 100644 (file)
@@ -22,7 +22,7 @@
  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  *
- * $FreeBSD: src/usr.bin/tar/config_freebsd.h,v 1.7 2008/11/08 04:43:24 kientzle Exp $
+ * $FreeBSD: src/usr.bin/tar/config_freebsd.h,v 1.8 2008/11/29 20:06:53 kientzle Exp $
  */
 
 /* A default configuration for FreeBSD, used if there is no config.h. */
 #define        HAVE_UNISTD_H 1
 #define        HAVE_UNSIGNED_LONG_LONG
 #define        HAVE_VPRINTF 1
+#define        HAVE_WCTYPE_H 1
 #define        HAVE_ZLIB_H 1
 #undef MAJOR_IN_MKDEV
 #define        STDC_HEADERS 1
index 23e64e3da0644e72891e97a1374b31a03aea00ca..2b076582becc4ed7925452b9140265bd2ed7db15 100644 (file)
@@ -43,7 +43,7 @@
  * regular dir or via fchdir(2) for a symlink).
  */
 #include "bsdtar_platform.h"
-__FBSDID("$FreeBSD: src/usr.bin/tar/tree.c,v 1.8 2007/03/11 10:36:42 kientzle Exp $");
+__FBSDID("$FreeBSD: src/usr.bin/tar/tree.c,v 1.9 2008/11/27 05:49:52 kientzle Exp $");
 
 #ifdef HAVE_SYS_STAT_H
 #include <sys/stat.h>
@@ -227,20 +227,28 @@ tree_open(const char *path)
 /*
  * We've finished a directory; ascend back to the parent.
  */
-static void
+static int
 tree_ascend(struct tree *t)
 {
        struct tree_entry *te;
+       int r = 0;
 
        te = t->stack;
        t->depth--;
        if (te->flags & isDirLink) {
-               fchdir(te->fd);
+               if (fchdir(te->fd) != 0) {
+                       t->tree_errno = errno;
+                       r = TREE_ERROR_FATAL;
+               }
                close(te->fd);
                t->openCount--;
        } else {
-               chdir("..");
+               if (chdir("..") != 0) {
+                       t->tree_errno = errno;
+                       r = TREE_ERROR_FATAL;
+               }
        }
+       return (r);
 }
 
 /*
@@ -272,6 +280,17 @@ int
 tree_next(struct tree *t)
 {
        struct dirent *de = NULL;
+       int r;
+
+       /* If we're called again after a fatal error, that's an API
+        * violation.  Just crash now. */
+       if (t->visit_type == TREE_ERROR_FATAL) {
+               const char *msg = "Unable to continue traversing"
+                   " directory heirarchy after a fatal error.";
+               write(2, msg, strlen(msg));
+               *(int *)0 = 1; /* Deliberate SEGV; NULL pointer dereference. */
+               exit(1); /* In case the SEGV didn't work. */
+       }
 
        /* Handle the startup case by returning the initial entry. */
        if (t->flags & needsReturn) {
@@ -327,10 +346,11 @@ tree_next(struct tree *t)
                        t->depth++;
                        t->d = opendir(".");
                        if (t->d == NULL) {
-                               tree_ascend(t); /* Undo "chdir" */
+                               r = tree_ascend(t); /* Undo "chdir" */
                                tree_pop(t);
                                t->tree_errno = errno;
-                               return (t->visit_type = TREE_ERROR_DIR);
+                               t->visit_type = r != 0 ? r : TREE_ERROR_DIR;
+                               return (t->visit_type);
                        }
                        t->flags &= ~hasLstat;
                        t->flags &= ~hasStat;
@@ -340,11 +360,12 @@ tree_next(struct tree *t)
 
                /* We've done everything necessary for the top stack entry. */
                if (t->stack->flags & needsPostVisit) {
-                       tree_ascend(t);
+                       r = tree_ascend(t);
                        tree_pop(t);
                        t->flags &= ~hasLstat;
                        t->flags &= ~hasStat;
-                       return (t->visit_type = TREE_POSTASCENT);
+                       t->visit_type = r != 0 ? r : TREE_POSTASCENT;
+                       return (t->visit_type);
                }
        }
        return (t->visit_type = 0);
index a32a15f8f32571af00a29a27130c48a47afc9f52..30d96fe3b038ab565dbd7bd3b95eac8db6837775 100644 (file)
@@ -22,7 +22,7 @@
  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  *
- * $FreeBSD: src/usr.bin/tar/tree.h,v 1.3 2007/01/09 08:12:17 kientzle Exp $
+ * $FreeBSD: src/usr.bin/tar/tree.h,v 1.4 2008/11/27 05:49:52 kientzle Exp $
  */
 
 /*-
@@ -36,8 +36,7 @@
  *    * Supports very deep logical traversals.  The fts package
  *      uses "non-chdir" approach for logical traversals.  This
  *      package does use a chdir approach for logical traversals
- *      and can therefore handle pathnames much longer than
- *      PATH_MAX.
+ *      and can therefore handle pathnames much longer than PATH_MAX.
  *    * Supports deep physical traversals "out of the box."
  *      Due to the memory optimizations above, there's no need to
  *      limit dir names to 32k.
@@ -53,23 +52,31 @@ struct tree *tree_open(const char * /* pathname */);
 void tree_close(struct tree *);
 
 /*
- * tree_next() returns Zero if there is no next entry, non-zero if there is.
- * Note that directories are potentially visited three times.  The first
- * time as "regular" file.  If tree_descend() is invoked at that time,
- * the directory is added to a work list and will be visited two more
- * times:  once just after descending into the directory and again
- * just after ascending back to the parent.
+ * tree_next() returns Zero if there is no next entry, non-zero if
+ * there is.  Note that directories are potentially visited three
+ * times.  Directories are always visited first as part of enumerating
+ * their parent.  If tree_descend() is invoked at that time, the
+ * directory is added to a work list and will subsequently be visited
+ * two more times: once just after descending into the directory and
+ * again just after ascending back to the parent.
  *
- * TREE_ERROR is returned if the descent failed (because the
+ * TREE_ERROR_DIR is returned if the descent failed (because the
  * directory couldn't be opened, for instance).  This is returned
- * instead of TREE_PREVISIT/TREE_POSTVISIT.
+ * instead of TREE_PREVISIT/TREE_POSTVISIT.  TREE_ERROR_DIR is not a
+ * fatal error, but it does imply that the relevant subtree won't be
+ * visited.  TREE_ERROR_FATAL is returned for an error that left the
+ * traversal completely hosed.  Right now, this is only returned for
+ * chdir() failures during ascent.
  */
 #define        TREE_REGULAR    1
 #define        TREE_POSTDESCENT        2
 #define        TREE_POSTASCENT 3
 #define        TREE_ERROR_DIR  -1
+#define        TREE_ERROR_FATAL -2
+
 int tree_next(struct tree *);
 
+/* Errno value associated with the last traversal error. */
 int tree_errno(struct tree *);
 
 /*
@@ -85,7 +92,9 @@ void tree_descend(struct tree *);
  * Return information about the current entry.
  */
 
+/* Current depth in the traversal. */
 int tree_current_depth(struct tree *);
+
 /*
  * The current full pathname, length of the full pathname,
  * and a name that can be used to access the file.
@@ -95,6 +104,7 @@ int tree_current_depth(struct tree *);
 const char *tree_current_path(struct tree *);
 size_t tree_current_pathlen(struct tree *);
 const char *tree_current_access_path(struct tree *);
+
 /*
  * Request the lstat() or stat() data for the current path.  Since the
  * tree package needs to do some of this anyway, and caches the
@@ -103,7 +113,9 @@ const char *tree_current_access_path(struct tree *);
  */
 const struct stat *tree_current_stat(struct tree *);
 const struct stat *tree_current_lstat(struct tree *);
-/* The following tests may use mechanisms much faster than stat()/lstat(). */
+
+/* The following functions use tricks to avoid a certain number of
+ * stat()/lstat() calls. */
 /* "is_physical_dir" is equivalent to S_ISDIR(tree_current_lstat()->st_mode) */
 int tree_current_is_physical_dir(struct tree *);
 /* "is_physical_link" is equivalent to S_ISLNK(tree_current_lstat()->st_mode) */
index d357fabcb0cdf242b93ffa55fd2fc6752443b800..2a93419dc86e8d8577b05f4cce93586587827040 100644 (file)
@@ -24,7 +24,7 @@
  */
 
 #include "bsdtar_platform.h"
-__FBSDID("$FreeBSD: src/usr.bin/tar/util.c,v 1.21 2008/11/10 05:04:55 kientzle Exp $");
+__FBSDID("$FreeBSD: src/usr.bin/tar/util.c,v 1.22 2008/11/29 20:06:53 kientzle Exp $");
 
 #ifdef HAVE_SYS_STAT_H
 #include <sys/stat.h>
@@ -46,92 +46,161 @@ __FBSDID("$FreeBSD: src/usr.bin/tar/util.c,v 1.21 2008/11/10 05:04:55 kientzle E
 #ifdef HAVE_STRING_H
 #include <string.h>
 #endif
+#ifdef HAVE_WCTYPE_H
+#include <wctype.h>
+#else
+/* If we don't have wctype, we need to hack up some version of iswprint(). */
+#define iswprint isprint
+#endif
 
 #include "bsdtar.h"
 
 static void    bsdtar_vwarnc(struct bsdtar *, int code,
                    const char *fmt, va_list ap);
+static size_t  bsdtar_expand_char(char *, size_t, char);
 static const char *strip_components(const char *path, int elements);
 
+/* TODO:  Hack up a version of mbtowc for platforms with no wide
+ * character support at all.  I think the following might suffice,
+ * but it needs careful testing.
+ * #if !HAVE_MBTOWC
+ * #define mbtowc(wcp, p, n) ((*wcp = *p), 1)
+ * #endif
+ */
+
 /*
  * Print a string, taking care with any non-printable characters.
+ *
+ * Note that we use a stack-allocated buffer to receive the formatted
+ * string if we can.  This is partly performance (avoiding a call to
+ * malloc()), partly out of expedience (we have to call vsnprintf()
+ * before malloc() anyway to find out how big a buffer we need; we may
+ * as well point that first call at a small local buffer in case it
+ * works), but mostly for safety (so we can use this to print messages
+ * about out-of-memory conditions).
  */
 
 void
 safe_fprintf(FILE *f, const char *fmt, ...)
 {
-       char *buff;
-       char *buff_heap;
-       int buff_length;
+       char fmtbuff_stack[256]; /* Place to format the printf() string. */
+       char outbuff[256]; /* Buffer for outgoing characters. */
+       char *fmtbuff_heap; /* If fmtbuff_stack is too small, we use malloc */
+       char *fmtbuff;  /* Pointer to fmtbuff_stack or fmtbuff_heap. */
+       int fmtbuff_length;
        int length;
        va_list ap;
-       char *p;
+       const char *p;
        unsigned i;
-       char buff_stack[256];
-       char copy_buff[256];
+       wchar_t wc;
+       char try_wc;
 
        /* Use a stack-allocated buffer if we can, for speed and safety. */
-       buff_heap = NULL;
-       buff_length = sizeof(buff_stack);
-       buff = buff_stack;
+       fmtbuff_heap = NULL;
+       fmtbuff_length = sizeof(fmtbuff_stack);
+       fmtbuff = fmtbuff_stack;
 
+       /* Try formatting into the stack buffer. */
        va_start(ap, fmt);
-       length = vsnprintf(buff, buff_length, fmt, ap);
+       length = vsnprintf(fmtbuff, fmtbuff_length, fmt, ap);
        va_end(ap);
-       /* If the result is too large, allocate a buffer on the heap. */
-       if (length >= buff_length) {
-               buff_length = length+1;
-               buff_heap = malloc(buff_length);
-               /* Failsafe: use the truncated string if malloc fails. */
-               if (buff_heap != NULL) {
-                       buff = buff_heap;
+
+       /* If the result was too large, allocate a buffer on the heap. */
+       if (length >= fmtbuff_length) {
+               fmtbuff_length = length+1;
+               fmtbuff_heap = malloc(fmtbuff_length);
+
+               /* Reformat the result into the heap buffer if we can. */
+               if (fmtbuff_heap != NULL) {
+                       fmtbuff = fmtbuff_heap;
                        va_start(ap, fmt);
-                       length = vsnprintf(buff, buff_length, fmt, ap);
+                       length = vsnprintf(fmtbuff, fmtbuff_length, fmt, ap);
                        va_end(ap);
+               } else {
+                       /* Leave fmtbuff pointing to the truncated
+                        * string in fmtbuff_stack. */
+                       length = sizeof(fmtbuff_stack) - 1;
                }
        }
 
+       /* Note: mbrtowc() has a cleaner API, but mbtowc() seems a bit
+        * more portable, so we use that here instead. */
+       mbtowc(NULL, NULL, 0); /* Reset the shift state. */
+
        /* Write data, expanding unprintable characters. */
-       p = buff;
+       p = fmtbuff;
        i = 0;
+       try_wc = 1;
        while (*p != '\0') {
-               unsigned char c = *p++;
-
-               if (isprint(c) && c != '\\')
-                       copy_buff[i++] = c;
-               else {
-                       copy_buff[i++] = '\\';
-                       switch (c) {
-                       case '\a': copy_buff[i++] = 'a'; break;
-                       case '\b': copy_buff[i++] = 'b'; break;
-                       case '\f': copy_buff[i++] = 'f'; break;
-                       case '\n': copy_buff[i++] = 'n'; break;
-#if '\r' != '\n'
-                       /* On some platforms, \n and \r are the same. */
-                       case '\r': copy_buff[i++] = 'r'; break;
-#endif
-                       case '\t': copy_buff[i++] = 't'; break;
-                       case '\v': copy_buff[i++] = 'v'; break;
-                       case '\\': copy_buff[i++] = '\\'; break;
-                       default:
-                               sprintf(copy_buff + i, "%03o", c);
-                               i += 3;
+               int n;
+
+               /* Convert to wide char, test if the wide
+                * char is printable in the current locale. */
+               if (try_wc && (n = mbtowc(&wc, p, length)) != -1) {
+                       length -= n;
+                       if (iswprint(wc) && wc != L'\\') {
+                               /* Printable, copy the bytes through. */
+                               while (n-- > 0)
+                                       outbuff[i++] = *p++;
+                       } else {
+                               /* Not printable, format the bytes. */
+                               while (n-- > 0)
+                                       i += bsdtar_expand_char(
+                                           outbuff, i, *p++);
                        }
+               } else {
+                       /* After any conversion failure, don't bother
+                        * trying to convert the rest. */
+                       i += bsdtar_expand_char(outbuff, i, *p++);
+                       try_wc = 0;
                }
 
-               /* If our temp buffer is full, dump it and keep going. */
-               if (i > (sizeof(copy_buff) - 8)) {
-                       copy_buff[i++] = '\0';
-                       fprintf(f, "%s", copy_buff);
+               /* If our output buffer is full, dump it and keep going. */
+               if (i > (sizeof(outbuff) - 20)) {
+                       outbuff[i++] = '\0';
+                       fprintf(f, "%s", outbuff);
                        i = 0;
                }
        }
-       copy_buff[i++] = '\0';
-       fprintf(f, "%s", copy_buff);
+       outbuff[i++] = '\0';
+       fprintf(f, "%s", outbuff);
+
+       /* If we allocated a heap-based formatting buffer, free it now. */
+       if (fmtbuff_heap != NULL)
+               free(fmtbuff_heap);
+}
+
+/*
+ * Render an arbitrary sequence of bytes into printable ASCII characters.
+ */
+static size_t
+bsdtar_expand_char(char *buff, size_t offset, char c)
+{
+       size_t i = offset;
+
+       if (isprint(c) && c != '\\')
+               buff[i++] = c;
+       else {
+               buff[i++] = '\\';
+               switch (c) {
+               case '\a': buff[i++] = 'a'; break;
+               case '\b': buff[i++] = 'b'; break;
+               case '\f': buff[i++] = 'f'; break;
+               case '\n': buff[i++] = 'n'; break;
+#if '\r' != '\n'
+               /* On some platforms, \n and \r are the same. */
+               case '\r': buff[i++] = 'r'; break;
+#endif
+               case '\t': buff[i++] = 't'; break;
+               case '\v': buff[i++] = 'v'; break;
+               case '\\': buff[i++] = '\\'; break;
+               default:
+                       sprintf(buff + i, "%03o", 0xFF & (int)c);
+                       i += 3;
+               }
+       }
 
-       /* If we allocated a heap-based buffer, free it now. */
-       if (buff_heap != NULL)
-               free(buff_heap);
+       return (i - offset);
 }
 
 static void
index fca62568887bf82b77088a87adba201379c53a40..4d36018afec975c6e326615abe12c2b1ab26e046 100644 (file)
@@ -24,7 +24,7 @@
  */
 
 #include "bsdtar_platform.h"
-__FBSDID("$FreeBSD: src/usr.bin/tar/write.c,v 1.77 2008/09/14 03:49:00 kientzle Exp $");
+__FBSDID("$FreeBSD: src/usr.bin/tar/write.c,v 1.79 2008/11/27 05:49:52 kientzle Exp $");
 
 #ifdef HAVE_SYS_TYPES_H
 #include <sys/types.h>
@@ -655,8 +655,15 @@ write_hierarchy(struct bsdtar *bsdtar, struct archive *a, const char *path)
                const struct stat *st = NULL, *lst = NULL;
                int descend;
 
-               if (tree_ret == TREE_ERROR_DIR)
-                       bsdtar_warnc(bsdtar, errno, "%s: Couldn't visit directory", name);
+               if (tree_ret == TREE_ERROR_FATAL)
+                       bsdtar_errc(bsdtar, 1, tree_errno(tree),
+                           "%s: Unable to continue traversing directory tree",
+                           name);
+               if (tree_ret == TREE_ERROR_DIR) {
+                       bsdtar_warnc(bsdtar, errno,
+                           "%s: Couldn't visit directory", name);
+                       bsdtar->return_value = 1;
+               }
                if (tree_ret != TREE_REGULAR)
                        continue;
                lst = tree_current_lstat(tree);