summaryrefslogtreecommitdiffstats
path: root/src/systemctl/systemctl-switch-root.c
blob: ae4a1a72c2b56dd5e29beda3f0053723298e21bb (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
/* SPDX-License-Identifier: LGPL-2.1-or-later */

#include "argv-util.h"
#include "bus-error.h"
#include "bus-locator.h"
#include "chase.h"
#include "fd-util.h"
#include "initrd-util.h"
#include "parse-util.h"
#include "path-util.h"
#include "proc-cmdline.h"
#include "signal-util.h"
#include "stat-util.h"
#include "systemctl.h"
#include "systemctl-switch-root.h"
#include "systemctl-util.h"

static int same_file_in_root(
                const char *root,
                const char *a,
                const char *b) {

        struct stat sta, stb;
        int r;

        r = chase_and_stat(a, root, CHASE_PREFIX_ROOT, NULL, &sta);
        if (r < 0)
                return r;

        r = chase_and_stat(b, root, CHASE_PREFIX_ROOT, NULL, &stb);
        if (r < 0)
                return r;

        return stat_inode_same(&sta, &stb);
}

int verb_switch_root(int argc, char *argv[], void *userdata) {
        _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
        _cleanup_free_ char *cmdline_init = NULL;
        const char *root, *init;
        sd_bus *bus;
        int r;

        if (arg_transport != BUS_TRANSPORT_LOCAL)
                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Cannot switch root remotely.");

        if (argc > 3)
                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Too many arguments.");

        if (argc >= 2) {
                root = argv[1];

                if (!path_is_valid(root))
                        return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid root path: %s", root);

                if (!path_is_absolute(root))
                        return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Root path is not absolute: %s", root);

                r = path_is_root(root);
                if (r < 0)
                        return log_error_errno(r, "Failed to check if switch-root directory '%s' is current root: %m", root);
                if (r > 0)
                        return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Cannot switch to current root directory: %s", root);
        } else
                root = "/sysroot";

        if (!in_initrd())
                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Not in initrd, refusing switch-root operation.");

        if (argc >= 3)
                init = argv[2];
        else {
                r = proc_cmdline_get_key("init", 0, &cmdline_init);
                if (r < 0)
                        log_debug_errno(r, "Failed to parse /proc/cmdline: %m");

                init = cmdline_init;
        }

        init = empty_to_null(init);
        if (init) {
                if (!path_is_valid(init))
                        return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid path to init binary: %s", init);
                if (!path_is_absolute(init))
                        return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Path to init binary is not absolute: %s", init);

                /* If the passed init is actually the same as the systemd binary, then let's suppress it. */
                if (same_file_in_root(root, SYSTEMD_BINARY_PATH, init) > 0)
                        init = NULL;
        }

        /* Instruct PID1 to exclude us from its killing spree applied during the transition. Otherwise we
         * would exit with a failure status even though the switch to the new root has succeed. */
        assert(saved_argv);
        assert(saved_argv[0]);
        saved_argv[0][0] = '@';

        r = acquire_bus(BUS_MANAGER, &bus);
        if (r < 0)
                return r;

        /* If we are slow to exit after the root switch, the new systemd instance will send us a signal to
         * terminate. Just ignore it and exit normally.  This way the unit does not end up as failed. */
        r = ignore_signals(SIGTERM);
        if (r < 0)
                log_warning_errno(r, "Failed to change disposition of SIGTERM to ignore: %m");

        log_debug("Switching root - root: %s; init: %s", root, strna(init));

        r = bus_call_method(bus, bus_systemd_mgr, "SwitchRoot", &error, NULL, "ss", root, init);
        if (r < 0) {
                (void) default_signals(SIGTERM);

                return log_error_errno(r, "Failed to switch root: %s", bus_error_message(&error, r));
        }

        return 0;
}