Discussion:
[PATCH 1/2] Adding unmount functions to be used in shutdown
(too old to reply)
Gustavo Sverzut Barbieri
2010-10-06 01:21:23 UTC
Permalink
From: Fabiano Fidencio <***@profusion.mobi>

This functions will:
- umount all mount points that aren't API
- remount read-only all mount points that can't be umounted
- umount all swap devices.
- detach all loopback devices

TODO:
- umount dms

Mountpoints are being read from /proc/self/mountinfo.
Swaps are being read from /proc/swaps.
Loop devices from /sys/class/block/loop*.
---
.gitignore | 1 +
Makefile.am | 1 +
src/umount.c | 378 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
src/umount.h | 31 +++++
4 files changed, 411 insertions(+), 0 deletions(-)
create mode 100644 src/umount.c
create mode 100644 src/umount.h

diff --git a/.gitignore b/.gitignore
index 9ba0758..2ba000c 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,3 +1,4 @@
+systemd-shutdown
systemd-tmpfiles
systemd-readahead-collect
systemd-readahead-replay
diff --git a/Makefile.am b/Makefile.am
index c06f1ec..a9d1857 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -351,6 +351,7 @@ libsystemd_core_la_SOURCES = \
src/service.c \
src/automount.c \
src/mount.c \
+ src/umount.c \
src/swap.c \
src/device.c \
src/target.c \
diff --git a/src/umount.c b/src/umount.c
new file mode 100644
index 0000000..3031cd1
--- /dev/null
+++ b/src/umount.c
@@ -0,0 +1,378 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+/***
+ This file is part of systemd.
+
+ Copyright 2010 ProFUSION embedded systems
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <dirent.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <string.h>
+#include <sys/mount.h>
+#include <sys/swap.h>
+#include <unistd.h>
+
+#include "list.h"
+#include "mount-setup.h"
+#include "umount.h"
+#include "util.h"
+
+#define LOOP_CLR_FD 0x4C01
+
+typedef struct MountPoint {
+ char *path;
+ bool read_only;
+ LIST_FIELDS (struct MountPoint, mount_point);
+} MountPoint;
+
+static MountPoint *mount_point_alloc(char *path) {
+ MountPoint *mp;
+
+ if (!(mp = new(MountPoint, 1)))
+ return NULL;
+
+ mp->path = path;
+ mp->read_only = false;
+
+ return mp;
+}
+
+static void mount_point_remove_and_free(MountPoint *mount_point, MountPoint **mount_point_list_head) {
+ LIST_REMOVE(MountPoint, mount_point, *mount_point_list_head, mount_point);
+
+ free(mount_point->path);
+ free(mount_point);
+}
+
+static void mount_points_list_free(MountPoint **mount_point_list_head) {
+ while (*mount_point_list_head)
+ mount_point_remove_and_free(*mount_point_list_head, mount_point_list_head);
+}
+
+static int mount_points_list_get(MountPoint **mount_point_list_head) {
+ FILE *proc_self_mountinfo;
+ char *path, *p;
+ unsigned int i;
+ int r;
+
+ if (!(proc_self_mountinfo = fopen("/proc/self/mountinfo", "re")))
+ return -errno;
+
+ for (i = 1;; i++) {
+ int k;
+ MountPoint *mp;
+
+ path = p = NULL;
+
+ if ((k = fscanf(proc_self_mountinfo,
+ "%*s " /* (1) mount id */
+ "%*s " /* (2) parent id */
+ "%*s " /* (3) major:minor */
+ "%*s " /* (4) root */
+ "%ms " /* (5) mount point */
+ "%*s" /* (6) mount options */
+ "%*[^-]" /* (7) optional fields */
+ "- " /* (8) separator */
+ "%*s " /* (9) file system type */
+ "%*s" /* (10) mount source */
+ "%*s" /* (11) mount options 2 */
+ "%*[^\n]", /* some rubbish at the end */
+ &path)) != 1) {
+ if (k == EOF)
+ break;
+
+ log_warning("Failed to parse /proc/self/mountinfo:%u.", i);
+
+ free(path);
+ continue;
+ }
+
+ if (mount_point_is_api(path)) {
+ free(path);
+ continue;
+ }
+
+ if (!(p = cunescape(path))) {
+ r = -ENOMEM;
+ goto finish;
+ }
+
+ if (!(mp = mount_point_alloc(p))) {
+ r = -ENOMEM;
+ goto finish;
+ }
+ LIST_PREPEND(MountPoint, mount_point, *mount_point_list_head, mp);
+
+ free(path);
+ }
+
+ r = 0;
+
+finish:
+ fclose(proc_self_mountinfo);
+
+ free(path);
+
+ return r;
+}
+
+static int swap_list_get(MountPoint **swap_list_head) {
+ FILE *proc_swaps;
+ unsigned int i;
+ int r;
+
+ if (!(proc_swaps = fopen("/proc/swaps", "re")))
+ return -errno;
+
+ (void) fscanf(proc_swaps, "%*s %*s %*s %*s %*s\n");
+
+ for (i = 2;; i++) {
+ MountPoint *swap;
+ char *dev = NULL, *d;
+ int k;
+
+ if ((k = fscanf(proc_swaps,
+ "%ms " /* device/file */
+ "%*s " /* type of swap */
+ "%*s " /* swap size */
+ "%*s " /* used */
+ "%*s\n", /* priority */
+ &dev)) != 1) {
+
+ if (k == EOF)
+ break;
+
+ log_warning("Failed to parse /proc/swaps:%u.", i);
+
+ free(dev);
+ continue;
+ }
+
+ d = cunescape(dev);
+ free(dev);
+
+ if (!d) {
+ r = -ENOMEM;
+ goto finish;
+ }
+
+ swap = mount_point_alloc(d);
+ if (!swap) {
+ free(d);
+ r = -ENOMEM;
+ goto finish;
+ }
+
+ LIST_PREPEND(MountPoint, mount_point, *swap_list_head, swap);
+ }
+
+ r = 0;
+
+ finish:
+ fclose(proc_swaps);
+
+ return r;
+}
+
+static int loopback_list_get(MountPoint **loopback_list_head) {
+ DIR *dir;
+ struct dirent *d;
+ int r;
+
+ if ((dir = opendir("/sys/class/block")) == NULL)
+ return -errno;
+
+ while ((d = readdir(dir))) {
+ MountPoint *lb;
+ char buf[PATH_MAX];
+ char *loop;
+
+ if (!strneq(d->d_name, "loop", 4))
+ continue;
+
+ snprintf(buf, sizeof(buf), "/dev/%s", d->d_name);
+ if (access(buf, R_OK) != 0)
+ continue;
+
+ loop = cunescape(buf);
+ if (!loop) {
+ r = -ENOMEM;
+ goto finish;
+ }
+
+ lb = mount_point_alloc(loop);
+ if (!lb) {
+ free(loop);
+ r = -ENOMEM;
+ goto finish;
+ }
+
+ LIST_PREPEND(MountPoint, mount_point, *loopback_list_head, lb);
+ }
+
+ r = 0;
+
+finish:
+ closedir(dir);
+ return r;
+}
+
+static int delete_loopback(const char *device) {
+ int fd, r;
+
+ if ((fd = open(device, O_RDONLY)) < 0)
+ return -errno;
+
+ ioctl(fd, LOOP_CLR_FD, 0);
+ r = errno;
+ close_nointr(fd);
+
+ errno = r;
+ return -errno;
+}
+
+static int mount_points_list_umount(MountPoint **mount_point_list_head) {
+ MountPoint *mp, *mp_next;
+ int failed = 0;
+
+ LIST_FOREACH_SAFE(mount_point, mp, mp_next, *mount_point_list_head) {
+ if (streq(mp->path, "/"))
+ continue;
+
+ /* Trying to umount. Forcing to umount if busy (only for NFS mounts) */
+ if (umount2(mp->path, MNT_FORCE) == 0)
+ mount_point_remove_and_free(mp, mount_point_list_head);
+ else {
+ log_debug("could not unmount %s: %m", mp->path);
+ failed++;
+ }
+ }
+
+ return failed;
+}
+
+static int mount_points_list_remount_read_only(MountPoint **mount_point_list_head) {
+ MountPoint *mp, *mp_next;
+ int failed = 0;
+
+ LIST_FOREACH_SAFE(mount_point, mp, mp_next, *mount_point_list_head) {
+ if (mp->read_only)
+ continue;
+
+ /* Trying to remount read-only */
+ if (mount(NULL, mp->path, NULL, MS_MGC_VAL|MS_REMOUNT|MS_RDONLY, NULL) == 0) {
+ mp->read_only = true;
+ mount_point_remove_and_free(mp, mount_point_list_head);
+ } else {
+ log_debug("could not remount as read-only %s: %m", mp->path);
+ failed++;
+ }
+ }
+
+ return failed;
+}
+
+static int swap_points_list_off(MountPoint **swap_list_head) {
+ MountPoint *swap, *swap_next;
+ int failed = 0;
+
+ LIST_FOREACH_SAFE(mount_point, swap, swap_next, *swap_list_head) {
+ if (swapoff(swap->path) == 0)
+ mount_point_remove_and_free(swap, swap_list_head);
+ else {
+ log_debug("could not swapoff %s: %m", swap->path);
+ failed++;
+ }
+ }
+
+ return failed;
+}
+
+static int loopback_points_list_detach(MountPoint **loopback_list_head) {
+ MountPoint *loopback, *loopback_next;
+ int failed = 0;
+
+ LIST_FOREACH_SAFE(mount_point, loopback, loopback_next, *loopback_list_head) {
+ if (delete_loopback(loopback->path) == 0)
+ mount_point_remove_and_free(loopback, loopback_list_head);
+ else {
+ log_debug("could not delete loopback %s: %m", loopback->path);
+ failed++;
+ }
+ }
+
+ return failed;
+}
+
+int umount_all(void) {
+ int r;
+ LIST_HEAD(MountPoint, mp_list_head);
+
+ LIST_HEAD_INIT(MountPoint, mp_list_head);
+
+ r = mount_points_list_get(&mp_list_head);
+ if (r < 0)
+ goto end;
+
+ r = mount_points_list_umount(&mp_list_head);
+ if (r <= 0)
+ goto end;
+
+ r = mount_points_list_remount_read_only(&mp_list_head);
+
+ end:
+ mount_points_list_free(&mp_list_head);
+
+ return r;
+}
+
+int swapoff_all(void) {
+ int r;
+ LIST_HEAD(MountPoint, swap_list_head);
+
+ LIST_HEAD_INIT(MountPoint, swap_list_head);
+
+ r = swap_list_get(&swap_list_head);
+ if (r < 0)
+ goto end;
+
+ r = swap_points_list_off(&swap_list_head);
+
+ end:
+ mount_points_list_free(&swap_list_head);
+
+ return r;
+}
+
+int loopback_detach_all(void) {
+ int r;
+ LIST_HEAD(MountPoint, loopback_list_head);
+
+ LIST_HEAD_INIT(MountPoint, loopback_list_head);
+
+ r = loopback_list_get(&loopback_list_head);
+ if (r < 0)
+ goto end;
+
+ r = loopback_points_list_detach(&loopback_list_head);
+
+ end:
+ mount_points_list_free(&loopback_list_head);
+
+ return r;
+}
diff --git a/src/umount.h b/src/umount.h
new file mode 100644
index 0000000..aeccc00
--- /dev/null
+++ b/src/umount.h
@@ -0,0 +1,31 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+#ifndef fooumounthfoo
+#define fooumounthfoo
+
+/***
+ This file is part of systemd.
+
+ Copyright 2010 ProFUSION embedded systems
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+int umount_all(void);
+
+int swapoff_all(void);
+
+int loopback_detach_all(void);
+
+#endif
--
1.7.2.2
Gustavo Sverzut Barbieri
2010-10-06 01:21:24 UTC
Permalink
From: Fabiano Fidencio <***@profusion.mobi>

This functions are working as follows:
- Send a SIGTERM to all processes that may be finished
- Send a SIGKILL to all processes that still live and may be finished
- Try to unmount all mount points
- Try to remount read-only all mount points that can't be umounted
- Umount all swap devices
- Umount and detach all loopback devices
- Call [poweroff|halt|reboot|kexec]

TODO:
- Umount device-mapper.
- Make log work. So far it is being useless as we do not parse
/etc/systemd/system.conf, kernel command line but just
environment, however we're executed by init and thus have no
useful variables. Forcing it to target=kmsg/console and
level=debug also does not produce any output, however writing to
/dev/console does work (hack used during debug).
---
Makefile.am | 15 +++
src/shutdown.c | 304 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++
2 files changed, 319 insertions(+), 0 deletions(-)
create mode 100644 src/shutdown.c

diff --git a/Makefile.am b/Makefile.am
index a9d1857..72f998d 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -35,6 +35,7 @@ tmpfilesdir=$(sysconfdir)/tmpfiles.d
# And these are the special ones for /
rootdir=@rootdir@
rootbindir=$(rootdir)/bin
+rootsbindir=$(rootdir)/sbin
rootlibexecdir=$(rootdir)/lib/systemd
systemunitdir=$(rootdir)/lib/systemd/system

@@ -50,9 +51,11 @@ AM_CPPFLAGS = \
-DSESSION_DATA_UNIT_PATH=\"$(sessionunitdir)\" \
-DCGROUP_AGENT_PATH=\"$(rootlibexecdir)/systemd-cgroups-agent\" \
-DSYSTEMD_BINARY_PATH=\"$(rootbindir)/systemd\" \
+ -DSYSTEMD_SHUTDOWN_BINARY_PATH=\"$(rootlibexecdir)/systemd-shutdown\" \
-DSYSTEMCTL_BINARY_PATH=\"$(rootbindir)/systemctl\" \
-DRUNTIME_DIR=\"$(localstatedir)/run\" \
-DRANDOM_SEED=\"$(localstatedir)/lib/random-seed\" \
+ -DKEXEC_BINARY_PATH=\"$(rootsbindir)/kexec\" \
-I $(top_srcdir)/src

if TARGET_GENTOO
@@ -89,6 +92,7 @@ rootlibexec_PROGRAMS = \
systemd-update-utmp \
systemd-random-seed \
systemd-shutdownd \
+ systemd-shutdown \
systemd-modules-load \
systemd-remount-api-vfs \
systemd-kmsg-syslogd \
@@ -633,6 +637,17 @@ systemd_shutdownd_CFLAGS = \
systemd_shutdownd_LDADD = \
libsystemd-basic.la

+systemd_shutdown_SOURCES = \
+ src/mount-setup.c \
+ src/umount.c \
+ src/shutdown.c
+
+systemd_shutdown_CFLAGS = \
+ $(AM_CFLAGS)
+
+systemd_shutdown_LDADD = \
+ libsystemd-basic.la
+
systemd_modules_load_SOURCES = \
src/modules-load.c

diff --git a/src/shutdown.c b/src/shutdown.c
new file mode 100644
index 0000000..96c68f9
--- /dev/null
+++ b/src/shutdown.c
@@ -0,0 +1,304 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+/***
+ This file is part of systemd.
+
+ Copyright 2010 ProFUSION embedded systems
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <sys/mman.h>
+#include <sys/types.h>
+#include <sys/reboot.h>
+#include <linux/reboot.h>
+#include <sys/wait.h>
+#include <dirent.h>
+#include <errno.h>
+#include <unistd.h>
+#include <signal.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "log.h"
+#include "umount.h"
+#include "util.h"
+
+#define TIMEOUT_USEC (5 * USEC_PER_SEC)
+#define FINALIZE_ATTEMPTS 50
+#define FINALIZE_CRITICAL_ATTEMPTS 10
+
+_noreturn_ static void freeze(void) {
+ for (;;)
+ pause();
+}
+
+static bool ignore_proc(pid_t pid) {
+ if (pid == 1)
+ return true;
+
+ /* TODO: add more ignore rules here: device-mapper, etc */
+
+ return false;
+}
+
+static int killall(int sign) {
+ DIR *dir;
+ struct dirent *d;
+ unsigned int processes = 0;
+
+ if ((dir = opendir("/proc")) == NULL)
+ return -errno;
+
+ while ((d = readdir(dir))) {
+ pid_t pid;
+ if (parse_pid(d->d_name, &pid) < 0)
+ continue;
+
+ if (ignore_proc(pid))
+ continue;
+
+ if (kill(pid, sign) == 0)
+ processes++;
+ else
+ log_warning("Could not kill %d: %m", pid);
+ }
+
+ closedir(dir);
+
+ return processes;
+}
+
+static int send_signal(int sign) {
+ sigset_t mask, oldmask;
+ usec_t until;
+ int processes;
+ struct timespec ts;
+
+ sigemptyset(&mask);
+ sigaddset(&mask, SIGCHLD);
+ if (sigprocmask(SIG_BLOCK, &mask, &oldmask) != 0)
+ return -errno;
+
+ if (kill(-1, SIGSTOP) < 0)
+ log_warning("kill(-1, SIGSTOP): %m");
+
+ processes = killall(sign);
+
+ if (kill(-1, SIGCONT) < 0)
+ log_warning("kill(-1, SIGCONT): %m");
+
+ if (processes <= 0)
+ goto finish;
+
+ until = now(CLOCK_MONOTONIC) + TIMEOUT_USEC;
+ for (;;) {
+ usec_t n;
+
+ for (;;) {
+ n = now(CLOCK_MONOTONIC);
+ if (n >= until)
+ goto finish;
+
+ if (waitpid(-1, NULL, WNOHANG) <= 0)
+ break;
+
+ if (--processes == 0)
+ goto finish;
+ }
+
+ timespec_store(&ts, until - n);
+ if (sigtimedwait(&mask, NULL, &ts) != SIGCHLD)
+ log_warning("sigtimedwait did not return SIGCHLD: %m");
+ }
+
+finish:
+ sigprocmask(SIG_SETMASK, &oldmask, NULL);
+
+ return processes;
+}
+
+static int rescue_send_signal(int sign) {
+ sigset_t mask, oldmask;
+ usec_t until;
+ struct timespec ts;
+ int r;
+
+ sigemptyset(&mask);
+ sigaddset(&mask, SIGCHLD);
+ if (sigprocmask(SIG_BLOCK, &mask, &oldmask) != 0)
+ return -errno;
+
+ if (kill(-1, SIGSTOP) < 0)
+ log_warning("kill(-1, SIGSTOP): %m");
+
+ r = kill(-1, sign);
+ if (r < 0)
+ log_warning("kill(-1, %d): %m", sign);
+
+ if (kill(-1, SIGCONT) < 0)
+ log_warning("kill(-1, SIGCONT): %m");
+
+ if (r < 0)
+ goto finish;
+
+ until = now(CLOCK_MONOTONIC) + TIMEOUT_USEC;
+ for (;;) {
+ usec_t n;
+
+ for (;;) {
+ n = now(CLOCK_MONOTONIC);
+ if (n >= until)
+ goto finish;
+
+ if (waitpid(-1, NULL, WNOHANG) <= 0)
+ break;
+ }
+
+ timespec_store(&ts, until - n);
+ if (sigtimedwait(&mask, NULL, &ts) != SIGCHLD)
+ log_warning("sigtimedwait did not return SIGCHLD: %m");
+ }
+
+finish:
+ sigprocmask(SIG_SETMASK, &oldmask, NULL);
+
+ return r;
+}
+
+
+int main(int argc, char *argv[]) {
+ int cmd, r, retries;
+ bool need_umount = true, need_swapoff = true, need_loop_detach = true;
+
+ log_parse_environment();
+ log_set_target(LOG_TARGET_KMSG); /* syslog will die if not gone yet */
+ log_open();
+
+ if (getpid() != 1) {
+ log_debug("only init may exec this binary");
+ r = -EPERM;
+ goto error;
+ }
+
+ if (argc != 2) {
+ log_debug("invalid number of arguments");
+ r = -EINVAL;
+ goto error;
+ }
+
+ if (streq(argv[1], "reboot"))
+ cmd = RB_AUTOBOOT;
+ else if (streq(argv[1], "poweroff"))
+ cmd = RB_POWER_OFF;
+ else if (streq(argv[1], "halt"))
+ cmd = RB_HALT_SYSTEM;
+ else if (streq(argv[1], "kexec"))
+ cmd = LINUX_REBOOT_CMD_KEXEC;
+ else {
+ log_debug("unknown action %s", argv[1]);
+ r = -EINVAL;
+ goto error;
+ }
+
+ /* lock us into memory */
+ if (mlockall(MCL_CURRENT|MCL_FUTURE) != 0)
+ log_warning("cannot lock process memory: %m");
+
+ log_info("Sending SIGTERM To Processes");
+ r = send_signal(SIGTERM);
+ if (r < 0)
+ log_warning("cannot send SIGTERM to all process: %s", strerror(r));
+
+ log_info("Sending SIGKILL To Processes");
+ r = send_signal(SIGKILL);
+ if (r < 0)
+ log_warning("cannot send SIGKILL to all process: %s", strerror(r));
+
+
+ /* preventing that we won't block umounts */
+ if (chdir("/") != 0)
+ log_warning("cannot chdir(\"/\"). Unmounts likely to fail.");
+
+ /* umount all mountpoints, swaps, and loopback devices */
+ retries = FINALIZE_ATTEMPTS;
+ while (need_umount || need_swapoff || need_loop_detach) {
+ if (need_umount) {
+ log_info("Unmounting Filesystems");
+ r = umount_all();
+ if (r == 0)
+ need_umount = false;
+ else
+ log_warning("Not all filesystems unmounted");
+ }
+
+ if (need_swapoff) {
+ log_info("Disabling Swaps");
+ r = swapoff_all();
+ if (r == 0)
+ need_swapoff = false;
+ else
+ log_warning("Not all swaps are off ");
+ }
+
+ if (need_loop_detach) {
+ log_info("Umounting and Detaching Loopback Devices");
+ r = loopback_detach_all();
+ if (r == 0)
+ need_loop_detach = false;
+ else
+ log_warning("Not all loop devices detached");
+ }
+
+ if (need_umount || need_swapoff || need_loop_detach) {
+ retries--;
+
+ if (retries <= FINALIZE_CRITICAL_ATTEMPTS) {
+ log_warning("Approaching critical level to finalize filesystem and devices, try to kill all processes.");
+ rescue_send_signal(SIGTERM);
+ rescue_send_signal(SIGKILL);
+ }
+
+ if (retries > 0)
+ log_info("Action still required, %d tries left", retries);
+ else {
+ log_error("Tried enough but still action required need_umount=%d, need_swapoff=%d, need_loop_detach=%d", need_umount, need_swapoff, need_loop_detach);
+ r = -EBUSY;
+ goto error;
+ }
+ }
+ }
+
+ sync();
+
+ if (cmd == LINUX_REBOOT_CMD_KEXEC) {
+ const char *args[5] = {KEXEC_BINARY_PATH, "-e", "-x", "-f", NULL};
+ /* we cheat and exec kexec to avoid doing all its work */
+ execv(args[0], (char * const *) args);
+ log_warning("kexec not supported, falling back to reboot");
+ cmd = RB_AUTOBOOT;
+ }
+
+ reboot(cmd);
+ r = errno;
+
+ error:
+ sync();
+ if (r < 0)
+ r = -r;
+ log_error("Critical error while doing system shutdown: %s", strerror(r));
+ freeze();
+ return 0;
+}
--
1.7.2.2
Gustavo Sverzut Barbieri
2010-10-06 05:05:43 UTC
Permalink
From: Fabiano Fidencio <***@profusion.mobi>

