author | Olivier Brunel
<jjk@jjacky.com> 2015-02-21 13:51:27 UTC |
committer | Olivier Brunel
<jjk@jjacky.com> 2015-04-04 12:47:34 UTC |
parent | ef808c2f3eadcd3d677cd08c3c5a43b5be5d74fd |
doc/aa-terminate.pod | +110 | -0 |
package/modes | +1 | -0 |
package/targets.mak | +2 | -0 |
src/libanopa/scan_dir.c | +7 | -1 |
src/utils/aa-terminate.c | +432 | -0 |
src/utils/deps-exe/aa-terminate | +2 | -0 |
diff --git a/doc/aa-terminate.pod b/doc/aa-terminate.pod new file mode 100644 index 0000000..1b25eb8 --- /dev/null +++ b/doc/aa-terminate.pod @@ -0,0 +1,110 @@ +=head1 NAME + +aa-teminate - Tries to close/unmount everything it can + +=head1 SYNOPSIS + +B<aa-terminate> [B<-D>] [B<-l>] [B<-a>] [B<-q> | B<-v>] + +=head1 OPTIONS + +=over + +=item B<-a, --apis> + +When done, unmount API file systems: I</run>, I</sys>, I</proc> and I</dev> If a +regular umount call fails, a lazy umount will be performed. + +=item B<-D, --double-output> + +Enable double-output mode. Instead of using stdout for regular output, and +stderr for warnings and errors, everything is sent both to stdout and stderr. +This is intended to redirect stderr to a log file, so full output can be both +shown on console and logged. + +=item B<-h, --help> + +Show help screen and exit. + +=item B<-l, --lazy-umounts> + +When nothing can be done anymore (see L<B<DESCRIPTION>|/DESCRIPTION> below) and +there are still active mount points, run the loop again performing lazy +unmounts: making the mount point unavailable for new accesses, and actually +performing the unmount when the mount point ceases to be busy. + +=item B<-q, --quiet> + +Do not output anything (not even warnings of "left overs" when done; See +B<--verbose> for more). + +=item B<-V, --version> + +Show version information and exit. + +=item B<-v, --verbose> + +Out information about everything that is tried. By default it only outputs when +something succeeds (and warnings of "left-overs" when done, listing anything +that couldn't be closed/removed/unmounted). +With this option, B<aa-terminate>(1) will output about any operation it +attempts, as well as an error message on failure. This is usually not important +information and mostly useful for testing/debugging purposes. + +=back + +=head1 DESCRIPTION + +Ideally, when shutting down/rebooting the system, anything that was +opened/mounted on boot (i.e. via B<aa-start>(1) during stage 0/1) should be +closed/unmounted automatically (i.e. via B<aa-stop>(1) during stage 3/4), as +defined on the relevant services. + +However, things might not go as planned, for one reason or another (starting +with, things might have been opened/mounted outside of any services). + +B<aa-terminate>(1) is aimed at closing/unmounting all that can be done at the +end of e.g. stage 3/4, right before powering off/rebooting. + +To do so, it will try to: + +=over + +=item 1. Turn all swaps off + +=item 2. Unmount everything but I</>, I</dev>, I</proc>, I</sys> and I</run> + +=item 3. Close all loop devices (I</dev/loop*>) + +=item 4. Remove all DM block devices (I</dev/dm-*>) + +=back + +When done, if there are still things to do (i.e. certain operations failed) and +at least one attempt was successful, it will try it all again, until either +there's nothing left to do, or it can't do anything else. + +If option B<--lazy-umounts> was specified and there are still active mount +points, it will try all over again, performing lazy umounts. + +Then, if option B<--apis> was specified it will umount API file systems: +I</run>, I</sys>, I</proc> and I</dev> If a regular umount call fails, a lazy +umount will be performed (regardless of whether option B<--lazy-umounts> was +used or not). + +Finally, unless option B<--quiet> was specified, warnings will be emitted for +everything left (swaps, mount points, loop/block devices that couldn't be +closed/unmounted/removed), if there is any. + +=head1 NOTE + +B<aa-terminate>(1) requires I</dev> and I</proc> to be mounted. Specifically, it +reads I</proc/swaps> to list active swaps and I</proc/mounts> for mount points; +It also reads I</dev> for loop/block devices, and uses I</dev/mapper/control> to +remove block devices. + +=head1 RETURN VALUES + +B<aa-terminate>(1) returns 0 on success (no "left-overs"), 1 on syntax error +(e.g. invalid option) and 2 if there is at least one left-over (excluding API +file systems). diff --git a/package/modes b/package/modes index 51a9c93..988df5c 100644 --- a/package/modes +++ b/package/modes @@ -7,5 +7,6 @@ aa-pivot 0755 aa-start 0755 aa-stop 0755 aa-sync 0755 +aa-terminate 0755 aa-test 0755 aa-umount 0755 diff --git a/package/targets.mak b/package/targets.mak index 5f923ce..a5dccb3 100644 --- a/package/targets.mak +++ b/package/targets.mak @@ -10,6 +10,7 @@ aa-kill \ aa-mount \ aa-pivot \ aa-sync \ +aa-terminate \ aa-test \ aa-umount @@ -24,6 +25,7 @@ aa-pivot.1 \ aa-start.1 \ aa-stop.1 \ aa-sync.1 \ +aa-terminate.1 \ aa-test.1 \ aa-umount.1 diff --git a/src/libanopa/scan_dir.c b/src/libanopa/scan_dir.c index e0bf2e5..5c37e14 100644 --- a/src/libanopa/scan_dir.c +++ b/src/libanopa/scan_dir.c @@ -54,8 +54,14 @@ aa_scan_dir (stralloc *sa, int files_only, aa_sd_it_fn iterator, void *data) d->d_type = DT_REG; else if (S_ISDIR (st.st_mode)) d->d_type = DT_DIR; + else if (S_ISBLK (st.st_mode)) + d->d_type = DT_BLK; } - if (d->d_type != DT_REG && (files_only || d->d_type != DT_DIR)) + if (d->d_type != DT_REG && ( + files_only == 1 + || (files_only == 0 && d->d_type != DT_DIR) + || (files_only == 2 && d->d_type != DT_BLK) + )) continue; r = iterator (d, data); diff --git a/src/utils/aa-terminate.c b/src/utils/aa-terminate.c new file mode 100644 index 0000000..d3d474f --- /dev/null +++ b/src/utils/aa-terminate.c @@ -0,0 +1,432 @@ + +#define _BSD_SOURCE + +#include <getopt.h> +#include <stdio.h> +#include <mntent.h> +#include <errno.h> +#include <sys/mount.h> +#include <unistd.h> +#include <sys/swap.h> +#include <string.h> +#include <sys/ioctl.h> +#include <linux/dm-ioctl.h> +#include <linux/loop.h> +#include <skalibs/djbunix.h> +#include <skalibs/stralloc.h> +#include <skalibs/strerr2.h> +#include <skalibs/error.h> +#include <anopa/common.h> +#include <anopa/scan_dir.h> +#include <anopa/output.h> + +const char *PROG; + +typedef int (*do_fn) (const char *path); +static int umnt_flags = 0; +static int level = 1; + +static void +verbose_do (const char *s1, const char *s2) +{ + if (level < 2) + return; + aa_bs_noflush (AA_OUT, s1); + aa_bs_noflush (AA_OUT, s2); + aa_bs_flush (AA_OUT, "...\n"); +} + +static void +verbose_fail (int e) +{ + if (level < 2) + return; + aa_bs_noflush (AA_OUT, "Failed: "); + aa_bs_noflush (AA_OUT, error_str (e)); + aa_bs_flush (AA_OUT, "\n"); +} + +static int +do_swapoff (const char *path) +{ + int r; + + verbose_do ("Turning off swap on ", path); + r = swapoff (path); + if (r < 0) + verbose_fail (errno); + else if (level > 0) + aa_put_title (0, "Swap turned off", path, 1); + return r; +} + +static int +do_umount (const char *path) +{ + int r; + + verbose_do ("Unmounting ", path); + r = umount2 (path, umnt_flags); + if (r < 0) + verbose_fail (errno); + else if (level > 0) + aa_put_title (0, "Unmounted", path, 1); + return r; +} + +static int +do_loop_close (const char *path) +{ + int fd; + int r; + + verbose_do ("Closing loop device ", path); + fd = open_read (path); + if (fd < 0) + { + verbose_fail (errno); + return -1; + } + + r = ioctl (fd, LOOP_CLR_FD, 0); + if (r < 0) + verbose_fail (errno); + else if (level > 0) + aa_put_title (0, "Loop device closed", path, 1); + + fd_close (fd); + return r; +} + +static int +do_dm_close (const char *path) +{ + struct dm_ioctl dm = { + .version = { DM_VERSION_MAJOR, DM_VERSION_MINOR, DM_VERSION_PATCHLEVEL }, + .data_size = sizeof (dm) + }; + struct stat st; + int fd; + int r; + + verbose_do ("Removing block device ", path); + if (stat (path, &st) < 0) + { + verbose_fail (errno); + return -1; + } + dm.dev = st.st_rdev; + + fd = open_write ("/dev/mapper/control"); + if (fd < 0) + { + verbose_fail (errno); + return -1; + } + + r = ioctl (fd, DM_DEV_REMOVE, &dm); + if (r < 0) + verbose_fail (errno); + else if (level > 0) + aa_put_title (0, "Block device removed", path, 1); + + fd_close (fd); + return r; +} + +static int +do_work (stralloc *sa, do_fn do_it) +{ + int did = 0; + int i; + + for (i = 0; i < sa->len; ) + { + int l; + + l = strlen (sa->s + i) + 1; + if (do_it (sa->s + i) < 0) + i += l; + else + { + ++did; + if (i + l < sa->len) + memmove (sa->s + i, sa->s + i + l, sa->len - i - l); + sa->len -= l; + } + } + + return did; +} + +static int +it_loops_dms (direntry *d, void *data) +{ + stralloc **sas = data; + int i; + int l; + + if (d->d_type != DT_BLK) + return 0; + + if (!str_diffn (d->d_name, "loop", 4) && d->d_name[4] >= '0' && d->d_name[4] <= '9') + i = 0; + else if (!str_diffn (d->d_name, "dm-", 3)) + i = 1; + else + return 0; + + l = sas[i]->len; + if (!stralloc_cats (sas[i], "/dev/") || !stralloc_cats (sas[i], d->d_name) + || !stralloc_0 (sas[i])) + strerr_diefu1sys (2, "stralloc_catb"); + + /* /dev/loop* always exists, let's find the actual ones */ + if (i == 0) + { + int fd; + + fd = open_read (sas[i]->s + l); + if (fd < 0) + sas[i]->len = l; + else + { + struct loop_info64 info; + int r; + + /* make sure it is one/in use; we get ENXIO when not */ + r = ioctl (fd, LOOP_GET_STATUS64, &info); + if (r < 0) + /* treat all errors the same though */ + sas[i]->len = l; + + fd_close (fd); + } + } + + return 0; +} + +static void +show_left (const char *prefix, stralloc *sa) +{ + int i; + + for (i = 0; i < sa->len; ) + { + int l; + + l = strlen (sa->s + i) + 1; + aa_put_warn (prefix, sa->s +i, 1); + i += l; + } +} + +static void +umount_api (const char *path) +{ + verbose_do ("Unmounting ", path); + if (umount2 (path, 0) < 0 && umount2 (path, MNT_DETACH) < 0) + verbose_fail (errno); + else if (level > 0) + aa_put_title (0, "Unmounted", path, 1); +} + +static void +dieusage (int rc) +{ + aa_die_usage (rc, "[OPTION...]", + " -D, --double-output Enable double-output mode\n" + " -v, --verbose Show what was done\n" + " -q, --quiet No warnings for what's left\n" + " -l, --lazy-umounts Try lazy umount as last resort\n" + " -a, --apis Umount /run, /sys, /proc & /dev too\n" + " -h, --help Show this help screen and exit\n" + " -V, --version Show version information and exit\n" + ); +} + +int +main (int argc, char * const argv[]) +{ + PROG = "aa-terminate"; + stralloc sa = STRALLOC_ZERO; + stralloc sa_swaps = STRALLOC_ZERO; + stralloc sa_mounts = STRALLOC_ZERO; + stralloc sa_loops = STRALLOC_ZERO; + stralloc sa_dms = STRALLOC_ZERO; + int mode_both = 0; + int apis = 0; + int lazy = 0; + int did_smthg; + + for (;;) + { + struct option longopts[] = { + { "apis", no_argument, NULL, 'a' }, + { "double-output", no_argument, NULL, 'D' }, + { "help", no_argument, NULL, 'h' }, + { "lazy-umounts", no_argument, NULL, 'l' }, + { "quiet", no_argument, NULL, 'q' }, + { "version", no_argument, NULL, 'V' }, + { "verbose", no_argument, NULL, 'v' }, + { NULL, 0, 0, 0 } + }; + int c; + + c = getopt_long (argc, argv, "aDhlqVv", longopts, NULL); + if (c == -1) + break; + switch (c) + { + case 'a': + apis = 1; + break; + + case 'D': + mode_both = 1; + break; + + case 'h': + dieusage (0); + + case 'l': + lazy = 1; + break; + + case 'q': + level = 0; + break; + + case 'V': + aa_die_version (); + + case 'v': + level = 2; + break; + + default: + dieusage (1); + } + } + argc -= optind; + argv += optind; + + if (argc > 0) + dieusage (1); + + aa_init_output (mode_both); + +again: + for (;;) + { + /* re-init */ + sa_swaps.len = 0; + sa_mounts.len = 0; + sa_loops.len = 0; + sa_dms.len = 0; + + /* read swaps */ + if (!openslurpclose (&sa, "/proc/swaps")) + strerr_diefu1sys (2, "read /proc/swaps"); + + if (sa.len > 0) + { + int l; + + l = byte_chr (sa.s, sa.len, '\n') + 1; + for ( ; l < sa.len; ) + { + int e; + + /* FIXME: how are spaces-in-filename treated? */ + e = byte_chr (sa.s + l, sa.len - l, ' '); + if (e < sa.len - l) + { + if (!stralloc_catb (&sa_swaps, sa.s + l, e) + || !stralloc_0 (&sa_swaps)) + strerr_diefu1sys (2, "stralloc_catb"); + } + l += byte_chr (sa.s + l, sa.len - l, '\n') + 1; + } + sa.len = 0; + } + + + /* read mounts */ + { + FILE *mounts; + struct mntent *mnt; + + mounts = setmntent ("/proc/mounts", "r"); + if (!mounts) + strerr_diefu1sys (2, "read /proc/mounts"); + + while ((mnt = getmntent (mounts))) + { + if (str_equal (mnt->mnt_dir, "/") + || str_equal (mnt->mnt_dir, "/dev") + || str_equal (mnt->mnt_dir, "/proc") + || str_equal (mnt->mnt_dir, "/sys") + || str_equal (mnt->mnt_dir, "/run")) + continue; + + if (!stralloc_catb (&sa_mounts, mnt->mnt_dir, strlen (mnt->mnt_dir) + 1)) + strerr_diefu1sys (2, "stralloc_catb"); + } + endmntent (mounts); + } + + + /* read loops + dms */ + { + stralloc *sas[2] = { &sa_loops, &sa_dms }; + int r; + + stralloc_catb (&sa, "/dev", 5); + r = aa_scan_dir (&sa, 2, it_loops_dms, &sas); + if (r < 0) + strerr_diefu1sys (2, "scan /dev"); + sa.len = 0; + } + + did_smthg = 0; + + if (do_work (&sa_swaps, do_swapoff)) + did_smthg = 1; + if (do_work (&sa_mounts, do_umount)) + did_smthg = 1; + if (do_work (&sa_loops, do_loop_close)) + did_smthg = 1; + if (do_work (&sa_dms, do_dm_close)) + did_smthg = 1; + + if (!did_smthg) + break; + } + + if (lazy && umnt_flags == 0 && sa_mounts.len > 0) + { + verbose_do ("Switching to lazy umount mode", ""); + umnt_flags = MNT_DETACH; + goto again; + } + + if (apis) + { + umount_api ("/run"); + umount_api ("/sys"); + umount_api ("/proc"); + umount_api ("/dev"); + } + + if (level > 0) + { + show_left ("Remaining swap", &sa_swaps); + show_left ("Remaining mountpoint", &sa_mounts); + show_left ("Remaining loop device", &sa_loops); + show_left ("Remaining block device", &sa_dms); + } + + return (sa_swaps.len + sa_mounts.len + sa_loops.len + sa_dms.len == 0) ? 0 : 2; +} diff --git a/src/utils/deps-exe/aa-terminate b/src/utils/deps-exe/aa-terminate new file mode 100644 index 0000000..30987b4 --- /dev/null +++ b/src/utils/deps-exe/aa-terminate @@ -0,0 +1,2 @@ +${LIBANOPA} +-lskarnet