libziparchive: add zipinfo(1).
Useful for debugging and hermetic builds. (Various places in the build
check to see that a file was stored uncompressed.)
Test: manual
Change-Id: I127e5689cd493ab06739b765beed50912dc9cc1d
diff --git a/libziparchive/unzip.cpp b/libziparchive/unzip.cpp
index 426325e..e936614 100644
--- a/libziparchive/unzip.cpp
+++ b/libziparchive/unzip.cpp
@@ -40,12 +40,15 @@
kPrompt,
};
+static bool is_unzip;
static OverwriteMode overwrite_mode = kPrompt;
+static bool flag_1 = false;
static const char* flag_d = nullptr;
static bool flag_l = false;
static bool flag_p = false;
static bool flag_q = false;
static bool flag_v = false;
+static bool flag_x = false;
static const char* archive_name = nullptr;
static std::set<std::string> includes;
static std::set<std::string> excludes;
@@ -88,32 +91,51 @@
return static_cast<int>((100LL * (uncompressed - compressed)) / uncompressed);
}
-static void MaybeShowHeader() {
- if (!flag_q) printf("Archive: %s\n", archive_name);
- if (flag_v) {
- printf(
- " Length Method Size Cmpr Date Time CRC-32 Name\n"
- "-------- ------ ------- ---- ---------- ----- -------- ----\n");
- } else if (flag_l) {
- printf(
- " Length Date Time Name\n"
- "--------- ---------- ----- ----\n");
+static void MaybeShowHeader(ZipArchiveHandle zah) {
+ if (is_unzip) {
+ // unzip has three formats.
+ if (!flag_q) printf("Archive: %s\n", archive_name);
+ if (flag_v) {
+ printf(
+ " Length Method Size Cmpr Date Time CRC-32 Name\n"
+ "-------- ------ ------- ---- ---------- ----- -------- ----\n");
+ } else if (flag_l) {
+ printf(
+ " Length Date Time Name\n"
+ "--------- ---------- ----- ----\n");
+ }
+ } else {
+ // zipinfo.
+ if (!flag_1 && includes.empty() && excludes.empty()) {
+ ZipArchiveInfo info{GetArchiveInfo(zah)};
+ printf("Archive: %s\n", archive_name);
+ printf("Zip file size: %" PRId64 " bytes, number of entries: %zu\n", info.archive_size,
+ info.entry_count);
+ }
}
}
static void MaybeShowFooter() {
- if (flag_v) {
- printf(
- "-------- ------- --- -------\n"
- "%8" PRId64 " %8" PRId64 " %3d%% %zu file%s\n",
- total_uncompressed_length, total_compressed_length,
- CompressionRatio(total_uncompressed_length, total_compressed_length), file_count,
- (file_count == 1) ? "" : "s");
- } else if (flag_l) {
- printf(
- "--------- -------\n"
- "%9" PRId64 " %zu file%s\n",
- total_uncompressed_length, file_count, (file_count == 1) ? "" : "s");
+ if (is_unzip) {
+ if (flag_v) {
+ printf(
+ "-------- ------- --- -------\n"
+ "%8" PRId64 " %8" PRId64 " %3d%% %zu file%s\n",
+ total_uncompressed_length, total_compressed_length,
+ CompressionRatio(total_uncompressed_length, total_compressed_length), file_count,
+ (file_count == 1) ? "" : "s");
+ } else if (flag_l) {
+ printf(
+ "--------- -------\n"
+ "%9" PRId64 " %zu file%s\n",
+ total_uncompressed_length, file_count, (file_count == 1) ? "" : "s");
+ }
+ } else {
+ if (!flag_1 && includes.empty() && excludes.empty()) {
+ printf("%zu files, %" PRId64 " bytes uncompressed, %" PRId64 " bytes compressed: %3d%%\n",
+ file_count, total_uncompressed_length, total_compressed_length,
+ CompressionRatio(total_uncompressed_length, total_compressed_length));
+ }
}
}
@@ -226,17 +248,61 @@
}
}
+static void InfoOne(const ZipEntry& entry, const std::string& name) {
+ if (flag_1) {
+ // "android-ndk-r19b/sources/android/NOTICE"
+ printf("%s\n", name.c_str());
+ return;
+ }
+
+ int version = entry.version_made_by & 0xff;
+ int os = (entry.version_made_by >> 8) & 0xff;
+
+ // TODO: Support suid/sgid? Non-Unix host file system attributes?
+ char mode[] = "??????????";
+ if (os == 3) {
+ mode[0] = S_ISDIR(entry.unix_mode) ? 'd' : (S_ISREG(entry.unix_mode) ? '-' : '?');
+ mode[1] = entry.unix_mode & S_IRUSR ? 'r' : '-';
+ mode[2] = entry.unix_mode & S_IWUSR ? 'w' : '-';
+ mode[3] = entry.unix_mode & S_IXUSR ? 'x' : '-';
+ mode[4] = entry.unix_mode & S_IRGRP ? 'r' : '-';
+ mode[5] = entry.unix_mode & S_IWGRP ? 'w' : '-';
+ mode[6] = entry.unix_mode & S_IXGRP ? 'x' : '-';
+ mode[7] = entry.unix_mode & S_IROTH ? 'r' : '-';
+ mode[8] = entry.unix_mode & S_IWOTH ? 'w' : '-';
+ mode[9] = entry.unix_mode & S_IXOTH ? 'x' : '-';
+ }
+
+ // TODO: zipinfo (unlike unzip) sometimes uses time zone?
+ // TODO: this uses 4-digit years because we're not barbarians unless interoperability forces it.
+ tm t = entry.GetModificationTime();
+ char time[32];
+ snprintf(time, sizeof(time), "%04d-%02d-%02d %02d:%02d", t.tm_year + 1900, t.tm_mon + 1,
+ t.tm_mday, t.tm_hour, t.tm_min);
+
+ // "-rw-r--r-- 3.0 unx 577 t- defX 19-Feb-12 16:09 android-ndk-r19b/sources/android/NOTICE"
+ printf("%s %2d.%d %s %8d %c%c %s %s %s\n", mode, version / 10, version % 10,
+ os == 3 ? "unx" : "???", entry.uncompressed_length, entry.is_text ? 't' : 'b',
+ entry.has_data_descriptor ? 'X' : 'x', entry.method == kCompressStored ? "stor" : "defX",
+ time, name.c_str());
+}
+
static void ProcessOne(ZipArchiveHandle zah, ZipEntry& entry, const std::string& name) {
- if (flag_l || flag_v) {
- // -l or -lv or -lq or -v.
- ListOne(entry, name);
- } else {
- // Actually extract.
- if (flag_p) {
- ExtractToPipe(zah, entry, name);
+ if (is_unzip) {
+ if (flag_l || flag_v) {
+ // -l or -lv or -lq or -v.
+ ListOne(entry, name);
} else {
- ExtractOne(zah, entry, name);
+ // Actually extract.
+ if (flag_p) {
+ ExtractToPipe(zah, entry, name);
+ } else {
+ ExtractOne(zah, entry, name);
+ }
}
+ } else {
+ // zipinfo or zipinfo -1.
+ InfoOne(entry, name);
}
total_uncompressed_length += entry.uncompressed_length;
total_compressed_length += entry.compressed_length;
@@ -244,7 +310,7 @@
}
static void ProcessAll(ZipArchiveHandle zah) {
- MaybeShowHeader();
+ MaybeShowHeader(zah);
// libziparchive iteration order doesn't match the central directory.
// We could sort, but that would cost extra and wouldn't match either.
@@ -267,73 +333,110 @@
}
static void ShowHelp(bool full) {
- fprintf(full ? stdout : stderr, "usage: unzip [-d DIR] [-lnopqv] ZIP [FILE...] [-x FILE...]\n");
- if (!full) exit(EXIT_FAILURE);
+ if (is_unzip) {
+ fprintf(full ? stdout : stderr, "usage: unzip [-d DIR] [-lnopqv] ZIP [FILE...] [-x FILE...]\n");
+ if (!full) exit(EXIT_FAILURE);
- printf(
- "\n"
- "Extract FILEs from ZIP archive. Default is all files. Both the include and\n"
- "exclude (-x) lists use shell glob patterns.\n"
- "\n"
- "-d DIR Extract into DIR\n"
- "-l List contents (-lq excludes archive name, -lv is verbose)\n"
- "-n Never overwrite files (default: prompt)\n"
- "-o Always overwrite files\n"
- "-p Pipe to stdout\n"
- "-q Quiet\n"
- "-v List contents verbosely\n"
- "-x FILE Exclude files\n");
+ printf(
+ "\n"
+ "Extract FILEs from ZIP archive. Default is all files. Both the include and\n"
+ "exclude (-x) lists use shell glob patterns.\n"
+ "\n"
+ "-d DIR Extract into DIR\n"
+ "-l List contents (-lq excludes archive name, -lv is verbose)\n"
+ "-n Never overwrite files (default: prompt)\n"
+ "-o Always overwrite files\n"
+ "-p Pipe to stdout\n"
+ "-q Quiet\n"
+ "-v List contents verbosely\n"
+ "-x FILE Exclude files\n");
+ } else {
+ fprintf(full ? stdout : stderr, "usage: zipinfo [-1] ZIP [FILE...] [-x FILE...]\n");
+ if (!full) exit(EXIT_FAILURE);
+
+ printf(
+ "\n"
+ "Show information about FILEs from ZIP archive. Default is all files.\n"
+ "Both the include and exclude (-x) lists use shell glob patterns.\n"
+ "\n"
+ "-1 Show filenames only, one per line\n"
+ "-x FILE Exclude files\n");
+ }
exit(EXIT_SUCCESS);
}
+static void HandleCommonOption(int opt) {
+ switch (opt) {
+ case 'h':
+ ShowHelp(true);
+ break;
+ case 'x':
+ flag_x = true;
+ break;
+ case 1:
+ // -x swallows all following arguments, so we use '-' in the getopt
+ // string and collect files here.
+ if (!archive_name) {
+ archive_name = optarg;
+ } else if (flag_x) {
+ excludes.insert(optarg);
+ } else {
+ includes.insert(optarg);
+ }
+ break;
+ default:
+ ShowHelp(false);
+ break;
+ }
+}
+
int main(int argc, char* argv[]) {
static struct option opts[] = {
{"help", no_argument, 0, 'h'},
};
- bool saw_x = false;
- int opt;
- while ((opt = getopt_long(argc, argv, "-d:hlnopqvx", opts, nullptr)) != -1) {
- switch (opt) {
- case 'd':
- flag_d = optarg;
- break;
- case 'h':
- ShowHelp(true);
- break;
- case 'l':
- flag_l = true;
- break;
- case 'n':
- overwrite_mode = kNever;
- break;
- case 'o':
- overwrite_mode = kAlways;
- break;
- case 'p':
- flag_p = flag_q = true;
- break;
- case 'q':
- flag_q = true;
- break;
- case 'v':
- flag_v = true;
- break;
- case 'x':
- saw_x = true;
- break;
- case 1:
- // -x swallows all following arguments, so we use '-' in the getopt
- // string and collect files here.
- if (!archive_name) {
- archive_name = optarg;
- } else if (saw_x) {
- excludes.insert(optarg);
- } else {
- includes.insert(optarg);
- }
- break;
- default:
- ShowHelp(false);
+
+ is_unzip = !strcmp(basename(argv[0]), "unzip");
+ if (is_unzip) {
+ int opt;
+ while ((opt = getopt_long(argc, argv, "-d:hlnopqvx", opts, nullptr)) != -1) {
+ switch (opt) {
+ case 'd':
+ flag_d = optarg;
+ break;
+ case 'l':
+ flag_l = true;
+ break;
+ case 'n':
+ overwrite_mode = kNever;
+ break;
+ case 'o':
+ overwrite_mode = kAlways;
+ break;
+ case 'p':
+ flag_p = flag_q = true;
+ break;
+ case 'q':
+ flag_q = true;
+ break;
+ case 'v':
+ flag_v = true;
+ break;
+ default:
+ HandleCommonOption(opt);
+ break;
+ }
+ }
+ } else {
+ int opt;
+ while ((opt = getopt_long(argc, argv, "-1hx", opts, nullptr)) != -1) {
+ switch (opt) {
+ case '1':
+ flag_1 = true;
+ break;
+ default:
+ HandleCommonOption(opt);
+ break;
+ }
}
}