machinectl: add new "machinectl clean" command
authorLennart Poettering <lennart@poettering.net>
Mon, 11 Apr 2016 15:24:08 +0000 (17:24 +0200)
committerLennart Poettering <lennart@poettering.net>
Tue, 12 Apr 2016 11:43:33 +0000 (13:43 +0200)
This new command removes all, or all hidden container images that have been
downloaded.

man/machinectl.xml
src/machine/machinectl.c
src/machine/machined-dbus.c
src/shared/machine-image.c
src/shared/machine-image.h

index cee4bb7..a77d241 100644 (file)
 
         <para>When listing VM or container images, do not suppress
         images beginning in a dot character
-        (<literal>.</literal>).</para></listitem>
+        (<literal>.</literal>).</para>
+
+        <para>When cleaning VM or container images, remove all images, not just hidden ones.</para></listitem>
       </varlistentry>
 
        <varlistentry>
         <term><option>--read-only</option></term>
 
         <listitem><para>When used with <command>bind</command>, applies
-        a read-only bind mount.</para></listitem>
-      </varlistentry>
+        a read-only bind mount.</para>
 
+        <para>When used with <command>clone</command>, <command>import-raw</command> or <command>import-tar</command> a
+        read-only container or VM image is created.</para></listitem>
+      </varlistentry>
 
       <varlistentry>
         <term><option>-n</option></term>
         all other settings that could identify the instance
         unmodified. The original image and the cloned copy will hence
         share these credentials, and it might be necessary to manually
-        change them in the copy.</para></listitem>
+        change them in the copy.</para>
+
+        <para>If combined with the <option>--read-only</option> switch a read-only cloned image is
+        created.</para></listitem>
       </varlistentry>
 
       <varlistentry>
         itself.</para></listitem>
       </varlistentry>
 
+      <varlistentry>
+        <term><command>clean</command></term>
+
+        <listitem><para>Remove hidden VM or container images (or all). This command removes all hidden machine images
+        from <filename>/var/lib/machines</filename>, i.e. those whose name begins with a dot. Use <command>machinectl
+        list-images --all</command> to see a list of all machine images, including the hidden ones.</para>
+
+        <para>When combined with the <option>--all</option> switch removes all images, not just hidden ones. This
+        command effectively empties <filename>/var/lib/machines</filename>.</para>
+
+        <para>Note that commands such as <command>machinectl pull-tar</command> or <command>machinectl
+        pull-raw</command> usually create hidden, read-only, unmodified machine images from the downloaded image first,
+        before cloning a writable working copy of it, in order to avoid duplicate downloads in case of images that are
+        reused multiple times. Use <command>machinectl clean</command> to remove old, hidden images created this
+        way.</para></listitem>
+      </varlistentry>
+
     </variablelist></refsect2>
 
     <refsect2><title>Image Transfer Commands</title><variablelist>
index 1d3264a..35177aa 100644 (file)
@@ -2338,6 +2338,50 @@ static int set_limit(int argc, char *argv[], void *userdata) {
         return 0;
 }
 
