summaryrefslogtreecommitdiff
path: root/src/sulogin-shell/sulogin-shell.c
blob: e81bb527ff8a524aec1128d6ac99898abc0f0fd4 (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
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
/* SPDX-License-Identifier: LGPL-2.1-or-later */
/***
  Copyright © 2017 Felipe Sateler
***/

#include <errno.h>
#include <sys/prctl.h>

#include "sd-bus.h"

#include "bus-locator.h"
#include "bus-util.h"
#include "bus-error.h"
#include "constants.h"
#include "env-util.h"
#include "initrd-util.h"
#include "log.h"
#include "main-func.h"
#include "process-util.h"
#include "proc-cmdline.h"
#include "signal-util.h"
#include "special.h"
#include "unit-def.h"

static int reload_manager(sd_bus *bus) {
        _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
        _cleanup_(sd_bus_message_unrefp) sd_bus_message *m = NULL;
        int r;

        log_info("Reloading system manager configuration");

        r = bus_message_new_method_call(
                        bus,
                        &m,
                        bus_systemd_mgr,
                        "Reload");
        if (r < 0)
                return bus_log_create_error(r);

        /* Reloading the daemon may take long, hence set a longer timeout here */
        r = sd_bus_call(bus, m, DAEMON_RELOAD_TIMEOUT_SEC, &error, NULL);
        if (r < 0)
                return log_error_errno(r, "Failed to reload daemon: %s", bus_error_message(&error, r));

        return 0;
}

static int target_is_inactive(sd_bus *bus, const char *target) {
        _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
        _cleanup_free_ char *path = NULL, *state = NULL;
        int r;

        path = unit_dbus_path_from_name(target);
        if (!path)
                return log_oom();

        r = sd_bus_get_property_string(bus,
                                       "org.freedesktop.systemd1",
                                       path,
                                       "org.freedesktop.systemd1.Unit",
                                       "ActiveState",
                                       &error,
                                       &state);
        if (r < 0)
                return log_error_errno(r, "Failed to retrieve unit state: %s", bus_error_message(&error, r));

        return streq_ptr(state, "inactive");
}

static int start_target(sd_bus *bus, const char *target) {
        _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
        int r;

        log_info("Starting %s", target);

        /* Start this unit only if we can replace basic.target with it */
        r = bus_call_method(
                        bus,
                        bus_systemd_mgr,
                        "StartUnit",
                        &error,
                        NULL,
                        "ss", target, "isolate");

        if (r < 0)
                return log_error_errno(r, "Failed to start %s: %s", target, bus_error_message(&error, r));

        return 0;
}

static int fork_wait(const char* const cmdline[]) {
        pid_t pid;
        int r;

        r = safe_fork("(sulogin)", FORK_RESET_SIGNALS|FORK_DEATHSIG|FORK_RLIMIT_NOFILE_SAFE|FORK_LOG, &pid);
        if (r < 0)
                return r;
        if (r == 0) {
                /* Child */
                execv(cmdline[0], (char**) cmdline);
                log_error_errno(errno, "Failed to execute %s: %m", cmdline[0]);
                _exit(EXIT_FAILURE); /* Operational error */
        }

        return wait_for_terminate_and_check(cmdline[0], pid, WAIT_LOG_ABNORMAL);
}

static void print_mode(const char* mode) {
        printf("You are in %s mode. After logging in, type \"journalctl -xb\" to view\n"
               "system logs, \"systemctl reboot\" to reboot, or \"exit\"\n" "to continue bootup.\n", mode);
        fflush(stdout);
}

static int run(int argc, char *argv[]) {
        const char* sulogin_cmdline[] = {
                SULOGIN,
                NULL,             /* --force */
                NULL
        };
        bool force = false;
        int r;

        log_setup();

        print_mode(argc > 1 ? argv[1] : "");

        if (getenv_bool("SYSTEMD_SULOGIN_FORCE") > 0)
                force = true;

        if (!force) {
                /* We look the argument in the kernel cmdline under the same name as the environment variable
                 * to express that this is not supported at the same level as the regular kernel cmdline
                 * switches. */
                r = proc_cmdline_get_bool("SYSTEMD_SULOGIN_FORCE", &force);
                if (r < 0)
                        log_debug_errno(r, "Failed to parse SYSTEMD_SULOGIN_FORCE from kernel command line, ignoring: %m");
        }

        if (force)
                /* allows passwordless logins if root account is locked. */
                sulogin_cmdline[1] = "--force";

        for (;;) {
                _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;

                (void) fork_wait(sulogin_cmdline);

                r = bus_connect_system_systemd(&bus);
                if (r < 0) {
                        log_warning_errno(r, "Failed to get D-Bus connection: %m");
                        goto fallback;
                }

                if (reload_manager(bus) < 0)
                        goto fallback;

                const char *target = in_initrd() ? SPECIAL_INITRD_TARGET : SPECIAL_DEFAULT_TARGET;

                r = target_is_inactive(bus, target);
                if (r < 0)
                        goto fallback;
                if (!r) {
                        log_warning("%s is not inactive. Please review the %s setting.\n", target, target);
                        goto fallback;
                }

                if (start_target(bus, target) >= 0)
                        break;

        fallback:
                log_warning("Fallback to the single-user shell.\n");
        }

        return 0;
}

DEFINE_MAIN_FUNCTION(run);