]> git.ipfire.org Git - thirdparty/libarchive.git/commitdiff
Shar utility by Jaakko Heinonen
authorTim Kientzle <kientzle@gmail.com>
Sun, 9 Nov 2008 19:38:06 +0000 (14:38 -0500)
committerTim Kientzle <kientzle@gmail.com>
Sun, 9 Nov 2008 19:38:06 +0000 (14:38 -0500)
SVN-Revision: 244

contrib/shar/Makefile [new file with mode: 0644]
contrib/shar/shar.1 [new file with mode: 0644]
contrib/shar/shar.c [new file with mode: 0644]
contrib/shar/tree.c [new file with mode: 0644]
contrib/shar/tree.h [new file with mode: 0644]
contrib/shar/tree_config.h [new file with mode: 0644]

diff --git a/contrib/shar/Makefile b/contrib/shar/Makefile
new file mode 100644 (file)
index 0000000..3bd94d4
--- /dev/null
@@ -0,0 +1,14 @@
+# $FreeBSD$
+
+PROG=  shar
+SRCS=  shar.c tree.c
+
+WARNS?=        6
+
+DPADD= ${LIBARCHIVE}
+LDADD= -larchive
+
+LINKS= ${BINDIR}/shar
+MLINKS=        shar.1
+
+.include <bsd.prog.mk>
diff --git a/contrib/shar/shar.1 b/contrib/shar/shar.1
new file mode 100644 (file)
index 0000000..e3152f2
--- /dev/null
@@ -0,0 +1,128 @@
+.\" Copyright (c) 1990, 1993
+.\"    The Regents of the University of California.  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.
+.\" 3. All advertising materials mentioning features or use of this software
+.\"    must display the following acknowledgement:
+.\"    This product includes software developed by the University of
+.\"    California, Berkeley and its contributors.
+.\" 4. Neither the name of the University nor the names of its contributors
+.\"    may be used to endorse or promote products derived from this software
+.\"    without specific prior written permission.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``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 REGENTS OR CONTRIBUTORS 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.
+.\"
+.\"     @(#)shar.1     8.1 (Berkeley) 6/6/93
+.\" $FreeBSD$
+.\"
+.Dd April 17, 2008
+.Dt SHAR 1
+.Os
+.Sh NAME
+.Nm shar
+.Nd create a shell archive of files
+.Sh SYNOPSIS
+.Nm
+.Op Fl br
+.Op Fl o Ar archive-file
+.Ar
+.Sh DESCRIPTION
+The
+.Nm
+command writes a
+.Xr sh 1
+shell script which will recreate the file hierarchy specified by the command
+line operands.
+.Pp
+The
+.Nm
+command is normally used for distributing files by
+.Xr ftp 1
+or
+.Xr mail 1 .
+.Pp
+The following options are available:
+.Bl -tag -width indent
+.It Fl b
+Use an alternative binary format.  Content of files will be uuencoded.
+This option should be used to archive binary files correctly.
+In this mode also file permissions will be stored to the archive.
+uudecode(1) is needed to extract archives created with this option.
+.It Fl o Ar archive-file
+Redirect output to
+.Ar archive-file .
+.It Fl r
+If
+.Ar file
+given on command line is a directory the entire subtree will be archived.
+Symbolic links given on command line are followed.  Other symbolic links will
+be archived as such.
+.El
+.Sh EXAMPLES
+To create a shell archive of the program
+.Xr ls 1
+and mail it to Rick:
+.Bd -literal -offset indent
+cd ls
+shar -r . \&| mail -s "ls source" rick
+.Ed
+.Pp
+To recreate the program directory:
+.Bd -literal -offset indent
+mkdir ls
+cd ls
+\&...
+<delete header lines and examine mailed archive>
+\&...
+sh archive
+.Ed
+.Sh SEE ALSO
+.Xr compress 1 ,
+.Xr mail 1 ,
+.Xr tar 1 ,
+.Xr uuencode 1 ,
+.Xr uuencode 5
+.Sh HISTORY
+The
+.Nm
+command appeared in
+.Bx 4.4 .
+This is a re-implementation based on the libarchive(3) library.
+.Sh BUGS
+The
+.Nm
+command makes no provisions for hard links.
+.Pp
+Files containing magic characters or files without a newline ('\\n') as the
+last character are not handled correctly with the default format.  Use the -b
+option for binary files.
+.Pp
+It is easy to insert trojan horses into
+.Nm
+files.
+It is strongly recommended that all shell archive files be examined
+before running them through
+.Xr sh 1 .
+Archives produced using this implementation of
+.Nm
+may be easily examined with the command:
+.Bd -literal -offset indent
+egrep -v '^[X#]' shar.file
+.Ed
diff --git a/contrib/shar/shar.c b/contrib/shar/shar.c
new file mode 100644 (file)
index 0000000..12c8425
--- /dev/null
@@ -0,0 +1,314 @@
+/*-
+ * Copyright (c) 2008 Jaakko Heinonen
+ * 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
+ *    in this position and unchanged.
+ * 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 <sys/cdefs.h>
+#ifdef __FBSDID
+__FBSDID("$FreeBSD$");
+#endif
+
+#include <sys/stat.h>
+#include <sys/types.h>
+
+#include <archive.h>
+#include <archive_entry.h>
+#include <assert.h>
+#include <err.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sysexits.h>
+#include <unistd.h>
+
+#include "tree.h"
+
+/* command line options */
+static int     b_opt;  /* use alternative shar binary format */
+static int     r_opt;  /* recurse into subdirectories */
+static char    *o_arg; /* output file name */
+
+static void
+usage(void)
+{
+       fprintf(stderr, "Usage: shar [-br] [-o filename] file ...\n");
+       exit(EX_USAGE);
+}
+
+/*
+ * Initialize archive structure and create a shar archive.
+ */
+static struct archive *
+shar_create(void)
+{
+       struct archive *a;
+
+       if ((a = archive_write_new()) == NULL)
+               errx(EXIT_FAILURE, "%s", archive_error_string(a));
+
+       if (b_opt)
+               archive_write_set_format_shar_dump(a);
+       else
+               archive_write_set_format_shar(a);
+       archive_write_set_compression_none(a);
+
+       if (archive_write_open_filename(a, o_arg) != ARCHIVE_OK)
+               errx(EX_CANTCREAT, "%s", archive_error_string(a));
+
+       return (a);
+}
+
+/* buffer for file data */
+static char buffer[32768];
+
+/*
+ * Write file data to an archive entry.
+ */
+static int
+shar_write_entry_data(struct archive *a, const int fd)
+{
+       ssize_t bytes_read, bytes_written;
+
+       assert(a != NULL);
+       assert(fd >= 0);
+
+       bytes_read = read(fd, buffer, sizeof(buffer));
+       while (bytes_read != 0) {
+               if (bytes_read < 0) {
+                       archive_set_error(a, errno, "Read failed");
+                       return (ARCHIVE_WARN);
+               }
+               bytes_written = archive_write_data(a, buffer, bytes_read);
+               if (bytes_written < 0)
+                       return (ARCHIVE_WARN);
+               bytes_read = read(fd, buffer, sizeof(buffer));
+       }
+
+       return (ARCHIVE_OK);
+}
+
+/*
+ * Write a file to the archive. We have special handling for symbolic links.
+ */
+static int
+shar_write_entry(struct archive *a, const char *pathname, const char *accpath,
+    const struct stat *st)
+{
+       struct archive_entry *entry;
+       int fd = -1;
+       int ret = ARCHIVE_OK;
+
+       assert(a != NULL);
+       assert(pathname != NULL);
+       assert(accpath != NULL);
+       assert(st != NULL);
+
+       entry = archive_entry_new();
+
+       if (S_ISREG(st->st_mode) && st->st_size > 0) {
+               /* regular file */
+               if ((fd = open(accpath, O_RDONLY)) == -1) {
+                       warn("%s", accpath);
+                       ret = ARCHIVE_WARN;
+                       goto out;
+               }
+       } else if (S_ISLNK(st->st_mode)) {
+               /* symbolic link */
+               char lnkbuff[PATH_MAX + 1];
+               int lnklen;
+               if ((lnklen = readlink(accpath, lnkbuff, PATH_MAX)) == -1) {
+                       warn("%s", accpath);
+                       ret = ARCHIVE_WARN;
+                       goto out;
+               }
+               lnkbuff[lnklen] = '\0';
+               archive_entry_set_symlink(entry, lnkbuff);
+       }
+       archive_entry_copy_stat(entry, st);
+       archive_entry_set_pathname(entry, pathname);
+       if (!S_ISREG(st->st_mode) || st->st_size == 0)
+               archive_entry_set_size(entry, 0);
+       if (archive_write_header(a, entry) != ARCHIVE_OK) {
+               warnx("%s: %s", pathname, archive_error_string(a));
+               ret = ARCHIVE_WARN;
+               goto out;
+       }
+       if (fd >= 0) {
+               if ((ret = shar_write_entry_data(a, fd)) != ARCHIVE_OK)
+                       warnx("%s: %s", accpath, archive_error_string(a));
+       }
+out:
+       archive_entry_free(entry);
+       if (fd >= 0)
+               close(fd);
+
+       return (ret);
+}
+
+/*
+ * Write singe path to the archive. The path can be a regular file, directory
+ * or device. Symbolic links are followed.
+ */
+static int
+shar_write_path(struct archive *a, const char *pathname)
+{
+       struct stat st;
+
+       assert(a != NULL);
+       assert(pathname != NULL);
+
+       if ((stat(pathname, &st)) == -1) {
+               warn("%s", pathname);
+               return (ARCHIVE_WARN);
+       }
+
+       return (shar_write_entry(a, pathname, pathname, &st));
+}
+
+/*
+ * Write tree to the archive. If pathname is a symbolic link it will be
+ * followed. Other symbolic links are stored as such to the archive.
+ */
+static int
+shar_write_tree(struct archive *a, const char *pathname)
+{
+       struct tree *t;
+       const struct stat *lst, *st;
+       int error = 0;
+       int tree_ret;
+       int first;
+
+       assert(a != NULL);
+       assert(pathname != NULL);
+
+       t = tree_open(pathname);
+       for (first = 1; (tree_ret = tree_next(t)); first = 0) {
+               if (tree_ret == TREE_ERROR_DIR) {
+                       warnx("%s: %s", tree_current_path(t),
+                           strerror(tree_errno(t)));
+                       error = 1;
+                       continue;
+               } else if (tree_ret != TREE_REGULAR)
+                       continue;
+               if ((lst = tree_current_lstat(t)) == NULL) {
+                       warn("%s", tree_current_path(t));
+                       error = 1;
+                       continue;
+               }
+               /*
+                * If the symlink was given on command line then
+                * follow it rather than write it as symlink.
+                */
+               if (first && S_ISLNK(lst->st_mode)) {
+                       if ((st = tree_current_stat(t)) == NULL) {
+                               warn("%s", tree_current_path(t));
+                               error = 1;
+                               continue;
+                       }
+               } else
+                       st = lst;
+
+               if (shar_write_entry(a, tree_current_path(t),
+                   tree_current_access_path(t), st) != ARCHIVE_OK)
+                       error = 1;
+
+               tree_descend(t);
+       }
+
+       tree_close(t);
+
+       return ((error != 0) ? ARCHIVE_WARN : ARCHIVE_OK);
+}
+
+/*
+ * Create a shar archive and write files/trees into it.
+ */
+static int
+shar_write(char **fn, size_t nfn)
+{
+       struct archive *a;
+       size_t i;
+       int error = 0;
+
+       assert(fn != NULL);
+       assert(nfn > 0);
+
+       a = shar_create();
+
+       for (i = 0; i < nfn; i++) {
+               if (r_opt) {
+                       if (shar_write_tree(a, fn[i]) !=  ARCHIVE_OK)
+                               error = 1;
+               } else {
+                       if (shar_write_path(a, fn[i]) != ARCHIVE_OK)
+                               error = 1;
+               }
+       }
+
+       if (archive_write_finish(a) != ARCHIVE_OK)
+               errx(EXIT_FAILURE, "%s", archive_error_string(a));
+
+       if (error != 0)
+               warnx("Error exit delayed from previous errors.");
+
+       return (error);
+}
+
+int
+main(int argc, char **argv)
+{
+       int opt;
+
+       while ((opt = getopt(argc, argv, "bro:")) != -1) {
+               switch (opt) {
+               case 'b':
+                       b_opt = 1;
+                       break;
+               case 'o':
+                       o_arg = optarg;
+                       break;
+               case 'r':
+                       r_opt = 1;
+                       break;
+               default:
+                       usage();
+                       /* NOTREACHED */
+               }
+       }
+       argc -= optind;
+       argv += optind;
+
+       if(argc < 1)
+               usage();
+
+       if (shar_write(argv, argc) != 0)
+               exit(EXIT_FAILURE);
+       else
+               exit(EXIT_SUCCESS);
+       /* NOTREACHED */
+}
+
diff --git a/contrib/shar/tree.c b/contrib/shar/tree.c
new file mode 100644 (file)
index 0000000..d5a04ab
--- /dev/null
@@ -0,0 +1,542 @@
+/*-
+ * 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.
+ */
+
+/*-
+ * This is a new directory-walking system that addresses a number
+ * of problems I've had with fts(3).  In particular, it has no
+ * pathname-length limits (other than the size of 'int'), handles
+ * deep logical traversals, uses considerably less memory, and has
+ * an opaque interface (easier to modify in the future).
+ *
+ * Internally, it keeps a single list of "tree_entry" items that
+ * represent filesystem objects that require further attention.
+ * Non-directories are not kept in memory: they are pulled from
+ * readdir(), returned to the client, then freed as soon as possible.
+ * Any directory entry to be traversed gets pushed onto the stack.
+ *
+ * There is surprisingly little information that needs to be kept for
+ * each item on the stack.  Just the name, depth (represented here as the
+ * string length of the parent directory's pathname), and some markers
+ * indicating how to get back to the parent (via chdir("..") for a
+ * regular dir or via fchdir(2) for a symlink).
+ */
+#include "tree_config.h"
+__FBSDID("$FreeBSD$");
+
+#ifdef HAVE_SYS_STAT_H
+#include <sys/stat.h>
+#endif
+#ifdef HAVE_DIRENT_H
+#include <dirent.h>
+#endif
+#ifdef HAVE_ERRNO_H
+#include <errno.h>
+#endif
+#ifdef HAVE_FCNTL_H
+#include <fcntl.h>
+#endif
+#ifdef HAVE_STDLIB_H
+#include <stdlib.h>
+#endif
+#ifdef HAVE_STRING_H
+#include <string.h>
+#endif
+#ifdef HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+
+#include "tree.h"
+
+/*
+ * TODO:
+ *    1) Loop checking.
+ *    3) Arbitrary logical traversals by closing/reopening intermediate fds.
+ */
+
+struct tree_entry {
+       struct tree_entry *next;
+       struct tree_entry *parent;
+       char *name;
+       size_t dirname_length;
+       dev_t dev;
+       ino_t ino;
+       int fd;
+       int flags;
+};
+
+/* Definitions for tree_entry.flags bitmap. */
+#define        isDir 1 /* This entry is a regular directory. */
+#define        isDirLink 2 /* This entry is a symbolic link to a directory. */
+#define        needsPreVisit 4 /* This entry needs to be previsited. */
+#define        needsPostVisit 8 /* This entry needs to be postvisited. */
+
+/*
+ * Local data for this package.
+ */
+struct tree {
+       struct tree_entry       *stack;
+       struct tree_entry       *current;
+       DIR     *d;
+       int      initialDirFd;
+       int      flags;
+       int      visit_type;
+       int      tree_errno; /* Error code from last failed operation. */
+
+       char    *buff;
+       const char      *basename;
+       size_t   buff_length;
+       size_t   path_length;
+       size_t   dirname_length;
+
+       int      depth;
+       int      openCount;
+       int      maxOpenCount;
+
+       struct stat     lst;
+       struct stat     st;
+};
+
+/* Definitions for tree.flags bitmap. */
+#define needsReturn 8  /* Marks first entry as not having been returned yet. */
+#define hasStat 16  /* The st entry is set. */
+#define hasLstat 32 /* The lst entry is set. */
+
+
+#ifdef HAVE_DIRENT_D_NAMLEN
+/* BSD extension; avoids need for a strlen() call. */
+#define D_NAMELEN(dp)  (dp)->d_namlen
+#else
+#define D_NAMELEN(dp)  (strlen((dp)->d_name))
+#endif
+
+#if 0
+#include <stdio.h>
+void
+tree_dump(struct tree *t, FILE *out)
+{
+       struct tree_entry *te;
+
+       fprintf(out, "\tdepth: %d\n", t->depth);
+       fprintf(out, "\tbuff: %s\n", t->buff);
+       fprintf(out, "\tpwd: "); fflush(stdout); system("pwd");
+       fprintf(out, "\taccess: %s\n", t->basename);
+       fprintf(out, "\tstack:\n");
+       for (te = t->stack; te != NULL; te = te->next) {
+               fprintf(out, "\t\tte->name: %s%s%s\n", te->name,
+                   te->flags & needsPreVisit ? "" : " *",
+                   t->current == te ? " (current)" : "");
+       }
+}
+#endif
+
+/*
+ * Add a directory path to the current stack.
+ */
+static void
+tree_push(struct tree *t, const char *path)
+{
+       struct tree_entry *te;
+
+       te = malloc(sizeof(*te));
+       memset(te, 0, sizeof(*te));
+       te->next = t->stack;
+       t->stack = te;
+       te->fd = -1;
+       te->name = strdup(path);
+       te->flags = needsPreVisit | needsPostVisit;
+       te->dirname_length = t->dirname_length;
+}
+
+/*
+ * Append a name to the current path.
+ */
+static void
+tree_append(struct tree *t, const char *name, size_t name_length)
+{
+       char *p;
+
+       if (t->buff != NULL)
+               t->buff[t->dirname_length] = '\0';
+       /* Strip trailing '/' from name, unless entire name is "/". */
+       while (name_length > 1 && name[name_length - 1] == '/')
+               name_length--;
+
+       /* Resize pathname buffer as needed. */
+       while (name_length + 1 + t->dirname_length >= t->buff_length) {
+               t->buff_length *= 2;
+               if (t->buff_length < 1024)
+                       t->buff_length = 1024;
+               t->buff = realloc(t->buff, t->buff_length);
+       }
+       p = t->buff + t->dirname_length;
+       t->path_length = t->dirname_length + name_length;
+       /* Add a separating '/' if it's needed. */
+       if (t->dirname_length > 0 && p[-1] != '/') {
+               *p++ = '/';
+               t->path_length ++;
+       }
+       strncpy(p, name, name_length);
+       p[name_length] = '\0';
+       t->basename = p;
+}
+
+/*
+ * Open a directory tree for traversal.
+ */
+struct tree *
+tree_open(const char *path)
+{
+       struct tree *t;
+
+       t = malloc(sizeof(*t));
+       memset(t, 0, sizeof(*t));
+       tree_append(t, path, strlen(path));
+       t->initialDirFd = open(".", O_RDONLY);
+       /*
+        * During most of the traversal, items are set up and then
+        * returned immediately from tree_next().  That doesn't work
+        * for the very first entry, so we set a flag for this special
+        * case.
+        */
+       t->flags = needsReturn;
+       return (t);
+}
+
+/*
+ * We've finished a directory; ascend back to the parent.
+ */
+static void
+tree_ascend(struct tree *t)
+{
+       struct tree_entry *te;
+
+       te = t->stack;
+       t->depth--;
+       if (te->flags & isDirLink) {
+               fchdir(te->fd);
+               close(te->fd);
+               t->openCount--;
+       } else {
+               chdir("..");
+       }
+}
+
+/*
+ * Pop the working stack.
+ */
+static void
+tree_pop(struct tree *t)
+{
+       struct tree_entry *te;
+
+       t->buff[t->dirname_length] = '\0';
+       if (t->stack == t->current && t->current != NULL)
+               t->current = t->current->parent;
+       te = t->stack;
+       t->stack = te->next;
+       t->dirname_length = te->dirname_length;
+       t->basename = t->buff + t->dirname_length;
+       /* Special case: starting dir doesn't skip leading '/'. */
+       if (t->dirname_length > 0)
+               t->basename++;
+       free(te->name);
+       free(te);
+}
+
+/*
+ * Get the next item in the tree traversal.
+ */
+int
+tree_next(struct tree *t)
+{
+       struct dirent *de = NULL;
+
+       /* Handle the startup case by returning the initial entry. */
+       if (t->flags & needsReturn) {
+               t->flags &= ~needsReturn;
+               return (t->visit_type = TREE_REGULAR);
+       }
+
+       while (t->stack != NULL) {
+               /* If there's an open dir, get the next entry from there. */
+               while (t->d != NULL) {
+                       de = readdir(t->d);
+                       if (de == NULL) {
+                               closedir(t->d);
+                               t->d = NULL;
+                       } else if (de->d_name[0] == '.'
+                           && de->d_name[1] == '\0') {
+                               /* Skip '.' */
+                       } else if (de->d_name[0] == '.'
+                           && de->d_name[1] == '.'
+                           && de->d_name[2] == '\0') {
+                               /* Skip '..' */
+                       } else {
+                               /*
+                                * Append the path to the current path
+                                * and return it.
+                                */
+                               tree_append(t, de->d_name, D_NAMELEN(de));
+                               t->flags &= ~hasLstat;
+                               t->flags &= ~hasStat;
+                               return (t->visit_type = TREE_REGULAR);
+                       }
+               }
+
+               /* If the current dir needs to be visited, set it up. */
+               if (t->stack->flags & needsPreVisit) {
+                       t->current = t->stack;
+                       tree_append(t, t->stack->name, strlen(t->stack->name));
+                       t->stack->flags &= ~needsPreVisit;
+                       /* If it is a link, set up fd for the ascent. */
+                       if (t->stack->flags & isDirLink) {
+                               t->stack->fd = open(".", O_RDONLY);
+                               t->openCount++;
+                               if (t->openCount > t->maxOpenCount)
+                                       t->maxOpenCount = t->openCount;
+                       }
+                       t->dirname_length = t->path_length;
+                       if (chdir(t->stack->name) != 0) {
+                               /* chdir() failed; return error */
+                               tree_pop(t);
+                               t->tree_errno = errno;
+                               return (t->visit_type = TREE_ERROR_DIR);
+                       }
+                       t->depth++;
+                       t->d = opendir(".");
+                       if (t->d == NULL) {
+                               tree_ascend(t); /* Undo "chdir" */
+                               tree_pop(t);
+                               t->tree_errno = errno;
+                               return (t->visit_type = TREE_ERROR_DIR);
+                       }
+                       t->flags &= ~hasLstat;
+                       t->flags &= ~hasStat;
+                       t->basename = ".";
+                       return (t->visit_type = TREE_POSTDESCENT);
+               }
+
+               /* We've done everything necessary for the top stack entry. */
+               if (t->stack->flags & needsPostVisit) {
+                       tree_ascend(t);
+                       tree_pop(t);
+                       t->flags &= ~hasLstat;
+                       t->flags &= ~hasStat;
+                       return (t->visit_type = TREE_POSTASCENT);
+               }
+       }
+       return (t->visit_type = 0);
+}
+
+/*
+ * Return error code.
+ */
+int
+tree_errno(struct tree *t)
+{
+       return (t->tree_errno);
+}
+
+/*
+ * Called by the client to mark the directory just returned from
+ * tree_next() as needing to be visited.
+ */
+void
+tree_descend(struct tree *t)
+{
+       if (t->visit_type != TREE_REGULAR)
+               return;
+
+       if (tree_current_is_physical_dir(t)) {
+               tree_push(t, t->basename);
+               t->stack->flags |= isDir;
+       } else if (tree_current_is_dir(t)) {
+               tree_push(t, t->basename);
+               t->stack->flags |= isDirLink;
+       }
+}
+
+/*
+ * Get the stat() data for the entry just returned from tree_next().
+ */
+const struct stat *
+tree_current_stat(struct tree *t)
+{
+       if (!(t->flags & hasStat)) {
+               if (stat(t->basename, &t->st) != 0)
+                       return NULL;
+               t->flags |= hasStat;
+       }
+       return (&t->st);
+}
+
+/*
+ * Get the lstat() data for the entry just returned from tree_next().
+ */
+const struct stat *
+tree_current_lstat(struct tree *t)
+{
+       if (!(t->flags & hasLstat)) {
+               if (lstat(t->basename, &t->lst) != 0)
+                       return NULL;
+               t->flags |= hasLstat;
+       }
+       return (&t->lst);
+}
+
+/*
+ * Test whether current entry is a dir or link to a dir.
+ */
+int
+tree_current_is_dir(struct tree *t)
+{
+       const struct stat *st;
+
+       /*
+        * If we already have lstat() info, then try some
+        * cheap tests to determine if this is a dir.
+        */
+       if (t->flags & hasLstat) {
+               /* If lstat() says it's a dir, it must be a dir. */
+               if (S_ISDIR(tree_current_lstat(t)->st_mode))
+                       return 1;
+               /* Not a dir; might be a link to a dir. */
+               /* If it's not a link, then it's not a link to a dir. */
+               if (!S_ISLNK(tree_current_lstat(t)->st_mode))
+                       return 0;
+               /*
+                * It's a link, but we don't know what it's a link to,
+                * so we'll have to use stat().
+                */
+       }
+
+       st = tree_current_stat(t);
+       /* If we can't stat it, it's not a dir. */
+       if (st == NULL)
+               return 0;
+       /* Use the definitive test.  Hopefully this is cached. */
+       return (S_ISDIR(st->st_mode));
+}
+
+/*
+ * Test whether current entry is a physical directory.  Usually, we
+ * already have at least one of stat() or lstat() in memory, so we
+ * use tricks to try to avoid an extra trip to the disk.
+ */
+int
+tree_current_is_physical_dir(struct tree *t)
+{
+       const struct stat *st;
+
+       /*
+        * If stat() says it isn't a dir, then it's not a dir.
+        * If stat() data is cached, this check is free, so do it first.
+        */
+       if ((t->flags & hasStat)
+           && (!S_ISDIR(tree_current_stat(t)->st_mode)))
+               return 0;
+
+       /*
+        * Either stat() said it was a dir (in which case, we have
+        * to determine whether it's really a link to a dir) or
+        * stat() info wasn't available.  So we use lstat(), which
+        * hopefully is already cached.
+        */
+
+       st = tree_current_lstat(t);
+       /* If we can't stat it, it's not a dir. */
+       if (st == NULL)
+               return 0;
+       /* Use the definitive test.  Hopefully this is cached. */
+       return (S_ISDIR(st->st_mode));
+}
+
+/*
+ * Test whether current entry is a symbolic link.
+ */
+int
+tree_current_is_physical_link(struct tree *t)
+{
+       const struct stat *st = tree_current_lstat(t);
+       if (st == NULL)
+               return 0;
+       return (S_ISLNK(st->st_mode));
+}
+
+/*
+ * Return the access path for the entry just returned from tree_next().
+ */
+const char *
+tree_current_access_path(struct tree *t)
+{
+       return (t->basename);
+}
+
+/*
+ * Return the full path for the entry just returned from tree_next().
+ */
+const char *
+tree_current_path(struct tree *t)
+{
+       return (t->buff);
+}
+
+/*
+ * Return the length of the path for the entry just returned from tree_next().
+ */
+size_t
+tree_current_pathlen(struct tree *t)
+{
+       return (t->path_length);
+}
+
+/*
+ * Return the nesting depth of the entry just returned from tree_next().
+ */
+int
+tree_current_depth(struct tree *t)
+{
+       return (t->depth);
+}
+
+/*
+ * Terminate the traversal and release any resources.
+ */
+void
+tree_close(struct tree *t)
+{
+       /* Release anything remaining in the stack. */
+       while (t->stack != NULL)
+               tree_pop(t);
+       if (t->buff)
+               free(t->buff);
+       /* chdir() back to where we started. */
+       if (t->initialDirFd >= 0) {
+               fchdir(t->initialDirFd);
+               close(t->initialDirFd);
+               t->initialDirFd = -1;
+       }
+       free(t);
+}
diff --git a/contrib/shar/tree.h b/contrib/shar/tree.h
new file mode 100644 (file)
index 0000000..ff38f53
--- /dev/null
@@ -0,0 +1,115 @@
+/*-
+ * 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.
+ *
+ * $FreeBSD$
+ */
+
+/*-
+ * A set of routines for traversing directory trees.
+ * Similar in concept to the fts library, but with a few
+ * important differences:
+ *    * Uses less memory.  In particular, fts stores an entire directory
+ *      in memory at a time.  This package only keeps enough subdirectory
+ *      information in memory to track the traversal.  Information
+ *      about non-directories is discarded as soon as possible.
+ *    * 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.
+ *    * Supports deep physical traversals "out of the box."
+ *      Due to the memory optimizations above, there's no need to
+ *      limit dir names to 32k.
+ */
+
+#include <sys/stat.h>
+#include <stdio.h>
+
+struct tree;
+
+/* Initiate/terminate a tree traversal. */
+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_ERROR is returned if the descent failed (because the
+ * directory couldn't be opened, for instance).  This is returned
+ * instead of TREE_PREVISIT/TREE_POSTVISIT.
+ */
+#define        TREE_REGULAR    1
+#define        TREE_POSTDESCENT        2
+#define        TREE_POSTASCENT 3
+#define        TREE_ERROR_DIR  -1
+int tree_next(struct tree *);
+
+int tree_errno(struct tree *);
+
+/*
+ * Request that current entry be visited.  If you invoke it on every
+ * directory, you'll get a physical traversal.  This is ignored if the
+ * current entry isn't a directory or a link to a directory.  So, if
+ * you invoke this on every returned path, you'll get a full logical
+ * traversal.
+ */
+void tree_descend(struct tree *);
+
+/*
+ * Return information about the current entry.
+ */
+
+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.
+ * Because tree does use chdir extensively, the access path is
+ * almost never the same as the full current path.
+ */
+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
+ * results, you should take advantage of it here if you need it rather
+ * than make a redundant stat() or lstat() call of your own.
+ */
+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(). */
+/* "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) */
+int tree_current_is_physical_link(struct tree *);
+/* "is_dir" is equivalent to S_ISDIR(tree_current_stat()->st_mode) */
+int tree_current_is_dir(struct tree *);
+
+/* For testing/debugging: Dump the internal status to the given filehandle. */
+void tree_dump(struct tree *, FILE *);
diff --git a/contrib/shar/tree_config.h b/contrib/shar/tree_config.h
new file mode 100644 (file)
index 0000000..8dfd90b
--- /dev/null
@@ -0,0 +1,78 @@
+/*-
+ * 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.
+ *
+ * $FreeBSD$
+ */
+#ifndef TREE_CONFIG_H_INCLUDED
+#define        TREE_CONFIG_H_INCLUDED
+
+#if defined(PLATFORM_CONFIG_H)
+/*
+ * Use hand-built config.h in environments that need it.
+ */
+#include PLATFORM_CONFIG_H
+#elif defined(HAVE_CONFIG_H)
+/*
+ * Most POSIX platforms use the 'configure' script to build config.h
+ */
+#include "../config.h"
+#elif defined(__FreeBSD__)
+/*
+ * Built-in definitions for FreeBSD.
+ */
+#define        HAVE_DIRENT_D_NAMLEN 1
+#define        HAVE_DIRENT_H 1
+#define        HAVE_ERRNO_H 1
+#define        HAVE_FCNTL_H 1
+#define        HAVE_LIBARCHIVE 1
+#define        HAVE_STDLIB_H 1
+#define        HAVE_STRING_H 1
+#define        HAVE_SYS_STAT_H 1
+#define        HAVE_UNISTD_H 1
+#else
+/*
+ * Warn if there's no platform configuration.
+ */
+#error Oops: No config.h and no built-in configuration in bsdtar_platform.h.
+#endif /* !HAVE_CONFIG_H */
+
+/* No non-FreeBSD platform will have __FBSDID, so just define it here. */
+#ifdef __FreeBSD__
+#include <sys/cdefs.h>  /* For __FBSDID */
+#else
+/* Just leaving this macro replacement empty leads to a dangling semicolon. */
+#define        __FBSDID(a)     struct _undefined_hack
+#endif
+
+#ifdef HAVE_LIBARCHIVE
+/* If we're using the platform libarchive, include system headers. */
+#include <archive.h>
+#include <archive_entry.h>
+#else
+/* Otherwise, include user headers. */
+#include "archive.h"
+#include "archive_entry.h"
+#endif
+
+#endif /* !TREE_CONFIG_H_INCLUDED */