Merge "Remove .(ll)ndk suffix from (ll)ndk_library"
diff --git a/debuggerd/libdebuggerd/utility.cpp b/debuggerd/libdebuggerd/utility.cpp
index 22fde5e..7f450e6 100644
--- a/debuggerd/libdebuggerd/utility.cpp
+++ b/debuggerd/libdebuggerd/utility.cpp
@@ -22,16 +22,22 @@
#include <signal.h>
#include <string.h>
#include <sys/ptrace.h>
+#include <sys/uio.h>
#include <sys/wait.h>
#include <unistd.h>
#include <string>
+#include <android-base/logging.h>
+#include <android-base/properties.h>
#include <android-base/stringprintf.h>
+#include <android-base/strings.h>
#include <android-base/unique_fd.h>
#include <backtrace/Backtrace.h>
#include <log/log.h>
+using android::base::unique_fd;
+
// Whitelist output desired in the logcat output.
bool is_allowed_in_logcat(enum logtype ltype) {
if ((ltype == HEADER)
@@ -42,6 +48,19 @@
return false;
}
+static bool should_write_to_kmsg() {
+ // Write to kmsg if tombstoned isn't up, and we're able to do so.
+ if (!android::base::GetBoolProperty("ro.debuggable", false)) {
+ return false;
+ }
+
+ if (android::base::GetProperty("init.svc.tombstoned", "") == "running") {
+ return false;
+ }
+
+ return true;
+}
+
__attribute__((__weak__, visibility("default")))
void _LOG(log_t* log, enum logtype ltype, const char* fmt, ...) {
bool write_to_tombstone = (log->tfd != -1);
@@ -49,6 +68,7 @@
&& log->crashed_tid != -1
&& log->current_tid != -1
&& (log->crashed_tid == log->current_tid);
+ static bool write_to_kmsg = should_write_to_kmsg();
char buf[512];
va_list ap;
@@ -70,6 +90,30 @@
if (log->amfd_data != nullptr) {
*log->amfd_data += buf;
}
+
+ if (write_to_kmsg) {
+ unique_fd kmsg_fd(open("/dev/kmsg_debug", O_WRONLY | O_APPEND | O_CLOEXEC));
+ if (kmsg_fd.get() >= 0) {
+ // Our output might contain newlines which would otherwise be handled by the android logger.
+ // Split the lines up ourselves before sending to the kernel logger.
+ if (buf[len - 1] == '\n') {
+ buf[len - 1] = '\0';
+ }
+
+ std::vector<std::string> fragments = android::base::Split(buf, "\n");
+ for (const std::string& fragment : fragments) {
+ static constexpr char prefix[] = "<3>DEBUG: ";
+ struct iovec iov[3];
+ iov[0].iov_base = const_cast<char*>(prefix);
+ iov[0].iov_len = strlen(prefix);
+ iov[1].iov_base = const_cast<char*>(fragment.c_str());
+ iov[1].iov_len = fragment.length();
+ iov[2].iov_base = const_cast<char*>("\n");
+ iov[2].iov_len = 1;
+ TEMP_FAILURE_RETRY(writev(kmsg_fd.get(), iov, 3));
+ }
+ }
+ }
}
}
@@ -205,7 +249,7 @@
}
void read_with_default(const char* path, char* buf, size_t len, const char* default_value) {
- android::base::unique_fd fd(open(path, O_RDONLY));
+ unique_fd fd(open(path, O_RDONLY | O_CLOEXEC));
if (fd != -1) {
int rc = TEMP_FAILURE_RETRY(read(fd.get(), buf, len - 1));
if (rc != -1) {
diff --git a/include/nativebridge/native_bridge.h b/include/nativebridge/native_bridge.h
index 45266de..929b8ae 100644
--- a/include/nativebridge/native_bridge.h
+++ b/include/nativebridge/native_bridge.h
@@ -116,14 +116,25 @@
// Use NativeBridgeIsSupported() instead in non-namespace scenario.
bool NativeBridgeIsPathSupported(const char* path);
-// Initializes public and anonymous namespace at native bridge side.
+// Initializes anonymous namespace.
+// NativeBridge's peer of android_init_anonymous_namespace() of dynamic linker.
+//
+// The anonymous namespace is used in the case when a NativeBridge implementation
+// cannot identify the caller of dlopen/dlsym which happens for the code not loaded
+// by dynamic linker; for example calls from the mono-compiled code.
//
// Starting with v3, NativeBridge has two scenarios: with/without namespace.
// Should not use in non-namespace scenario.
-bool NativeBridgeInitNamespace(const char* public_ns_sonames,
- const char* anon_ns_library_path);
+bool NativeBridgeInitAnonymousNamespace(const char* public_ns_sonames,
+ const char* anon_ns_library_path);
-// Create a namespace and pass the key of related namespaces to native bridge.
+// Create new namespace in which native libraries will be loaded.
+// NativeBridge's peer of android_create_namespace() of dynamic linker.
+//
+// The libraries in the namespace are searched by folowing order:
+// 1. ld_library_path (Think of this as namespace-local LD_LIBRARY_PATH)
+// 2. In directories specified by DT_RUNPATH of the "needed by" binary.
+// 3. deault_library_path (This of this as namespace-local default library path)
//
// Starting with v3, NativeBridge has two scenarios: with/without namespace.
// Should not use in non-namespace scenario.
@@ -134,7 +145,17 @@
const char* permitted_when_isolated_path,
native_bridge_namespace_t* parent_ns);
+// Creates a link which shares some libraries from one namespace to another.
+// NativeBridge's peer of android_link_namespaces() of dynamic linker.
+//
+// Starting with v3, NativeBridge has two scenarios: with/without namespace.
+// Should not use in non-namespace scenario.
+bool NativeBridgeLinkNamespaces(native_bridge_namespace_t* from, native_bridge_namespace_t* to,
+ const char* shared_libs_sonames);
+
// Load a shared library with namespace key that is supported by the native bridge.
+// NativeBridge's peer of android_dlopen_ext() of dynamic linker, only supports namespace
+// extension.
//
// Starting with v3, NativeBridge has two scenarios: with/without namespace.
// Use NativeBridgeLoadLibrary() instead in non-namespace scenario.
@@ -152,7 +173,7 @@
// Parameters:
// runtime_cbs [IN] the pointer to NativeBridgeRuntimeCallbacks.
// Returns:
- // true iff initialization was successful.
+ // true if initialization was successful.
bool (*initialize)(const NativeBridgeRuntimeCallbacks* runtime_cbs, const char* private_dir,
const char* instruction_set);
@@ -194,10 +215,10 @@
// instruction set.
//
// Parameters:
- // instruction_set [IN] the instruction set of the app
+ // instruction_set [IN] the instruction set of the app
// Returns:
- // NULL if not supported by native bridge.
- // Otherwise, return all environment values to be set after fork.
+ // NULL if not supported by native bridge.
+ // Otherwise, return all environment values to be set after fork.
const struct NativeBridgeRuntimeValues* (*getAppEnv)(const char* instruction_set);
// Added callbacks in version 2.
@@ -206,9 +227,9 @@
// forwards- or backwards-compatible, and libnativebridge will then stop using it.
//
// Parameters:
- // bridge_version [IN] the version of libnativebridge.
+ // bridge_version [IN] the version of libnativebridge.
// Returns:
- // true iff the native bridge supports the given version of libnativebridge.
+ // true if the native bridge supports the given version of libnativebridge.
bool (*isCompatibleWith)(uint32_t bridge_version);
// A callback to retrieve a native bridge's signal handler for the specified signal. The runtime
@@ -217,12 +238,12 @@
// that will potentially lead to cycles.
//
// Parameters:
- // signal [IN] the signal for which the handler is asked for. Currently, only SIGSEGV is
+ // signal [IN] the signal for which the handler is asked for. Currently, only SIGSEGV is
// supported by the runtime.
// Returns:
- // NULL if the native bridge doesn't use a handler or doesn't want it to be managed by the
- // runtime.
- // Otherwise, a pointer to the signal handler.
+ // NULL if the native bridge doesn't use a handler or doesn't want it to be managed by the
+ // runtime.
+ // Otherwise, a pointer to the signal handler.
NativeBridgeSignalHandlerFn (*getSignalHandler)(int signal);
// Added callbacks in version 3.
@@ -231,7 +252,7 @@
// to zero then the dynamic library is unloaded.
//
// Parameters:
- // handle [IN] the handler of a dynamic library.
+ // handle [IN] the handler of a dynamic library.
//
// Returns:
// 0 on success, and nonzero on error.
@@ -257,33 +278,36 @@
// Use isSupported instead in non-namespace scenario.
bool (*isPathSupported)(const char* library_path);
- // Initializes anonymous namespace at native bridge side and pass the key of
- // two namespaces(default and anonymous) owned by dynamic linker to native bridge.
+ // Initializes anonymous namespace at native bridge side.
+ // NativeBridge's peer of android_init_anonymous_namespace() of dynamic linker.
+ //
+ // The anonymous namespace is used in the case when a NativeBridge implementation
+ // cannot identify the caller of dlopen/dlsym which happens for the code not loaded
+ // by dynamic linker; for example calls from the mono-compiled code.
//
// Parameters:
- // public_ns_sonames [IN] the name of "public" libraries.
- // anon_ns_library_path [IN] the library search path of (anonymous) namespace.
+ // public_ns_sonames [IN] the name of "public" libraries.
+ // anon_ns_library_path [IN] the library search path of (anonymous) namespace.
// Returns:
- // true if the pass is ok.
- // Otherwise, false.
+ // true if the pass is ok.
+ // Otherwise, false.
//
// Starting with v3, NativeBridge has two scenarios: with/without namespace.
// Should not use in non-namespace scenario.
- bool (*initNamespace)(const char* public_ns_sonames,
- const char* anon_ns_library_path);
+ bool (*initAnonymousNamespace)(const char* public_ns_sonames, const char* anon_ns_library_path);
-
- // Create a namespace and pass the key of releated namespaces to native bridge.
+ // Create new namespace in which native libraries will be loaded.
+ // NativeBridge's peer of android_create_namespace() of dynamic linker.
//
// Parameters:
- // name [IN] the name of the namespace.
- // ld_library_path [IN] the first set of library search paths of the namespace.
- // default_library_path [IN] the second set of library search path of the namespace.
- // type [IN] the attribute of the namespace.
- // permitted_when_isolated_path [IN] the permitted path for isolated namespace(if it is).
- // parent_ns [IN] the pointer of the parent namespace to be inherited from.
+ // name [IN] the name of the namespace.
+ // ld_library_path [IN] the first set of library search paths of the namespace.
+ // default_library_path [IN] the second set of library search path of the namespace.
+ // type [IN] the attribute of the namespace.
+ // permitted_when_isolated_path [IN] the permitted path for isolated namespace(if it is).
+ // parent_ns [IN] the pointer of the parent namespace to be inherited from.
// Returns:
- // native_bridge_namespace_t* for created namespace or nullptr in the case of error.
+ // native_bridge_namespace_t* for created namespace or nullptr in the case of error.
//
// Starting with v3, NativeBridge has two scenarios: with/without namespace.
// Should not use in non-namespace scenario.
@@ -294,7 +318,25 @@
const char* permitted_when_isolated_path,
native_bridge_namespace_t* parent_ns);
+ // Creates a link which shares some libraries from one namespace to another.
+ // NativeBridge's peer of android_link_namespaces() of dynamic linker.
+ //
+ // Parameters:
+ // from [IN] the namespace where libraries are accessed.
+ // to [IN] the namespace where libraries are loaded.
+ // shared_libs_sonames [IN] the libraries to be shared.
+ //
+ // Returns:
+ // Whether successed or not.
+ //
+ // Starting with v3, NativeBridge has two scenarios: with/without namespace.
+ // Should not use in non-namespace scenario.
+ bool (*linkNamespaces)(native_bridge_namespace_t* from, native_bridge_namespace_t* to,
+ const char* shared_libs_sonames);
+
// Load a shared library within a namespace.
+ // NativeBridge's peer of android_dlopen_ext() of dynamic linker, only supports namespace
+ // extension.
//
// Parameters:
// libpath [IN] path to the shared library
diff --git a/init/Android.mk b/init/Android.mk
index 730ffc4..dbbf40a 100644
--- a/init/Android.mk
+++ b/init/Android.mk
@@ -8,12 +8,16 @@
init_options += \
-DALLOW_LOCAL_PROP_OVERRIDE=1 \
-DALLOW_PERMISSIVE_SELINUX=1 \
- -DREBOOT_BOOTLOADER_ON_PANIC=1
+ -DREBOOT_BOOTLOADER_ON_PANIC=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
+ -DREBOOT_BOOTLOADER_ON_PANIC=0 \
+ -DWORLD_WRITABLE_KMSG=0 \
+ -DDUMP_ON_UMOUNT_FAILURE=0
endif
ifneq (,$(filter eng,$(TARGET_BUILD_VARIANT)))
diff --git a/init/builtins.cpp b/init/builtins.cpp
index e0a98f5..7298dff 100644
--- a/init/builtins.cpp
+++ b/init/builtins.cpp
@@ -33,6 +33,7 @@
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/syscall.h>
+#include <sys/system_properties.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/wait.h>
diff --git a/init/init.cpp b/init/init.cpp
index 023d727..543f38e 100644
--- a/init/init.cpp
+++ b/init/init.cpp
@@ -890,6 +890,9 @@
LOG(INFO) << "Running restorecon...";
restorecon("/dev");
restorecon("/dev/kmsg");
+ if constexpr (WORLD_WRITABLE_KMSG) {
+ restorecon("/dev/kmsg_debug");
+ }
restorecon("/dev/socket");
restorecon("/dev/random");
restorecon("/dev/urandom");
@@ -1166,7 +1169,13 @@
setgroups(arraysize(groups), groups);
mount("sysfs", "/sys", "sysfs", 0, NULL);
mount("selinuxfs", "/sys/fs/selinux", "selinuxfs", 0, NULL);
+
mknod("/dev/kmsg", S_IFCHR | 0600, makedev(1, 11));
+
+ if constexpr (WORLD_WRITABLE_KMSG) {
+ mknod("/dev/kmsg_debug", S_IFCHR | 0622, makedev(1, 11));
+ }
+
mknod("/dev/random", S_IFCHR | 0666, makedev(1, 8));
mknod("/dev/urandom", S_IFCHR | 0666, makedev(1, 9));
diff --git a/init/property_service.cpp b/init/property_service.cpp
index 9857223..2aa89ff 100644
--- a/init/property_service.cpp
+++ b/init/property_service.cpp
@@ -24,6 +24,7 @@
#include <limits.h>
#include <netinet/in.h>
#include <stdarg.h>
+#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
diff --git a/init/property_service.h b/init/property_service.h
index 585cd0f..9a5b6f6 100644
--- a/init/property_service.h
+++ b/init/property_service.h
@@ -17,9 +17,7 @@
#ifndef _INIT_PROPERTY_H
#define _INIT_PROPERTY_H
-#include <stddef.h>
#include <sys/socket.h>
-#include <sys/system_properties.h>
#include <string>
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/init/service.cpp b/init/service.cpp
index 8cb8dc1..4adbbf0 100644
--- a/init/service.cpp
+++ b/init/service.cpp
@@ -24,6 +24,7 @@
#include <sys/prctl.h>
#include <sys/resource.h>
#include <sys/stat.h>
+#include <sys/system_properties.h>
#include <sys/time.h>
#include <sys/wait.h>
#include <termios.h>
diff --git a/libnativebridge/native_bridge.cc b/libnativebridge/native_bridge.cc
index 83f35b1..050373a 100644
--- a/libnativebridge/native_bridge.cc
+++ b/libnativebridge/native_bridge.cc
@@ -573,11 +573,11 @@
return false;
}
-bool NativeBridgeInitNamespace(const char* public_ns_sonames,
- const char* anon_ns_library_path) {
+bool NativeBridgeInitAnonymousNamespace(const char* public_ns_sonames,
+ const char* anon_ns_library_path) {
if (NativeBridgeInitialized()) {
if (isCompatibleWith(NAMESPACE_VERSION)) {
- return callbacks->initNamespace(public_ns_sonames, anon_ns_library_path);
+ return callbacks->initAnonymousNamespace(public_ns_sonames, anon_ns_library_path);
} else {
ALOGE("not compatible with version %d, cannot init namespace", NAMESPACE_VERSION);
}
@@ -608,6 +608,19 @@
return nullptr;
}
+bool NativeBridgeLinkNamespaces(native_bridge_namespace_t* from, native_bridge_namespace_t* to,
+ const char* shared_libs_sonames) {
+ if (NativeBridgeInitialized()) {
+ if (isCompatibleWith(NAMESPACE_VERSION)) {
+ return callbacks->linkNamespaces(from, to, shared_libs_sonames);
+ } else {
+ ALOGE("not compatible with version %d, cannot init namespace", NAMESPACE_VERSION);
+ }
+ }
+
+ return false;
+}
+
void* NativeBridgeLoadLibraryExt(const char* libpath, int flag, native_bridge_namespace_t* ns) {
if (NativeBridgeInitialized()) {
if (isCompatibleWith(NAMESPACE_VERSION)) {
diff --git a/libnativebridge/tests/Android.mk b/libnativebridge/tests/Android.mk
index 4c3e862..c9468f0 100644
--- a/libnativebridge/tests/Android.mk
+++ b/libnativebridge/tests/Android.mk
@@ -24,7 +24,7 @@
NativeBridge3UnloadLibrary_test.cpp \
NativeBridge3GetError_test.cpp \
NativeBridge3IsPathSupported_test.cpp \
- NativeBridge3InitNamespace_test.cpp \
+ NativeBridge3InitAnonymousNamespace_test.cpp \
NativeBridge3CreateNamespace_test.cpp \
NativeBridge3LoadLibraryExt_test.cpp
diff --git a/libnativebridge/tests/DummyNativeBridge3.cpp b/libnativebridge/tests/DummyNativeBridge3.cpp
index 13fce85..4ef1c82 100644
--- a/libnativebridge/tests/DummyNativeBridge3.cpp
+++ b/libnativebridge/tests/DummyNativeBridge3.cpp
@@ -76,8 +76,8 @@
return true;
}
-extern "C" bool native_bridge3_initNamespace(const char* /* public_ns_sonames */,
- const char* /* anon_ns_library_path */) {
+extern "C" bool native_bridge3_initAnonymousNamespace(const char* /* public_ns_sonames */,
+ const char* /* anon_ns_library_path */) {
return true;
}
@@ -91,30 +91,34 @@
return nullptr;
}
+extern "C" bool native_bridge3_linkNamespaces(android::native_bridge_namespace_t* /* from */,
+ android::native_bridge_namespace_t* /* to */,
+ const char* /* shared_libs_soname */) {
+ return true;
+}
+
extern "C" void* native_bridge3_loadLibraryExt(const char* /* libpath */,
int /* flag */,
android::native_bridge_namespace_t* /* ns */) {
return nullptr;
}
-
-android::NativeBridgeCallbacks NativeBridgeItf {
- // v1
- .version = 3,
- .initialize = &native_bridge3_initialize,
- .loadLibrary = &native_bridge3_loadLibrary,
- .getTrampoline = &native_bridge3_getTrampoline,
- .isSupported = &native_bridge3_isSupported,
- .getAppEnv = &native_bridge3_getAppEnv,
- // v2
- .isCompatibleWith = &native_bridge3_isCompatibleWith,
- .getSignalHandler = &native_bridge3_getSignalHandler,
- // v3
- .unloadLibrary = &native_bridge3_unloadLibrary,
- .getError = &native_bridge3_getError,
- .isPathSupported = &native_bridge3_isPathSupported,
- .initNamespace = &native_bridge3_initNamespace,
- .createNamespace = &native_bridge3_createNamespace,
- .loadLibraryExt = &native_bridge3_loadLibraryExt
-};
-
+android::NativeBridgeCallbacks NativeBridgeItf{
+ // v1
+ .version = 3,
+ .initialize = &native_bridge3_initialize,
+ .loadLibrary = &native_bridge3_loadLibrary,
+ .getTrampoline = &native_bridge3_getTrampoline,
+ .isSupported = &native_bridge3_isSupported,
+ .getAppEnv = &native_bridge3_getAppEnv,
+ // v2
+ .isCompatibleWith = &native_bridge3_isCompatibleWith,
+ .getSignalHandler = &native_bridge3_getSignalHandler,
+ // v3
+ .unloadLibrary = &native_bridge3_unloadLibrary,
+ .getError = &native_bridge3_getError,
+ .isPathSupported = &native_bridge3_isPathSupported,
+ .initAnonymousNamespace = &native_bridge3_initAnonymousNamespace,
+ .createNamespace = &native_bridge3_createNamespace,
+ .linkNamespaces = &native_bridge3_linkNamespaces,
+ .loadLibraryExt = &native_bridge3_loadLibraryExt};
diff --git a/libnativebridge/tests/NativeBridge3InitAnonymousNamespace_test.cpp b/libnativebridge/tests/NativeBridge3InitAnonymousNamespace_test.cpp
new file mode 100644
index 0000000..b0d6b09
--- /dev/null
+++ b/libnativebridge/tests/NativeBridge3InitAnonymousNamespace_test.cpp
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "NativeBridgeTest.h"
+
+namespace android {
+
+constexpr const char* kNativeBridgeLibrary3 = "libnativebridge3-dummy.so";
+
+TEST_F(NativeBridgeTest, V3_InitAnonymousNamespace) {
+ // Init
+ ASSERT_TRUE(LoadNativeBridge(kNativeBridgeLibrary3, nullptr));
+ ASSERT_TRUE(NativeBridgeAvailable());
+ ASSERT_TRUE(PreInitializeNativeBridge(".", "isa"));
+ ASSERT_TRUE(NativeBridgeAvailable());
+ ASSERT_TRUE(InitializeNativeBridge(nullptr, nullptr));
+ ASSERT_TRUE(NativeBridgeAvailable());
+
+ ASSERT_EQ(3U, NativeBridgeGetVersion());
+ ASSERT_EQ(true, NativeBridgeInitAnonymousNamespace(nullptr, nullptr));
+
+ // Clean-up code_cache
+ ASSERT_EQ(0, rmdir(kCodeCache));
+}
+
+} // namespace android
diff --git a/libnativebridge/tests/NativeBridge3InitNamespace_test.cpp b/libnativebridge/tests/NativeBridge3InitNamespace_test.cpp
deleted file mode 100644
index ae0fd2b..0000000
--- a/libnativebridge/tests/NativeBridge3InitNamespace_test.cpp
+++ /dev/null
@@ -1,39 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include "NativeBridgeTest.h"
-
-namespace android {
-
-constexpr const char* kNativeBridgeLibrary3 = "libnativebridge3-dummy.so";
-
-TEST_F(NativeBridgeTest, V3_InitNamespace) {
- // Init
- ASSERT_TRUE(LoadNativeBridge(kNativeBridgeLibrary3, nullptr));
- ASSERT_TRUE(NativeBridgeAvailable());
- ASSERT_TRUE(PreInitializeNativeBridge(".", "isa"));
- ASSERT_TRUE(NativeBridgeAvailable());
- ASSERT_TRUE(InitializeNativeBridge(nullptr, nullptr));
- ASSERT_TRUE(NativeBridgeAvailable());
-
- ASSERT_EQ(3U, NativeBridgeGetVersion());
- ASSERT_EQ(true, NativeBridgeInitNamespace(nullptr, nullptr));
-
- // Clean-up code_cache
- ASSERT_EQ(0, rmdir(kCodeCache));
-}
-
-} // namespace android
diff --git a/libnativeloader/native_loader.cpp b/libnativeloader/native_loader.cpp
index 74f2f1d..f3391d1 100644
--- a/libnativeloader/native_loader.cpp
+++ b/libnativeloader/native_loader.cpp
@@ -183,6 +183,11 @@
return false;
}
+ if (!NativeBridgeLinkNamespaces(ns, nullptr, public_libraries_.c_str())) {
+ *error_msg = NativeBridgeGetError();
+ return false;
+ }
+
native_loader_ns = NativeLoaderNamespace(ns);
}
@@ -324,8 +329,8 @@
// and now initialize native bridge namespaces if necessary.
if (NativeBridgeInitialized()) {
- initialized_ = NativeBridgeInitNamespace(public_libraries_.c_str(),
- is_native_bridge ? library_path : nullptr);
+ initialized_ = NativeBridgeInitAnonymousNamespace(public_libraries_.c_str(),
+ is_native_bridge ? library_path : nullptr);
if (!initialized_) {
*error_msg = NativeBridgeGetError();
}
diff --git a/rootdir/init.rc b/rootdir/init.rc
index ff96c14..3758362 100644
--- a/rootdir/init.rc
+++ b/rootdir/init.rc
@@ -301,9 +301,6 @@
start logd
start hwservicemanager
- # HALs required before data is mounted
- class_start early_hal
-
# once everything is setup, no need to modify /
mount rootfs rootfs / ro remount
# Mount shared so changes propagate into child namespaces
@@ -351,6 +348,10 @@
# create the lost+found directories, so as to enforce our permissions
mkdir /cache/lost+found 0770 root root
+on late-fs
+ # HALs required before storage encryption can get unlocked (FBE/FDE)
+ class_start early_hal
+
on post-fs-data
# We chown/chmod /data again so because mount is run as root + defaults
chown system system /data
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;
+}