chiark / gitweb /
importd: automatically grow /var/lib/machines/ loopback filesystem during downloads
[elogind.git] / src / import / import-dkr.c
index cebec28144f4851278baa5c1082af636847f588a..2d4e9b398f9e5f680d6408f9e559ace09519675e 100644 (file)
 #include <curl/curl.h>
 #include <sys/prctl.h>
 
-#include "set.h"
+#include "sd-daemon.h"
 #include "json.h"
 #include "strv.h"
 #include "btrfs-util.h"
 #include "utf8.h"
 #include "mkdir.h"
+#include "path-util.h"
+#include "import-util.h"
 #include "curl-util.h"
 #include "aufs-util.h"
-#include "import-util.h"
 #include "import-job.h"
+#include "import-common.h"
 #include "import-dkr.h"
 
+typedef enum DkrProgress {
+        DKR_SEARCHING,
+        DKR_RESOLVING,
+        DKR_METADATA,
+        DKR_DOWNLOADING,
+        DKR_COPYING,
+} DkrProgress;
+
 struct DkrImport {
         sd_event *event;
         CurlGlue *glue;
@@ -55,6 +65,7 @@ struct DkrImport {
         char **response_registries;
 
         char **ancestry;
+        unsigned n_ancestry;
         unsigned current_ancestry;
 
         DkrImportFinished on_finished;
@@ -62,6 +73,7 @@ struct DkrImport {
 
         char *local;
         bool force_local;
+        bool grow_machine_directory;
 
         char *temp_path;
         char *final_path;
@@ -146,6 +158,8 @@ int dkr_import_new(
         if (!i->image_root)
                 return -ENOMEM;
 
+        i->grow_machine_directory = path_startswith(i->image_root, "/var/lib/machines");
+
         i->index_url = strdup(index_url);
         if (!i->index_url)
                 return -ENOMEM;
@@ -175,6 +189,53 @@ int dkr_import_new(
         return 0;
 }
 
+static void dkr_import_report_progress(DkrImport *i, DkrProgress p) {
+        unsigned percent;
+
+        assert(i);
+
+        switch (p) {
+
+        case DKR_SEARCHING:
+                percent = 0;
+                if (i->images_job)
+                        percent += i->images_job->progress_percent * 5 / 100;
+                break;
+
+        case DKR_RESOLVING:
+                percent = 5;
+                if (i->tags_job)
+                        percent += i->tags_job->progress_percent * 5 / 100;
+                break;
+
+        case DKR_METADATA:
+                percent = 10;
+                if (i->ancestry_job)
+                        percent += i->ancestry_job->progress_percent * 5 / 100;
+                if (i->json_job)
+                        percent += i->json_job->progress_percent * 5 / 100;
+                break;
+
+        case DKR_DOWNLOADING:
+                percent = 20;
+                percent += 75 * i->current_ancestry / MAX(1U, i->n_ancestry);
+                if (i->layer_job)
+                        percent += i->layer_job->progress_percent * 75 / MAX(1U, i->n_ancestry) / 100;
+
+                break;
+
+        case DKR_COPYING:
+                percent = 95;
+                break;
+
+        default:
+                assert_not_reached("Unknown progress state");
+        }
+
+        sd_notifyf(false, "X_IMPORT_PROGRESS=%u", percent);
+        log_debug("Combined progress %u%%", percent);
+}
+
 static int parse_id(const void *payload, size_t size, char **ret) {
         _cleanup_free_ char *buf = NULL, *id = NULL, *other = NULL;
         union json_value v = {};
@@ -342,7 +403,7 @@ static int dkr_import_add_token(DkrImport *i, ImportJob *j) {
         assert(j);
 
         if (i->response_token)
-                t = strappenda("Authorization: Token ", i->response_token);
+                t = strjoina("Authorization: Token ", i->response_token);
         else
                 t = HEADER_TOKEN " true";
 
@@ -400,7 +461,6 @@ static int dkr_import_make_local_copy(DkrImport *i) {
 }
 
 static int dkr_import_job_on_open_disk(ImportJob *j) {
-        _cleanup_close_pair_ int pipefd[2] = { -1, -1 };
         const char *base;
         DkrImport *i;
         int r;
@@ -424,63 +484,34 @@ static int dkr_import_job_on_open_disk(ImportJob *j) {
         if (base) {
                 const char *base_path;
 
-                base_path = strappenda(i->image_root, "/.dkr-", base);
+                base_path = strjoina(i->image_root, "/.dkr-", base);
                 r = btrfs_subvol_snapshot(base_path, i->temp_path, false, true);
         } else
                 r = btrfs_subvol_make(i->temp_path);
         if (r < 0)
                 return log_error_errno(r, "Failed to make btrfs subvolume %s: %m", i->temp_path);
 
-        if (pipe2(pipefd, O_CLOEXEC) < 0)
-                return log_error_errno(errno, "Failed to create pipe for tar: %m");
-
-        i->tar_pid = fork();
-        if (i->tar_pid < 0)
-                return log_error_errno(errno, "Failed to fork off tar: %m");
-        if (i->tar_pid == 0) {
-                int null_fd;
-
-                /* Child */
-
-                reset_all_signal_handlers();
-                reset_signal_mask();
-                assert_se(prctl(PR_SET_PDEATHSIG, SIGTERM) == 0);
+        j->disk_fd = import_fork_tar(i->temp_path, &i->tar_pid);
+        if (j->disk_fd < 0)
+                return j->disk_fd;
 
-                pipefd[1] = safe_close(pipefd[1]);
-
-                if (dup2(pipefd[0], STDIN_FILENO) != STDIN_FILENO) {
-                        log_error_errno(errno, "Failed to dup2() fd: %m");
-                        _exit(EXIT_FAILURE);
-                }
-
-                if (pipefd[0] != STDIN_FILENO)
-                        safe_close(pipefd[0]);
-
-                null_fd = open("/dev/null", O_WRONLY|O_NOCTTY);
-                if (null_fd < 0) {
-                        log_error_errno(errno, "Failed to open /dev/null: %m");
-                        _exit(EXIT_FAILURE);
-                }
-
-                if (dup2(null_fd, STDOUT_FILENO) != STDOUT_FILENO) {
-                        log_error_errno(errno, "Failed to dup2() fd: %m");
-                        _exit(EXIT_FAILURE);
-                }
-
-                if (null_fd != STDOUT_FILENO)
-                        safe_close(null_fd);
+        return 0;
+}
 
-                execlp("tar", "tar", "--numeric-owner", "-C", i->temp_path, "-px", NULL);
-                log_error_errno(errno, "Failed to execute tar: %m");
-                _exit(EXIT_FAILURE);
-        }
+static void dkr_import_job_on_progress(ImportJob *j) {
+        DkrImport *i;
 
-        pipefd[0] = safe_close(pipefd[0]);
+        assert(j);
+        assert(j->userdata);
 
-        j->disk_fd = pipefd[1];
-        pipefd[1] = -1;
+        i = j->userdata;
 
-        return 0;
+        dkr_import_report_progress(
+                        i,
+                        j == i->images_job                       ? DKR_SEARCHING :
+                        j == i->tags_job                         ? DKR_RESOLVING :
+                        j == i->ancestry_job || j == i->json_job ? DKR_METADATA :
+                                                                   DKR_DOWNLOADING);
 }
 
 static int dkr_import_pull_layer(DkrImport *i) {
@@ -522,7 +553,7 @@ static int dkr_import_pull_layer(DkrImport *i) {
         i->final_path = path;
         path = NULL;
 
-        url = strappenda(PROTOCOL_PREFIX, i->response_registries[0], "/v1/images/", layer, "/layer");
+        url = strjoina(PROTOCOL_PREFIX, i->response_registries[0], "/v1/images/", layer, "/layer");
         r = import_job_new(&i->layer_job, url, i->glue, i);
         if (r < 0)
                 return log_error_errno(r, "Failed to allocate layer job: %m");
@@ -533,6 +564,8 @@ static int dkr_import_pull_layer(DkrImport *i) {
 
         i->layer_job->on_finished = dkr_import_job_on_finished;
         i->layer_job->on_open_disk = dkr_import_job_on_open_disk;
+        i->layer_job->on_progress = dkr_import_job_on_progress;
+        i->layer_job->grow_machine_directory = i->grow_machine_directory;
 
         r = import_job_begin(i->layer_job);
         if (r < 0)
@@ -580,8 +613,9 @@ static void dkr_import_job_on_finished(ImportJob *j) {
                 }
 
                 log_info("Index lookup succeeded, directed to registry %s.", i->response_registries[0]);
+                dkr_import_report_progress(i, DKR_RESOLVING);
 
-                url = strappenda(PROTOCOL_PREFIX, i->response_registries[0], "/v1/repositories/", i->name, "/tags/", i->tag);
+                url = strjoina(PROTOCOL_PREFIX, i->response_registries[0], "/v1/repositories/", i->name, "/tags/", i->tag);
                 r = import_job_new(&i->tags_job, url, i->glue, i);
                 if (r < 0) {
                         log_error_errno(r, "Failed to allocate tags job: %m");
@@ -595,6 +629,7 @@ static void dkr_import_job_on_finished(ImportJob *j) {
                 }
 
                 i->tags_job->on_finished = dkr_import_job_on_finished;
+                i->tags_job->on_progress = dkr_import_job_on_progress;
 
                 r = import_job_begin(i->tags_job);
                 if (r < 0) {
@@ -620,8 +655,9 @@ static void dkr_import_job_on_finished(ImportJob *j) {
                 i->id = id;
 
                 log_info("Tag lookup succeeded, resolved to layer %s.", i->id);
+                dkr_import_report_progress(i, DKR_METADATA);
 
-                url = strappenda(PROTOCOL_PREFIX, i->response_registries[0], "/v1/images/", i->id, "/ancestry");
+                url = strjoina(PROTOCOL_PREFIX, i->response_registries[0], "/v1/images/", i->id, "/ancestry");
                 r = import_job_new(&i->ancestry_job, url, i->glue, i);
                 if (r < 0) {
                         log_error_errno(r, "Failed to allocate ancestry job: %m");
@@ -635,8 +671,9 @@ static void dkr_import_job_on_finished(ImportJob *j) {
                 }
 
                 i->ancestry_job->on_finished = dkr_import_job_on_finished;
+                i->ancestry_job->on_progress = dkr_import_job_on_progress;
 
-                url = strappenda(PROTOCOL_PREFIX, i->response_registries[0], "/v1/images/", i->id, "/json");
+                url = strjoina(PROTOCOL_PREFIX, i->response_registries[0], "/v1/images/", i->id, "/json");
                 r = import_job_new(&i->json_job, url, i->glue, i);
                 if (r < 0) {
                         log_error_errno(r, "Failed to allocate json job: %m");
@@ -650,6 +687,7 @@ static void dkr_import_job_on_finished(ImportJob *j) {
                 }
 
                 i->json_job->on_finished = dkr_import_job_on_finished;
+                i->json_job->on_progress = dkr_import_job_on_progress;
 
                 r = import_job_begin(i->ancestry_job);
                 if (r < 0) {
@@ -689,8 +727,11 @@ static void dkr_import_job_on_finished(ImportJob *j) {
 
                 strv_free(i->ancestry);
                 i->ancestry = ancestry;
-
+                i->n_ancestry = n;
                 i->current_ancestry = 0;
+
+                dkr_import_report_progress(i, DKR_DOWNLOADING);
+
                 r = dkr_import_pull_layer(i);
                 if (r < 0)
                         goto finish;
@@ -716,7 +757,7 @@ static void dkr_import_job_on_finished(ImportJob *j) {
 
                 r = btrfs_subvol_set_read_only(i->temp_path, true);
                 if (r < 0) {
-                        log_error_errno(r, "Failed to mark snapshort read-only: %m");
+                        log_error_errno(r, "Failed to mark snapshot read-only: %m");
                         goto finish;
                 }
 
@@ -744,6 +785,8 @@ static void dkr_import_job_on_finished(ImportJob *j) {
         if (!dkr_import_is_done(i))
                 return;
 
+        dkr_import_report_progress(i, DKR_COPYING);
+
         r = dkr_import_make_local_copy(i);
         if (r < 0)
                 goto finish;
@@ -835,7 +878,7 @@ int dkr_import_pull(DkrImport *i, const char *name, const char *tag, const char
         if (r < 0)
                 return r;
 
-        url = strappenda(i->index_url, "/v1/repositories/", name, "/images");
+        url = strjoina(i->index_url, "/v1/repositories/", name, "/images");
 
         r = import_job_new(&i->images_job, url, i->glue, i);
         if (r < 0)
@@ -847,37 +890,7 @@ int dkr_import_pull(DkrImport *i, const char *name, const char *tag, const char
 
         i->images_job->on_finished = dkr_import_job_on_finished;
         i->images_job->on_header = dkr_import_job_on_header;
+        i->images_job->on_progress = dkr_import_job_on_progress;
 
         return import_job_begin(i->images_job);
 }
-
-bool dkr_name_is_valid(const char *name) {
-        const char *slash, *p;
-
-        if (isempty(name))
-                return false;
-
-        slash = strchr(name, '/');
-        if (!slash)
-                return false;
-
-        if (!filename_is_valid(slash + 1))
-                return false;
-
-        p = strndupa(name, slash - name);
-        if (!filename_is_valid(p))
-                return false;
-
-        return true;
-}
-
-bool dkr_id_is_valid(const char *id) {
-
-        if (!filename_is_valid(id))
-                return false;
-
-        if (!in_charset(id, "0123456789abcdef"))
-                return false;
-
-        return true;
-}