]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
machined: run clone operation asynchronously in the background
authorLennart Poettering <lennart@poettering.net>
Fri, 29 Apr 2016 17:14:52 +0000 (19:14 +0200)
committerLennart Poettering <lennart@poettering.net>
Mon, 2 May 2016 09:15:30 +0000 (11:15 +0200)
Cloning an image can be slow, if the image is not on a btrfs subvolume, hence
let's make sure we do this asynchronously in a child process, so that machined
isn't blocked as long as we process the client request.

This adds a new, generic "Operation" object to machined, that is used to track
these kind of background processes.

This is inspired by the MachineOperation object that already exists to make
copy operations asynchronous. A later patch will rework the MachineOperation
logic to use the generic Operation instead.

Makefile.am
src/machine/image-dbus.c
src/machine/machined.c
src/machine/machined.h
src/machine/operation.c [new file with mode: 0644]
src/machine/operation.h [new file with mode: 0644]

index b323de55c6a08917a705120357dcc4c7b56bd0b1..8ff9eeb5a58227c3532cc39df8807b414e547915 100644 (file)
@@ -4942,7 +4942,9 @@ libmachine_core_la_SOURCES = \
        src/machine/machine-dbus.c \
        src/machine/machine-dbus.h \
        src/machine/image-dbus.c \
-       src/machine/image-dbus.h
+       src/machine/image-dbus.h \
+       src/machine/operation.c \
+       src/machine/operation.h
 
 libmachine_core_la_LIBADD = \
        libshared.la
index b764bc43a0d9d920eb6d0e6f110cf71929598cd8..ca38f61dd389ef60d9d2961af878d6e226cb425e 100644 (file)
 #include "alloc-util.h"
 #include "bus-label.h"
 #include "bus-util.h"
+#include "fd-util.h"
 #include "image-dbus.h"
 #include "io-util.h"
 #include "machine-image.h"
+#include "process-util.h"
 #include "strv.h"
 #include "user-util.h"
 
