[systemd-commits] 19 commits - TODO src/import src/login src/machine src/shared src/test

Lennart Poettering lennart at kemper.freedesktop.org
Fri Dec 26 11:13:10 PST 2014


 TODO                     |    4 
 src/import/import-dkr.c  |   43 +++++--
 src/import/import-dkr.h  |    2 
 src/import/import-gpt.c  |  278 ++++++++++++++++++++++++++++++++++++++++-------
 src/import/import-gpt.h  |    2 
 src/import/import.c      |   86 ++++++++------
 src/login/loginctl.c     |   32 ++---
 src/machine/image.c      |  109 +++++++++++++-----
 src/machine/machinectl.c |  192 ++++++++++++++++++++++++++++++--
 src/shared/copy.c        |  103 +++++++++++++++++
 src/shared/copy.h        |    2 
 src/shared/util.c        |    2 
 src/test/test-btrfs.c    |   14 +-
 13 files changed, 716 insertions(+), 153 deletions(-)

New commits:
commit 01c51934cbca705d854c3986870ca424df0cae46
Author: Lennart Poettering <lennart at poettering.net>
Date:   Fri Dec 26 20:10:01 2014 +0100

    loginctl: reindent --help text

diff --git a/src/login/loginctl.c b/src/login/loginctl.c
index 8e5239c..cc27544 100644
--- a/src/login/loginctl.c
+++ b/src/login/loginctl.c
@@ -1040,18 +1040,18 @@ static int terminate_seat(sd_bus *bus, char **args, unsigned n) {
 static void help(void) {
         printf("%s [OPTIONS...] {COMMAND} ...\n\n"
                "Send control commands to or query the login manager.\n\n"
-               "  -h --help              Show this help\n"
-               "     --version           Show package version\n"
-               "     --no-pager          Do not pipe output into a pager\n"
-               "     --no-legend         Do not show the headers and footers\n"
-               "     --no-ask-password   Don't prompt for password\n"
-               "  -H --host=[USER@]HOST  Operate on remote host\n"
-               "  -M --machine=CONTAINER Operate on local container\n"
-               "  -p --property=NAME     Show only properties by this name\n"
-               "  -a --all               Show all properties, including empty ones\n"
-               "  -l --full              Do not ellipsize output\n"
-               "     --kill-who=WHO      Who to send signal to\n"
-               "  -s --signal=SIGNAL     Which signal to send\n\n"
+               "  -h --help                Show this help\n"
+               "     --version             Show package version\n"
+               "     --no-pager            Do not pipe output into a pager\n"
+               "     --no-legend           Do not show the headers and footers\n"
+               "     --no-ask-password     Don't prompt for password\n"
+               "  -H --host=[USER@]HOST    Operate on remote host\n"
+               "  -M --machine=CONTAINER   Operate on local container\n"
+               "  -p --property=NAME       Show only properties by this name\n"
+               "  -a --all                 Show all properties, including empty ones\n"
+               "  -l --full                Do not ellipsize output\n"
+               "     --kill-who=WHO        Who to send signal to\n"
+               "  -s --signal=SIGNAL       Which signal to send\n\n"
                "Session Commands:\n"
                "  list-sessions            List sessions\n"
                "  session-status ID...     Show session status\n"

commit 2520f939bae6c26425d6737fd2e285c28b9f49c8
Author: Lennart Poettering <lennart at poettering.net>
Date:   Fri Dec 26 20:09:21 2014 +0100

    loginctl: add more --help sections

diff --git a/src/login/loginctl.c b/src/login/loginctl.c
index 03abb75..8e5239c 100644
--- a/src/login/loginctl.c
+++ b/src/login/loginctl.c
@@ -1052,7 +1052,7 @@ static void help(void) {
                "  -l --full              Do not ellipsize output\n"
                "     --kill-who=WHO      Who to send signal to\n"
                "  -s --signal=SIGNAL     Which signal to send\n\n"
-               "Commands:\n"
+               "Session Commands:\n"
                "  list-sessions            List sessions\n"
                "  session-status ID...     Show session status\n"
                "  show-session [ID...]     Show properties of sessions or the manager\n"
@@ -1062,14 +1062,16 @@ static void help(void) {
                "  lock-sessions            Screen lock all current sessions\n"
                "  unlock-sessions          Screen unlock all current sessions\n"
                "  terminate-session ID...  Terminate one or more sessions\n"
-               "  kill-session ID...       Send signal to processes of a session\n"
+               "  kill-session ID...       Send signal to processes of a session\n\n"
+               "User Commands:\n"
                "  list-users               List users\n"
                "  user-status USER...      Show user status\n"
                "  show-user [USER...]      Show properties of users or the manager\n"
                "  enable-linger USER...    Enable linger state of one or more users\n"
                "  disable-linger USER...   Disable linger state of one or more users\n"
                "  terminate-user USER...   Terminate all sessions of one or more users\n"
-               "  kill-user USER...        Send signal to processes of a user\n"
+               "  kill-user USER...        Send signal to processes of a user\n\n"
+               "Seat Commands:\n"
                "  list-seats               List seats\n"
                "  seat-status NAME...      Show seat status\n"
                "  show-seat NAME...        Show properties of one or more seats\n"

commit fefdc04b38725457a91651218feb7000f6ccc1f4
Author: Lennart Poettering <lennart at poettering.net>
Date:   Fri Dec 26 20:09:04 2014 +0100

    machinectl: add status commands

diff --git a/src/machine/machinectl.c b/src/machine/machinectl.c
index 04e6cb9..0f69734 100644
--- a/src/machine/machinectl.c
+++ b/src/machine/machinectl.c
@@ -525,7 +525,7 @@ static int map_netif(sd_bus *bus, const char *member, sd_bus_message *m, sd_bus_
         return 0;
 }
 
-static int show_info(const char *verb, sd_bus *bus, const char *path, bool *new_line) {
+static int show_machine_info(const char *verb, sd_bus *bus, const char *path, bool *new_line) {
 
         static const struct bus_properties_map map[]  = {
                 { "Name",              "s",  NULL,          offsetof(MachineStatusInfo, name) },
@@ -572,7 +572,7 @@ static int show_info(const char *verb, sd_bus *bus, const char *path, bool *new_
         return r;
 }
 
-static int show_properties(sd_bus *bus, const char *path, bool *new_line) {
+static int show_machine_properties(sd_bus *bus, const char *path, bool *new_line) {
         int r;
 
         assert(bus);
@@ -591,7 +591,7 @@ static int show_properties(sd_bus *bus, const char *path, bool *new_line) {
         return r;
 }
 
-static int show(int argc, char *argv[], void *userdata) {
+static int show_machine(int argc, char *argv[], void *userdata) {
 
         _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
         _cleanup_bus_message_unref_ sd_bus_message *reply = NULL;
@@ -609,7 +609,7 @@ static int show(int argc, char *argv[], void *userdata) {
 
                 /* If no argument is specified, inspect the manager
                  * itself */
-                r = show_properties(bus, "/org/freedesktop/machine1", &new_line);
+                r = show_machine_properties(bus, "/org/freedesktop/machine1", &new_line);
                 if (r < 0)
                         return r;
         }
@@ -636,9 +636,169 @@ static int show(int argc, char *argv[], void *userdata) {
                         return bus_log_parse_error(r);
 
                 if (properties)
-                        r = show_properties(bus, path, &new_line);
+                        r = show_machine_properties(bus, path, &new_line);
                 else
-                        r = show_info(argv[0], bus, path, &new_line);
+                        r = show_machine_info(argv[0], bus, path, &new_line);
+        }
+
+        return r;
+}
+
+typedef struct ImageStatusInfo {
+        char *name;
+        char *path;
+        char *type;
+        int read_only;
+        usec_t crtime;
+        usec_t mtime;
+} ImageStatusInfo;
+
+static void print_image_status_info(sd_bus *bus, ImageStatusInfo *i) {
+        char ts_relative[FORMAT_TIMESTAMP_RELATIVE_MAX], *s1;
+        char ts_absolute[FORMAT_TIMESTAMP_MAX], *s2;
+
+        assert(bus);
+        assert(i);
+
+        if (i->name) {
+                fputs(i->name, stdout);
+                putchar('\n');
+        }
+
+        if (i->path)
+                printf("\t    Type: %s\n", i->type);
+
+        if (i->path)
+                printf("\t    Path: %s\n", i->path);
+
+        printf("\t      RO: %s%s%s\n",
+               i->read_only ? ansi_highlight_red() : "",
+               i->read_only ? "read-only" : "writable",
+               i->read_only ? ansi_highlight_off() : "");
+
+        s1 = format_timestamp_relative(ts_relative, sizeof(ts_relative), i->crtime);
+        s2 = format_timestamp(ts_absolute, sizeof(ts_absolute), i->crtime);
+        if (s1)
+                printf("\t Created: %s; %s\n", s2, s1);
+        else if (s2)
+                printf("\t Created: %s\n", s2);
+
+        s1 = format_timestamp_relative(ts_relative, sizeof(ts_relative), i->mtime);
+        s2 = format_timestamp(ts_absolute, sizeof(ts_absolute), i->mtime);
+        if (s1)
+                printf("\tModified: %s; %s\n", s2, s1);
+        else if (s2)
+                printf("\tModified: %s\n", s2);
+}
+
+static int show_image_info(const char *verb, sd_bus *bus, const char *path, bool *new_line) {
+
+        static const struct bus_properties_map map[]  = {
+                { "Name",                  "s",  NULL, offsetof(ImageStatusInfo, name)      },
+                { "Path",                  "s",  NULL, offsetof(ImageStatusInfo, path)      },
+                { "Type",                  "s",  NULL, offsetof(ImageStatusInfo, type)      },
+                { "ReadOnly",              "b",  NULL, offsetof(ImageStatusInfo, read_only) },
+                { "CreationTimestamp",     "t",  NULL, offsetof(ImageStatusInfo, crtime)    },
+                { "ModificationTimestamp", "t",  NULL, offsetof(ImageStatusInfo, mtime)     },
+                {}
+        };
+
+        ImageStatusInfo info = {};
+        int r;
+
+        assert(verb);
+        assert(bus);
+        assert(path);
+        assert(new_line);
+
+        r = bus_map_all_properties(bus,
+                                   "org.freedesktop.machine1",
+                                   path,
+                                   map,
+                                   &info);
+        if (r < 0)
+                return log_error_errno(r, "Could not get properties: %m");
+
+        if (*new_line)
+                printf("\n");
+        *new_line = true;
+
+        print_image_status_info(bus, &info);
+
+        free(info.name);
+        free(info.path);
+        free(info.type);
+
+        return r;
+}
+
+static int show_image_properties(sd_bus *bus, const char *path, bool *new_line) {
+        int r;
+
+        assert(bus);
+        assert(path);
+        assert(new_line);
+
+        if (*new_line)
+                printf("\n");
+
+        *new_line = true;
+
+        r = bus_print_all_properties(bus, "org.freedesktop.machine1", path, arg_property, arg_all);
+        if (r < 0)
+                log_error_errno(r, "Could not get properties: %m");
+
+        return r;
+}
+
+static int show_image(int argc, char *argv[], void *userdata) {
+
+        _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
+        _cleanup_bus_message_unref_ sd_bus_message *reply = NULL;
+        bool properties, new_line = false;
+        sd_bus *bus = userdata;
+        int r = 0, i;
+
+        assert(bus);
+
+        properties = !strstr(argv[0], "status");
+
+        pager_open_if_enabled();
+
+        if (properties && argc <= 1) {
+
+                /* If no argument is specified, inspect the manager
+                 * itself */
+                r = show_image_properties(bus, "/org/freedesktop/machine1", &new_line);
+                if (r < 0)
+                        return r;
+        }
+
+        for (i = 1; i < argc; i++) {
+                const char *path = NULL;
+
+                r = sd_bus_call_method(
+                                        bus,
+                                        "org.freedesktop.machine1",
+                                        "/org/freedesktop/machine1",
+                                        "org.freedesktop.machine1.Manager",
+                                        "GetImage",
+                                        &error,
+                                        &reply,
+                                        "s", argv[i]);
+                if (r < 0) {
+                        log_error("Could not get path to image: %s", bus_error_message(&error, -r));
+                        return r;
+                }
+
+                r = sd_bus_message_read(reply, "o", &path);
+                if (r < 0)
+                        return bus_log_parse_error(r);
+
+                if (properties)
+                        r = show_image_properties(bus, path, &new_line);
+                else
+                        r = show_image_info(argv[0], bus, path, &new_line);
         }
 
         return r;
@@ -1143,7 +1303,7 @@ static int help(int argc, char *argv[], void *userdata) {
                "     --mkdir                  Create directory before bind mounting, if missing\n\n"
                "Machine Commands:\n"
                "  list                        List running VMs and containers\n"
-               "  status NAME...              Show VM/container status\n"
+               "  status NAME...              Show VM/container details\n"
                "  show NAME...                Show properties of one or more VMs/containers\n"
                "  login NAME                  Get a login prompt on a container\n"
                "  poweroff NAME...            Power off one or more containers\n"
@@ -1153,8 +1313,10 @@ static int help(int argc, char *argv[], void *userdata) {
                "  bind NAME PATH [PATH]       Bind mount a path from the host into a container\n"
                "  copy-to NAME PATH [PATH]    Copy files from the host to a container\n"
                "  copy-from NAME PATH [PATH]  Copy files from a container to the host\n\n"
-               "Image commands:\n"
-               "  list-images                 Show available images\n",
+               "Image Commands:\n"
+               "  list-images                 Show available images\n"
+               "  image-status NAME...        Show image details\n"
+               "  show-image NAME...          Show properties of image\n",
                program_invocation_short_name);
 
         return 0;
@@ -1278,8 +1440,10 @@ static int machinectl_main(int argc, char *argv[], sd_bus *bus) {
                 { "help",        VERB_ANY, VERB_ANY, 0,            help              },
                 { "list",        VERB_ANY, 1,        VERB_DEFAULT, list_machines     },
                 { "list-images", VERB_ANY, 1,        0,            list_images       },
-                { "status",      2,        VERB_ANY, 0,            show              },
-                { "show",        VERB_ANY, VERB_ANY, 0,            show              },
+                { "status",      2,        VERB_ANY, 0,            show_machine      },
+                { "image-status",2,        VERB_ANY, 0,            show_image        },
+                { "show",        VERB_ANY, VERB_ANY, 0,            show_machine      },
+                { "show-image",  VERB_ANY, VERB_ANY, 0,            show_image        },
                 { "terminate",   2,        VERB_ANY, 0,            terminate_machine },
                 { "reboot",      2,        VERB_ANY, 0,            reboot_machine    },
                 { "poweroff",    2,        VERB_ANY, 0,            poweroff_machine  },

commit 27c88c4e23f1f062ae69d54485033f88a7d7fbb3
Author: Lennart Poettering <lennart at poettering.net>
Date:   Fri Dec 26 20:05:11 2014 +0100

    machined: fix search patch magic for '.host' image

diff --git a/src/machine/image.c b/src/machine/image.c
index 8a119e5..46a216b 100644
--- a/src/machine/image.c
+++ b/src/machine/image.c
@@ -243,7 +243,7 @@ int image_find(const char *name, Image **ret) {
         }
 
         if (streq(name, ".host"))
-                return image_make(NULL, AT_FDCWD, NULL, "/", ret);
+                return image_make(".host", AT_FDCWD, NULL, "/", ret);
 
         return 0;
 };

commit 08ff5529dfd4fd2588d6fc9e194cc732795a85e6
Author: Lennart Poettering <lennart at poettering.net>
Date:   Fri Dec 26 19:36:25 2014 +0100

    machined: make image read-only check indepenednt on own privs

diff --git a/src/machine/image.c b/src/machine/image.c
index 1574adf..8a119e5 100644
--- a/src/machine/image.c
+++ b/src/machine/image.c
@@ -112,7 +112,7 @@ static int image_make(
 
         read_only =
                 (path && path_startswith(path, "/usr")) ||
-                faccessat(dfd, filename, W_OK, AT_EACCESS) < 0;
+                (faccessat(dfd, filename, W_OK, AT_EACCESS) < 0 && errno == EROFS);
 
         if (S_ISDIR(st.st_mode)) {
 

commit 8937e7b68940d0fa0d0aab90eb7425fa7dccebc9
Author: Lennart Poettering <lennart at poettering.net>
Date:   Fri Dec 26 19:32:55 2014 +0100

    machinectl: mark read-only images when listing in red

diff --git a/src/machine/machinectl.c b/src/machine/machinectl.c
index 64caf7c..04e6cb9 100644
--- a/src/machine/machinectl.c
+++ b/src/machine/machinectl.c
@@ -231,10 +231,10 @@ static int list_images(int argc, char *argv[], void *userdata) {
         for (j = 0; j < n_images; j++) {
                 char crtime_buf[FORMAT_TIMESTAMP_MAX], mtime_buf[FORMAT_TIMESTAMP_MAX];
 
-                printf("%-*s %-*s %-3s %-*s %-*s\n",
+                printf("%-*s %-*s %s%-3s%s %-*s %-*s\n",
                        (int) max_name, images[j].name,
                        (int) max_type, images[j].type,
-                       yes_no(images[j].read_only),
+                       images[j].read_only ? ansi_highlight_red() : "", yes_no(images[j].read_only), images[j].read_only ? ansi_highlight_off() : "",
                        (int) max_crtime, images[j].crtime != 0 ? format_timestamp(crtime_buf, sizeof(crtime_buf), images[j].crtime) : "-",
                        (int) max_mtime, images[j].mtime != 0 ? format_timestamp(mtime_buf, sizeof(mtime_buf), images[j].mtime) : "-");
         }

commit 679829e4aa1461a2db7537aae4c68c6780362c06
Author: Lennart Poettering <lennart at poettering.net>
Date:   Fri Dec 26 17:11:03 2014 +0100

    update TODO

diff --git a/TODO b/TODO
index d5b7ff2..ea9933d 100644
--- a/TODO
+++ b/TODO
@@ -31,7 +31,7 @@ External:
 
 Features:
 
-* teach fd_get_crtime_at() the btrfs crtime
+* change default container location from /var/lib/container to /var/lib/machines
 
 * import pull-gpt: create writable snapshot of downloaded image, by the right name
 
@@ -51,6 +51,8 @@ Features:
 
 * "machinectl diff"
 
+* "machinectl read-only"
+
 * show btrfs quota in machinectl
 
 * "machinectl commit" that takes a writable snapshot of a tree, invokes a shell in it, and marks it read-only after use

commit 087682d103e08670963686d9b1bc1d35c412a63f
Author: Lennart Poettering <lennart at poettering.net>
Date:   Fri Dec 26 19:21:09 2014 +0100

    import: make image root directory configurable, instead of hardcoding /var/lib/container

diff --git a/src/import/import-dkr.c b/src/import/import-dkr.c
index fbd6412..a3f390c 100644
--- a/src/import/import-dkr.c
+++ b/src/import/import-dkr.c
@@ -94,6 +94,7 @@ struct DkrImport {
         CurlGlue *glue;
 
         char *index_url;
+        char *image_root;
 
         Hashmap *names;
         Hashmap *jobs;
@@ -406,8 +407,8 @@ static void dkr_import_name_maybe_finish(DkrImportName *name) {
 
                 assert(name->id);
 
-                p = strappenda("/var/lib/container/", name->local);
-                q = strappenda("/var/lib/container/.dkr-", name->id);
+                p = strappenda(name->import->image_root, "/", name->local);
+                q = strappenda(name->import->image_root, "/.dkr-", name->id);
 
                 if (name->force_local) {
                         (void) btrfs_subvol_remove(p);
@@ -534,7 +535,7 @@ static int dkr_import_name_pull_layer(DkrImportName *name) {
                         return 0;
                 }
 
-                path = strjoin("/var/lib/container/.dkr-", layer, NULL);
+                path = strjoin(name->import->image_root, "/.dkr-", layer, NULL);
                 if (!path)
                         return log_oom();
 
@@ -575,7 +576,7 @@ static int dkr_import_name_pull_layer(DkrImportName *name) {
         if (base) {
                 const char *base_path;
 
-                base_path = strappend("/var/lib/container/.dkr-", base);
+                base_path = strappenda(name->import->image_root, "/.dkr-", base);
                 r = btrfs_subvol_snapshot(base_path, temp, false, true);
         } else
                 r = btrfs_subvol_make(temp);
@@ -1021,13 +1022,21 @@ static int dkr_import_name_begin(DkrImportName *name) {
         return dkr_import_name_add_job(name, DKR_IMPORT_JOB_IMAGES, url, &name->job_images);
 }
 
-int dkr_import_new(DkrImport **import, sd_event *event, const char *index_url, dkr_import_on_finished on_finished, void *userdata) {
+int dkr_import_new(
+                DkrImport **import,
+                sd_event *event,
+                const char *index_url,
+                const char *image_root,
+                dkr_import_on_finished on_finished,
+                void *userdata) {
+
         _cleanup_(dkr_import_unrefp) DkrImport *i = NULL;
         char *e;
         int r;
 
         assert(import);
         assert(dkr_url_is_valid(index_url));
+        assert(image_root);
 
         i = new0(DkrImport, 1);
         if (!i)
@@ -1040,6 +1049,10 @@ int dkr_import_new(DkrImport **import, sd_event *event, const char *index_url, d
         if (!i->index_url)
                 return -ENOMEM;
 
+        i->image_root = strdup(image_root);
+        if (!i->image_root)
+                return -ENOMEM;
+
         e = endswith(i->index_url, "/");
         if (e)
                 *e = 0;
@@ -1084,7 +1097,7 @@ DkrImport* dkr_import_unref(DkrImport *import) {
         sd_event_unref(import->event);
 
         free(import->index_url);
-
+        free(import->image_root);
         free(import);
 
         return NULL;
diff --git a/src/import/import-dkr.h b/src/import/import-dkr.h
index 8e6a462..5f88876 100644
--- a/src/import/import-dkr.h
+++ b/src/import/import-dkr.h
@@ -26,7 +26,7 @@ typedef struct DkrImport DkrImport;
 
 typedef void (*dkr_import_on_finished)(DkrImport *import, int error, void *userdata);
 
-int dkr_import_new(DkrImport **import, sd_event *event, const char *index_url, dkr_import_on_finished on_finished, void *userdata);
+int dkr_import_new(DkrImport **import, sd_event *event, const char *index_url, const char *image_root, dkr_import_on_finished on_finished, void *userdata);
 DkrImport* dkr_import_unref(DkrImport *import);
 
 DEFINE_TRIVIAL_CLEANUP_FUNC(DkrImport*, dkr_import_unref);
diff --git a/src/import/import-gpt.c b/src/import/import-gpt.c
index e1c493b..a85ceee 100644
--- a/src/import/import-gpt.c
+++ b/src/import/import-gpt.c
@@ -60,6 +60,7 @@ struct GptImport {
         sd_event *event;
         CurlGlue *glue;
 
+        char *image_root;
         Hashmap *files;
 
         gpt_import_on_finished on_finished;
@@ -129,9 +130,9 @@ static int gpt_import_file_make_final_path(GptImportFile *f) {
                 if (!escaped_etag)
                         return -ENOMEM;
 
-                f->final_path = strjoin("/var/lib/container/.gpt-", escaped_url, ".", escaped_etag, ".gpt", NULL);
+                f->final_path = strjoin(f->import->image_root, "/.gpt-", escaped_url, ".", escaped_etag, ".gpt", NULL);
         } else
-                f->final_path = strjoin("/var/lib/container/.gpt-", escaped_url, ".gpt", NULL);
+                f->final_path = strjoin(f->import->image_root, "/.gpt-", escaped_url, ".gpt", NULL);
         if (!f->final_path)
                 return -ENOMEM;
 
@@ -169,7 +170,7 @@ static void gpt_import_file_success(GptImportFile *f) {
                         }
                 }
 
-                p = strappenda("/var/lib/container/", f->local, ".gpt");
+                p = strappenda(f->import->image_root, "/", f->local, ".gpt");
                 if (f->force_local)
                         (void) rm_rf_dangerous(p, false, true, false);
 
@@ -469,7 +470,7 @@ static int gpt_import_file_find_old_etags(GptImportFile *f) {
         if (!escaped_url)
                 return -ENOMEM;
 
-        d = opendir("/var/lib/container/");
+        d = opendir(f->import->image_root);
         if (!d) {
                 if (errno == ENOENT)
                         return 0;
@@ -575,11 +576,12 @@ static int gpt_import_file_begin(GptImportFile *f) {
         return 0;
 }
 
-int gpt_import_new(GptImport **import, sd_event *event, gpt_import_on_finished on_finished, void *userdata) {
+int gpt_import_new(GptImport **import, sd_event *event, const char *image_root, gpt_import_on_finished on_finished, void *userdata) {
         _cleanup_(gpt_import_unrefp) GptImport *i = NULL;
         int r;
 
         assert(import);
+        assert(image_root);
 
         i = new0(GptImport, 1);
         if (!i)
@@ -588,6 +590,10 @@ int gpt_import_new(GptImport **import, sd_event *event, gpt_import_on_finished o
         i->on_finished = on_finished;
         i->userdata = userdata;
 
+        i->image_root = strdup(image_root);
+        if (!i->image_root)
+                return -ENOMEM;
+
         if (event)
                 i->event = sd_event_ref(event);
         else {
@@ -622,6 +628,7 @@ GptImport* gpt_import_unref(GptImport *import) {
         curl_glue_unref(import->glue);
         sd_event_unref(import->event);
 
+        free(import->image_root);
         free(import);
 
         return NULL;
diff --git a/src/import/import-gpt.h b/src/import/import-gpt.h
index e4c534c..e9003db 100644
--- a/src/import/import-gpt.h
+++ b/src/import/import-gpt.h
@@ -26,7 +26,7 @@ typedef struct GptImport GptImport;
 
 typedef void (*gpt_import_on_finished)(GptImport *import, int error, void *userdata);
 
-int gpt_import_new(GptImport **import, sd_event *event, gpt_import_on_finished on_finished, void *userdata);
+int gpt_import_new(GptImport **import, sd_event *event, const char *image_root, gpt_import_on_finished on_finished, void *userdata);
 GptImport* gpt_import_unref(GptImport *import);
 
 DEFINE_TRIVIAL_CLEANUP_FUNC(GptImport*, gpt_import_unref);
diff --git a/src/import/import.c b/src/import/import.c
index 79dd203..c28ff8f 100644
--- a/src/import/import.c
+++ b/src/import/import.c
@@ -29,6 +29,7 @@
 #include "import-dkr.h"
 
 static bool arg_force = false;
+static const char *arg_image_root = "/var/lib/container";
 
 static const char* arg_dkr_index_url = DEFAULT_DKR_INDEX_URL;
 
@@ -87,7 +88,7 @@ static int pull_gpt(int argc, char *argv[], void *userdata) {
                         return -EINVAL;
                 }
 
-                p = strappenda("/var/lib/container/", local, ".gpt");
+                p = strappenda(arg_image_root, "/", local, ".gpt");
                 if (laccess(p, F_OK) >= 0) {
                         if (!arg_force) {
                                 log_info("Image '%s' already exists.", local);
@@ -108,7 +109,7 @@ static int pull_gpt(int argc, char *argv[], void *userdata) {
         sd_event_add_signal(event, NULL, SIGTERM, NULL,  NULL);
         sd_event_add_signal(event, NULL, SIGINT, NULL, NULL);
 
-        r = gpt_import_new(&import, event, on_gpt_finished, event);
+        r = gpt_import_new(&import, event, arg_image_root, on_gpt_finished, event);
         if (r < 0)
                 return log_error_errno(r, "Failed to allocate importer: %m");
 
@@ -188,7 +189,7 @@ static int pull_dkr(int argc, char *argv[], void *userdata) {
                         return -EINVAL;
                 }
 
-                p = strappenda("/var/lib/container/", local);
+                p = strappenda(arg_image_root, "/", local);
                 if (laccess(p, F_OK) >= 0) {
                         if (!arg_force) {
                                 log_info("Image '%s' already exists.", local);
@@ -209,7 +210,7 @@ static int pull_dkr(int argc, char *argv[], void *userdata) {
         sd_event_add_signal(event, NULL, SIGTERM, NULL,  NULL);
         sd_event_add_signal(event, NULL, SIGINT, NULL, NULL);
 
-        r = dkr_import_new(&import, event, arg_dkr_index_url, on_dkr_finished, event);
+        r = dkr_import_new(&import, event, arg_dkr_index_url, arg_image_root, on_dkr_finished, event);
         if (r < 0)
                 return log_error_errno(r, "Failed to allocate importer: %m");
 
@@ -233,6 +234,7 @@ static int help(int argc, char *argv[], void *userdata) {
                "  -h --help                   Show this help\n"
                "     --version                Show package version\n"
                "     --force                  Force creation of image\n"
+               "     --image-root=            Image root directory\n"
                "     --dkr-index-url=URL      Specify index URL to use for downloads\n\n"
                "Commands:\n"
                "  pull-dkr REMOTE [NAME]      Download a DKR image\n"
@@ -248,6 +250,7 @@ static int parse_argv(int argc, char *argv[]) {
                 ARG_VERSION = 0x100,
                 ARG_FORCE,
                 ARG_DKR_INDEX_URL,
+                ARG_IMAGE_ROOT,
         };
 
         static const struct option options[] = {
@@ -255,6 +258,7 @@ static int parse_argv(int argc, char *argv[]) {
                 { "version",         no_argument,       NULL, ARG_VERSION         },
                 { "force",           no_argument,       NULL, ARG_FORCE           },
                 { "dkr-index-url",   required_argument, NULL, ARG_DKR_INDEX_URL   },
+                { "image-root",      required_argument, NULL, ARG_IMAGE_ROOT      },
                 {}
         };
 
@@ -288,6 +292,10 @@ static int parse_argv(int argc, char *argv[]) {
                         arg_dkr_index_url = optarg;
                         break;
 
+                case ARG_IMAGE_ROOT:
+                        arg_image_root = optarg;
+                        break;
+
                 case '?':
                         return -EINVAL;
 

commit 5fc7f358420883c73dd662769b1670c0694111a0
Author: Lennart Poettering <lennart at poettering.net>
Date:   Fri Dec 26 18:59:26 2014 +0100

    machined: when discovering images, implicitly add ".host" as pseudo image referring to the host's own directory tree

diff --git a/src/machine/image.c b/src/machine/image.c
index 4f59c57..1574adf 100644
--- a/src/machine/image.c
+++ b/src/machine/image.c
@@ -45,8 +45,9 @@ Image *image_unref(Image *i) {
 
 static int image_new(
                 ImageType t,
-                const char *name,
+                const char *pretty,
                 const char *path,
+                const char *filename,
                 bool read_only,
                 usec_t crtime,
                 usec_t mtime,
@@ -56,7 +57,8 @@ static int image_new(
 
         assert(t >= 0);
         assert(t < _IMAGE_TYPE_MAX);
-        assert(name);
+        assert(pretty);
+        assert(filename);
         assert(ret);
 
         i = new0(Image, 1);
@@ -68,17 +70,19 @@ static int image_new(
         i->crtime = crtime;
         i->mtime = mtime;
 
-        i->name = strdup(name);
+        i->name = strdup(pretty);
         if (!i->name)
                 return -ENOMEM;
 
-        if (path) {
-                i->path = strjoin(path, "/", name, NULL);
-                if (!i->path)
-                        return -ENOMEM;
+        if (path)
+                i->path = strjoin(path, "/", filename, NULL);
+        else
+                i->path = strdup(filename);
 
-                path_kill_slashes(i->path);
-        }
+        if (!i->path)
+                return -ENOMEM;
+
+        path_kill_slashes(i->path);
 
         *ret = i;
         i = NULL;
@@ -86,34 +90,44 @@ static int image_new(
         return 0;
 }
 
-static int image_make(int dfd, const char *name, const char *path, Image **ret) {
+static int image_make(
+                const char *pretty,
+                int dfd,
+                const char *path,
+                const char *filename,
+                Image **ret) {
+
         struct stat st;
-        bool writable;
+        bool read_only;
         int r;
 
-        assert(dfd >= 0);
-        assert(name);
+        assert(filename);
 
         /* We explicitly *do* follow symlinks here, since we want to
          * allow symlinking trees into /var/lib/container/, and treat
          * them normally. */
 
-        if (fstatat(dfd, name, &st, 0) < 0)
+        if (fstatat(dfd, filename, &st, 0) < 0)
                 return -errno;
 
-        writable = faccessat(dfd, name, W_OK, AT_EACCESS) >= 0;
+        read_only =
+                (path && path_startswith(path, "/usr")) ||
+                faccessat(dfd, filename, W_OK, AT_EACCESS) < 0;
 
         if (S_ISDIR(st.st_mode)) {
 
                 if (!ret)
                         return 1;
 
+                if (!pretty)
+                        pretty = filename;
+
                 /* btrfs subvolumes have inode 256 */
                 if (st.st_ino == 256) {
                         _cleanup_close_ int fd = -1;
                         struct statfs sfs;
 
-                        fd = openat(dfd, name, O_CLOEXEC|O_NOCTTY|O_DIRECTORY);
+                        fd = openat(dfd, filename, O_CLOEXEC|O_NOCTTY|O_DIRECTORY);
                         if (fd < 0)
                                 return -errno;
 
@@ -130,9 +144,10 @@ static int image_make(int dfd, const char *name, const char *path, Image **ret)
                                         return r;
 
                                 r = image_new(IMAGE_SUBVOLUME,
-                                              name,
+                                              pretty,
                                               path,
-                                              info.read_only || !writable,
+                                              filename,
+                                              info.read_only || read_only,
                                               info.otime,
                                               0,
                                               ret);
@@ -146,9 +161,10 @@ static int image_make(int dfd, const char *name, const char *path, Image **ret)
                 /* It's just a normal directory. */
 
                 r = image_new(IMAGE_DIRECTORY,
-                              name,
+                              pretty,
                               path,
-                              !writable,
+                              filename,
+                              read_only,
                               0,
                               0,
                               ret);
@@ -157,8 +173,7 @@ static int image_make(int dfd, const char *name, const char *path, Image **ret)
 
                 return 1;
 
-        } else if (S_ISREG(st.st_mode) && endswith(name, ".gpt")) {
-                const char *truncated;
+        } else if (S_ISREG(st.st_mode) && endswith(filename, ".gpt")) {
                 usec_t crtime = 0;
 
                 /* It's a GPT block device */
@@ -166,14 +181,16 @@ static int image_make(int dfd, const char *name, const char *path, Image **ret)
                 if (!ret)
                         return 1;
 
-                fd_getcrtime_at(dfd, name, &crtime, 0);
+                fd_getcrtime_at(dfd, filename, &crtime, 0);
 
-                truncated = strndupa(name, strlen(name) - 4);
+                if (!pretty)
+                        pretty = strndupa(filename, strlen(filename) - 4);
 
                 r = image_new(IMAGE_GPT,
-                              truncated,
+                              pretty,
                               path,
-                              !(st.st_mode & 0222) || !writable,
+                              filename,
+                              !(st.st_mode & 0222) || read_only,
                               crtime,
                               timespec_load(&st.st_mtim),
                               ret);
@@ -207,15 +224,27 @@ int image_find(const char *name, Image **ret) {
                         return -errno;
                 }
 
-                r = image_make(dirfd(d), name, path, ret);
-                if (r == 0 || r == -ENOENT)
-                        continue;
+                r = image_make(NULL, dirfd(d), path, name, ret);
+                if (r == 0 || r == -ENOENT) {
+                        _cleanup_free_ char *gpt = NULL;
+
+                        gpt = strappend(name, ".gpt");
+                        if (!gpt)
+                                return -ENOMEM;
+
+                        r = image_make(NULL, dirfd(d), path, gpt, ret);
+                        if (r == 0 || r == -ENOENT)
+                                continue;
+                }
                 if (r < 0)
                         return r;
 
                 return 1;
         }
 
+        if (streq(name, ".host"))
+                return image_make(NULL, AT_FDCWD, NULL, "/", ret);
+
         return 0;
 };
 
@@ -246,7 +275,7 @@ int image_discover(Hashmap *h) {
                         if (hashmap_contains(h, de->d_name))
                                 continue;
 
-                        r = image_make(dirfd(d), de->d_name, path, &image);
+                        r = image_make(NULL, dirfd(d), path, de->d_name, &image);
                         if (r == 0 || r == -ENOENT)
                                 continue;
                         if (r < 0)
@@ -260,6 +289,21 @@ int image_discover(Hashmap *h) {
                 }
         }
 
+        if (!hashmap_contains(h, ".host")) {
+                _cleanup_(image_unrefp) Image *image = NULL;
+
+                r = image_make(".host", AT_FDCWD, NULL, "/", &image);
+                if (r < 0)
+                        return r;
+
+                r = hashmap_put(h, image->name, image);
+                if (r < 0)
+                        return r;
+
+                image = NULL;
+
+        }
+
         return 0;
 }
 

commit a67a4c8cb728db1d803ebd43dcc39c7de50f2725
Author: Lennart Poettering <lennart at poettering.net>
Date:   Fri Dec 26 18:19:47 2014 +0100

    machined: fix image search path iteration

diff --git a/src/machine/image.c b/src/machine/image.c
index c4645a1..4f59c57 100644
--- a/src/machine/image.c
+++ b/src/machine/image.c
@@ -232,7 +232,7 @@ int image_discover(Hashmap *h) {
                 d = opendir(path);
                 if (!d) {
                         if (errno == ENOENT)
-                                return 0;
+                                continue;
 
                         return -errno;
                 }

commit 42c6f2c9b2e5c2f013ce0af20bb11f63e9ec13c9
Author: Lennart Poettering <lennart at poettering.net>
Date:   Fri Dec 26 18:19:22 2014 +0100

    machined: let's also check machine directories in /usr and /usr/local

diff --git a/src/machine/image.c b/src/machine/image.c
index f72a5c3..c4645a1 100644
--- a/src/machine/image.c
+++ b/src/machine/image.c
@@ -28,8 +28,10 @@
 #include "image.h"
 
 static const char image_search_path[] =
+        "/var/lib/machines\0"
         "/var/lib/container\0"
-        "/var/lib/machine\0";
+        "/usr/local/lib/machines\0"
+        "/usr/lib/machines\0";
 
 Image *image_unref(Image *i) {
         if (!i)

commit f0be89eee9bf887bac87b59702bf688f691c2340
Author: Lennart Poettering <lennart at poettering.net>
Date:   Fri Dec 26 18:18:47 2014 +0100

    import: properly remove pre-existing images if --force is used

diff --git a/src/import/import-dkr.c b/src/import/import-dkr.c
index 1658053..fbd6412 100644
--- a/src/import/import-dkr.c
+++ b/src/import/import-dkr.c
@@ -411,12 +411,12 @@ static void dkr_import_name_maybe_finish(DkrImportName *name) {
 
                 if (name->force_local) {
                         (void) btrfs_subvol_remove(p);
-                        (void) rm_rf(p, false, true, false);
+                        (void) rm_rf_dangerous(p, false, true, false);
                 }
 
                 r = btrfs_subvol_snapshot(q, p, false, false);
                 if (r < 0) {
-                        log_error_errno(r, "Failed to snapshot final image: %m");
+                        log_error_errno(r, "Failed to snapshot local image: %m");
                         dkr_import_finish(name->import, r);
                         return;
                 }

commit 8620a9a32391fd74d70ddc07c9b79729ad4ec067
Author: Lennart Poettering <lennart at poettering.net>
Date:   Fri Dec 26 17:10:31 2014 +0100

    import: beef up gpt importer to optionally make writable copy of read-only vendor image

diff --git a/src/import/import-gpt.c b/src/import/import-gpt.c
index a81efa2..e1c493b 100644
--- a/src/import/import-gpt.c
+++ b/src/import/import-gpt.c
@@ -26,6 +26,8 @@
 #include "utf8.h"
 #include "curl-util.h"
 #include "import-gpt.h"
+#include "strv.h"
+#include "copy.h"
 
 typedef struct GptImportFile GptImportFile;
 
@@ -41,7 +43,7 @@ struct GptImportFile {
         char *temp_path;
         char *final_path;
         char *etag;
-        char *old_etag;
+        char **old_etags;
 
         uint64_t content_length;
         uint64_t written;
@@ -66,6 +68,8 @@ struct GptImport {
         bool finished;
 };
 
+#define FILENAME_ESCAPE "/.#\"\'"
+
 static GptImportFile *gpt_import_file_unref(GptImportFile *f) {
         if (!f)
                 return NULL;
@@ -86,7 +90,7 @@ static GptImportFile *gpt_import_file_unref(GptImportFile *f) {
         free(f->url);
         free(f->local);
         free(f->etag);
-        free(f->old_etag);
+        strv_free(f->old_etags);
         free(f);
 
         return NULL;
@@ -108,6 +112,108 @@ static void gpt_import_finish(GptImport *import, int error) {
                 sd_event_exit(import->event, error);
 }
 
+static int gpt_import_file_make_final_path(GptImportFile *f) {
+        _cleanup_free_ char *escaped_url = NULL, *escaped_etag = NULL;
+
+        assert(f);
+
+        if (f->final_path)
+                return 0;
+
+        escaped_url = xescape(f->url, FILENAME_ESCAPE);
+        if (!escaped_url)
+                return -ENOMEM;
+
+        if (f->etag) {
+                escaped_etag = xescape(f->etag, FILENAME_ESCAPE);
+                if (!escaped_etag)
+                        return -ENOMEM;
+
+                f->final_path = strjoin("/var/lib/container/.gpt-", escaped_url, ".", escaped_etag, ".gpt", NULL);
+        } else
+                f->final_path = strjoin("/var/lib/container/.gpt-", escaped_url, ".gpt", NULL);
+        if (!f->final_path)
+                return -ENOMEM;
+
+        return 0;
+}
+
+static void gpt_import_file_success(GptImportFile *f) {
+        int r;
+
+        assert(f);
+
+        f->done = true;
+
+        if (f->local) {
+                _cleanup_free_ char *tp = NULL;
+                _cleanup_close_ int dfd = -1;
+                const char *p;
+
+                if (f->disk_fd >= 0) {
+                        if (lseek(f->disk_fd, SEEK_SET, 0) == (off_t) -1) {
+                                r = log_error_errno(errno, "Failed to seek to beginning of vendor image: %m");
+                                goto finish;
+                        }
+                } else {
+                        r = gpt_import_file_make_final_path(f);
+                        if (r < 0) {
+                                log_oom();
+                                goto finish;
+                        }
+
+                        f->disk_fd = open(f->final_path, O_RDONLY|O_NOCTTY|O_CLOEXEC);
+                        if (f->disk_fd < 0) {
+                                r = log_error_errno(errno, "Failed top open vendor image: %m");
+                                goto finish;
+                        }
+                }
+
+                p = strappenda("/var/lib/container/", f->local, ".gpt");
+                if (f->force_local)
+                        (void) rm_rf_dangerous(p, false, true, false);
+
+                r = tempfn_random(p, &tp);
+                if (r < 0) {
+                        log_oom();
+                        goto finish;
+                }
+
+                dfd = open(tp, O_WRONLY|O_CREAT|O_EXCL|O_NOCTTY|O_CLOEXEC, 0664);
+                if (dfd < 0) {
+                        r = log_error_errno(errno, "Failed to create writable copy of image: %m");
+                        goto finish;
+                }
+
+                r = copy_bytes(f->disk_fd, dfd, (off_t) -1, true);
+                if (r < 0) {
+                        log_error_errno(r, "Failed to make writable copy of image: %m");
+                        unlink(tp);
+                        goto finish;
+                }
+
+                (void) copy_times(f->disk_fd, dfd);
+                (void) copy_xattr(f->disk_fd, dfd);
+
+                dfd = safe_close(dfd);
+
+                r = rename(tp, p);
+                if (r < 0) {
+                        r = log_error_errno(errno, "Failed to move writable image into place: %m");
+                        unlink(tp);
+                        goto finish;
+                }
+
+                log_info("Created new local image %s.", p);
+        }
+
+        f->disk_fd = safe_close(f->disk_fd);
+        r = 0;
+
+finish:
+        gpt_import_finish(f->import, r);
+}
+
 static void gpt_import_curl_on_finished(CurlGlue *g, CURL *curl, CURLcode result) {
         GptImportFile *f = NULL;
         struct stat st;
@@ -118,7 +224,7 @@ static void gpt_import_curl_on_finished(CurlGlue *g, CURL *curl, CURLcode result
         if (curl_easy_getinfo(curl, CURLINFO_PRIVATE, &f) != CURLE_OK)
                 return;
 
-        if (!f)
+        if (!f || f->done)
                 return;
 
         f->done = true;
@@ -135,9 +241,9 @@ static void gpt_import_curl_on_finished(CurlGlue *g, CURL *curl, CURLcode result
                 r = -EIO;
                 goto fail;
         } else if (status == 304) {
-                log_info("File unmodified.");
-                r = 0;
-                goto fail;
+                log_info("Image already downloaded. Skipping download.");
+                gpt_import_file_success(f);
+                return;
         } else if (status >= 300) {
                 log_error("HTTP request to %s failed with code %li.", f->url, status);
                 r = -EIO;
@@ -162,14 +268,15 @@ static void gpt_import_curl_on_finished(CurlGlue *g, CURL *curl, CURLcode result
         }
 
         if (f->etag)
-                (void) fsetxattr(f->disk_fd, "user.etag", f->etag, strlen(f->etag), XATTR_CREATE);
+                (void) fsetxattr(f->disk_fd, "user.source_etag", f->etag, strlen(f->etag), 0);
+        if (f->url)
+                (void) fsetxattr(f->disk_fd, "user.source_url", f->url, strlen(f->url), 0);
 
         if (f->mtime != 0) {
                 struct timespec ut[2];
 
                 timespec_store(&ut[0], f->mtime);
                 ut[1] = ut[0];
-
                 (void) futimens(f->disk_fd, ut);
 
                 fd_setcrtime(f->disk_fd, f->mtime);
@@ -183,8 +290,6 @@ static void gpt_import_curl_on_finished(CurlGlue *g, CURL *curl, CURLcode result
         /* Mark read-only */
         (void) fchmod(f->disk_fd, st.st_mode & 07444);
 
-        f->disk_fd = safe_close(f->disk_fd);
-
         assert(f->temp_path);
         assert(f->final_path);
 
@@ -194,13 +299,19 @@ static void gpt_import_curl_on_finished(CurlGlue *g, CURL *curl, CURLcode result
                 goto fail;
         }
 
-        r = 0;
+        free(f->temp_path);
+        f->temp_path = NULL;
+
+        log_info("Completed writing vendor image %s.", f->final_path);
+
+        gpt_import_file_success(f);
+        return;
 
 fail:
         gpt_import_finish(f->import, r);
 }
 
-static int gpt_import_file_open_disk(GptImportFile *f) {
+static int gpt_import_file_open_disk_for_write(GptImportFile *f) {
         int r;
 
         assert(f);
@@ -208,7 +319,9 @@ static int gpt_import_file_open_disk(GptImportFile *f) {
         if (f->disk_fd >= 0)
                 return 0;
 
-        assert(f->final_path);
+        r = gpt_import_file_make_final_path(f);
+        if (r < 0)
+                return log_oom();
 
         if (!f->temp_path) {
                 r = tempfn_random(f->final_path, &f->temp_path);
@@ -216,7 +329,7 @@ static int gpt_import_file_open_disk(GptImportFile *f) {
                         return log_oom();
         }
 
-        f->disk_fd = open(f->temp_path, O_CREAT|O_EXCL|O_NOCTTY|O_CLOEXEC|O_WRONLY, 0644);
+        f->disk_fd = open(f->temp_path, O_RDWR|O_CREAT|O_EXCL|O_NOCTTY|O_CLOEXEC, 0644);
         if (f->disk_fd < 0)
                 return log_error_errno(errno, "Failed to create %s: %m", f->temp_path);
 
@@ -232,7 +345,12 @@ static size_t gpt_import_file_write_callback(void *contents, size_t size, size_t
         assert(contents);
         assert(f);
 
-        r = gpt_import_file_open_disk(f);
+        if (f->done) {
+                r = -ESTALE;
+                goto fail;
+        }
+
+        r = gpt_import_file_open_disk_for_write(f);
         if (r < 0)
                 goto fail;
 
@@ -280,6 +398,11 @@ static size_t gpt_import_file_header_callback(void *contents, size_t size, size_
         assert(contents);
         assert(f);
 
+        if (f->done) {
+                r = -ESTALE;
+                goto fail;
+        }
+
         r = curl_header_strdup(contents, sz, "ETag:", &etag);
         if (r < 0) {
                 log_oom();
@@ -289,9 +412,9 @@ static size_t gpt_import_file_header_callback(void *contents, size_t size, size_
                 free(f->etag);
                 f->etag = etag;
 
-                if (streq_ptr(f->old_etag, f->etag)) {
-                        log_info("Image already up to date. Finishing.");
-                        gpt_import_finish(f->import, 0);
+                if (strv_contains(f->old_etags, f->etag)) {
+                        log_info("Image already downloaded. Skipping download.");
+                        gpt_import_file_success(f);
                         return sz;
                 }
 
@@ -325,6 +448,79 @@ fail:
         return 0;
 }
 
+static bool etag_is_valid(const char *etag) {
+
+        if (!endswith(etag, "\""))
+                return false;
+
+        if (!startswith(etag, "\"") && !startswith(etag, "W/\""))
+                return false;
+
+        return true;
+}
+
+static int gpt_import_file_find_old_etags(GptImportFile *f) {
+        _cleanup_free_ char *escaped_url = NULL;
+        _cleanup_closedir_ DIR *d = NULL;
+        struct dirent *de;
+        int r;
+
+        escaped_url = xescape(f->url, FILENAME_ESCAPE);
+        if (!escaped_url)
+                return -ENOMEM;
+
+        d = opendir("/var/lib/container/");
+        if (!d) {
+                if (errno == ENOENT)
+                        return 0;
+
+                return -errno;
+        }
+
+        FOREACH_DIRENT_ALL(de, d, return -errno) {
+                const char *a, *b;
+                char *u;
+
+                if (de->d_type != DT_UNKNOWN &&
+                    de->d_type != DT_REG)
+                        continue;
+
+                a = startswith(de->d_name, ".gpt-");
+                if (!a)
+                        continue;
+
+                a = startswith(a, escaped_url);
+                if (!a)
+                        continue;
+
+                a = startswith(a, ".");
+                if (!a)
+                        continue;
+
+                b = endswith(de->d_name, ".gpt");
+                if (!b)
+                        continue;
+
+                if (a >= b)
+                        continue;
+
+                u = cunescape_length(a, b - a);
+                if (!u)
+                        return -ENOMEM;
+
+                if (!etag_is_valid(u)) {
+                        free(u);
+                        continue;
+                }
+
+                r = strv_consume(&f->old_etags, u);
+                if (r < 0)
+                        return r;
+        }
+
+        return 0;
+}
+
 static int gpt_import_file_begin(GptImportFile *f) {
         int r;
 
@@ -333,14 +529,24 @@ static int gpt_import_file_begin(GptImportFile *f) {
 
         log_info("Getting %s.", f->url);
 
+        r = gpt_import_file_find_old_etags(f);
+        if (r < 0)
+                return r;
+
         r = curl_glue_make(&f->curl, f->url, f);
         if (r < 0)
                 return r;
 
-        if (f->old_etag) {
-                const char *hdr;
+        if (!strv_isempty(f->old_etags)) {
+                _cleanup_free_ char *cc = NULL, *hdr = NULL;
 
-                hdr = strappenda("If-None-Match: ", f->old_etag);
+                cc = strv_join(f->old_etags, ", ");
+                if (!cc)
+                        return -ENOMEM;
+
+                hdr = strappend("If-None-Match: ", cc);
+                if (!hdr)
+                        return -ENOMEM;
 
                 f->request_header = curl_slist_new(hdr, NULL);
                 if (!f->request_header)
@@ -437,13 +643,11 @@ int gpt_import_cancel(GptImport *import, const char *url) {
 
 int gpt_import_pull(GptImport *import, const char *url, const char *local, bool force_local) {
         _cleanup_(gpt_import_file_unrefp) GptImportFile *f = NULL;
-        char etag[LINE_MAX];
-        ssize_t n;
         int r;
 
         assert(import);
         assert(gpt_url_is_valid(url));
-        assert(machine_name_is_valid(local));
+        assert(!local || machine_name_is_valid(local));
 
         if (hashmap_get(import->files, url))
                 return -EEXIST;
@@ -464,19 +668,12 @@ int gpt_import_pull(GptImport *import, const char *url, const char *local, bool
         if (!f->url)
                 return -ENOMEM;
 
-        f->local = strdup(local);
-        if (!f->local)
-                return -ENOMEM;
-
-        f->final_path = strjoin("/var/lib/container/", local, ".gpt", NULL);
-        if (!f->final_path)
-                return -ENOMEM;
-
-        n = getxattr(f->final_path, "user.etag", etag, sizeof(etag));
-        if (n > 0) {
-                f->old_etag = strndup(etag, n);
-                if (!f->old_etag)
+        if (local) {
+                f->local = strdup(local);
+                if (!f->local)
                         return -ENOMEM;
+
+                f->force_local = force_local;
         }
 
         r = hashmap_put(import->files, f->url, f);
diff --git a/src/import/import.c b/src/import/import.c
index a81200c..79dd203 100644
--- a/src/import/import.c
+++ b/src/import/import.c
@@ -47,22 +47,18 @@ static void on_gpt_finished(GptImport *import, int error, void *userdata) {
 static int pull_gpt(int argc, char *argv[], void *userdata) {
         _cleanup_(gpt_import_unrefp) GptImport *import = NULL;
         _cleanup_event_unref_ sd_event *event = NULL;
-        _cleanup_free_ char *local_truncated = NULL, *local_generated = NULL;
         const char *url, *local, *suffix;
         int r;
 
         url = argv[1];
-        local = argv[2];
-
         if (!gpt_url_is_valid(url)) {
                 log_error("URL '%s' is not valid.", url);
                 return -EINVAL;
         }
 
-        if (isempty(local) || streq(local, "-"))
-                local = NULL;
-
-        if (!local) {
+        if (argc >= 3)
+                local = argv[2];
+        else {
                 const char *e, *p;
 
                 e = url + strlen(url);
@@ -73,28 +69,36 @@ static int pull_gpt(int argc, char *argv[], void *userdata) {
                 while (p > url && p[-1] != '/')
                         p--;
 
-                local_generated = strndup(p, e - p);
-                if (!local_generated)
-                        return log_oom();
-
-                local = local_generated;
+                local = strndupa(p, e - p);
         }
 
-        suffix = endswith(local, ".gpt");
-        if (suffix) {
-                local_truncated = strndup(local, suffix - local);
-                if (!local_truncated)
-                        return log_oom();
+        if (isempty(local) || streq(local, "-"))
+                local = NULL;
 
-                local = local_truncated;
-        }
+        if (local) {
+                const char *p;
 
-        if (!machine_name_is_valid(local)) {
-                log_error("Local image name '%s' is not valid.", local);
-                return -EINVAL;
-        }
+                suffix = endswith(local, ".gpt");
+                if (suffix)
+                        local = strndupa(local, suffix - local);
 
-        log_info("Pulling '%s' as '%s'", url, local);
+                if (!machine_name_is_valid(local)) {
+                        log_error("Local image name '%s' is not valid.", local);
+                        return -EINVAL;
+                }
+
+                p = strappenda("/var/lib/container/", local, ".gpt");
+                if (laccess(p, F_OK) >= 0) {
+                        if (!arg_force) {
+                                log_info("Image '%s' already exists.", local);
+                                return 0;
+                        }
+                } else if (errno != ENOENT)
+                        return log_error_errno(errno, "Can't check if image '%s' already exists: %m", local);
+
+                log_info("Pulling '%s', saving as '%s'.", url, local);
+        } else
+                log_info("Pulling '%s'.", url);
 
         r = sd_event_default(&event);
         if (r < 0)
@@ -153,6 +157,16 @@ static int pull_dkr(int argc, char *argv[], void *userdata) {
                 tag = "latest";
         }
 
+        if (!dkr_name_is_valid(name)) {
+                log_error("Remote name '%s' is not valid.", name);
+                return -EINVAL;
+        }
+
+        if (!dkr_tag_is_valid(tag)) {
+                log_error("Tag name '%s' is not valid.", tag);
+                return -EINVAL;
+        }
+
         if (argc >= 3)
                 local = argv[2];
         else {
@@ -166,16 +180,6 @@ static int pull_dkr(int argc, char *argv[], void *userdata) {
         if (isempty(local) || streq(local, "-"))
                 local = NULL;
 
-        if (!dkr_name_is_valid(name)) {
-                log_error("Remote name '%s' is not valid.", name);
-                return -EINVAL;
-        }
-
-        if (!dkr_tag_is_valid(tag)) {
-                log_error("Tag name '%s' is not valid.", tag);
-                return -EINVAL;
-        }
-
         if (local) {
                 const char *p;
 

commit e9d7333468ff02fd45a8aeb957e758f641026278
Author: Lennart Poettering <lennart at poettering.net>
Date:   Fri Dec 26 17:10:05 2014 +0100

    import: minor improvements to dkr importer

diff --git a/src/import/import-dkr.c b/src/import/import-dkr.c
index b290619..1658053 100644
--- a/src/import/import-dkr.c
+++ b/src/import/import-dkr.c
@@ -421,7 +421,7 @@ static void dkr_import_name_maybe_finish(DkrImportName *name) {
                         return;
                 }
 
-                log_info("Created new image %s.", p);
+                log_info("Created new local image %s.", p);
         }
 
         dkr_import_finish(name->import, 0);
@@ -718,7 +718,7 @@ static void dkr_import_curl_on_finished(CurlGlue *g, CURL *curl, CURLcode result
         if (curl_easy_getinfo(curl, CURLINFO_PRIVATE, &job) != CURLE_OK)
                 return;
 
-        if (!job)
+        if (!job || job->done)
                 return;
 
         job->done = true;
@@ -817,6 +817,11 @@ static size_t dkr_import_job_write_callback(void *contents, size_t size, size_t
         assert(contents);
         assert(j);
 
+        if (j->done) {
+                r = -ESTALE;
+                goto fail;
+        }
+
         if (j->tar_stream) {
                 size_t l;
 
@@ -866,6 +871,11 @@ static size_t dkr_import_job_header_callback(void *contents, size_t size, size_t
         assert(contents);
         assert(j);
 
+        if (j->done) {
+                r = -ESTALE;
+                goto fail;
+        }
+
         r = curl_header_strdup(contents, sz, HEADER_TOKEN, &token);
         if (r < 0) {
                 log_oom();

commit 2c39ea529b35383022946a07eeecd6711bcd2684
Author: Lennart Poettering <lennart at poettering.net>
Date:   Fri Dec 26 17:03:01 2014 +0100

    util: always override crtime xattr

diff --git a/src/shared/util.c b/src/shared/util.c
index 52e5df4..d04d738 100644
--- a/src/shared/util.c
+++ b/src/shared/util.c
@@ -7645,7 +7645,7 @@ int fd_setcrtime(int fd, usec_t usec) {
         assert(fd >= 0);
 
         le = htole64((uint64_t) usec);
-        if (fsetxattr(fd, "user.crtime_usec", &le, sizeof(le), XATTR_CREATE) < 0)
+        if (fsetxattr(fd, "user.crtime_usec", &le, sizeof(le), 0) < 0)
                 return -errno;
 
         return 0;

commit 6389e747d5b09b18e00d85b9b13c1be2ff884015
Author: Lennart Poettering <lennart at poettering.net>
Date:   Fri Dec 26 17:01:00 2014 +0100

    machinectl: left-align times

diff --git a/src/machine/machinectl.c b/src/machine/machinectl.c
index 1cc5dfc..64caf7c 100644
--- a/src/machine/machinectl.c
+++ b/src/machine/machinectl.c
@@ -221,7 +221,7 @@ static int list_images(int argc, char *argv[], void *userdata) {
         qsort_safe(images, n_images, sizeof(ImageInfo), compare_image_info);
 
         if (arg_legend)
-                printf("%-*s %-*s %-3s %*s %*s\n",
+                printf("%-*s %-*s %-3s %-*s %-*s\n",
                        (int) max_name, "NAME",
                        (int) max_type, "TYPE",
                        "RO",
@@ -231,7 +231,7 @@ static int list_images(int argc, char *argv[], void *userdata) {
         for (j = 0; j < n_images; j++) {
                 char crtime_buf[FORMAT_TIMESTAMP_MAX], mtime_buf[FORMAT_TIMESTAMP_MAX];
 
-                printf("%-*s %-*s %-3s %*s %*s\n",
+                printf("%-*s %-*s %-3s %-*s %-*s\n",
                        (int) max_name, images[j].name,
                        (int) max_type, images[j].type,
                        yes_no(images[j].read_only),

commit e6bd041c973c1f4074c3268b23c380ede0d64f19
Author: Lennart Poettering <lennart at poettering.net>
Date:   Fri Dec 26 17:00:39 2014 +0100

    copy: try top copy atime/time/xattrs when copying files

diff --git a/src/shared/copy.c b/src/shared/copy.c
index 0c2cdc8..92f6e1e 100644
--- a/src/shared/copy.c
+++ b/src/shared/copy.c
@@ -20,6 +20,7 @@
 ***/
 
 #include <sys/sendfile.h>
+#include <sys/xattr.h>
 
 #include "util.h"
 #include "btrfs-util.h"
@@ -145,6 +146,9 @@ static int fd_copy_regular(int df, const char *from, const struct stat *st, int
         if (fchmod(fdt, st->st_mode & 07777) < 0)
                 r = -errno;
 
+        (void) copy_times(fdf, fdt);
+        (void) copy_xattr(fdf, fdt);
+
         q = close(fdt);
         fdt = -1;
 
@@ -244,6 +248,9 @@ static int fd_copy_directory(
 
                 if (fchmod(fdt, st->st_mode & 07777) < 0)
                         r = -errno;
+
+                (void) copy_times(fdf, fdt);
+                (void) copy_xattr(fdf, fdt);
         }
 
         FOREACH_DIRENT(de, d, return -errno) {
@@ -326,6 +333,7 @@ int copy_directory_fd(int dirfd, const char *to, bool merge) {
 
 int copy_file_fd(const char *from, int fdt, bool try_reflink) {
         _cleanup_close_ int fdf = -1;
+        int r;
 
         assert(from);
         assert(fdt >= 0);
@@ -334,7 +342,12 @@ int copy_file_fd(const char *from, int fdt, bool try_reflink) {
         if (fdf < 0)
                 return -errno;
 
-        return copy_bytes(fdf, fdt, (off_t) -1, try_reflink);
+        r = copy_bytes(fdf, fdt, (off_t) -1, try_reflink);
+
+        (void) copy_times(fdf, fdt);
+        (void) copy_xattr(fdf, fdt);
+
+        return r;
 }
 
 int copy_file(const char *from, const char *to, int flags, mode_t mode) {
@@ -361,3 +374,91 @@ int copy_file(const char *from, const char *to, int flags, mode_t mode) {
 
         return 0;
 }
+
+int copy_times(int fdf, int fdt) {
+        struct timespec ut[2];
+        struct stat st;
+        usec_t crtime;
+
+        assert(fdf >= 0);
+        assert(fdt >= 0);
+
+        if (fstat(fdf, &st) < 0)
+                return -errno;
+
+        ut[0] = st.st_atim;
+        ut[1] = st.st_mtim;
+
+        if (futimens(fdt, ut) < 0)
+                return -errno;
+
+        if (fd_getcrtime(fdf, &crtime) >= 0)
+                (void) fd_setcrtime(fdt, crtime);
+
+        return 0;
+}
+
+int copy_xattr(int fdf, int fdt) {
+        _cleanup_free_ char *bufa = NULL, *bufb = NULL;
+        size_t sza = 100, szb = 100;
+        ssize_t n;
+        int ret = 0;
+        const char *p;
+
+        for (;;) {
+                bufa = malloc(sza);
+                if (!bufa)
+                        return -ENOMEM;
+
+                n = flistxattr(fdf, bufa, sza);
+                if (n == 0)
+                        return 0;
+                if (n > 0)
+                        break;
+                if (errno != ERANGE)
+                        return -errno;
+
+                sza *= 2;
+
+                free(bufa);
+                bufa = NULL;
+        }
+
+        p = bufa;
+        while (n > 0) {
+                size_t l;
+
+                l = strlen(p);
+                assert(l < (size_t) n);
+
+                if (startswith(p, "user.")) {
+                        ssize_t m;
+
+                        if (!bufb) {
+                                bufb = malloc(szb);
+                                if (!bufb)
+                                        return -ENOMEM;
+                        }
+
+                        m = fgetxattr(fdf, p, bufb, szb);
+                        if (m < 0) {
+                                if (errno == ERANGE) {
+                                        szb *= 2;
+                                        free(bufb);
+                                        bufb = NULL;
+                                        continue;
+                                }
+
+                                return -errno;
+                        }
+
+                        if (fsetxattr(fdt, p, bufb, m, 0) < 0)
+                                ret = -errno;
+                }
+
+                p += l + 1;
+                n -= l + 1;
+        }
+
+        return ret;
+}
diff --git a/src/shared/copy.h b/src/shared/copy.h
index 714addf..6d725ef 100644
--- a/src/shared/copy.h
+++ b/src/shared/copy.h
@@ -30,3 +30,5 @@ int copy_tree(const char *from, const char *to, bool merge);
 int copy_tree_at(int fdf, const char *from, int fdt, const char *to, bool merge);
 int copy_directory_fd(int dirfd, const char *to, bool merge);
 int copy_bytes(int fdf, int fdt, off_t max_bytes, bool try_reflink);
+int copy_times(int fdf, int fdt);
+int copy_xattr(int fdf, int fdt);

commit c75f27ea2b483f91d437ebaf8494457dc76f3fd6
Author: Lennart Poettering <lennart at poettering.net>
Date:   Fri Dec 26 16:59:53 2014 +0100

    test: improve btrfs test case

diff --git a/src/test/test-btrfs.c b/src/test/test-btrfs.c
index 4a08c72..43b445d 100644
--- a/src/test/test-btrfs.c
+++ b/src/test/test-btrfs.c
@@ -29,26 +29,30 @@
 
 int main(int argc, char *argv[]) {
         int r;
-        BtrfsSubvolInfo info;
-        char ts[FORMAT_TIMESTAMP_MAX];
         int fd;
 
         fd = open("/", O_RDONLY|O_CLOEXEC|O_DIRECTORY);
         if (fd < 0)
                 log_error_errno(errno, "Failed to open root directory: %m");
         else {
+                BtrfsSubvolInfo info;
+                char ts[FORMAT_TIMESTAMP_MAX];
+
                 r = btrfs_subvol_get_info_fd(fd, &info);
                 if (r < 0)
                         log_error_errno(r, "Failed to get subvolume info: %m");
                 else {
                         log_info("otime: %s", format_timestamp(ts, sizeof(ts), info.otime));
-                        log_info("read-only: %s", yes_no(info.read_only));
+                        log_info("read-only (search): %s", yes_no(info.read_only));
                 }
 
                 r = btrfs_subvol_get_read_only_fd(fd);
-                assert_se(r >= 0);
+                if (r < 0)
+                        log_error_errno(r, "Failed to get read only flag: %m");
+                else
+                        log_info("read-only (ioctl): %s", yes_no(r));
 
-                log_info("read-only: %s", yes_no(r));
+                safe_close(fd);
         }
 
         r = btrfs_subvol_make("/xxxtest");

commit 86e339c8846cdf614a41653384c0b4e84b233696
Author: Lennart Poettering <lennart at poettering.net>
Date:   Fri Dec 26 16:44:15 2014 +0100

    machined: be more thorough when checking whether an image is writable or not

diff --git a/src/machine/image.c b/src/machine/image.c
index 9f88b05..f72a5c3 100644
--- a/src/machine/image.c
+++ b/src/machine/image.c
@@ -86,6 +86,7 @@ static int image_new(
 
 static int image_make(int dfd, const char *name, const char *path, Image **ret) {
         struct stat st;
+        bool writable;
         int r;
 
         assert(dfd >= 0);
@@ -98,6 +99,8 @@ static int image_make(int dfd, const char *name, const char *path, Image **ret)
         if (fstatat(dfd, name, &st, 0) < 0)
                 return -errno;
 
+        writable = faccessat(dfd, name, W_OK, AT_EACCESS) >= 0;
+
         if (S_ISDIR(st.st_mode)) {
 
                 if (!ret)
@@ -127,7 +130,7 @@ static int image_make(int dfd, const char *name, const char *path, Image **ret)
                                 r = image_new(IMAGE_SUBVOLUME,
                                               name,
                                               path,
-                                              info.read_only,
+                                              info.read_only || !writable,
                                               info.otime,
                                               0,
                                               ret);
@@ -143,7 +146,7 @@ static int image_make(int dfd, const char *name, const char *path, Image **ret)
                 r = image_new(IMAGE_DIRECTORY,
                               name,
                               path,
-                              false,
+                              !writable,
                               0,
                               0,
                               ret);
@@ -168,7 +171,7 @@ static int image_make(int dfd, const char *name, const char *path, Image **ret)
                 r = image_new(IMAGE_GPT,
                               truncated,
                               path,
-                              !(st.st_mode & 0222),
+                              !(st.st_mode & 0222) || !writable,
                               crtime,
                               timespec_load(&st.st_mtim),
                               ret);



More information about the systemd-commits mailing list