Welcome to little lamb

Code » anopa » commit cdbcdbf

Add aa-terminate

author Olivier Brunel
2015-02-21 13:51:27 UTC
committer Olivier Brunel
2015-04-04 12:47:34 UTC
parent ef808c2f3eadcd3d677cd08c3c5a43b5be5d74fd

Add aa-terminate

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