@@ -107,13 +109,19 @@ int bus_image_method_clone(
                 void *userdata,
                 sd_bus_error *error) {
 
+        _cleanup_close_pair_ int errno_pipe_fd[2] = { -1, -1 };
         Image *image = userdata;
         Manager *m = image->userdata;
         const char *new_name;
         int r, read_only;
+        pid_t child;
 
         assert(message);
         assert(image);
+        assert(m);
+
+        if (m->n_operations >= OPERATIONS_MAX)
+                return sd_bus_error_setf(error, SD_BUS_ERROR_LIMITS_EXCEEDED, "Too many ongoing operations.");
 
         r = sd_bus_message_read(message, "sb", &new_name, &read_only);
         if (r < 0)
@@ -136,13 +144,35 @@ int bus_image_method_clone(
         if (r == 0)
                 return 1; /* Will call us back */
 
-        r = image_clone(image, new_name, read_only);
-        if (r == -EOPNOTSUPP)
-                return sd_bus_reply_method_errnof(message, r, "Image cloning is currently only supported on btrfs file systems.");
-        if (r < 0)
+        if (pipe2(errno_pipe_fd, O_CLOEXEC|O_NONBLOCK) < 0)
+                return sd_bus_error_set_errnof(error, errno, "Failed to create pipe: %m");
+
+        child = fork();
+        if (child < 0)
+                return sd_bus_error_set_errnof(error, errno, "Failed to fork(): %m");
+        if (child == 0) {
+                errno_pipe_fd[0] = safe_close(errno_pipe_fd[0]);
+
+                r = image_clone(image, new_name, read_only);
+                if (r < 0) {
+                        (void) write(errno_pipe_fd[1], &r, sizeof(r));
+                        _exit(EXIT_FAILURE);
+                }
+
+                _exit(EXIT_SUCCESS);
+        }
+
+        errno_pipe_fd[1] = safe_close(errno_pipe_fd[1]);
+
+        r = operation_new(m, child, message, errno_pipe_fd[0]);
+        if (r < 0) {
+                (void) sigkill_wait(&child);
                 return r;
+        }
 
-        return sd_bus_reply_method_return(message, NULL);
+        errno_pipe_fd[0] = -1;
+
+        return 1;
 }
 
 int bus_image_method_mark_read_only(
index f2c1966a6b6ce143eb5791f10c006466ebd27fb7..f7ceb5e603d4628188380eb58e4f178b8136dde5 100644 (file)
@@ -70,6 +70,11 @@ void manager_free(Manager *m) {
 
         assert(m);
 
+        while (m->operations)
+                operation_free(m->operations);
+
+        assert(m->n_operations == 0);
+
         while ((machine = hashmap_first(m->machines)))
                 machine_free(machine);
 
@@ -336,6 +341,9 @@ int manager_startup(Manager *m) {
 static bool check_idle(void *userdata) {
         Manager *m = userdata;
 
+        if (m->operations)
+                return false;
+
         manager_gc(m, true);
 
         return hashmap_isempty(m->machines);
index e7d7dfdcebcbf7e41fdbb08a47f08e43c1823f9c..7b9b148044987034a5e9a7b0b828f465ff1a4066 100644 (file)
@@ -32,6 +32,7 @@ typedef struct Manager Manager;
 #include "image-dbus.h"
 #include "machine-dbus.h"
 #include "machine.h"
+#include "operation.h"
 
 struct Manager {
         sd_event *event;
@@ -49,6 +50,9 @@ struct Manager {
         LIST_HEAD(Machine, machine_gc_queue);
 
         Machine *host_machine;
+
+        LIST_HEAD(Operation, operations);
+        unsigned n_operations;
 };
 
 Manager *manager_new(void);
diff --git a/src/machine/operation.c b/src/machine/operation.c
new file mode 100644 (file)
index 0000000..53e996b
--- /dev/null
@@ -0,0 +1,118 @@
+/***
+  This file is part of systemd.
+
+  Copyright 2016 Lennart Poettering
+
+  systemd is free software; you can redistribute it and/or modify it
+  under the terms of the GNU Lesser General Public License as published by
+  the Free Software Foundation; either version 2.1 of the License, or
+  (at your option) any later version.
+
+  systemd is distributed in the hope that it will be useful, but
+  WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+  Lesser General Public License for more details.
+
+  You should have received a copy of the GNU Lesser General Public License
+  along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include "alloc-util.h"
+#include "fd-util.h"
+#include "operation.h"
+#include "process-util.h"
+
+static int operation_done(sd_event_source *s, const siginfo_t *si, void *userdata) {
+        _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
+        Operation *o = userdata;
+        int r;
+
+        assert(o);
+        assert(si);
+
+        log_debug("Operating " PID_FMT " is now complete with with code=%s status=%i",
+                  o->pid,
+                  sigchld_code_to_string(si->si_code), si->si_status);
+
+        o->pid = 0;
+
+        if (si->si_code != CLD_EXITED) {
+                r = sd_bus_error_setf(&error, SD_BUS_ERROR_FAILED, "Child died abnormally.");
+                goto fail;
+        }
+
+        if (si->si_status != EXIT_SUCCESS) {
+                if (read(o->errno_fd, &r, sizeof(r)) == sizeof(r))
+                        r = sd_bus_error_set_errnof(&error, r, "%m");
+                else
+                        r = sd_bus_error_setf(&error, SD_BUS_ERROR_FAILED, "Child failed.");
+
+                goto fail;
+        }
+
+        r = sd_bus_reply_method_return(o->message, NULL);
+        if (r < 0)
+                log_error_errno(r, "Failed to reply to message: %m");
+
+        operation_free(o);
+        return 0;
+
+fail:
+        r = sd_bus_reply_method_error(o->message, &error);
+        if (r < 0)
+                log_error_errno(r, "Failed to reply to message: %m");
+
+        operation_free(o);
+        return 0;
+}
+
+int operation_new(Manager *m, pid_t child, sd_bus_message *message, int errno_fd) {
+        Operation *o;
+        int r;
+
+        o = new0(Operation, 1);
+        if (!o)
+                return -ENOMEM;
+
+        r = sd_event_add_child(m->event, &o->event_source, child, WEXITED, operation_done, o);
+        if (r < 0) {
+                free(o);
+                return r;
+        }
+
+        o->pid = child;
+        o->message = sd_bus_message_ref(message);
+        o->errno_fd = errno_fd;
+
+        LIST_PREPEND(operations, m->operations, o);
+        m->n_operations++;
+        o->manager = m;
+
+        log_debug("Started new operation " PID_FMT ".", child);
+
+        /* At this point we took ownership of both the child and the errno file descriptor! */
+
+        return 0;
+}
+
+Operation *operation_free(Operation *o) {
+        if (!o)
+                return NULL;
+
+        sd_event_source_unref(o->event_source);
+
+        safe_close(o->errno_fd);
+
+        if (o->pid > 1)
+                (void) sigkill_wait(&o->pid);
+
+        sd_bus_message_unref(o->message);
+
+        if (o->manager) {
+                LIST_REMOVE(operations, o->manager->operations, o);
+                o->manager->n_operations--;
+        }
+
+        free(o);
+        return NULL;
+}
diff --git a/src/machine/operation.h b/src/machine/operation.h
new file mode 100644 (file)
index 0000000..9d4c3af
--- /dev/null
@@ -0,0 +1,45 @@
+#pragma once
+
+/***
+  This file is part of systemd.
+
+  Copyright 2016 Lennart Poettering
+
+  systemd is free software; you can redistribute it and/or modify it
+  under the terms of the GNU Lesser General Public License as published by
+  the Free Software Foundation; either version 2.1 of the License, or
+  (at your option) any later version.
+
+  systemd is distributed in the hope that it will be useful, but
+  WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+  Lesser General Public License for more details.
+
+  You should have received a copy of the GNU Lesser General Public License
+  along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <sys/types.h>
+
+#include "sd-bus.h"
+#include "sd-event.h"
+
+#include "list.h"
+
+typedef struct Operation Operation;
+
+#include "machined.h"
+
+#define OPERATIONS_MAX 64
+
+struct Operation {
+        Manager *manager;
+        pid_t pid;
+        sd_bus_message *message;
+        int errno_fd;
+        sd_event_source *event_source;
+        LIST_FIELDS(Operation, operations);
+};
+
+int operation_new(Manager *m, pid_t child, sd_bus_message *message, int errno_fd);
+Operation *operation_free(Operation *o);