This functions will:
- umount all mount points that aren't API
- remount read-only all mount points that can't be umounted
- umount all swap devices.
- detach all loopback devices

TODO:
- umount dms

Mountpoints are being read from /proc/self/mountinfo.
Swaps are being read from /proc/swaps.
Loop devices from /sys/class/block/loop*.
---
.gitignore | 1 +
Makefile.am | 1 +
src/umount.c | 380 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
src/umount.h | 31 +++++
4 files changed, 413 insertions(+), 0 deletions(-)
create mode 100644 src/umount.c
create mode 100644 src/umount.h

diff --git a/.gitignore b/.gitignore
index 9ba0758..2ba000c 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,3 +1,4 @@
+systemd-shutdown
systemd-tmpfiles
systemd-readahead-collect
systemd-readahead-replay
diff --git a/Makefile.am b/Makefile.am
index c06f1ec..a9d1857 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -351,6 +351,7 @@ libsystemd_core_la_SOURCES = \
src/service.c \
src/automount.c \
src/mount.c \
+ src/umount.c \
src/swap.c \
src/device.c \
src/target.c \
diff --git a/src/umount.c b/src/umount.c
new file mode 100644
index 0000000..96b9873
--- /dev/null
+++ b/src/umount.c
@@ -0,0 +1,380 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+/***
+ This file is part of systemd.
+
+ Copyright 2010 ProFUSION embedded systems
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <dirent.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <string.h>
+#include <sys/mount.h>
+#include <sys/swap.h>
+#include <unistd.h>
+
+#include "list.h"
+#include "mount-setup.h"
+#include "umount.h"
+#include "util.h"
+
+#define LOOP_CLR_FD 0x4C01
+
+typedef struct MountPoint {
+ char *path;
+ bool read_only;
+ LIST_FIELDS (struct MountPoint, mount_point);
+} MountPoint;
+
+static MountPoint *mount_point_alloc(char *path) {
+ MountPoint *mp;
+
+ if (!(mp = new(MountPoint, 1)))
+ return NULL;
+
+ mp->path = path;
+ mp->read_only = false;
+
+ return mp;
+}
+
+static void mount_point_remove_and_free(MountPoint *mount_point, MountPoint **mount_point_list_head) {
+ LIST_REMOVE(MountPoint, mount_point, *mount_point_list_head, mount_point);
+
+ free(mount_point->path);
+ free(mount_point);
+}
+
+static void mount_points_list_free(MountPoint **mount_point_list_head) {
+ while (*mount_point_list_head)
+ mount_point_remove_and_free(*mount_point_list_head, mount_point_list_head);
+}
+
+static int mount_points_list_get(MountPoint **mount_point_list_head) {
+ FILE *proc_self_mountinfo;
+ char *path, *p;
+ unsigned int i;
+ int r;
+
+ if (!(proc_self_mountinfo = fopen("/proc/self/mountinfo", "re")))
+ return -errno;
+
+ for (i = 1;; i++) {
+ int k;
+ MountPoint *mp;
+
+ path = p = NULL;
+
+ if ((k = fscanf(proc_self_mountinfo,
+ "%*s " /* (1) mount id */
+ "%*s " /* (2) parent id */
+ "%*s " /* (3) major:minor */
+ "%*s " /* (4) root */
+ "%ms " /* (5) mount point */
+ "%*s" /* (6) mount options */
+ "%*[^-]" /* (7) optional fields */
+ "- " /* (8) separator */
+ "%*s " /* (9) file system type */
+ "%*s" /* (10) mount source */
+ "%*s" /* (11) mount options 2 */
+ "%*[^\n]", /* some rubbish at the end */
+ &path)) != 1) {
+ if (k == EOF)
+ break;
+
+ log_warning("Failed to parse /proc/self/mountinfo:%u.", i);
+
+ free(path);
+ continue;
+ }
+
+ if (mount_point_is_api(path)) {
+ free(path);
+ continue;
+ }
+
+ if (!(p = cunescape(path))) {
+ r = -ENOMEM;
+ goto finish;
+ }
+
+ if (!(mp = mount_point_alloc(p))) {
+ r = -ENOMEM;
+ goto finish;
+ }
+ LIST_PREPEND(MountPoint, mount_point, *mount_point_list_head, mp);
+
+ free(path);
+ }
+
+ r = 0;
+
+finish:
+ fclose(proc_self_mountinfo);
+
+ free(path);
+
+ return r;
+}
+
+static int swap_list_get(MountPoint **swap_list_head) {
+ FILE *proc_swaps;
+ unsigned int i;
+ int r;
+
+ if (!(proc_swaps = fopen("/proc/swaps", "re")))
+ return -errno;
+
+ (void) fscanf(proc_swaps, "%*s %*s %*s %*s %*s\n");
+
+ for (i = 2;; i++) {
+ MountPoint *swap;
+ char *dev = NULL, *d;
+ int k;
+
+ if ((k = fscanf(proc_swaps,
+ "%ms " /* device/file */
+ "%*s " /* type of swap */
+ "%*s " /* swap size */
+ "%*s " /* used */
+ "%*s\n", /* priority */
+ &dev)) != 1) {
+
+ if (k == EOF)
+ break;
+
+ log_warning("Failed to parse /proc/swaps:%u.", i);
+
+ free(dev);
+ continue;
+ }
+
+ d = cunescape(dev);
+ free(dev);
+
+ if (!d) {
+ r = -ENOMEM;
+ goto finish;
+ }
+
+ swap = mount_point_alloc(d);
+ if (!swap) {
+ free(d);
+ r = -ENOMEM;
+ goto finish;
+ }
+
+ LIST_PREPEND(MountPoint, mount_point, *swap_list_head, swap);
+ }
+
+ r = 0;
+
+ finish:
+ fclose(proc_swaps);
+
+ return r;
+}
+
+static int loopback_list_get(MountPoint **loopback_list_head) {
+ DIR *dir;
+ struct dirent *d;
+ int r;
+
+ if ((dir = opendir("/sys/class/block")) == NULL)
+ return -errno;
+
+ while ((d = readdir(dir))) {
+ MountPoint *lb;
+ char buf[PATH_MAX];
+ char *loop;
+
+ if (!strneq(d->d_name, "loop", 4))
+ continue;
+
+ snprintf(buf, sizeof(buf), "/dev/%s", d->d_name);
+ if (access(buf, R_OK) != 0)
+ continue;
+
+ loop = cunescape(buf);
+ if (!loop) {
+ r = -ENOMEM;
+ goto finish;
+ }
+
+ lb = mount_point_alloc(loop);
+ if (!lb) {
+ free(loop);
+ r = -ENOMEM;
+ goto finish;
+ }
+
+ LIST_PREPEND(MountPoint, mount_point, *loopback_list_head, lb);
+ }
+
+ r = 0;
+
+finish:
+ closedir(dir);
+ return r;
+}
+
+static int delete_loopback(const char *device) {
+ int fd, r;
+
+ if ((fd = open(device, O_RDONLY)) < 0)
+ return -errno;
+
+ ioctl(fd, LOOP_CLR_FD, 0);
+ r = errno;
+ close_nointr(fd);
+
+ if (r == ENXIO) /* not bound, so no error */
+ r = 0;
+ errno = r;
+ return -errno;
+}
+
+static int mount_points_list_umount(MountPoint **mount_point_list_head) {
+ MountPoint *mp, *mp_next;
+ int failed = 0;
+
+ LIST_FOREACH_SAFE(mount_point, mp, mp_next, *mount_point_list_head) {
+ if (streq(mp->path, "/"))
+ continue;
+
+ /* Trying to umount. Forcing to umount if busy (only for NFS mounts) */
+ if (umount2(mp->path, MNT_FORCE) == 0)
+ mount_point_remove_and_free(mp, mount_point_list_head);
+ else {
+ log_debug("could not unmount %s: %m", mp->path);
+ failed++;
+ }
+ }
+
+ return failed;
+}
+
+static int mount_points_list_remount_read_only(MountPoint **mount_point_list_head) {
+ MountPoint *mp, *mp_next;
+ int failed = 0;
+
+ LIST_FOREACH_SAFE(mount_point, mp, mp_next, *mount_point_list_head) {
+ if (mp->read_only)
+ continue;
+
+ /* Trying to remount read-only */
+ if (mount(NULL, mp->path, NULL, MS_MGC_VAL|MS_REMOUNT|MS_RDONLY, NULL) == 0) {
+ mp->read_only = true;
+ mount_point_remove_and_free(mp, mount_point_list_head);
+ } else {
+ log_debug("could not remount as read-only %s: %m", mp->path);
+ failed++;
+ }
+ }
+
+ return failed;
+}
+
+static int swap_points_list_off(MountPoint **swap_list_head) {
+ MountPoint *swap, *swap_next;
+ int failed = 0;
+
+ LIST_FOREACH_SAFE(mount_point, swap, swap_next, *swap_list_head) {
+ if (swapoff(swap->path) == 0)
+ mount_point_remove_and_free(swap, swap_list_head);
+ else {
+ log_debug("could not swapoff %s: %m", swap->path);
+ failed++;
+ }
+ }
+
+ return failed;
+}
+
+static int loopback_points_list_detach(MountPoint **loopback_list_head) {
+ MountPoint *loopback, *loopback_next;
+ int failed = 0;
+
+ LIST_FOREACH_SAFE(mount_point, loopback, loopback_next, *loopback_list_head) {
+ if (delete_loopback(loopback->path) == 0)
+ mount_point_remove_and_free(loopback, loopback_list_head);
+ else {
+ log_debug("could not delete loopback %s: %m", loopback->path);
+ failed++;
+ }
+ }
+
+ return failed;
+}
+
+int umount_all(void) {
+ int r;
+ LIST_HEAD(MountPoint, mp_list_head);
+
+ LIST_HEAD_INIT(MountPoint, mp_list_head);
+
+ r = mount_points_list_get(&mp_list_head);
+ if (r < 0)
+ goto end;
+
+ r = mount_points_list_umount(&mp_list_head);
+ if (r <= 0)
+ goto end;
+
+ r = mount_points_list_remount_read_only(&mp_list_head);
+
+ end:
+ mount_points_list_free(&mp_list_head);
+
+ return r;
+}
+
+int swapoff_all(void) {
+ int r;
+ LIST_HEAD(MountPoint, swap_list_head);
+
+ LIST_HEAD_INIT(MountPoint, swap_list_head);
+
+ r = swap_list_get(&swap_list_head);
+ if (r < 0)
+ goto end;
+
+ r = swap_points_list_off(&swap_list_head);
+
+ end:
+ mount_points_list_free(&swap_list_head);
+
+ return r;
+}
+
+int loopback_detach_all(void) {
+ int r;
+ LIST_HEAD(MountPoint, loopback_list_head);
+
+ LIST_HEAD_INIT(MountPoint, loopback_list_head);
+
+ r = loopback_list_get(&loopback_list_head);
+ if (r < 0)
+ goto end;
+
+ r = loopback_points_list_detach(&loopback_list_head);
+
+ end:
+ mount_points_list_free(&loopback_list_head);
+
+ return r;
+}
diff --git a/src/umount.h b/src/umount.h
new file mode 100644
index 0000000..aeccc00
--- /dev/null
+++ b/src/umount.h
@@ -0,0 +1,31 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+#ifndef fooumounthfoo
+#define fooumounthfoo
+
+/***
+ This file is part of systemd.
+
+ Copyright 2010 ProFUSION embedded systems
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+int umount_all(void);
+
+int swapoff_all(void);
+
+int loopback_detach_all(void);
+
+#endif
--
1.7.2.2
Gustavo Sverzut Barbieri
2010-10-06 05:05:44 UTC
Permalink
From: Fabiano Fidencio <***@profusion.mobi>

This functions are working as follows:
- Send a SIGTERM to all processes that may be finished
- Send a SIGKILL to all processes that still live and may be finished
- Try to unmount all mount points
- Try to remount read-only all mount points that can't be umounted
- Umount all swap devices
- Umount and detach all loopback devices
- Call [poweroff|halt|reboot|kexec]

TODO:
- Umount device-mapper.
- Make log work. So far it is being useless as we do not parse
/etc/systemd/system.conf, kernel command line but just
environment, however we're executed by init and thus have no
useful variables. Forcing it to target=kmsg/console and
level=debug also does not produce any output, however writing to
/dev/console does work (hack used during debug).
---
Makefile.am | 15 +++
src/shutdown.c | 338 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++
2 files changed, 353 insertions(+), 0 deletions(-)
create mode 100644 src/shutdown.c

diff --git a/Makefile.am b/Makefile.am
index a9d1857..72f998d 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -35,6 +35,7 @@ tmpfilesdir=$(sysconfdir)/tmpfiles.d
# And these are the special ones for /
rootdir=@rootdir@
rootbindir=$(rootdir)/bin
+rootsbindir=$(rootdir)/sbin
rootlibexecdir=$(rootdir)/lib/systemd
systemunitdir=$(rootdir)/lib/systemd/system

@@ -50,9 +51,11 @@ AM_CPPFLAGS = \
-DSESSION_DATA_UNIT_PATH=\"$(sessionunitdir)\" \
-DCGROUP_AGENT_PATH=\"$(rootlibexecdir)/systemd-cgroups-agent\" \
-DSYSTEMD_BINARY_PATH=\"$(rootbindir)/systemd\" \
+ -DSYSTEMD_SHUTDOWN_BINARY_PATH=\"$(rootlibexecdir)/systemd-shutdown\" \
-DSYSTEMCTL_BINARY_PATH=\"$(rootbindir)/systemctl\" \
-DRUNTIME_DIR=\"$(localstatedir)/run\" \
-DRANDOM_SEED=\"$(localstatedir)/lib/random-seed\" \
+ -DKEXEC_BINARY_PATH=\"$(rootsbindir)/kexec\" \
-I $(top_srcdir)/src

if TARGET_GENTOO
@@ -89,6 +92,7 @@ rootlibexec_PROGRAMS = \
systemd-update-utmp \
systemd-random-seed \
systemd-shutdownd \
+ systemd-shutdown \
systemd-modules-load \
systemd-remount-api-vfs \
systemd-kmsg-syslogd \
@@ -633,6 +637,17 @@ systemd_shutdownd_CFLAGS = \
systemd_shutdownd_LDADD = \
libsystemd-basic.la

+systemd_shutdown_SOURCES = \
+ src/mount-setup.c \
+ src/umount.c \
+ src/shutdown.c
+
+systemd_shutdown_CFLAGS = \
+ $(AM_CFLAGS)
+
+systemd_shutdown_LDADD = \
+ libsystemd-basic.la
+
systemd_modules_load_SOURCES = \
src/modules-load.c

diff --git a/src/shutdown.c b/src/shutdown.c
new file mode 100644
index 0000000..527ca09
--- /dev/null
+++ b/src/shutdown.c
@@ -0,0 +1,338 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+/***
+ This file is part of systemd.
+
+ Copyright 2010 ProFUSION embedded systems
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <sys/mman.h>
+#include <sys/types.h>
+#include <sys/reboot.h>
+#include <linux/reboot.h>
+#include <sys/wait.h>
+#include <dirent.h>
+#include <errno.h>
+#include <unistd.h>
+#include <signal.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "log.h"
+#include "umount.h"
+#include "util.h"
+
+#define TIMEOUT_USEC (5 * USEC_PER_SEC)
+#define FINALIZE_ATTEMPTS 50
+#define FINALIZE_CRITICAL_ATTEMPTS 10
+
+_noreturn_ static void freeze(void) {
+ for (;;)
+ pause();
+}
+
+static bool ignore_proc(pid_t pid) {
+ if (pid == 1)
+ return true;
+
+ /* TODO: add more ignore rules here: device-mapper, etc */
+
+ return false;
+}
+
+static bool is_kernel_thread(pid_t pid)
+{
+ char buf[PATH_MAX];
+ FILE *f;
+ char c;
+ size_t count;
+
+ snprintf(buf, sizeof(buf), "/proc/%lu/cmdline", (unsigned long)pid);
+ f = fopen(buf, "re");
+ if (!f)
+ return true; /* not really, but has the desired effect */
+
+ count = fread(&c, 1, 1, f);
+ fclose(f);
+ return count != 1;
+}
+
+static int killall(int sign) {
+ DIR *dir;
+ struct dirent *d;
+ unsigned int processes = 0;
+
+ if ((dir = opendir("/proc")) == NULL)
+ return -errno;
+
+ while ((d = readdir(dir))) {
+ pid_t pid;
+
+ if (parse_pid(d->d_name, &pid) < 0)
+ continue;
+
+ if (is_kernel_thread(pid))
+ continue;
+
+ if (ignore_proc(pid))
+ continue;
+
+ if (kill(pid, sign) == 0)
+ processes++;
+ else
+ log_warning("Could not kill %d: %m", pid);
+ }
+
+ closedir(dir);
+
+ return processes;
+}
+
+static int send_signal(int sign) {
+ sigset_t mask, oldmask;
+ usec_t until;
+ int processes;
+ struct timespec ts;
+
+ sigemptyset(&mask);
+ sigaddset(&mask, SIGCHLD);
+ if (sigprocmask(SIG_BLOCK, &mask, &oldmask) != 0)
+ return -errno;
+
+ if (kill(-1, SIGSTOP) < 0)
+ log_warning("kill(-1, SIGSTOP): %m");
+
+ processes = killall(sign);
+
+ if (kill(-1, SIGCONT) < 0)
+ log_warning("kill(-1, SIGCONT): %m");
+
+ if (processes <= 0)
+ goto finish;
+
+ until = now(CLOCK_MONOTONIC) + TIMEOUT_USEC;
+ for (;;) {
+ usec_t n;
+
+ for (;;) {
+ pid_t pid;
+
+ n = now(CLOCK_MONOTONIC);
+ if (n >= until)
+ goto finish;
+
+ pid = waitpid(-1, NULL, WNOHANG);
+ if (pid == 0)
+ break;
+ else if (pid < 0 && errno == ECHILD) {
+ processes = 0;
+ goto finish;
+ }
+
+ if (--processes == 0)
+ goto finish;
+ }
+
+ timespec_store(&ts, until - n);
+ if (sigtimedwait(&mask, NULL, &ts) != SIGCHLD)
+ log_warning("sigtimedwait did not return SIGCHLD: %m");
+ }
+
+finish:
+ sigprocmask(SIG_SETMASK, &oldmask, NULL);
+
+ return processes;
+}
+
+static int rescue_send_signal(int sign) {
+ sigset_t mask, oldmask;
+ usec_t until;
+ struct timespec ts;
+ int r;
+
+ sigemptyset(&mask);
+ sigaddset(&mask, SIGCHLD);
+ if (sigprocmask(SIG_BLOCK, &mask, &oldmask) != 0)
+ return -errno;
+
+ if (kill(-1, SIGSTOP) < 0)
+ log_warning("kill(-1, SIGSTOP): %m");
+
+ r = kill(-1, sign);
+ if (r < 0)
+ log_warning("kill(-1, %d): %m", sign);
+
+ if (kill(-1, SIGCONT) < 0)
+ log_warning("kill(-1, SIGCONT): %m");
+
+ if (r < 0)
+ goto finish;
+
+ until = now(CLOCK_MONOTONIC) + TIMEOUT_USEC;
+ for (;;) {
+ usec_t n;
+
+ for (;;) {
+ pid_t pid;
+
+ n = now(CLOCK_MONOTONIC);
+ if (n >= until)
+ goto finish;
+
+
+ pid = waitpid(-1, NULL, WNOHANG);
+ if (pid == 0)
+ break;
+ else if (pid < 0 && errno == ECHILD)
+ goto finish;
+ }
+
+ timespec_store(&ts, until - n);
+ if (sigtimedwait(&mask, NULL, &ts) != SIGCHLD)
+ log_warning("sigtimedwait did not return SIGCHLD: %m");
+ }
+
+finish:
+ sigprocmask(SIG_SETMASK, &oldmask, NULL);
+
+ return r;
+}
+
+
+int main(int argc, char *argv[]) {
+ int cmd, r, retries;
+ bool need_umount = true, need_swapoff = true, need_loop_detach = true;
+
+ log_parse_environment();
+ log_set_target(LOG_TARGET_KMSG); /* syslog will die if not gone yet */
+ log_open();
+
+ if (getpid() != 1) {
+ log_debug("only init may exec this binary");
+ r = -EPERM;
+ goto error;
+ }
+
+ if (argc != 2) {
+ log_debug("invalid number of arguments");
+ r = -EINVAL;
+ goto error;
+ }
+
+ if (streq(argv[1], "reboot"))
+ cmd = RB_AUTOBOOT;
+ else if (streq(argv[1], "poweroff"))
+ cmd = RB_POWER_OFF;
+ else if (streq(argv[1], "halt"))
+ cmd = RB_HALT_SYSTEM;
+ else if (streq(argv[1], "kexec"))
+ cmd = LINUX_REBOOT_CMD_KEXEC;
+ else {
+ log_debug("unknown action %s", argv[1]);
+ r = -EINVAL;
+ goto error;
+ }
+
+ /* lock us into memory */
+ if (mlockall(MCL_CURRENT|MCL_FUTURE) != 0)
+ log_warning("cannot lock process memory: %m");
+
+ log_info("Sending SIGTERM To Processes");
+ r = send_signal(SIGTERM);
+ if (r < 0)
+ log_warning("cannot send SIGTERM to all process: %s", strerror(r));
+
+ log_info("Sending SIGKILL To Processes");
+ r = send_signal(SIGKILL);
+ if (r < 0)
+ log_warning("cannot send SIGKILL to all process: %s", strerror(r));
+
+
+ /* preventing that we won't block umounts */
+ if (chdir("/") != 0)
+ log_warning("cannot chdir(\"/\"). Unmounts likely to fail.");
+
+ /* umount all mountpoints, swaps, and loopback devices */
+ retries = FINALIZE_ATTEMPTS;
+ while (need_umount || need_swapoff || need_loop_detach) {
+ if (need_umount) {
+ log_info("Unmounting Filesystems");
+ r = umount_all();
+ if (r == 0)
+ need_umount = false;
+ else
+ log_warning("Not all filesystems unmounted");
+ }
+
+ if (need_swapoff) {
+ log_info("Disabling Swaps");
+ r = swapoff_all();
+ if (r == 0)
+ need_swapoff = false;
+ else
+ log_warning("Not all swaps are off ");
+ }
+
+ if (need_loop_detach) {
+ log_info("Umounting and Detaching Loopback Devices");
+ r = loopback_detach_all();
+ if (r == 0)
+ need_loop_detach = false;
+ else
+ log_warning("Not all loop devices detached");
+ }
+
+ if (need_umount || need_swapoff || need_loop_detach) {
+ retries--;
+
+ if (retries <= FINALIZE_CRITICAL_ATTEMPTS) {
+ log_warning("Approaching critical level to finalize filesystem and devices, try to kill all processes.");
+ rescue_send_signal(SIGTERM);
+ rescue_send_signal(SIGKILL);
+ }
+
+ if (retries > 0)
+ log_info("Action still required, %d tries left", retries);
+ else {
+ log_error("Tried enough but still action required need_umount=%d, need_swapoff=%d, need_loop_detach=%d", need_umount, need_swapoff, need_loop_detach);
+ r = -EBUSY;
+ goto error;
+ }
+ }
+ }
+
+ sync();
+
+ if (cmd == LINUX_REBOOT_CMD_KEXEC) {
+ const char *args[5] = {KEXEC_BINARY_PATH, "-e", "-x", "-f", NULL};
+ /* we cheat and exec kexec to avoid doing all its work */
+ execv(args[0], (char * const *) args);
+ log_warning("kexec not supported, falling back to reboot");
+ cmd = RB_AUTOBOOT;
+ }
+
+ reboot(cmd);
+ r = errno;
+
+ error:
+ sync();
+ if (r < 0)
+ r = -r;
+ log_error("Critical error while doing system shutdown: %s", strerror(r));
+ freeze();
+ return 0;
+}
--
1.7.2.2
Gustavo Sverzut Barbieri
2010-10-06 05:11:16 UTC
Permalink
On Wed, Oct 6, 2010 at 2:05 AM, Gustavo Sverzut Barbieri
   - Send a SIGTERM to all processes that may be finished
   - Send a SIGKILL to all processes that still live and may be finished
   - Try to unmount all mount points
   - Try to remount read-only all mount points that can't be umounted
   - Umount all swap devices
   - Umount and detach all loopback devices
   - Call [poweroff|halt|reboot|kexec]
   - Umount device-mapper.
   - Make log work. So far it is being useless as we do not parse
     /etc/systemd/system.conf, kernel command line but just
     environment, however we're executed by init and thus have no
     useful variables. Forcing it to target=kmsg/console and
     level=debug also does not produce any output, however writing to
     /dev/console does work (hack used during debug).
