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;
+}