Merge changes from topic 'trusty_km2_hal'
* changes:
trusty: keymaster: update device tests to use 2.0 API
trusty: keymaster: Implement abort
trusty: keymaster: Implement finish
trusty: keymaster: Implement update
trusty: keymaster: Implement begin
trusty: keymaster: Implement upgrade_key
trusty: keymaster: Implement attest_key
trusty: keymaster: Implement export_key
trusty: keymaster: Implement import_key
trusty: keymaster: Implement get_key_characteristics
trusty: keymaster: Implement generate_key
trusty: keymaster: Implement add_rng_entropy
trusty: keymaster: Implement configure
trusty: keymaster: Begin update from Keymaster 0.3 to 2.0
diff --git a/init/Android.mk b/init/Android.mk
index e35571b..dbbf40a 100644
--- a/init/Android.mk
+++ b/init/Android.mk
@@ -9,13 +9,15 @@
-DALLOW_LOCAL_PROP_OVERRIDE=1 \
-DALLOW_PERMISSIVE_SELINUX=1 \
-DREBOOT_BOOTLOADER_ON_PANIC=1 \
- -DWORLD_WRITABLE_KMSG=1
+ -DWORLD_WRITABLE_KMSG=1 \
+ -DDUMP_ON_UMOUNT_FAILURE=1
else
init_options += \
-DALLOW_LOCAL_PROP_OVERRIDE=0 \
-DALLOW_PERMISSIVE_SELINUX=0 \
-DREBOOT_BOOTLOADER_ON_PANIC=0 \
- -DWORLD_WRITABLE_KMSG=0
+ -DWORLD_WRITABLE_KMSG=0 \
+ -DDUMP_ON_UMOUNT_FAILURE=0
endif
ifneq (,$(filter eng,$(TARGET_BUILD_VARIANT)))
diff --git a/init/reboot.cpp b/init/reboot.cpp
index 53bdeb1..8de3c78 100644
--- a/init/reboot.cpp
+++ b/init/reboot.cpp
@@ -18,10 +18,12 @@
#include <dirent.h>
#include <fcntl.h>
+#include <linux/fs.h>
#include <mntent.h>
+#include <selinux/selinux.h>
#include <sys/cdefs.h>
+#include <sys/ioctl.h>
#include <sys/mount.h>
-#include <sys/quota.h>
#include <sys/reboot.h>
#include <sys/stat.h>
#include <sys/syscall.h>
@@ -39,6 +41,7 @@
#include <android-base/properties.h>
#include <android-base/stringprintf.h>
#include <android-base/strings.h>
+#include <android-base/unique_fd.h>
#include <bootloader_message/bootloader_message.h>
#include <cutils/android_reboot.h>
#include <fs_mgr.h>
@@ -67,39 +70,58 @@
// Utility for struct mntent
class MountEntry {
public:
- explicit MountEntry(const mntent& entry, bool isMounted = true)
+ explicit MountEntry(const mntent& entry)
: mnt_fsname_(entry.mnt_fsname),
mnt_dir_(entry.mnt_dir),
mnt_type_(entry.mnt_type),
- is_mounted_(isMounted) {}
+ mnt_opts_(entry.mnt_opts) {}
- bool IsF2Fs() const { return mnt_type_ == "f2fs"; }
+ bool Umount() {
+ int r = umount2(mnt_dir_.c_str(), 0);
+ if (r == 0) {
+ LOG(INFO) << "umounted " << mnt_fsname_ << ":" << mnt_dir_ << " opts " << mnt_opts_;
+ return true;
+ } else {
+ PLOG(WARNING) << "cannot umount " << mnt_fsname_ << ":" << mnt_dir_ << " opts "
+ << mnt_opts_;
+ return false;
+ }
+ }
- bool IsExt4() const { return mnt_type_ == "ext4"; }
-
- bool is_mounted() const { return is_mounted_; }
-
- void set_is_mounted() { is_mounted_ = false; }
-
- const std::string& mnt_fsname() const { return mnt_fsname_; }
-
- const std::string& mnt_dir() const { return mnt_dir_; }
+ void DoFsck() {
+ int st;
+ if (IsF2Fs()) {
+ const char* f2fs_argv[] = {
+ "/system/bin/fsck.f2fs", "-f", mnt_fsname_.c_str(),
+ };
+ android_fork_execvp_ext(arraysize(f2fs_argv), (char**)f2fs_argv, &st, true, LOG_KLOG,
+ true, nullptr, nullptr, 0);
+ } else if (IsExt4()) {
+ const char* ext4_argv[] = {
+ "/system/bin/e2fsck", "-f", "-y", mnt_fsname_.c_str(),
+ };
+ android_fork_execvp_ext(arraysize(ext4_argv), (char**)ext4_argv, &st, true, LOG_KLOG,
+ true, nullptr, nullptr, 0);
+ }
+ }
static bool IsBlockDevice(const struct mntent& mntent) {
return android::base::StartsWith(mntent.mnt_fsname, "/dev/block");
}
static bool IsEmulatedDevice(const struct mntent& mntent) {
- static const std::string SDCARDFS_NAME = "sdcardfs";
- return android::base::StartsWith(mntent.mnt_fsname, "/data/") &&
- SDCARDFS_NAME == mntent.mnt_type;
+ return android::base::StartsWith(mntent.mnt_fsname, "/data/");
}
private:
+ bool IsF2Fs() const { return mnt_type_ == "f2fs"; }
+
+ bool IsExt4() const { return mnt_type_ == "ext4"; }
+
std::string mnt_fsname_;
std::string mnt_dir_;
std::string mnt_type_;
- bool is_mounted_;
+ std::string mnt_opts_;
};
// Turn off backlight while we are performing power down cleanup activities.
@@ -125,50 +147,6 @@
}
}
-static void DoFsck(const MountEntry& entry) {
- static constexpr int UNMOUNT_CHECK_TIMES = 10;
-
- if (!entry.IsF2Fs() && !entry.IsExt4()) return;
-
- int count = 0;
- while (count++ < UNMOUNT_CHECK_TIMES) {
- int fd = TEMP_FAILURE_RETRY(open(entry.mnt_fsname().c_str(), O_RDONLY | O_EXCL));
- if (fd >= 0) {
- /* |entry->mnt_dir| has sucessfully been unmounted. */
- close(fd);
- break;
- } else if (errno == EBUSY) {
- // Some processes using |entry->mnt_dir| are still alive. Wait for a
- // while then retry.
- std::this_thread::sleep_for(5000ms / UNMOUNT_CHECK_TIMES);
- continue;
- } else {
- /* Cannot open the device. Give up. */
- return;
- }
- }
-
- // NB: With watchdog still running, there is no cap on the time it takes
- // to complete the fsck, from the users perspective the device graphics
- // and responses are locked-up and they may choose to hold the power
- // button in frustration if it drags out.
-
- int st;
- if (entry.IsF2Fs()) {
- const char* f2fs_argv[] = {
- "/system/bin/fsck.f2fs", "-f", entry.mnt_fsname().c_str(),
- };
- android_fork_execvp_ext(arraysize(f2fs_argv), (char**)f2fs_argv, &st, true, LOG_KLOG, true,
- nullptr, nullptr, 0);
- } else if (entry.IsExt4()) {
- const char* ext4_argv[] = {
- "/system/bin/e2fsck", "-f", "-y", entry.mnt_fsname().c_str(),
- };
- android_fork_execvp_ext(arraysize(ext4_argv), (char**)ext4_argv, &st, true, LOG_KLOG, true,
- nullptr, nullptr, 0);
- }
-}
-
static void ShutdownVold() {
const char* vdc_argv[] = {"/system/bin/vdc", "volume", "shutdown"};
int status;
@@ -202,21 +180,11 @@
abort();
}
-static void DoSync() {
- // quota sync is not done by sync call, so should be done separately.
- // quota sync is in VFS level, so do it before sync, which goes down to fs level.
- int r = quotactl(QCMD(Q_SYNC, 0), nullptr, 0 /* do not care */, 0 /* do not care */);
- if (r < 0) {
- PLOG(ERROR) << "quotactl failed";
- }
- sync();
-}
-
/* Find all read+write block devices and emulated devices in /proc/mounts
* and add them to correpsponding list.
*/
static bool FindPartitionsToUmount(std::vector<MountEntry>* blockDevPartitions,
- std::vector<MountEntry>* emulatedPartitions) {
+ std::vector<MountEntry>* emulatedPartitions, bool dump) {
std::unique_ptr<std::FILE, int (*)(std::FILE*)> fp(setmntent("/proc/mounts", "r"), endmntent);
if (fp == nullptr) {
PLOG(ERROR) << "Failed to open /proc/mounts";
@@ -224,44 +192,63 @@
}
mntent* mentry;
while ((mentry = getmntent(fp.get())) != nullptr) {
- if (MountEntry::IsBlockDevice(*mentry) && hasmntopt(mentry, "rw")) {
- blockDevPartitions->emplace_back(*mentry);
+ if (dump) {
+ LOG(INFO) << "mount entry " << mentry->mnt_fsname << ":" << mentry->mnt_dir << " opts "
+ << mentry->mnt_opts << " type " << mentry->mnt_type;
+ } else if (MountEntry::IsBlockDevice(*mentry) && hasmntopt(mentry, "rw")) {
+ blockDevPartitions->emplace(blockDevPartitions->begin(), *mentry);
} else if (MountEntry::IsEmulatedDevice(*mentry)) {
- emulatedPartitions->emplace_back(*mentry);
+ emulatedPartitions->emplace(emulatedPartitions->begin(), *mentry);
}
}
return true;
}
-static bool UmountPartitions(std::vector<MountEntry>* partitions, int maxRetry, int flags) {
- static constexpr int SLEEP_AFTER_RETRY_US = 100000;
-
- bool umountDone;
- int retryCounter = 0;
-
- while (true) {
- umountDone = true;
- for (auto& entry : *partitions) {
- if (entry.is_mounted()) {
- int r = umount2(entry.mnt_dir().c_str(), flags);
- if (r == 0) {
- entry.set_is_mounted();
- LOG(INFO) << StringPrintf("umounted %s, flags:0x%x", entry.mnt_fsname().c_str(),
- flags);
- } else {
- umountDone = false;
- PLOG(WARNING) << StringPrintf("cannot umount %s, mnt_dir %s, flags:0x%x",
- entry.mnt_fsname().c_str(),
- entry.mnt_dir().c_str(), flags);
- }
- }
- }
- if (umountDone) break;
- retryCounter++;
- if (retryCounter >= maxRetry) break;
- usleep(SLEEP_AFTER_RETRY_US);
+static void DumpUmountDebuggingInfo() {
+ int status;
+ if (!security_getenforce()) {
+ LOG(INFO) << "Run lsof";
+ const char* lsof_argv[] = {"/system/bin/lsof"};
+ android_fork_execvp_ext(arraysize(lsof_argv), (char**)lsof_argv, &status, true, LOG_KLOG,
+ true, nullptr, nullptr, 0);
}
- return umountDone;
+ FindPartitionsToUmount(nullptr, nullptr, true);
+}
+
+static UmountStat UmountPartitions(int timeoutMs) {
+ Timer t;
+ UmountStat stat = UMOUNT_STAT_TIMEOUT;
+ int retry = 0;
+ /* data partition needs all pending writes to be completed and all emulated partitions
+ * umounted.If the current waiting is not good enough, give
+ * up and leave it to e2fsck after reboot to fix it.
+ */
+ while (true) {
+ std::vector<MountEntry> block_devices;
+ std::vector<MountEntry> emulated_devices;
+ if (!FindPartitionsToUmount(&block_devices, &emulated_devices, false)) {
+ return UMOUNT_STAT_ERROR;
+ }
+ if (block_devices.size() == 0) {
+ stat = UMOUNT_STAT_SUCCESS;
+ break;
+ }
+ if ((timeoutMs < t.duration_ms()) && retry > 0) { // try umount at least once
+ stat = UMOUNT_STAT_TIMEOUT;
+ break;
+ }
+ if (emulated_devices.size() > 0 &&
+ std::all_of(emulated_devices.begin(), emulated_devices.end(),
+ [](auto& entry) { return entry.Umount(); })) {
+ sync();
+ }
+ for (auto& entry : block_devices) {
+ entry.Umount();
+ }
+ retry++;
+ std::this_thread::sleep_for(100ms);
+ }
+ return stat;
}
static void KillAllProcesses() { android::base::WriteStringToFile("i", "/proc/sysrq-trigger"); }
@@ -277,56 +264,38 @@
*/
static UmountStat TryUmountAndFsck(bool runFsck, int timeoutMs) {
Timer t;
- std::vector<MountEntry> emulatedPartitions;
- std::vector<MountEntry> blockDevRwPartitions;
+ std::vector<MountEntry> block_devices;
+ std::vector<MountEntry> emulated_devices;
TurnOffBacklight(); // this part can take time. save power.
- if (!FindPartitionsToUmount(&blockDevRwPartitions, &emulatedPartitions)) {
+ if (runFsck && !FindPartitionsToUmount(&block_devices, &emulated_devices, false)) {
return UMOUNT_STAT_ERROR;
}
- if (emulatedPartitions.size() > 0) {
- LOG(WARNING) << "emulated partitions still exist, will umount";
- /* Pending writes in emulated partitions can fail umount. After a few trials, detach
- * it so that it can be umounted when all writes are done.
- */
- if (!UmountPartitions(&emulatedPartitions, 1, 0)) {
- UmountPartitions(&emulatedPartitions, 1, MNT_DETACH);
- }
- }
- DoSync(); // emulated partition change can lead to update
- UmountStat stat = UMOUNT_STAT_SUCCESS;
- /* data partition needs all pending writes to be completed and all emulated partitions
- * umounted. If umount failed in the above step, it DETACH is requested, so umount can
- * still happen while waiting for /data. If the current waiting is not good enough, give
- * up and leave it to e2fsck after reboot to fix it.
- */
- int remainingTimeMs = timeoutMs - t.duration_ms();
- // each retry takes 100ms, and run at least once.
- int retry = std::max(remainingTimeMs / 100, 1);
- if (!UmountPartitions(&blockDevRwPartitions, retry, 0)) {
- /* Last resort, kill all and try again */
- LOG(WARNING) << "umount still failing, trying kill all";
+
+ UmountStat stat = UmountPartitions(timeoutMs - t.duration_ms());
+ if (stat != UMOUNT_STAT_SUCCESS) {
+ LOG(INFO) << "umount timeout, last resort, kill all and try";
+ if (DUMP_ON_UMOUNT_FAILURE) DumpUmountDebuggingInfo();
KillAllProcesses();
- DoSync();
- if (!UmountPartitions(&blockDevRwPartitions, 1, 0)) {
- stat = UMOUNT_STAT_TIMEOUT;
- }
- }
- // fsck part is excluded from timeout check. It only runs for user initiated shutdown
- // and should not affect reboot time.
- if (stat == UMOUNT_STAT_SUCCESS && runFsck) {
- for (auto& entry : blockDevRwPartitions) {
- DoFsck(entry);
- }
+ // even if it succeeds, still it is timeout and do not run fsck with all processes killed
+ UmountPartitions(0);
+ if (DUMP_ON_UMOUNT_FAILURE) DumpUmountDebuggingInfo();
}
+ if (stat == UMOUNT_STAT_SUCCESS && runFsck) {
+ // fsck part is excluded from timeout check. It only runs for user initiated shutdown
+ // and should not affect reboot time.
+ for (auto& entry : block_devices) {
+ entry.DoFsck();
+ }
+ }
return stat;
}
static void __attribute__((noreturn)) DoThermalOff() {
LOG(WARNING) << "Thermal system shutdown";
- DoSync();
+ sync();
RebootSystem(ANDROID_RB_THERMOFF, "");
abort();
}
@@ -426,8 +395,8 @@
// minimum safety steps before restarting
// 2. kill all services except ones that are necessary for the shutdown sequence.
- ServiceManager::GetInstance().ForEachService([&kill_after_apps](Service* s) {
- if (!s->IsShutdownCritical() || kill_after_apps.count(s->name())) s->Stop();
+ ServiceManager::GetInstance().ForEachService([](Service* s) {
+ if (!s->IsShutdownCritical()) s->Stop();
});
ServiceManager::GetInstance().ReapAnyOutstandingChildren();
@@ -435,12 +404,20 @@
Service* voldService = ServiceManager::GetInstance().FindServiceByName("vold");
if (voldService != nullptr && voldService->IsRunning()) {
ShutdownVold();
+ voldService->Stop();
} else {
LOG(INFO) << "vold not running, skipping vold shutdown";
}
+ // logcat stopped here
+ ServiceManager::GetInstance().ForEachService([&kill_after_apps](Service* s) {
+ if (kill_after_apps.count(s->name())) s->Stop();
+ });
// 4. sync, try umount, and optionally run fsck for user shutdown
- DoSync();
+ sync();
UmountStat stat = TryUmountAndFsck(runFsck, shutdownTimeout * 1000 - t.duration_ms());
+ // Follow what linux shutdown is doing: one more sync with little bit delay
+ sync();
+ std::this_thread::sleep_for(100ms);
LogShutdownTime(stat, &t);
// Reboot regardless of umount status. If umount fails, fsck after reboot will fix it.
RebootSystem(cmd, rebootTarget);
diff --git a/liblog/Android.bp b/liblog/Android.bp
index bc262db..e74aa82 100644
--- a/liblog/Android.bp
+++ b/liblog/Android.bp
@@ -108,14 +108,14 @@
}
ndk_library {
- name: "liblog.ndk",
+ name: "liblog",
symbol_file: "liblog.map.txt",
first_version: "9",
unversioned_until: "current",
}
llndk_library {
- name: "liblog.llndk",
+ name: "liblog",
symbol_file: "liblog.map.txt",
unversioned: true,
export_include_dirs: ["include_vndk"],
diff --git a/toolbox/Android.mk b/toolbox/Android.mk
index d6ead1a..aa755ed 100644
--- a/toolbox/Android.mk
+++ b/toolbox/Android.mk
@@ -95,3 +95,13 @@
LOCAL_MODULE := grep
LOCAL_POST_INSTALL_CMD := $(hide) $(foreach t,egrep fgrep,ln -sf grep $(TARGET_OUT)/bin/$(t);)
include $(BUILD_EXECUTABLE)
+
+
+# We build gzip separately, so it can provide gunzip and zcat too.
+include $(CLEAR_VARS)
+LOCAL_MODULE := gzip
+LOCAL_SRC_FILES := gzip.c
+LOCAL_CFLAGS += -Wall -Werror
+LOCAL_SHARED_LIBRARIES += libz
+LOCAL_POST_INSTALL_CMD := $(hide) $(foreach t,gunzip zcat,ln -sf gzip $(TARGET_OUT)/bin/$(t);)
+include $(BUILD_EXECUTABLE)
diff --git a/toolbox/gzip.c b/toolbox/gzip.c
new file mode 100644
index 0000000..62c4518
--- /dev/null
+++ b/toolbox/gzip.c
@@ -0,0 +1,261 @@
+/* gzip.c - gzip/gunzip/zcat tools for gzip data
+ *
+ * Copyright 2017 The Android Open Source Project
+ *
+ * GZIP RFC: http://www.ietf.org/rfc/rfc1952.txt
+
+TODO: port to toybox.
+
+*/
+
+#define _GNU_SOURCE
+
+#include <errno.h>
+#include <error.h>
+#include <fcntl.h>
+#include <getopt.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/stat.h>
+
+#include <zlib.h>
+
+// toybox-style flags/globals.
+#define FLAG_c 1
+#define FLAG_d 2
+#define FLAG_f 4
+#define FLAG_k 8
+static struct {
+ int optflags;
+} toys;
+static struct {
+ int level;
+} TT;
+
+static void xstat(const char *path, struct stat *sb)
+{
+ if (stat(path, sb)) error(1, errno, "stat %s", path);
+}
+
+static void fix_time(const char *path, struct stat *sb)
+{
+ struct timespec times[] = { sb->st_atim, sb->st_mtim };
+
+ if (utimensat(AT_FDCWD, path, times, 0)) error(1, errno, "utimes");
+}
+
+static FILE *xfdopen(const char *name, int flags, mode_t open_mode,
+ const char *mode)
+{
+ FILE *fp;
+ int fd;
+
+ if (!strcmp(name, "-")) fd = dup((*mode == 'r') ? 0 : 1);
+ else fd = open(name, flags, open_mode);
+
+ if (fd == -1) error(1, errno, "open %s (%s)", name, mode);
+ fp = fdopen(fd, mode);
+ if (fp == NULL) error(1, errno, "fopen %s (%s)", name, mode);
+ return fp;
+}
+
+static gzFile xgzopen(const char *name, int flags, mode_t open_mode,
+ const char *mode)
+{
+ gzFile f;
+ int fd;
+
+ if (!strcmp(name, "-")) fd = dup((*mode == 'r') ? 0 : 1);
+ else fd = open(name, flags, open_mode);
+
+ if (fd == -1) error(1, errno, "open %s (%s)", name, mode);
+ f = gzdopen(fd, mode);
+ if (f == NULL) error(1, errno, "gzdopen %s (%s)", name, mode);
+ return f;
+}
+
+static void gzfatal(gzFile f, char *what)
+{
+ int err;
+ const char *msg = gzerror(f, &err);
+
+ error(1, (err == Z_ERRNO) ? errno : 0, "%s: %s", what, msg);
+}
+
+static void gunzip(char *arg)
+{
+ struct stat sb;
+ char buf[BUFSIZ];
+ int len, both_files;
+ char *in_name, *out_name;
+ gzFile in;
+ FILE *out;
+
+ // "gunzip x.gz" will decompress "x.gz" to "x".
+ len = strlen(arg);
+ if (len > 3 && !strcmp(arg+len-3, ".gz")) {
+ in_name = strdup(arg);
+ out_name = strdup(arg);
+ out_name[len-3] = '\0';
+ } else if (!strcmp(arg, "-")) {
+ // "-" means stdin; assume output to stdout.
+ // TODO: require -f to read compressed data from tty?
+ in_name = strdup("-");
+ out_name = strdup("-");
+ } else error(1, 0, "unknown suffix");
+
+ if (toys.optflags&FLAG_c) {
+ free(out_name);
+ out_name = strdup("-");
+ }
+
+ both_files = strcmp(in_name, "-") && strcmp(out_name, "-");
+ if (both_files) xstat(in_name, &sb);
+
+ in = xgzopen(in_name, O_RDONLY, 0, "r");
+ out = xfdopen(out_name, O_CREAT|O_WRONLY|((toys.optflags&FLAG_f)?0:O_EXCL),
+ both_files?sb.st_mode:0666, "w");
+
+ while ((len = gzread(in, buf, sizeof(buf))) > 0) {
+ if (fwrite(buf, 1, len, out) != (size_t) len) error(1, errno, "fwrite");
+ }
+ if (len < 0) gzfatal(in, "gzread");
+ if (fclose(out)) error(1, errno, "fclose");
+ if (gzclose(in) != Z_OK) error(1, 0, "gzclose");
+
+ if (both_files) fix_time(out_name, &sb);
+ if (!(toys.optflags&(FLAG_c|FLAG_k))) unlink(in_name);
+ free(in_name);
+ free(out_name);
+}
+
+static void gzip(char *in_name)
+{
+ char buf[BUFSIZ];
+ size_t len;
+ char *out_name;
+ FILE *in;
+ gzFile out;
+ struct stat sb;
+ int both_files;
+
+ if (toys.optflags&FLAG_c) {
+ out_name = strdup("-");
+ } else {
+ if (asprintf(&out_name, "%s.gz", in_name) == -1) {
+ error(1, errno, "asprintf");
+ }
+ }
+
+ both_files = strcmp(in_name, "-") && strcmp(out_name, "-");
+ if (both_files) xstat(in_name, &sb);
+
+ snprintf(buf, sizeof(buf), "w%d", TT.level);
+ in = xfdopen(in_name, O_RDONLY, 0, "r");
+ out = xgzopen(out_name, O_CREAT|O_WRONLY|((toys.optflags&FLAG_f)?0:O_EXCL),
+ both_files?sb.st_mode:0, buf);
+
+ while ((len = fread(buf, 1, sizeof(buf), in)) > 0) {
+ if (gzwrite(out, buf, len) != (int) len) gzfatal(out, "gzwrite");
+ }
+ if (ferror(in)) error(1, errno, "fread");
+ if (fclose(in)) error(1, errno, "fclose");
+ if (gzclose(out) != Z_OK) error(1, 0, "gzclose");
+
+ if (both_files) fix_time(out_name, &sb);
+ if (!(toys.optflags&(FLAG_c|FLAG_k))) unlink(in_name);
+ free(out_name);
+}
+
+static void do_file(char *arg)
+{
+ if (toys.optflags&FLAG_d) gunzip(arg);
+ else gzip(arg);
+}
+
+static void usage()
+{
+ char *cmd = basename(getprogname());
+
+ printf("usage: %s [-c] [-d] [-f] [-#] [FILE...]\n", cmd);
+ printf("\n");
+ if (!strcmp(cmd, "zcat")) {
+ printf("Decompress files to stdout. Like `gzip -dc`.\n");
+ printf("\n");
+ printf("-c\tOutput to stdout\n");
+ printf("-f\tForce: allow read from tty\n");
+ } else if (!strcmp(cmd, "gunzip")) {
+ printf("Decompress files. With no files, decompresses stdin to stdout.\n");
+ printf("On success, the input files are removed and replaced by new\n");
+ printf("files without the .gz suffix.\n");
+ printf("\n");
+ printf("-c\tOutput to stdout\n");
+ printf("-f\tForce: allow read from tty\n");
+ printf("-k\tKeep input files (don't remove)\n");
+ } else { // gzip
+ printf("Compress files. With no files, compresses stdin to stdout.\n");
+ printf("On success, the input files are removed and replaced by new\n");
+ printf("files with the .gz suffix.\n");
+ printf("\n");
+ printf("-c\tOutput to stdout\n");
+ printf("-d\tDecompress (act as gunzip)\n");
+ printf("-f\tForce: allow overwrite of output file\n");
+ printf("-k\tKeep input files (don't remove)\n");
+ printf("-#\tCompression level 1-9 (1:fastest, 6:default, 9:best)\n");
+ }
+ printf("\n");
+}
+
+int main(int argc, char *argv[])
+{
+ char *cmd = basename(argv[0]);
+ int opt_ch;
+
+ toys.optflags = 0;
+ TT.level = 6;
+
+ if (!strcmp(cmd, "gunzip")) {
+ // gunzip == gzip -d
+ toys.optflags = FLAG_d;
+ } else if (!strcmp(cmd, "zcat")) {
+ // zcat == gzip -dc
+ toys.optflags = (FLAG_c|FLAG_d);
+ }
+
+ while ((opt_ch = getopt(argc, argv, "cdfhk123456789")) != -1) {
+ switch (opt_ch) {
+ case 'c': toys.optflags |= FLAG_c; break;
+ case 'd': toys.optflags |= FLAG_d; break;
+ case 'f': toys.optflags |= FLAG_f; break;
+ case 'k': toys.optflags |= FLAG_k; break;
+
+ case '1':
+ case '2':
+ case '3':
+ case '4':
+ case '5':
+ case '6':
+ case '7':
+ case '8':
+ case '9':
+ TT.level = opt_ch - '0';
+ break;
+
+ default:
+ usage();
+ return 1;
+ }
+ }
+
+ if (optind == argc) {
+ // With no arguments, we go from stdin to stdout.
+ toys.optflags |= FLAG_c;
+ do_file("-");
+ return 0;
+ }
+
+ // Otherwise process each file in turn.
+ while (optind < argc) do_file(argv[optind++]);
+ return 0;
+}