This is a new version more correct than the first one as it ignore the
kernel threads, it will also handle cases where there are no child
left. The original code was always hitting the 5 seconds limit, making
it slow (at least 10 seconds to shutdown).
+static bool is_kernel_thread(pid_t pid)
+{
+        char buf[PATH_MAX];
+        FILE *f;
+        char c;
+        size_t count;
+
+        snprintf(buf, sizeof(buf), "/proc/%lu/cmdline", (unsigned long)pid);
+        f = fopen(buf, "re");
+        if (!f)
+                return true; /* not really, but has the desired effect */
+
+        count = fread(&c, 1, 1, f);
+        fclose(f);
+        return count != 1;
+}
+
+static int killall(int sign) {
+        DIR *dir;
+        struct dirent *d;
+        unsigned int processes = 0;
+
+        if ((dir = opendir("/proc")) == NULL)
+                return -errno;
+
+        while ((d = readdir(dir))) {
+                pid_t pid;
+
+                if (parse_pid(d->d_name, &pid) < 0)
+                        continue;
+
+                if (is_kernel_thread(pid))
+                        continue;
+
+                if (ignore_proc(pid))
+                        continue;
+
+                if (kill(pid, sign) == 0)
+                        processes++;
+                else
+                        pid = waitpid(-1, NULL, WNOHANG);
+                        if (pid == 0)
+                                break;
+                        else if (pid < 0 && errno == ECHILD) {
+                                processes = 0;
+                                goto finish;
+                        }
these are basically the difference. Please review and apply, ready to rock!
--
Gustavo Sverzut Barbieri
http://profusion.mobi embedded systems
--------------------------------------
MSN: ***@gm
Karel Zak
2010-10-06 12:25:37 UTC
Permalink
Post by Gustavo Sverzut Barbieri
+ /* umount all mountpoints, swaps, and loopback devices */
+ retries = FINALIZE_ATTEMPTS;
+ while (need_umount || need_swapoff || need_loop_detach) {
+ if (need_umount) {
+ log_info("Unmounting Filesystems");
+ r = umount_all();
+ if (r == 0)
+ need_umount = false;
+ else
+ log_warning("Not all filesystems unmounted");
+ }
+
+ if (need_swapoff) {
+ log_info("Disabling Swaps");
+ r = swapoff_all();
+ if (r == 0)
+ need_swapoff = false;
+ else
+ log_warning("Not all swaps are off ");
+ }
it's probably better to call swapoff_all before umount_all (hint:
Linux supports swap-area in regular files).
Post by Gustavo Sverzut Barbieri
+ if (need_loop_detach) {
+ log_info("Umounting and Detaching Loopback Devices");
+ r = loopback_detach_all();
+ if (r == 0)
+ need_loop_detach = false;
+ else
+ log_warning("Not all loop devices detached");
+ }
+
+ if (need_umount || need_swapoff || need_loop_detach) {
+ retries--;
+
+ if (retries <= FINALIZE_CRITICAL_ATTEMPTS) {
+ log_warning("Approaching critical level to finalize filesystem and devices, try to kill all processes.");
+ rescue_send_signal(SIGTERM);
+ rescue_send_signal(SIGKILL);
+ }
+
+ if (retries > 0)
+ log_info("Action still required, %d tries left", retries);
+ else {
+ log_error("Tried enough but still action required need_umount=%d, need_swapoff=%d, need_loop_detach=%d", need_umount, need_swapoff, need_loop_detach);
+ r = -EBUSY;
+ goto error;
+ }
+ }
+ }
what about

exec( swapoff -a );
exec( umount -a );


Karel
--
Karel Zak <***@redhat.com>
http://karelzak.blogspot.com
Lennart Poettering
2010-10-06 12:56:19 UTC
Permalink
Post by Karel Zak
Post by Gustavo Sverzut Barbieri
+ if (need_swapoff) {
+ log_info("Disabling Swaps");
+ r = swapoff_all();
+ if (r == 0)
+ need_swapoff = false;
+ else
+ log_warning("Not all swaps are off ");
+ }
Linux supports swap-area in regular files).
The Fedora shutdown scripts unmount tmpfs before swap, to make sure that
the removal of the swaps does not cause heavy unswapping for data that
will be dumped right-away after, because the tmpfs that has its data in
the swap is killed anyway. I think that is an optimization that might be
worth it and hence I asked Gustavo to implement it in the shutdown code.