+static int clean_images(int argc, char *argv[], void *userdata) {
+        _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
+        _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
+        uint64_t usage, total = 0;
+        char fb[FORMAT_BYTES_MAX];
+        sd_bus *bus = userdata;
+        const char *name;
+        unsigned c = 0;
+        int r;
+
+        r = sd_bus_call_method(
+                        bus,
+                        "org.freedesktop.machine1",
+                        "/org/freedesktop/machine1",
+                        "org.freedesktop.machine1.Manager",
+                        "CleanPool",
+                        &error,
+                        &reply,
+                        "s", arg_all ? "all" : "hidden");
+        if (r < 0)
+                return log_error_errno(r, "Could not clean pool: %s", bus_error_message(&error, r));
+
+        r = sd_bus_message_enter_container(reply, 'a', "(st)");
+        if (r < 0)
+                return bus_log_parse_error(r);
+
+        while ((r = sd_bus_message_read(reply, "(st)", &name, &usage)) > 0) {
+                log_info("Removed image '%s'. Freed exclusive disk space: %s",
+                         name, format_bytes(fb, sizeof(fb), usage));
+
+                total += usage;
+                c++;
+        }
+
+        r = sd_bus_message_exit_container(reply);
+        if (r < 0)
+                return bus_log_parse_error(r);
+
+        log_info("Removed %u images in total. Total freed exclusive disk space %s.",
+                 c, format_bytes(fb, sizeof(fb), total));
+
+        return 0;
+}
+
 static int help(int argc, char *argv[], void *userdata) {
 
         printf("%s [OPTIONS...] {COMMAND} ...\n\n"
@@ -2396,6 +2440,7 @@ static int help(int argc, char *argv[], void *userdata) {
                "  read-only NAME [BOOL]       Mark or unmark image read-only\n"
                "  remove NAME...              Remove an image\n"
                "  set-limit [NAME] BYTES      Set image or pool size limit (disk quota)\n\n"
+               "  clean                       Remove hidden (or all) images\n"
                "Image Transfer Commands:\n"
                "  pull-tar URL [NAME]         Download a TAR container image\n"
                "  pull-raw URL [NAME]         Download a RAW container or VM image\n"
@@ -2635,6 +2680,7 @@ static int machinectl_main(int argc, char *argv[], sd_bus *bus) {
                 { "list-transfers",  VERB_ANY, 1,        0,            list_transfers    },
                 { "cancel-transfer", 2,        VERB_ANY, 0,            cancel_transfer   },
                 { "set-limit",       2,        3,        0,            set_limit         },
+                { "clean",           VERB_ANY, 1,        0,            clean_images      },
                 {}
         };
 
index 2089443..c9639c3 100644 (file)
@@ -802,6 +802,93 @@ static int method_mark_image_read_only(sd_bus_message *message, void *userdata,
         return bus_image_method_mark_read_only(message, i, error);
 }
 
+static int method_clean_pool(sd_bus_message *message, void *userdata, sd_bus_error *error) {
+        enum {
+                REMOVE_ALL,
+                REMOVE_HIDDEN,
+        } mode;
+
+        _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
+        _cleanup_(image_hashmap_freep) Hashmap *images = NULL;
+        Manager *m = userdata;
+        Image *image;
+        const char *mm;
+        Iterator i;
+        int r;
+
+        assert(message);
+
+        r = sd_bus_message_read(message, "s", &mm);
+        if (r < 0)
+                return r;
+
+        if (streq(mm, "all"))
+                mode = REMOVE_ALL;
+        else if (streq(mm, "hidden"))
+                mode = REMOVE_HIDDEN;
+        else
+                return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Unknown mode '%s'.", mm);
+
+        r = bus_verify_polkit_async(
+                        message,
+                        CAP_SYS_ADMIN,
+                        "org.freedesktop.machine1.manage-machines",
+                        NULL,
+                        false,
+                        UID_INVALID,
+                        &m->polkit_registry,
+                        error);
+        if (r < 0)
+                return r;
+        if (r == 0)
+                return 1; /* Will call us back */
+
+        images = hashmap_new(&string_hash_ops);
+        if (!images)
+                return -ENOMEM;
+
+        r = image_discover(images);
+        if (r < 0)
+                return r;
+
+        r = sd_bus_message_new_method_return(message, &reply);
+        if (r < 0)
+                return r;
+
+        r = sd_bus_message_open_container(reply, 'a', "(st)");
+        if (r < 0)
+                return r;
+
+        HASHMAP_FOREACH(image, images, i) {
+
+                /* We can't remove vendor images (i.e. those in /usr) */
+                if (IMAGE_IS_VENDOR(image))
+                        continue;
+
+                if (IMAGE_IS_HOST(image))
+                        continue;
+
+                if (mode == REMOVE_HIDDEN && !IMAGE_IS_HIDDEN(image))
+                        continue;
+
+                r = image_remove(image);
+                if (r == -EBUSY) /* keep images that are currently being used. */
+                        continue;
+                if (r < 0)
+                        return sd_bus_error_set_errnof(error, r, "Failed to remove image %s: %m", image->name);
+
+                r = sd_bus_message_append(reply, "(st)", image->name, image->usage_exclusive);
+                if (r < 0)
+                        return r;
+        }
+
+        r = sd_bus_message_close_container(reply);
+        if (r < 0)
+                return r;
+
+        return sd_bus_send(NULL, reply, NULL);
+}
+
 static int method_set_pool_limit(sd_bus_message *message, void *userdata, sd_bus_error *error) {
         Manager *m = userdata;
         uint64_t limit;
@@ -1144,6 +1231,7 @@ const sd_bus_vtable manager_vtable[] = {
         SD_BUS_METHOD("MarkImageReadOnly", "sb", NULL, method_mark_image_read_only, SD_BUS_VTABLE_UNPRIVILEGED),
         SD_BUS_METHOD("SetPoolLimit", "t", NULL, method_set_pool_limit, SD_BUS_VTABLE_UNPRIVILEGED),
         SD_BUS_METHOD("SetImageLimit", "st", NULL, method_set_image_limit, SD_BUS_VTABLE_UNPRIVILEGED),
+        SD_BUS_METHOD("CleanPool", "s", "a(st)", method_clean_pool, SD_BUS_VTABLE_UNPRIVILEGED),
         SD_BUS_METHOD("MapFromMachineUser", "su", "u", method_map_from_machine_user, SD_BUS_VTABLE_UNPRIVILEGED),
         SD_BUS_METHOD("MapToMachineUser", "u", "sou", method_map_to_machine_user, SD_BUS_VTABLE_UNPRIVILEGED),
         SD_BUS_METHOD("MapFromMachineGroup", "su", "u", method_map_from_machine_group, SD_BUS_VTABLE_UNPRIVILEGED),
index d2f1c4a..bebfc40 100644 (file)
@@ -401,8 +401,7 @@ int image_remove(Image *i) {
 
         assert(i);
 
-        if (path_equal(i->path, "/") ||
-            path_startswith(i->path, "/usr"))
+        if (IMAGE_IS_VENDOR(i) || IMAGE_IS_HOST(i))
                 return -EROFS;
 
         settings = image_settings_path(i);
@@ -474,8 +473,7 @@ int image_rename(Image *i, const char *new_name) {
         if (!image_name_is_valid(new_name))
                 return -EINVAL;
 
-        if (path_equal(i->path, "/") ||
-            path_startswith(i->path, "/usr"))
+        if (IMAGE_IS_VENDOR(i) || IMAGE_IS_HOST(i))
                 return -EROFS;
 
         settings = image_settings_path(i);
@@ -642,8 +640,7 @@ int image_read_only(Image *i, bool b) {
         int r;
         assert(i);
 
-        if (path_equal(i->path, "/") ||
-            path_startswith(i->path, "/usr"))
+        if (IMAGE_IS_VENDOR(i) || IMAGE_IS_HOST(i))
                 return -EROFS;
 
         /* Make sure we don't interfere with a running nspawn */
@@ -751,8 +748,7 @@ int image_path_lock(const char *path, int operation, LockFile *global, LockFile
 int image_set_limit(Image *i, uint64_t referenced_max) {
         assert(i);
 
-        if (path_equal(i->path, "/") ||
-            path_startswith(i->path, "/usr"))
+        if (IMAGE_IS_VENDOR(i) || IMAGE_IS_HOST(i))
                 return -EROFS;
 
         if (i->type != IMAGE_SUBVOLUME)
index 31b720d..7410168 100644 (file)
@@ -25,6 +25,8 @@
 #include "hashmap.h"
 #include "lockfile-util.h"
 #include "macro.h"
+#include "path-util.h"
+#include "string-util.h"
 #include "time-util.h"
 
 typedef enum ImageType {
@@ -75,3 +77,27 @@ int image_path_lock(const char *path, int operation, LockFile *global, LockFile
 int image_name_lock(const char *name, int operation, LockFile *ret);
 
 int image_set_limit(Image *i, uint64_t referenced_max);
+
+static inline bool IMAGE_IS_HIDDEN(const struct Image *i) {
+        assert(i);
+
+        return i->name && i->name[0] == '.';
+}
+
+static inline bool IMAGE_IS_VENDOR(const struct Image *i) {
+        assert(i);
+
+        return i->path && path_startswith(i->path, "/usr");
+}
+
+static inline bool IMAGE_IS_HOST(const struct Image *i) {
+        assert(i);
+
+        if (i->name && streq(i->name, ".host"))
+                return true;
+
+        if (i->path && path_equal(i->path, "/"))
+                return true;
+
+        return false;
+}