From owner-svn-src-stable-8@FreeBSD.ORG Tue May 4 19:18:01 2010 Return-Path: Delivered-To: svn-src-stable-8@freebsd.org Received: from mx1.freebsd.org (mx1.freebsd.org [69.147.83.52]) by hub.freebsd.org (Postfix) with ESMTP id 0CCE61065670; Tue, 4 May 2010 19:18:01 +0000 (UTC) (envelope-from delphij@FreeBSD.org) Received: from svn.freebsd.org (svn.freebsd.org [69.147.83.44]) by mx1.freebsd.org (Postfix) with ESMTP id EE4458FC25; Tue, 4 May 2010 19:18:00 +0000 (UTC) Received: from svn.freebsd.org (localhost [127.0.0.1]) by svn.freebsd.org (8.14.3/8.14.3) with ESMTP id o44JI0tS009326; Tue, 4 May 2010 19:18:00 GMT (envelope-from delphij@svn.freebsd.org) Received: (from delphij@localhost) by svn.freebsd.org (8.14.3/8.14.3/Submit) id o44JI0xh009322; Tue, 4 May 2010 19:18:00 GMT (envelope-from delphij@svn.freebsd.org) Message-Id: <201005041918.o44JI0xh009322@svn.freebsd.org> From: Xin LI Date: Tue, 4 May 2010 19:18:00 +0000 (UTC) To: src-committers@freebsd.org, svn-src-all@freebsd.org, svn-src-stable@freebsd.org, svn-src-stable-8@freebsd.org X-SVN-Group: stable-8 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Cc: Subject: svn commit: r207629 - stable/8/usr.bin/unzip X-BeenThere: svn-src-stable-8@freebsd.org X-Mailman-Version: 2.1.5 Precedence: list List-Id: SVN commit messages for only the 8-stable src tree List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , X-List-Received-Date: Tue, 04 May 2010 19:18:01 -0000 Author: delphij Date: Tue May 4 19:18:00 2010 New Revision: 207629 URL: http://svn.freebsd.org/changeset/base/207629 Log: MFC r196981, r200844, r201630, r203977, r203978, r204352: r196981 (rdivacky): Add C/c/f/p/v switches plus a bunch of minor fixes and cleanups. Obtained from: NetBSD r200844 (jh): Don't print the archive name with -p and -q options. PR: bin/141280 r201630 (kientzle): When restoring files, use the mode for the mode. Thanks to: Jun Kuriyama for pointing this out r203977 (gavin): Implement the rename query, for when a file with the same name as the one about to be extracted already exists. The question, and interpretation of the response is deliberately compatible with Info-Zip. This change was originally obtained from NetBSD, but has three changes: - better compatibility with Info-Zip in the handling of ^D - Use getdelim() rather than getline() - bug fix: != changed to == in the "file rename" code I suspect the latter is also a bug in NetBSD, but I can't easily confirm this. PR: bin/143307 Reviewed by: rdivacky (change to unzip.c only) Obtained from: NetBSD src/usr.bin/unzip/unzip.c 1.8 r203978 (gavin): Bump .Dd for r203977 r204352 (ru): Fixed static linkage. == Requested by: Alex Kozlov Modified: stable/8/usr.bin/unzip/Makefile stable/8/usr.bin/unzip/unzip.1 stable/8/usr.bin/unzip/unzip.c Directory Properties: stable/8/usr.bin/unzip/ (props changed) Modified: stable/8/usr.bin/unzip/Makefile ============================================================================== --- stable/8/usr.bin/unzip/Makefile Tue May 4 19:04:51 2010 (r207628) +++ stable/8/usr.bin/unzip/Makefile Tue May 4 19:18:00 2010 (r207629) @@ -3,7 +3,7 @@ PROG = unzip WARNS ?= 6 CSTD = c99 -DPADD = ${LIBARCHIVE} -LDADD = -larchive +DPADD = ${LIBARCHIVE} ${LIBZ} +LDADD = -larchive -lz .include Modified: stable/8/usr.bin/unzip/unzip.1 ============================================================================== --- stable/8/usr.bin/unzip/unzip.1 Tue May 4 19:04:51 2010 (r207628) +++ stable/8/usr.bin/unzip/unzip.1 Tue May 4 19:18:00 2010 (r207629) @@ -25,7 +25,7 @@ .\" .\" $FreeBSD$ .\" -.Dd June 30, 2008 +.Dd February 16, 2010 .Dt UNZIP 1 .Os .Sh NAME @@ -33,7 +33,7 @@ .Nd extract files from a ZIP archive .Sh SYNOPSIS .Nm -.Op Fl ajLlnoqtu +.Op Fl aCcfjLlnopqtuv .Op Fl d Ar dir .Ar zipfile .Sh DESCRIPTION @@ -44,9 +44,22 @@ The following options are available: .It Fl a When extracting a text file, convert DOS-style line endings to Unix-style line endings. +.It Fl C +Match file names case-insensitively. +.It Fl c +Extract to stdout/screen. +When extracting files from the zipfile, they are written to stdout. +This is similar to +.Fl p , +but doesn't suppress normal output. .It Fl d Ar dir Extract files into the specified directory rather than the current directory. +.It Fl f +Update existing. +Extract only files from the zipfile if a file with the same name +already exists on disk and is older than the former. +Otherwise, the file is silently skipped. .It Fl j Ignore directories stored in the zipfile; instead, extract all files directly into the extraction directory. @@ -56,13 +69,19 @@ Convert the names of the extracted files List, rather than extract, the contents of the zipfile. .It Fl n No overwrite. -When extacting a file from the zipfile, if a file with the same name +When extracting a file from the zipfile, if a file with the same name already exists on disk, the file is silently skipped. .It Fl o Overwrite. -When extacting a file from the zipfile, if a file with the same name +When extracting a file from the zipfile, if a file with the same name already exists on disk, the existing file is replaced with the file from the zipfile. +.It Fl p +Extract to stdout. +When extracting files from the zipfile, they are written to stdout. +The normal output is suppressed as if +.Fl q +was specified. .It Fl q Quiet: print less information while extracting. .It Fl t @@ -70,15 +89,25 @@ Test: do not extract anything, but verif in the archive. .It Fl u Update. -When extacting a file from the zipfile, if a file with the same name +When extracting a file from the zipfile, if a file with the same name already exists on disk, the existing file is replaced with the file from the zipfile if and only if the latter is newer than the former. Otherwise, the file is silently skipped. +.It Fl v +List verbosely, rather than extract, the contents of the zipfile. +This differs from +.Fl l +by using the long listing. +Note that most of the data is currently fake and does not reflect the +content of the archive. +.It Fl x Ar pattern +Exclude files matching the pattern +.Ar pattern . .El .Pp Note that only one of .Fl n , -.Fl o +.Fl o , and .Fl u may be specified. @@ -129,17 +158,6 @@ utility is only able to process ZIP arch Depending on the installed version of .Xr libarchive , this may or may not include self-extracting archives. -.Sh BUGS -The -.Nm -utility currently does not support asking the user whether to -overwrite or skip a file that already exists on disk. -To be on the safe side, it will fail if it encounters a file that -already exists and neither the -.Fl n -nor the -.Fl o -command line option was specified. .Sh SEE ALSO .Xr libarchive 3 .Sh HISTORY Modified: stable/8/usr.bin/unzip/unzip.c ============================================================================== --- stable/8/usr.bin/unzip/unzip.c Tue May 4 19:04:51 2010 (r207628) +++ stable/8/usr.bin/unzip/unzip.c Tue May 4 19:18:00 2010 (r207629) @@ -1,4 +1,5 @@ /*- + * Copyright (c) 2009 Joerg Sonnenberger * Copyright (c) 2007-2008 Dag-Erling Coïdan Smørgrav * All rights reserved. * @@ -51,15 +52,19 @@ /* command-line options */ static int a_opt; /* convert EOL */ +static int C_opt; /* match case-insensitively */ +static int c_opt; /* extract to stdout */ static const char *d_arg; /* directory */ +static int f_opt; /* update existing files only */ static int j_opt; /* junk directories */ static int L_opt; /* lowercase names */ -static int l_opt; /* list */ static int n_opt; /* never overwrite */ static int o_opt; /* always overwrite */ +static int p_opt; /* extract to stdout, quiet */ static int q_opt; /* quiet */ static int t_opt; /* test */ static int u_opt; /* update */ +static int v_opt; /* verbose/list */ /* time when unzip started */ static time_t now; @@ -70,9 +75,6 @@ static int unzip_debug; /* running on tty? */ static int tty; -/* error flag for -t */ -static int test_failed; - /* convenience macro */ /* XXX should differentiate between ARCHIVE_{WARN,FAIL,RETRY} */ #define ac(call) \ @@ -162,7 +164,6 @@ static void info(const char *fmt, ...) { va_list ap; - int i; if (q_opt && !unzip_debug) return; @@ -171,9 +172,10 @@ info(const char *fmt, ...) va_end(ap); fflush(stdout); - for (i = 0; fmt[i] != '\0'; ++i) - /* nothing */ ; - noeol = !(i && fmt[i - 1] == '\n'); + if (*fmt == '\0') + noeol = 1; + else + noeol = fmt[strlen(fmt) - 1] != '\n'; } /* debug message (if unzip_debug) */ @@ -181,7 +183,6 @@ static void debug(const char *fmt, ...) { va_list ap; - int i; if (!unzip_debug) return; @@ -190,9 +191,10 @@ debug(const char *fmt, ...) va_end(ap); fflush(stderr); - for (i = 0; fmt[i] != '\0'; ++i) - /* nothing */ ; - noeol = !(i && fmt[i - 1] == '\n'); + if (*fmt == '\0') + noeol = 1; + else + noeol = fmt[strlen(fmt) - 1] != '\n'; } /* duplicate a path name, possibly converting to lower case */ @@ -200,7 +202,7 @@ static char * pathdup(const char *path) { char *str; - int len; + size_t i, len; len = strlen(path); while (len && path[len - 1] == '/') @@ -209,8 +211,12 @@ pathdup(const char *path) errno = ENOMEM; error("malloc()"); } - for (int i = 0; i < len; ++i) - str[i] = L_opt ? tolower(path[i]) : path[i]; + if (L_opt) { + for (i = 0; i < len; ++i) + str[i] = tolower((unsigned char)path[i]); + } else { + memcpy(str, path, len); + } str[len] = '\0'; return (str); @@ -221,7 +227,7 @@ static char * pathcat(const char *prefix, const char *path) { char *str; - int prelen, len; + size_t prelen, len; prelen = prefix ? strlen(prefix) + 1 : 0; len = strlen(path) + 1; @@ -257,7 +263,7 @@ static void add_pattern(struct pattern_list *list, const char *pattern) { struct pattern *entry; - int len; + size_t len; debug("adding pattern '%s'\n", pattern); len = strlen(pattern); @@ -265,7 +271,6 @@ add_pattern(struct pattern_list *list, c errno = ENOMEM; error("malloc()"); } - memset(&entry->link, 0, sizeof entry->link); memcpy(entry->pattern, pattern, len + 1); STAILQ_INSERT_TAIL(list, entry, link); } @@ -279,7 +284,7 @@ match_pattern(struct pattern_list *list, struct pattern *entry; STAILQ_FOREACH(entry, list, link) { - if (fnmatch(entry->pattern, str, 0) == 0) + if (fnmatch(entry->pattern, str, C_opt ? FNM_CASEFOLD : 0) == 0) return (1); } return (0); @@ -378,7 +383,7 @@ extract_dir(struct archive *a, struct ar { int mode; - mode = archive_entry_filetype(e) & 0777; + mode = archive_entry_mode(e) & 0777; if (mode == 0) mode = 0755; @@ -406,49 +411,104 @@ extract_dir(struct archive *a, struct ar static unsigned char buffer[8192]; static char spinner[] = { '|', '/', '-', '\\' }; +static int +handle_existing_file(char **path) +{ + size_t alen; + ssize_t len; + char buf[4]; + + for (;;) { + fprintf(stderr, + "replace %s? [y]es, [n]o, [A]ll, [N]one, [r]ename: ", + *path); + if (fgets(buf, sizeof(buf), stdin) == 0) { + clearerr(stdin); + printf("NULL\n(EOF or read error, " + "treating as \"[N]one\"...)\n"); + n_opt = 1; + return -1; + } + switch (*buf) { + case 'A': + o_opt = 1; + /* FALLTHROUGH */ + case 'y': + case 'Y': + (void)unlink(*path); + return 1; + case 'N': + n_opt = 1; + /* FALLTHROUGH */ + case 'n': + return -1; + case 'r': + case 'R': + printf("New name: "); + fflush(stdout); + free(*path); + *path = NULL; + alen = 0; + len = getdelim(path, &alen, '\n', stdin); + if ((*path)[len - 1] == '\n') + (*path)[len - 1] = '\0'; + return 0; + default: + break; + } + } +} + /* * Extract a regular file. */ static void -extract_file(struct archive *a, struct archive_entry *e, const char *path) +extract_file(struct archive *a, struct archive_entry *e, char **path) { int mode; time_t mtime; struct stat sb; struct timeval tv[2]; - int cr, fd, text, warn; + int cr, fd, text, warn, check; ssize_t len; unsigned char *p, *q, *end; - mode = archive_entry_filetype(e) & 0777; + mode = archive_entry_mode(e) & 0777; if (mode == 0) mode = 0644; mtime = archive_entry_mtime(e); /* look for existing file of same name */ - if (lstat(path, &sb) == 0) { - if (u_opt) { +recheck: + if (lstat(*path, &sb) == 0) { + if (u_opt || f_opt) { /* check if up-to-date */ - if (S_ISREG(sb.st_mode) && sb.st_mtime > mtime) + if (S_ISREG(sb.st_mode) && sb.st_mtime >= mtime) return; - (void)unlink(path); + (void)unlink(*path); } else if (o_opt) { /* overwrite */ - (void)unlink(path); + (void)unlink(*path); } else if (n_opt) { /* do not overwrite */ return; } else { - /* XXX ask user */ - errorx("not implemented"); + check = handle_existing_file(path); + if (check == 0) + goto recheck; + if (check == -1) + return; /* do not overwrite */ } + } else { + if (f_opt) + return; } - if ((fd = open(path, O_RDWR|O_CREAT|O_TRUNC, mode)) < 0) - error("open('%s')", path); + if ((fd = open(*path, O_RDWR|O_CREAT|O_TRUNC, mode)) < 0) + error("open('%s')", *path); /* loop over file contents and write to disk */ - info("x %s", path); + info(" extracting: %s", *path); text = a_opt; warn = 0; cr = 0; @@ -465,7 +525,7 @@ extract_file(struct archive *a, struct a if (a_opt && cr) { if (len == 0 || buffer[0] != '\n') if (write(fd, "\r", 1) != 1) - error("write('%s')", path); + error("write('%s')", *path); cr = 0; } @@ -496,7 +556,7 @@ extract_file(struct archive *a, struct a /* simple case */ if (!a_opt || !text) { if (write(fd, buffer, len) != len) - error("write('%s')", path); + error("write('%s')", *path); continue; } @@ -506,7 +566,7 @@ extract_file(struct archive *a, struct a if (!warn && !isascii(*q)) { warningx("%s may be corrupted due" " to weak text file detection" - " heuristic", path); + " heuristic", *path); warn = 1; } if (q[0] != '\r') @@ -519,7 +579,7 @@ extract_file(struct archive *a, struct a break; } if (write(fd, p, q - p) != q - p) - error("write('%s')", path); + error("write('%s')", *path); } } if (tty) @@ -534,9 +594,9 @@ extract_file(struct archive *a, struct a tv[1].tv_sec = mtime; tv[1].tv_usec = 0; if (futimes(fd, tv) != 0) - error("utimes('%s')", path); + error("utimes('%s')", *path); if (close(fd) != 0) - error("close('%s')", path); + error("close('%s')", *path); } /* @@ -612,46 +672,178 @@ extract(struct archive *a, struct archiv if (S_ISDIR(filetype)) extract_dir(a, e, realpathname); else - extract_file(a, e, realpathname); + extract_file(a, e, &realpathname); free(realpathname); free(pathname); } +static void +extract_stdout(struct archive *a, struct archive_entry *e) +{ + char *pathname; + mode_t filetype; + int cr, text, warn; + ssize_t len; + unsigned char *p, *q, *end; + + pathname = pathdup(archive_entry_pathname(e)); + filetype = archive_entry_filetype(e); + + /* I don't think this can happen in a zipfile.. */ + if (!S_ISDIR(filetype) && !S_ISREG(filetype)) { + warningx("skipping non-regular entry '%s'", pathname); + ac(archive_read_data_skip(a)); + free(pathname); + return; + } + + /* skip directories in -j case */ + if (S_ISDIR(filetype)) { + ac(archive_read_data_skip(a)); + free(pathname); + return; + } + + /* apply include / exclude patterns */ + if (!accept_pathname(pathname)) { + ac(archive_read_data_skip(a)); + free(pathname); + return; + } + + if (c_opt) + info("x %s\n", pathname); + + text = a_opt; + warn = 0; + cr = 0; + for (int n = 0; ; n++) { + len = archive_read_data(a, buffer, sizeof buffer); + + if (len < 0) + ac(len); + + /* left over CR from previous buffer */ + if (a_opt && cr) { + if (len == 0 || buffer[0] != '\n') { + if (fwrite("\r", 1, 1, stderr) != 1) + error("write('%s')", pathname); + } + cr = 0; + } + + /* EOF */ + if (len == 0) + break; + end = buffer + len; + + /* + * Detect whether this is a text file. The correct way to + * do this is to check the least significant bit of the + * "internal file attributes" field of the corresponding + * file header in the central directory, but libarchive + * does not read the central directory, so we have to + * guess by looking for non-ASCII characters in the + * buffer. Hopefully we won't guess wrong. If we do + * guess wrong, we print a warning message later. + */ + if (a_opt && n == 0) { + for (p = buffer; p < end; ++p) { + if (!isascii((unsigned char)*p)) { + text = 0; + break; + } + } + } + + /* simple case */ + if (!a_opt || !text) { + if (fwrite(buffer, 1, len, stdout) != (size_t)len) + error("write('%s')", pathname); + continue; + } + + /* hard case: convert \r\n to \n (sigh...) */ + for (p = buffer; p < end; p = q + 1) { + for (q = p; q < end; q++) { + if (!warn && !isascii(*q)) { + warningx("%s may be corrupted due" + " to weak text file detection" + " heuristic", pathname); + warn = 1; + } + if (q[0] != '\r') + continue; + if (&q[1] == end) { + cr = 1; + break; + } + if (q[1] == '\n') + break; + } + if (fwrite(p, 1, q - p, stdout) != (size_t)(q - p)) + error("write('%s')", pathname); + } + } + + free(pathname); +} + /* * Print the name of an entry to stdout. */ static void list(struct archive *a, struct archive_entry *e) { + char buf[20]; + time_t mtime; - printf("%s\n", archive_entry_pathname(e)); + mtime = archive_entry_mtime(e); + strftime(buf, sizeof(buf), "%m-%d-%g %R", localtime(&mtime)); + + if (v_opt == 1) { + printf(" %8ju %s %s\n", + (uintmax_t)archive_entry_size(e), + buf, archive_entry_pathname(e)); + } else if (v_opt == 2) { + printf("%8ju Stored %7ju 0%% %s %08x %s\n", + (uintmax_t)archive_entry_size(e), + (uintmax_t)archive_entry_size(e), + buf, + 0U, + archive_entry_pathname(e)); + } ac(archive_read_data_skip(a)); } /* * Extract to memory to check CRC */ -static void +static int test(struct archive *a, struct archive_entry *e) { ssize_t len; + int error_count; + error_count = 0; if (S_ISDIR(archive_entry_filetype(e))) - return; + return 0; - info("%s ", archive_entry_pathname(e)); + info(" testing: %s\t", archive_entry_pathname(e)); while ((len = archive_read_data(a, buffer, sizeof buffer)) > 0) /* nothing */; if (len < 0) { - info("%s\n", archive_error_string(a)); - ++test_failed; + info(" %s\n", archive_error_string(a)); + ++error_count; } else { - info("OK\n"); + info(" OK\n"); } /* shouldn't be necessary, but it doesn't hurt */ ac(archive_read_data_skip(a)); + + return error_count; } @@ -665,6 +857,7 @@ unzip(const char *fn) struct archive *a; struct archive_entry *e; int fd, ret; + uintmax_t total_size, file_count, error_count; if ((fd = open(fn, O_RDONLY)) < 0) error("%s", fn); @@ -673,33 +866,70 @@ unzip(const char *fn) ac(archive_read_support_format_zip(a)); ac(archive_read_open_fd(a, fd, 8192)); + if (!p_opt && !q_opt) + printf("Archive: %s\n", fn); + if (v_opt == 1) { + printf(" Length Date Time Name\n"); + printf(" -------- ---- ---- ----\n"); + } else if (v_opt == 2) { + printf(" Length Method Size Ratio Date Time CRC-32 Name\n"); + printf("-------- ------ ------- ----- ---- ---- ------ ----\n"); + } + + total_size = 0; + file_count = 0; + error_count = 0; for (;;) { ret = archive_read_next_header(a, &e); if (ret == ARCHIVE_EOF) break; ac(ret); if (t_opt) - test(a, e); - else if (l_opt) + error_count += test(a, e); + else if (v_opt) list(a, e); + else if (p_opt || c_opt) + extract_stdout(a, e); else extract(a, e); + + total_size += archive_entry_size(e); + ++file_count; + } + + if (v_opt == 1) { + printf(" -------- -------\n"); + printf(" %8ju %ju file%s\n", + total_size, file_count, file_count != 1 ? "s" : ""); + } else if (v_opt == 2) { + printf("-------- ------- --- -------\n"); + printf("%8ju %7ju 0%% %ju file%s\n", + total_size, total_size, file_count, + file_count != 1 ? "s" : ""); } ac(archive_read_close(a)); (void)archive_read_finish(a); + if (close(fd) != 0) error("%s", fn); - if (t_opt && test_failed) - errorx("%d checksum error(s) found.", test_failed); + if (t_opt) { + if (error_count > 0) { + errorx("%d checksum error(s) found.", error_count); + } + else { + printf("No errors detected in compressed data of %s.\n", + fn); + } + } } static void usage(void) { - fprintf(stderr, "usage: unzip [-ajLlnoqtu] [-d dir] zipfile\n"); + fprintf(stderr, "usage: unzip [-aCcfjLlnopqtuv] [-d dir] [-x pattern] zipfile\n"); exit(1); } @@ -709,14 +939,23 @@ getopts(int argc, char *argv[]) int opt; optreset = optind = 1; - while ((opt = getopt(argc, argv, "ad:jLlnoqtux:")) != -1) + while ((opt = getopt(argc, argv, "aCcd:fjLlnopqtuvx:")) != -1) switch (opt) { case 'a': a_opt = 1; break; + case 'C': + C_opt = 1; + break; + case 'c': + c_opt = 1; + break; case 'd': d_arg = optarg; break; + case 'f': + f_opt = 1; + break; case 'j': j_opt = 1; break; @@ -724,13 +963,18 @@ getopts(int argc, char *argv[]) L_opt = 1; break; case 'l': - l_opt = 1; + if (v_opt == 0) + v_opt = 1; break; case 'n': n_opt = 1; break; case 'o': o_opt = 1; + q_opt = 1; + break; + case 'p': + p_opt = 1; break; case 'q': q_opt = 1; @@ -741,6 +985,9 @@ getopts(int argc, char *argv[]) case 'u': u_opt = 1; break; + case 'v': + v_opt = 2; + break; case 'x': add_pattern(&exclude, optarg); break;