The right way I think is to call all this in a loop: i.e. first get rid
of mounts as far as possible, then get rid of swaps as far as possible,
then try mounts again, until nothing can be removed anymore. Which is
more or less exactly what Gustavo's patch does.
Post by Karel Zak
Post by Gustavo Sverzut Barbieri
+ if (need_loop_detach) {
+ log_info("Umounting and Detaching Loopback Devices");
+ r = loopback_detach_all();
+ if (r == 0)
+ need_loop_detach = false;
+ else
+ log_warning("Not all loop devices detached");
+ }
+
+ if (need_umount || need_swapoff || need_loop_detach) {
+ retries--;
+
+ if (retries <= FINALIZE_CRITICAL_ATTEMPTS) {
+ log_warning("Approaching critical level to finalize filesystem and devices, try to kill all processes.");
+ rescue_send_signal(SIGTERM);
+ rescue_send_signal(SIGKILL);
+ }
+
+ if (retries > 0)
+ log_info("Action still required, %d tries left", retries);
+ else {
+ log_error("Tried enough but still action required need_umount=%d, need_swapoff=%d, need_loop_detach=%d", need_umount, need_swapoff, need_loop_detach);
+ r = -EBUSY;
+ goto error;
+ }
+ }
+ }
what about
exec( swapoff -a );
exec( umount -a );
We really need the information whether there are still some mounts/swaps
left to be removed so that we can loop around this properly. I am pretty
sure swapoff -a does not return that to us, does it?

Given that we want to loop around this in a tight loop I think it's
kinda nice to implement this in C instead of forking off processes, in
particular because the code in question is not overly complex.

Lennart
--
Lennart Poettering - Red Hat, Inc.
Lennart Poettering
2010-10-06 13:24:14 UTC
Permalink
Post by Gustavo Sverzut Barbieri
+static int send_signal(int sign) {
+ sigset_t mask, oldmask;
+ usec_t until;
+ int processes;
+ struct timespec ts;
+
+ sigemptyset(&mask);
Minor nitpick: I tend to enclose invocations like this with assert_se(x
== 0), e.g.

assert_se(sigemptyset(&mask) == 0);

This makes clear for the reader that there is is no realistic way this
could ever fail. But then again, this doesn't really matter.
Post by Gustavo Sverzut Barbieri
+ for (;;) {
+ usec_t n;
+
+ for (;;) {
+ pid_t pid;
+
+ n = now(CLOCK_MONOTONIC);
+ if (n >= until)
+ goto finish;
Put these two lines in the outer loop. There should be no need to query
the time each time.
Post by Gustavo Sverzut Barbieri
+static int rescue_send_signal(int sign) {
+ sigset_t mask, oldmask;
+ usec_t until;
+ struct timespec ts;
+ int r;
+
Hmm, this function does mostly the same as send_signal() but lets the
kernel iterate, right? Given how similar the functions I wonder if it
would be nicer to use a bool argument to switch between the two modes...
Post by Gustavo Sverzut Barbieri
+int main(int argc, char *argv[]) {
+ int cmd, r, retries;
+ bool need_umount = true, need_swapoff = true, need_loop_detach = true;
+
+ log_parse_environment();
+ log_set_target(LOG_TARGET_KMSG); /* syslog will die if not gone yet */
+ log_open();
Hmm, if logging does not work for you, maybe you disabled kernel console
logging for debug messages? An option might be to raise the kernel log
level here?
Post by Gustavo Sverzut Barbieri
+
+ if (getpid() != 1) {
+ log_debug("only init may exec this binary");
I think this should be log_error(). Also, please use proper sentences,
i.e. uppercase the first char and add a full stop to the end.
Post by Gustavo Sverzut Barbieri
+ r = -EPERM;
+ goto error;
+ }
+
+ if (argc != 2) {
+ log_debug("invalid number of arguments");
Siimilar here.
Post by Gustavo Sverzut Barbieri
+ r = -EINVAL;
+ goto error;
+ }
+
+ if (streq(argv[1], "reboot"))
+ cmd = RB_AUTOBOOT;
+ else if (streq(argv[1], "poweroff"))
+ cmd = RB_POWER_OFF;
+ else if (streq(argv[1], "halt"))
+ cmd = RB_HALT_SYSTEM;
+ else if (streq(argv[1], "kexec"))
+ cmd = LINUX_REBOOT_CMD_KEXEC;
+ else {
+ log_debug("unknown action %s", argv[1]);
+ r = -EINVAL;
And here.
Post by Gustavo Sverzut Barbieri
+ if (cmd == LINUX_REBOOT_CMD_KEXEC) {
+ const char *args[5] = {KEXEC_BINARY_PATH, "-e", "-x", "-f", NULL};
+ /* we cheat and exec kexec to avoid doing all its work */
+ execv(args[0], (char * const *) args);
+ log_warning("kexec not supported, falling back to reboot");
+ cmd = RB_AUTOBOOT;
+ }
Hmm, I wonder if we might need to fork things here... If we justz exec
kexec and it fails for some reason, is it capabale of invoking the
fallback reboot(RB_AUTOBOOT) itself? If it isn't we should fork off
kexec here, and wait for it, and if it fails do our own
reboot(RB_AUTOBOOT) instead.

Otherwise looks fantastic.

Lennart
--
Lennart Poettering - Red Hat, Inc.
Gustavo Sverzut Barbieri
2010-10-06 05:13:38 UTC
Permalink
On Wed, Oct 6, 2010 at 2:05 AM, Gustavo Sverzut Barbieri
   - umount all mount points that aren't API
   - remount read-only all mount points that can't be umounted
   - umount all swap devices.
   - detach all loopback devices
...
+        ioctl(fd, LOOP_CLR_FD, 0);
+        r = errno;
+        close_nointr(fd);
+
+        if (r == ENXIO) /* not bound, so no error */
+                r = 0;
+        errno = r;
+        return -errno;
this version handles ENXIO, issued by kernel whenever the loop was not
bound, thus we avoid looping through all devices until maximum retries
are reached, speeding up the shutdown.
--
Gustavo Sverzut Barbieri
http://profusion.mobi embedded systems
--------------------------------------
MSN: ***@gmail.com
Skype: gsbarbieri
Mobile: +55 (19) 9225-2202
Karel Zak
2010-10-06 12:17:14 UTC
Permalink
Post by Gustavo Sverzut Barbieri
+static int swap_list_get(MountPoint **swap_list_head) {
+ FILE *proc_swaps;
+ unsigned int i;
+ int r;
+
+ if (!(proc_swaps = fopen("/proc/swaps", "re")))
+ return -errno;
+
+ (void) fscanf(proc_swaps, "%*s %*s %*s %*s %*s\n");
+
+ for (i = 2;; i++) {
+ MountPoint *swap;
+ char *dev = NULL, *d;
+ int k;
+
+ if ((k = fscanf(proc_swaps,
+ "%ms " /* device/file */
+ "%*s " /* type of swap */
+ "%*s " /* swap size */
+ "%*s " /* used */
+ "%*s\n", /* priority */
+ &dev)) != 1) {
+
+ if (k == EOF)
+ break;
+
+ log_warning("Failed to parse /proc/swaps:%u.", i);
+
+ free(dev);
+ continue;
+ }
Note that kernel is able to returns "/foo/bar(deleted)" in /proc/swaps
(for moe details util-linux-ng commit dac4cc1dd6b855d781d2ff9689931786ece0acbf).

...
Post by Gustavo Sverzut Barbieri
+static int loopback_list_get(MountPoint **loopback_list_head) {
+ DIR *dir;
+ struct dirent *d;
+ int r;
+
+ if ((dir = opendir("/sys/class/block")) == NULL)
+ return -errno;
+
+ while ((d = readdir(dir))) {
+ MountPoint *lb;
+ char buf[PATH_MAX];
+ char *loop;
+
+ if (!strneq(d->d_name, "loop", 4))
+ continue;
+
+ snprintf(buf, sizeof(buf), "/dev/%s", d->d_name);
+ if (access(buf, R_OK) != 0)
+ continue;
what about /dev/loop/<N> ?
Post by Gustavo Sverzut Barbieri
+static int mount_points_list_umount(MountPoint **mount_point_list_head) {
+ MountPoint *mp, *mp_next;
+ int failed = 0;
+
+ LIST_FOREACH_SAFE(mount_point, mp, mp_next, *mount_point_list_head) {
you have to umount filesystems in reverse order, it means /foo/bar
before /foo, it's also possible that on the same mountpoint are
mounted two different filesystems.
Post by Gustavo Sverzut Barbieri
+ if (streq(mp->path, "/"))
+ continue;
+
+ /* Trying to umount. Forcing to umount if busy (only for NFS mounts) */
+ if (umount2(mp->path, MNT_FORCE) == 0)
You have to execute things like /sbin/umount.<type> if you want to run your
code on systems with NFS or cluster filesystems.
Post by Gustavo Sverzut Barbieri
+ mount_point_remove_and_free(mp, mount_point_list_head);
+ else {
+ log_debug("could not unmount %s: %m", mp->path);
+ failed++;
+ }
+ }
+
+ return failed;
+}
Karel
--
Karel Zak <***@redhat.com>
http://karelzak.blogspot.com
Kay Sievers
2010-10-06 12:50:56 UTC
Permalink
+        if ((dir = opendir("/sys/class/block")) == NULL)
+                return -errno;
+
+        while ((d = readdir(dir))) {
+                MountPoint *lb;
+                char buf[PATH_MAX];
+                char *loop;
+
+                if (!strneq(d->d_name, "loop", 4))
+                        continue;
+
+                snprintf(buf, sizeof(buf), "/dev/%s", d->d_name);
+                if (access(buf, R_OK) != 0)
+                        continue;
 what about /dev/loop/<N>  ?
Systemd depends on devtmpfs and will not work properly without it, and
these nodes are always create
Lennart Poettering
2010-10-06 13:01:02 UTC
Permalink
Post by Karel Zak
Post by Gustavo Sverzut Barbieri
+static int mount_points_list_umount(MountPoint **mount_point_list_head) {
+ MountPoint *mp, *mp_next;
+ int failed = 0;
+
+ LIST_FOREACH_SAFE(mount_point, mp, mp_next, *mount_point_list_head) {
you have to umount filesystems in reverse order, it means /foo/bar
before /foo, it's also possible that on the same mountpoint are
mounted two different filesystems.
The linked list is actually build in reverse order (because we prepend
to it, not append to it), hence Gustavo's patch should be fine
here. Also we call this all in a loop.
Post by Karel Zak
Post by Gustavo Sverzut Barbieri
+ if (streq(mp->path, "/"))
+ continue;
+
+ /* Trying to umount. Forcing to umount if busy (only for NFS mounts) */
+ if (umount2(mp->path, MNT_FORCE) == 0)
You have to execute things like /sbin/umount.<type> if you want to run your
code on systems with NFS or cluster filesystems.
Can you elaborate on this? what does the umount.nfs tool do that matters
here? I can only see /bin/umount.udisks in my install here...

Lennart
--
Lennart Poettering - Red Hat, Inc.
Karel Zak
2010-10-06 13:43:38 UTC
Permalink
Post by Lennart Poettering
Post by Karel Zak
Post by Gustavo Sverzut Barbieri
+ if (streq(mp->path, "/"))
+ continue;
+
+ /* Trying to umount. Forcing to umount if busy (only for NFS mounts) */
+ if (umount2(mp->path, MNT_FORCE) == 0)
You have to execute things like /sbin/umount.<type> if you want to run your
code on systems with NFS or cluster filesystems.
Can you elaborate on this? what does the umount.nfs tool do that matters
I don't know and I don't care, the umount.<type> is supported API :-)
Post by Lennart Poettering
here? I can only see /bin/umount.udisks in my install here...
$ repoquery -q --whatprovides /sbin/umount.*
glusterfs-client-0:2.0.9-2.fc14.x86_64
nfs-utils-1:1.2.2-6.fc14.x86_64
udisks-0:1.0.1-4.fc14.x86_64

(The umount.glusterfs is probably nonsense rhbz#640620)

Karel
--
Karel Zak <***@redhat.com>
http://karelzak.blogspot.com
Lennart Poettering
2010-10-07 17:08:03 UTC
Permalink
Post by Karel Zak
Post by Lennart Poettering
Post by Karel Zak
Post by Gustavo Sverzut Barbieri
+ if (streq(mp->path, "/"))
+ continue;
+
+ /* Trying to umount. Forcing to umount if busy (only for NFS mounts) */
+ if (umount2(mp->path, MNT_FORCE) == 0)
You have to execute things like /sbin/umount.<type> if you want to run your
code on systems with NFS or cluster filesystems.
Can you elaborate on this? what does the umount.nfs tool do that matters
I don't know and I don't care, the umount.<type> is supported API :-)
Hmm, note that we normally call /bin/umount for everything we
unmount. The code Gustavo and Fidencio prepared is simply the last
safety net for everything that might be left around by accident. As such
I believe it actually makes sense to go low-level here, since apparently
the high-level stuff failed if this code is even called.

Or in other words: Gustavo's and Fidencio's code is just about enough to
avoid unclean file systems. If everything went correctly during normal
operation the usual ordered .mount units will aready have called
/bin/umount for all file systems.

Lennart
--
Lennart Poettering - Red Hat, Inc.
Karel Zak
2010-10-07 17:24:35 UTC
Permalink
Post by Lennart Poettering
Or in other words: Gustavo's and Fidencio's code is just about enough to
avoid unclean file systems. If everything went correctly during normal
operation the usual ordered .mount units will aready have called
/bin/umount for all file systems.
This is good news :-) Thanks.

Karel
--
Karel Zak <***@redhat.com>
http://karelzak.blogspot.com
Lennart Poettering
2010-10-06 13:09:11 UTC
Permalink
Post by Gustavo Sverzut Barbieri
+#define LOOP_CLR_FD 0x4C01
Is there any particular reason you define this here? To me it appears
that linux/loop.h is perfectly fit to be included here.
Post by Gustavo Sverzut Barbieri
+
+ if ((dir = opendir("/sys/class/block")) == NULL)
+ return -errno;
Kay, should we use libudev for this? Right now we use libudev for all
accesses to /sys, should we do that here, too?
Post by Gustavo Sverzut Barbieri
+
+ while ((d = readdir(dir))) {
+ MountPoint *lb;
+ char buf[PATH_MAX];
+ char *loop;
+
+ if (!strneq(d->d_name, "loop", 4))
+ continue;
+
+ snprintf(buf, sizeof(buf), "/dev/%s", d->d_name);
+ if (access(buf, R_OK) != 0)
+ continue;
Hmm, what is this access() call good for? Calls to access() are more
often than not an indication for racy code? Can't this check be dropped
without ill effects? (if it cannot be dropped, then a comment would be cool.)
Post by Gustavo Sverzut Barbieri
+ if ((fd = open(device, O_RDONLY)) < 0)
+ return -errno;
More in the category of nitpicking, but we generally add O_CLOEXEC to
all open() calls. (Its not strictly needed in this case, but it's handy
to avoid false positives when grepping the sources to find open()s that
don't specifiy O_CLOEXEC, which I do from time to time). I wonder
whether O_NONBLOCK might be a good idea.
Post by Gustavo Sverzut Barbieri
+
+ ioctl(fd, LOOP_CLR_FD, 0);
+ r = errno;
+ close_nointr(fd);
+
+ if (r == ENXIO) /* not bound, so no error */
+ r = 0;
+ errno = r;
+ return -errno;
+}
Might be good to print an error message here.

Otherwise really cool. One last round of fixing and I'll merge it!

Lennart
--
Lennart Poettering - Red Hat, Inc.
Kay Sievers
2010-10-06 13:26:27 UTC
Permalink
Post by Lennart Poettering
+#define LOOP_CLR_FD     0x4C01
Is there any particular reason you define this here? To me it appears
that linux/loop.h is perfectly fit to be included here.
+
+        if ((dir = opendir("/sys/class/block")) == NULL)
+                return -errno;
Kay, should we use libudev for this? Right now we use libudev for all
accesses to /sys, should we do that here, too?
Yeah, would be nicer to enumerate the block subsystem with libudev.

This will also provide the device node name, while that should not be
the reason for libudev. In systemd context we can see /dev/loopX as
ABI, not a configuration option. Udev even does refuse to delete these
nodes.

Kay
Gustavo Sverzut Barbieri
2010-10-06 13:40:44 UTC
Permalink
On Wed, Oct 6, 2010 at 10:09 AM, Lennart Poettering
Post by Lennart Poettering
+#define LOOP_CLR_FD     0x4C01
Is there any particular reason you define this here? To me it appears
that linux/loop.h is perfectly fit to be included here.
not really, fidencio did it and i just kept... probably he found that
in some exiting tool. will send a new fixed version.
Post by Lennart Poettering
+
+        if ((dir = opendir("/sys/class/block")) == NULL)
+                return -errno;
Kay, should we use libudev for this? Right now we use libudev for all
accesses to /sys, should we do that here, too?
I just did that because the original version was looking for /dev/*
and filtering loop devices, too much to do when we could list all
block devices and just get the loop :-) I never used libudev, but if
you wish I can move to use it.
Post by Lennart Poettering
+
+        while ((d = readdir(dir))) {
+                MountPoint *lb;
+                char buf[PATH_MAX];
+                char *loop;
+
+                if (!strneq(d->d_name, "loop", 4))
+                        continue;
+
+                snprintf(buf, sizeof(buf), "/dev/%s", d->d_name);
+                if (access(buf, R_OK) != 0)
+                        continue;
Hmm, what is this access() call good for? Calls to access() are more
often than not an indication for racy code? Can't this check be dropped
without ill effects? (if it cannot be dropped, then a comment would be cool.)
not really useful, it is just to avoid cases whenever we have the loop
in /sys but no counterpart in /dev, it is quite weird corner case,
yeah... so I'll remove it if you wish.
Post by Lennart Poettering
+        if ((fd = open(device, O_RDONLY)) < 0)
+                return -errno;
More in the category of nitpicking, but we generally add O_CLOEXEC to
all open() calls. (Its not strictly needed in this case, but it's handy
to avoid false positives when grepping the sources to find open()s that
don't specifiy O_CLOEXEC, which I do from time to time). I wonder
whether O_NONBLOCK might be a good idea.
we're just closing it immediately below :-) and O_NONBLOCK is not
really helpful as we're doing just these tasks... as far as I checked
the kernel side is pretty simple and just check few flags in order to
reply.
Post by Lennart Poettering
+
+        ioctl(fd, LOOP_CLR_FD, 0);
+        r = errno;
+        close_nointr(fd);
+
+        if (r == ENXIO) /* not bound, so no error */
+                r = 0;
+        errno = r;
+        return -errno;
+}
Might be good to print an error message here.
In the ENXIO case? or others? The ENXIO will happen if you do not have
it in use, so in my machine it will always happen 8 times.
--
Gustavo Sverzut Barbieri
http://profusion.mobi embedded systems
--------------------------------------
MSN: ***@gmail.com
Skype: gsbarbieri
Mobile: +55 (19) 9225-2202
Gustavo Sverzut Barbieri
2010-10-06 13:44:48 UTC
Permalink
On Wed, Oct 6, 2010 at 10:40 AM, Gustavo Sverzut Barbieri
Post by Gustavo Sverzut Barbieri
Post by Lennart Poettering
+        ioctl(fd, LOOP_CLR_FD, 0);
+        r = errno;
+        close_nointr(fd);
+
+        if (r == ENXIO) /* not bound, so no error */
+                r = 0;
+        errno = r;
+        return -errno;
+}
Might be good to print an error message here.
In the ENXIO case? or others? The ENXIO will happen if you do not have
it in use, so in my machine it will always happen 8 times.
just double checked and the real error case is log_debug() later at
loopback_points_list_detach(), so it is all right!
--
Gustavo Sverzut Barbieri
http://profusion.mobi embedded systems
--------------------------------------
MSN: ***@gmail.com
Skype: gsbarbieri
Mobile: +55 (19) 9225-2202
Gustavo Sverzut Barbieri
2010-10-06 14:15:40 UTC
Permalink
From: Fabiano Fidencio <***@profusion.mobi>

This functions will:
- umount all mount points that aren't API
- remount read-only all mount points that can't be umounted
- umount all swap devices.
- detach all loopback devices

TODO:
- umount dms

Mountpoints are being read from /proc/self/mountinfo.
Swaps are being read from /proc/swaps.
Loop devices from /sys/class/block/loop*.
---
.gitignore | 1 +
Makefile.am | 1 +
src/umount.c | 387 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
src/umount.h | 31 +++++
4 files changed, 420 insertions(+), 0 deletions(-)
create mode 100644 src/umount.c
create mode 100644 src/umount.h

diff --git a/.gitignore b/.gitignore
index 9ba0758..2ba000c 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,3 +1,4 @@
+systemd-shutdown
systemd-tmpfiles
systemd-readahead-collect
systemd-readahead-replay
diff --git a/Makefile.am b/Makefile.am
index c06f1ec..a9d1857 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -351,6 +351,7 @@ libsystemd_core_la_SOURCES = \
src/service.c \
src/automount.c \
src/mount.c \
+ src/umount.c \
src/swap.c \
src/device.c \
src/target.c \
diff --git a/src/umount.c b/src/umount.c
new file mode 100644
index 0000000..193f46c
--- /dev/null
+++ b/src/umount.c
@@ -0,0 +1,387 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+/***
+ This file is part of systemd.
+
+ Copyright 2010 ProFUSION embedded systems
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <dirent.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <string.h>
+#include <sys/mount.h>
+#include <sys/swap.h>
+#include <unistd.h>
+#include <linux/loop.h>
+
+#include "list.h"
+#include "mount-setup.h"
+#include "umount.h"
+#include "util.h"
+
+typedef struct MountPoint {
+ char *path;
+ bool read_only;
+ LIST_FIELDS (struct MountPoint, mount_point);
+} MountPoint;
+
+static MountPoint *mount_point_alloc(char *path) {
+ MountPoint *mp;
+
+ if (!(mp = new(MountPoint, 1)))
+ return NULL;
+
+ mp->path = path;
+ mp->read_only = false;
+
+ return mp;
+}
+
+static void mount_point_remove_and_free(MountPoint *mount_point, MountPoint **mount_point_list_head) {
+ LIST_REMOVE(MountPoint, mount_point, *mount_point_list_head, mount_point);
+
+ free(mount_point->path);
+ free(mount_point);
+}
+
+static void mount_points_list_free(MountPoint **mount_point_list_head) {
+ while (*mount_point_list_head)
+ mount_point_remove_and_free(*mount_point_list_head, mount_point_list_head);
+}
+
+static int mount_points_list_get(MountPoint **mount_point_list_head) {
+ FILE *proc_self_mountinfo;
+ char *path, *p;
+ unsigned int i;
+ int r;
+
+ if (!(proc_self_mountinfo = fopen("/proc/self/mountinfo", "re")))
+ return -errno;
+
+ for (i = 1;; i++) {
+ int k;
+ MountPoint *mp;
+
+ path = p = NULL;
+
+ if ((k = fscanf(proc_self_mountinfo,
+ "%*s " /* (1) mount id */
+ "%*s " /* (2) parent id */
+ "%*s " /* (3) major:minor */
+ "%*s " /* (4) root */
+ "%ms " /* (5) mount point */
+ "%*s" /* (6) mount options */
+ "%*[^-]" /* (7) optional fields */
+ "- " /* (8) separator */
+ "%*s " /* (9) file system type */
+ "%*s" /* (10) mount source */
+ "%*s" /* (11) mount options 2 */
+ "%*[^\n]", /* some rubbish at the end */
+ &path)) != 1) {
+ if (k == EOF)
+ break;
+
+ log_warning("Failed to parse /proc/self/mountinfo:%u.", i);
+
+ free(path);
+ continue;
+ }
+
+ if (mount_point_is_api(path)) {
+ free(path);
+ continue;
+ }
+
+ if (!(p = cunescape(path))) {
+ r = -ENOMEM;
+ goto finish;
+ }
+
+ if (!(mp = mount_point_alloc(p))) {
+ r = -ENOMEM;
+ goto finish;
+ }
+ LIST_PREPEND(MountPoint, mount_point, *mount_point_list_head, mp);
+
+ free(path);
+ }
+
+ r = 0;
+
+finish:
+ fclose(proc_self_mountinfo);
+
+ free(path);
+
+ return r;
+}
+
+static int swap_list_get(MountPoint **swap_list_head) {
+ FILE *proc_swaps;
+ unsigned int i;
+ int r;
+
+ if (!(proc_swaps = fopen("/proc/swaps", "re")))
+ return -errno;
+
+ (void) fscanf(proc_swaps, "%*s %*s %*s %*s %*s\n");
+
+ for (i = 2;; i++) {
+ MountPoint *swap;
+ char *dev = NULL, *d;
+ int k;
+
+ if ((k = fscanf(proc_swaps,
+ "%ms " /* device/file */
+ "%*s " /* type of swap */
+ "%*s " /* swap size */
+ "%*s " /* used */
+ "%*s\n", /* priority */
+ &dev)) != 1) {
+
+ if (k == EOF)
+ break;
+
+ log_warning("Failed to parse /proc/swaps:%u.", i);
+
+ free(dev);
+ continue;
+ }
+
+ if (endswith(dev, "(deleted)")) {
+ free(dev);
+ continue;
+ }
+
+ d = cunescape(dev);
+ free(dev);
+
+ if (!d) {
+ r = -ENOMEM;
+ goto finish;
+ }
+
+ swap = mount_point_alloc(d);
+ if (!swap) {
+ free(d);
+ r = -ENOMEM;
+ goto finish;
+ }
+
+ LIST_PREPEND(MountPoint, mount_point, *swap_list_head, swap);
+ }
+
+ r = 0;
+
+ finish:
+ fclose(proc_swaps);
+
+ return r;
+}
+
+static int loopback_list_get(MountPoint **loopback_list_head) {
+ DIR *dir;
+ struct dirent *d;
+ int r;
+
+ if ((dir = opendir("/sys/class/block")) == NULL)
+ return -errno;
+
+ while ((d = readdir(dir))) {
+ MountPoint *lb;
+ char buf[PATH_MAX];
+ char *loop;
+
+ if (!strneq(d->d_name, "loop", 4))
+ continue;
+
+ snprintf(buf, sizeof(buf), "/dev/%s", d->d_name);
+ loop = cunescape(buf);
+ if (!loop) {
+ r = -ENOMEM;
+ goto finish;
+ }
+
+ lb = mount_point_alloc(loop);
+ if (!lb) {
+ free(loop);
+ r = -ENOMEM;
+ goto finish;
+ }
+
+ LIST_PREPEND(MountPoint, mount_point, *loopback_list_head, lb);
+ }
+
+ r = 0;
+
+finish:
+ closedir(dir);
+ return r;
+}
+
+static int delete_loopback(const char *device) {
+ int fd, r;
+
+ if ((fd = open(device, O_RDONLY|O_CLOEXEC)) < 0) {
+ if (errno == ENOENT) {
+ log_debug("Loop device %s does not exist.", device);
+ errno = 0;
+ return 0;
+ }
+ return -errno;
+ }
+
+ ioctl(fd, LOOP_CLR_FD, 0);
+ r = errno;
+ close_nointr(fd);
+
+ if (r == ENXIO) /* not bound, so no error */
+ r = 0;
+ errno = r;
+ return -errno;
+}
+
+static int mount_points_list_umount(MountPoint **mount_point_list_head) {
+ MountPoint *mp, *mp_next;
+ int failed = 0;
+
+ LIST_FOREACH_SAFE(mount_point, mp, mp_next, *mount_point_list_head) {
+ if (streq(mp->path, "/"))
+ continue;
+
+ /* Trying to umount. Forcing to umount if busy (only for NFS mounts) */
+ if (umount2(mp->path, MNT_FORCE) == 0)
+ mount_point_remove_and_free(mp, mount_point_list_head);
+ else {
+ log_debug("Could not unmount %s: %m", mp->path);
+ failed++;
+ }
+ }
+
+ return failed;
+}
+
+static int mount_points_list_remount_read_only(MountPoint **mount_point_list_head) {
+ MountPoint *mp, *mp_next;
+ int failed = 0;
+
+ LIST_FOREACH_SAFE(mount_point, mp, mp_next, *mount_point_list_head) {
+ if (mp->read_only)
+ continue;
+
+ /* Trying to remount read-only */
+ if (mount(NULL, mp->path, NULL, MS_MGC_VAL|MS_REMOUNT|MS_RDONLY, NULL) == 0) {
+ mp->read_only = true;
+ mount_point_remove_and_free(mp, mount_point_list_head);
+ } else {
+ log_debug("Could not remount as read-only %s: %m", mp->path);
+ failed++;
+ }
+ }
+
+ return failed;
+}
+
+static int swap_points_list_off(MountPoint **swap_list_head) {
+ MountPoint *swap, *swap_next;
+ int failed = 0;
+
+ LIST_FOREACH_SAFE(mount_point, swap, swap_next, *swap_list_head) {
+ if (swapoff(swap->path) == 0)
+ mount_point_remove_and_free(swap, swap_list_head);
+ else {
+ log_debug("Could not swapoff %s: %m", swap->path);
+ failed++;
+ }
+ }
+
+ return failed;
+}
+
+static int loopback_points_list_detach(MountPoint **loopback_list_head) {
+ MountPoint *loopback, *loopback_next;
+ int failed = 0;
+
+ LIST_FOREACH_SAFE(mount_point, loopback, loopback_next, *loopback_list_head) {
+ if (delete_loopback(loopback->path) == 0)
+ mount_point_remove_and_free(loopback, loopback_list_head);
+ else {
+ log_debug("Could not delete loopback %s: %m", loopback->path);
+ failed++;
+ }
+ }
+
+ return failed;
+}
+
+int umount_all(void) {
+ int r;
+ LIST_HEAD(MountPoint, mp_list_head);
+
+ LIST_HEAD_INIT(MountPoint, mp_list_head);
+
+ r = mount_points_list_get(&mp_list_head);
+ if (r < 0)
+ goto end;
+
+ r = mount_points_list_umount(&mp_list_head);
+ if (r <= 0)
+ goto end;
+
+ r = mount_points_list_remount_read_only(&mp_list_head);
+
+ end:
+ mount_points_list_free(&mp_list_head);
+
+ return r;
+}
+
+int swapoff_all(void) {
+ int r;
+ LIST_HEAD(MountPoint, swap_list_head);
+
+ LIST_HEAD_INIT(MountPoint, swap_list_head);
+
+ r = swap_list_get(&swap_list_head);
+ if (r < 0)
+ goto end;
+
+ r = swap_points_list_off(&swap_list_head);
+
+ end:
+ mount_points_list_free(&swap_list_head);
+
+ return r;
+}
+
+int loopback_detach_all(void) {
+ int r;
+ LIST_HEAD(MountPoint, loopback_list_head);
+
+ LIST_HEAD_INIT(MountPoint, loopback_list_head);
+
+ r = loopback_list_get(&loopback_list_head);
+ if (r < 0)
+ goto end;
+
+ r = loopback_points_list_detach(&loopback_list_head);
+
+ end:
+ mount_points_list_free(&loopback_list_head);
+
+ return r;
+}
diff --git a/src/umount.h b/src/umount.h
new file mode 100644
index 0000000..aeccc00
--- /dev/null
+++ b/src/umount.h
@@ -0,0 +1,31 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+#ifndef fooumounthfoo
+#define fooumounthfoo
+
+/***
+ This file is part of systemd.
+
+ Copyright 2010 ProFUSION embedded systems
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+int umount_all(void);
+
+int swapoff_all(void);
+
+int loopback_detach_all(void);
+
+#endif
--
1.7.2.2
Gustavo Sverzut Barbieri
2010-10-06 14:15:41 UTC
Permalink
From: Fabiano Fidencio <***@profusion.mobi>

This functions are working as follows:
- Send a SIGTERM to all processes that may be finished
- Send a SIGKILL to all processes that still live and may be finished
- Try to unmount all mount points
- Try to remount read-only all mount points that can't be umounted
- Umount all swap devices
- Umount and detach all loopback devices
- Call [poweroff|halt|reboot|kexec]

TODO:
- Umount device-mapper.
- Make log work. So far it is being useless as we do not parse
/etc/systemd/system.conf, kernel command line but just
environment, however we're executed by init and thus have no
useful variables. Forcing it to target=kmsg/console and
level=debug also does not produce any output, however writing to
/dev/console does work (hack used during debug).
---
Makefile.am | 15 +++
src/shutdown.c | 345 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++
2 files changed, 360 insertions(+), 0 deletions(-)
create mode 100644 src/shutdown.c

diff --git a/Makefile.am b/Makefile.am
index a9d1857..72f998d 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -35,6 +35,7 @@ tmpfilesdir=$(sysconfdir)/tmpfiles.d
# And these are the special ones for /
rootdir=@rootdir@
rootbindir=$(rootdir)/bin
+rootsbindir=$(rootdir)/sbin
rootlibexecdir=$(rootdir)/lib/systemd
systemunitdir=$(rootdir)/lib/systemd/system

@@ -50,9 +51,11 @@ AM_CPPFLAGS = \
-DSESSION_DATA_UNIT_PATH=\"$(sessionunitdir)\" \
-DCGROUP_AGENT_PATH=\"$(rootlibexecdir)/systemd-cgroups-agent\" \
-DSYSTEMD_BINARY_PATH=\"$(rootbindir)/systemd\" \
+ -DSYSTEMD_SHUTDOWN_BINARY_PATH=\"$(rootlibexecdir)/systemd-shutdown\" \
-DSYSTEMCTL_BINARY_PATH=\"$(rootbindir)/systemctl\" \
-DRUNTIME_DIR=\"$(localstatedir)/run\" \
-DRANDOM_SEED=\"$(localstatedir)/lib/random-seed\" \
+ -DKEXEC_BINARY_PATH=\"$(rootsbindir)/kexec\" \
-I $(top_srcdir)/src

if TARGET_GENTOO
@@ -89,6 +92,7 @@ rootlibexec_PROGRAMS = \
systemd-update-utmp \
systemd-random-seed \
systemd-shutdownd \
+ systemd-shutdown \
systemd-modules-load \
systemd-remount-api-vfs \
systemd-kmsg-syslogd \
@@ -633,6 +637,17 @@ systemd_shutdownd_CFLAGS = \
systemd_shutdownd_LDADD = \
libsystemd-basic.la

+systemd_shutdown_SOURCES = \
+ src/mount-setup.c \
+ src/umount.c \
+ src/shutdown.c
+
+systemd_shutdown_CFLAGS = \
+ $(AM_CFLAGS)
+
+systemd_shutdown_LDADD = \
+ libsystemd-basic.la
+
systemd_modules_load_SOURCES = \
src/modules-load.c

diff --git a/src/shutdown.c b/src/shutdown.c
new file mode 100644
index 0000000..1bacd56
--- /dev/null
+++ b/src/shutdown.c
@@ -0,0 +1,345 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+/***
+ This file is part of systemd.
+
+ Copyright 2010 ProFUSION embedded systems
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <sys/mman.h>
+#include <sys/types.h>
+#include <sys/reboot.h>
+#include <linux/reboot.h>
+#include <sys/wait.h>
+#include <dirent.h>
+#include <errno.h>
+#include <unistd.h>
+#include <signal.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "log.h"
+#include "umount.h"
+#include "util.h"
+
+#define TIMEOUT_USEC (5 * USEC_PER_SEC)
+#define FINALIZE_ATTEMPTS 50
+#define FINALIZE_CRITICAL_ATTEMPTS 10
+
+_noreturn_ static void freeze(void) {
+ for (;;)
+ pause();
+}
+
+static bool ignore_proc(pid_t pid) {
+ if (pid == 1)
+ return true;
+
+ /* TODO: add more ignore rules here: device-mapper, etc */
+
+ return false;
+}
+
+static bool is_kernel_thread(pid_t pid)
+{
+ char buf[PATH_MAX];
+ FILE *f;
+ char c;
+ size_t count;
+
+ snprintf(buf, sizeof(buf), "/proc/%lu/cmdline", (unsigned long)pid);
+ f = fopen(buf, "re");
+ if (!f)
+ return true; /* not really, but has the desired effect */
+
+ count = fread(&c, 1, 1, f);
+ fclose(f);
+ return count != 1;
+}
+
+static int killall(int sign) {
+ DIR *dir;
+ struct dirent *d;
+ unsigned int processes = 0;
+
+ if ((dir = opendir("/proc")) == NULL)
+ return -errno;
+
+ while ((d = readdir(dir))) {
+ pid_t pid;
+
+ if (parse_pid(d->d_name, &pid) < 0)
+ continue;
+
+ if (is_kernel_thread(pid))
+ continue;
+
+ if (ignore_proc(pid))
+ continue;
+
+ if (kill(pid, sign) == 0)
+ processes++;
+ else
+ log_warning("Could not kill %d: %m", pid);
+ }
+
+ closedir(dir);
+
+ return processes;
+}
+
+static int send_signal(int sign) {
+ sigset_t mask, oldmask;
+ usec_t until;
+ int processes;
+ struct timespec ts;
+
+ assert_se(sigemptyset(&mask) == 0);
+ assert_se(sigaddset(&mask, SIGCHLD) == 0);
+ if (sigprocmask(SIG_BLOCK, &mask, &oldmask) != 0)
+ return -errno;
+
+ if (kill(-1, SIGSTOP) < 0)
+ log_warning("Failed kill(-1, SIGSTOP): %m");
+
+ processes = killall(sign);
+
+ if (kill(-1, SIGCONT) < 0)
+ log_warning("Failed kill(-1, SIGCONT): %m");
+
+ if (processes <= 0)
+ goto finish;
+
+ until = now(CLOCK_MONOTONIC) + TIMEOUT_USEC;
+ for (;;) {
+ usec_t n = now(CLOCK_MONOTONIC);
+ for (;;) {
+ pid_t pid = waitpid(-1, NULL, WNOHANG);
+ if (pid == 0)
+ break;
+ else if (pid < 0 && errno == ECHILD) {
+ processes = 0;
+ goto finish;
+ }
+
+ if (--processes == 0)
+ goto finish;
+ }
+
+ if (n >= until)
+ goto finish;
+
+ timespec_store(&ts, until - n);
+ if (sigtimedwait(&mask, NULL, &ts) != SIGCHLD)
+ log_warning("Failed: sigtimedwait did not return SIGCHLD: %m");
+ }
+
+finish:
+ sigprocmask(SIG_SETMASK, &oldmask, NULL);
+
+ return processes;
+}
+
+static int rescue_send_signal(int sign) {
+ sigset_t mask, oldmask;
+ usec_t until;
+ struct timespec ts;
+ int r;
+
+ sigemptyset(&mask);
+ sigaddset(&mask, SIGCHLD);
+ if (sigprocmask(SIG_BLOCK, &mask, &oldmask) != 0)
+ return -errno;
+
+ if (kill(-1, SIGSTOP) < 0)
+ log_warning("Failed kill(-1, SIGSTOP): %m");
+
+ r = kill(-1, sign);
+ if (r < 0)
+ log_warning("Failed kill(-1, %d): %m", sign);
+
+ if (kill(-1, SIGCONT) < 0)
+ log_warning("Failed kill(-1, SIGCONT): %m");
+
+ if (r < 0)
+ goto finish;
+
+ until = now(CLOCK_MONOTONIC) + TIMEOUT_USEC;
+ for (;;) {
+ usec_t n = now(CLOCK_MONOTONIC);
+ for (;;) {
+ pid_t pid = waitpid(-1, NULL, WNOHANG);
+ if (pid == 0)
+ break;
+ else if (pid < 0 && errno == ECHILD)
+ goto finish;
+ }
+
+ if (n >= until)
+ goto finish;
+
+ timespec_store(&ts, until - n);
+ if (sigtimedwait(&mask, NULL, &ts) != SIGCHLD)
+ log_warning("Failed: sigtimedwait did not return SIGCHLD: %m");
+ }
+
+finish:
+ sigprocmask(SIG_SETMASK, &oldmask, NULL);
+
+ return r;
+}
+
+
+int main(int argc, char *argv[]) {
+ int cmd, r, retries;
+ bool need_umount = true, need_swapoff = true, need_loop_detach = true;
+
+ log_parse_environment();
+ log_set_target(LOG_TARGET_KMSG); /* syslog will die if not gone yet */
+ log_open();
+
+ if (getpid() != 1) {
+ log_error("Not executed by init (pid-1).");
+ r = -EPERM;
+ goto error;
+ }
+
+ if (argc != 2) {
+ log_error("Invalid number of arguments.");
+ r = -EINVAL;
+ goto error;
+ }
+
+ if (streq(argv[1], "reboot"))
+ cmd = RB_AUTOBOOT;
+ else if (streq(argv[1], "poweroff"))
+ cmd = RB_POWER_OFF;
+ else if (streq(argv[1], "halt"))
+ cmd = RB_HALT_SYSTEM;
+ else if (streq(argv[1], "kexec"))
+ cmd = LINUX_REBOOT_CMD_KEXEC;
+ else {
+ log_error("Unknown action '%s'.", argv[1]);
+ r = -EINVAL;
+ goto error;
+ }
+
+ /* lock us into memory */
+ if (mlockall(MCL_CURRENT|MCL_FUTURE) != 0)
+ log_warning("Cannot lock process memory: %m");
+
+ log_info("Sending SIGTERM to processes");
+ r = send_signal(SIGTERM);
+ if (r < 0)
+ log_warning("Cannot send SIGTERM to all process: %s", strerror(r));
+
+ log_info("Sending SIGKILL to processes");
+ r = send_signal(SIGKILL);
+ if (r < 0)
+ log_warning("Cannot send SIGKILL to all process: %s", strerror(r));
+
+
+ /* preventing that we won't block umounts */
+ if (chdir("/") != 0)
+ log_warning("Cannot chdir(\"/\"): %m. Unmounts likely to fail.");
+
+ /* umount all mountpoints, swaps, and loopback devices */
+ retries = FINALIZE_ATTEMPTS;
+ while (need_umount || need_swapoff || need_loop_detach) {
+ if (need_umount) {
+ log_info("Unmounting filesystems.");
+ r = umount_all();
+ if (r == 0)
+ need_umount = false;
+ else if (r > 0)
+ log_warning("Not all filesystems unmounted, %d left.", r);
+ else
+ log_error("Error unmounting filesystems: %s", strerror(-r));
+ }
+
+ if (need_swapoff) {
+ log_info("Disabling swaps.");
+ r = swapoff_all();
+ if (r == 0)
+ need_swapoff = false;
+ else if (r > 0)
+ log_warning("Not all swaps are off, %d left.", r);
+ else
+ log_error("Error turning off swaps: %s", strerror(-r));
+ }
+
+ if (need_loop_detach) {
+ log_info("Detaching loop devices.");
+ r = loopback_detach_all();
+ if (r == 0)
+ need_loop_detach = false;
+ else if (r > 0)
+ log_warning("Not all loop devices detached, %d left.", r);
+ else
+ log_error("Error detaching loop devices: %s", strerror(-r));
+
+ }
+
+ if (need_umount || need_swapoff || need_loop_detach) {
+ retries--;
+
+ if (retries <= FINALIZE_CRITICAL_ATTEMPTS) {
+ log_warning("Approaching critical level to finalize filesystem and devices, try to kill all processes.");
+ rescue_send_signal(SIGTERM);
+ rescue_send_signal(SIGKILL);
+ }
+
+ if (retries > 0)
+ log_info("Action still required, %d tries left", retries);
+ else {
+ log_error("Tried enough but still action required need_umount=%d, need_swapoff=%d, need_loop_detach=%d", need_umount, need_swapoff, need_loop_detach);
+ r = -EBUSY;
+ goto error;
+ }
+ }
+ }
+
+ sync();
+
+ if (cmd == LINUX_REBOOT_CMD_KEXEC) {
+ /* we cheat and exec kexec to avoid doing all its work */
+ pid_t pid = fork();
+ if (pid < 0) {
+ log_error("Could not fork: %m. Falling back to reboot.");
+ cmd = RB_AUTOBOOT;
+ } else if (pid > 0) {
+ waitpid(pid, NULL, 0);
+ log_warning("Failed %s -e -x -f. Falling back to reboot", KEXEC_BINARY_PATH);
+ cmd = RB_AUTOBOOT;
+ } else {
+ const char *args[5] = {KEXEC_BINARY_PATH, "-e", "-x", "-f", NULL};
+ execv(args[0], (char * const *) args);
+ return EXIT_FAILURE;
+ }
+ }
+
+ reboot(cmd);
+ r = errno;
+
+ error:
+ sync();
+ if (r < 0)
+ r = -r;
+ log_error("Critical error while doing system shutdown: %s", strerror(r));
+ freeze();
+ return 0;
+}
--
1.7.2.2
Harald Hoyer
2010-10-07 09:46:44 UTC
Permalink
Post by Gustavo Sverzut Barbieri
- Send a SIGTERM to all processes that may be finished
- Send a SIGKILL to all processes that still live and may be finished
- Try to unmount all mount points
- Try to remount read-only all mount points that can't be umounted
- Umount all swap devices
- Umount and detach all loopback devices
- Call [poweroff|halt|reboot|kexec]
- Umount device-mapper.
- Make log work. So far it is being useless as we do not parse
/etc/systemd/system.conf, kernel command line but just
environment, however we're executed by init and thus have no
useful variables. Forcing it to target=kmsg/console and
level=debug also does not produce any output, however writing to
/dev/console does work (hack used during debug).
....
Post by Gustavo Sverzut Barbieri
+ } else {
+ const char *args[5] = {KEXEC_BINARY_PATH, "-e", "-x", "-f", NULL};
+ execv(args[0], (char * const *) args);
+ return EXIT_FAILURE;
+ }
Continue reading on narkive:
Loading...