]>
Commit | Line | Data |
---|---|---|
db9ecf05 | 1 | /* SPDX-License-Identifier: LGPL-2.1-or-later */ |
e537352b | 2 | |
a8fbdf54 TA |
3 | #include <stdio.h> |
4 | #include <stdlib.h> | |
e537352b | 5 | #include <sys/utsname.h> |
07630cea | 6 | #include <utmpx.h> |
e537352b | 7 | |
93a1f792 | 8 | #include "log.h" |
0a970718 | 9 | #include "memory-util.h" |
07630cea | 10 | #include "string-util.h" |
a8fbdf54 | 11 | #include "time-util.h" |
958b66ea | 12 | #include "utmp-wtmp.h" |
e537352b LP |
13 | |
14 | int utmp_get_runlevel(int *runlevel, int *previous) { | |
d7ac0952 | 15 | _unused_ _cleanup_(utxent_cleanup) bool utmpx = false; |
b92bea5d | 16 | struct utmpx *found, lookup = { .ut_type = RUN_LVL }; |
e537352b LP |
17 | const char *e; |
18 | ||
19 | assert(runlevel); | |
20 | ||
21 | /* If these values are set in the environment this takes | |
22 | * precedence. Presumably, sysvinit does this to work around a | |
23 | * race condition that would otherwise exist where we'd always | |
24 | * go to disk and hence might read runlevel data that might be | |
ca1d199b | 25 | * very new and not apply to the current script being executed. */ |
e537352b | 26 | |
e7fb33ff | 27 | e = getenv("RUNLEVEL"); |
54d04cd1 | 28 | if (!isempty(e)) { |
e537352b | 29 | *runlevel = e[0]; |
54d04cd1 ZJS |
30 | if (previous) |
31 | *previous = 0; | |
e537352b LP |
32 | |
33 | return 0; | |
34 | } | |
35 | ||
51da613b | 36 | if (utmpxname(UTMPX_FILE) < 0) |
e537352b LP |
37 | return -errno; |
38 | ||
c2a99093 | 39 | utmpx = utxent_start(); |
e537352b | 40 | |
e7fb33ff LP |
41 | found = getutxid(&lookup); |
42 | if (!found) | |
c2a99093 | 43 | return -errno; |
e537352b | 44 | |
c2a99093 ZJS |
45 | *runlevel = found->ut_pid & 0xFF; |
46 | if (previous) | |
47 | *previous = (found->ut_pid >> 8) & 0xFF; | |
e537352b | 48 | |
c2a99093 | 49 | return 0; |
e537352b LP |
50 | } |
51 | ||
169c1bda | 52 | static void init_timestamp(struct utmpx *store, usec_t t) { |
e537352b LP |
53 | assert(store); |
54 | ||
871d7de4 LP |
55 | if (t <= 0) |
56 | t = now(CLOCK_REALTIME); | |
e537352b | 57 | |
871d7de4 LP |
58 | store->ut_tv.tv_sec = t / USEC_PER_SEC; |
59 | store->ut_tv.tv_usec = t % USEC_PER_SEC; | |
169c1bda LP |
60 | } |
61 | ||
62 | static void init_entry(struct utmpx *store, usec_t t) { | |
b92bea5d | 63 | struct utsname uts = {}; |
169c1bda LP |
64 | |
65 | assert(store); | |
66 | ||
67 | init_timestamp(store, t); | |
68 | ||
e537352b LP |
69 | if (uname(&uts) >= 0) |
70 | strncpy(store->ut_host, uts.release, sizeof(store->ut_host)); | |
71 | ||
72 | strncpy(store->ut_line, "~", sizeof(store->ut_line)); /* or ~~ ? */ | |
73 | strncpy(store->ut_id, "~~", sizeof(store->ut_id)); | |
74 | } | |
75 | ||
76 | static int write_entry_utmp(const struct utmpx *store) { | |
d7ac0952 | 77 | _unused_ _cleanup_(utxent_cleanup) bool utmpx = false; |
e537352b LP |
78 | |
79 | assert(store); | |
80 | ||
81 | /* utmp is similar to wtmp, but there is only one entry for | |
82 | * each entry type resp. user; i.e. basically a key/value | |
83 | * table. */ | |
84 | ||
51da613b | 85 | if (utmpxname(UTMPX_FILE) < 0) |
e537352b LP |
86 | return -errno; |
87 | ||
c2a99093 | 88 | utmpx = utxent_start(); |
e537352b | 89 | |
ca1d199b ZJS |
90 | if (pututxline(store)) |
91 | return 0; | |
92 | if (errno == ENOENT) { | |
93 | /* If utmp/wtmp have been disabled, that's a good thing, hence ignore the error. */ | |
94 | log_debug_errno(errno, "Not writing utmp: %m"); | |
95 | return 0; | |
96 | } | |
97 | return -errno; | |
e537352b LP |
98 | } |
99 | ||
100 | static int write_entry_wtmp(const struct utmpx *store) { | |
101 | assert(store); | |
102 | ||
103 | /* wtmp is a simple append-only file where each entry is | |
ca1d199b | 104 | * simply appended to the end; i.e. basically a log. */ |
e537352b LP |
105 | |
106 | errno = 0; | |
e11f01b7 | 107 | updwtmpx(WTMPX_FILE, store); |
ca1d199b ZJS |
108 | if (errno == ENOENT) { |
109 | /* If utmp/wtmp have been disabled, that's a good thing, hence ignore the error. */ | |
110 | log_debug_errno(errno, "Not writing wtmp: %m"); | |
111 | return 0; | |
112 | } | |
113 | if (errno == EROFS) { | |
114 | log_warning_errno(errno, "Failed to write wtmp record, ignoring: %m"); | |
115 | return 0; | |
116 | } | |
e537352b LP |
117 | return -errno; |
118 | } | |
119 | ||
4743137a | 120 | static int write_utmp_wtmp(const struct utmpx *store_utmp, const struct utmpx *store_wtmp) { |
e537352b LP |
121 | int r, s; |
122 | ||
4743137a MS |
123 | r = write_entry_utmp(store_utmp); |
124 | s = write_entry_wtmp(store_wtmp); | |
ca1d199b | 125 | return r < 0 ? r : s; |
e537352b LP |
126 | } |
127 | ||
4743137a MS |
128 | static int write_entry_both(const struct utmpx *store) { |
129 | return write_utmp_wtmp(store, store); | |
130 | } | |
131 | ||
0ad26e09 | 132 | int utmp_put_shutdown(void) { |
863f3ce0 | 133 | struct utmpx store = {}; |
e537352b | 134 | |
0ad26e09 | 135 | init_entry(&store, 0); |
e537352b LP |
136 | |
137 | store.ut_type = RUN_LVL; | |
138 | strncpy(store.ut_user, "shutdown", sizeof(store.ut_user)); | |
139 | ||
140 | return write_entry_both(&store); | |
141 | } | |
142 | ||
871d7de4 | 143 | int utmp_put_reboot(usec_t t) { |
863f3ce0 | 144 | struct utmpx store = {}; |
e537352b | 145 | |
871d7de4 | 146 | init_entry(&store, t); |
e537352b LP |
147 | |
148 | store.ut_type = BOOT_TIME; | |
55e39f40 | 149 | strncpy(store.ut_user, "reboot", sizeof(store.ut_user)); |
e537352b LP |
150 | |
151 | return write_entry_both(&store); | |
152 | } | |
153 | ||
f1d553e9 | 154 | static void copy_suffix(char *buf, size_t buf_size, const char *src) { |
169c1bda LP |
155 | size_t l; |
156 | ||
f1d553e9 ZJS |
157 | l = strlen(src); |
158 | if (l < buf_size) | |
159 | strncpy(buf, src, buf_size); | |
160 | else | |
161 | memcpy(buf, src + l - buf_size, buf_size); | |
169c1bda LP |
162 | } |
163 | ||
023a4f67 | 164 | int utmp_put_init_process(const char *id, pid_t pid, pid_t sid, const char *line, int ut_type, const char *user) { |
863f3ce0 TG |
165 | struct utmpx store = { |
166 | .ut_type = INIT_PROCESS, | |
167 | .ut_pid = pid, | |
168 | .ut_session = sid, | |
169 | }; | |
023a4f67 | 170 | int r; |
169c1bda LP |
171 | |
172 | assert(id); | |
d42b81f9 | 173 | assert(ut_type != USER_PROCESS || user); |
169c1bda | 174 | |
0ad26e09 | 175 | init_timestamp(&store, 0); |
169c1bda | 176 | |
f1d553e9 ZJS |
177 | /* Copy the whole string if it fits, or just the suffix without the terminating NUL. */ |
178 | copy_suffix(store.ut_id, sizeof(store.ut_id), id); | |
169c1bda LP |
179 | |
180 | if (line) | |
6695c200 | 181 | strncpy_exact(store.ut_line, line, sizeof(store.ut_line)); |
169c1bda | 182 | |
023a4f67 LP |
183 | r = write_entry_both(&store); |
184 | if (r < 0) | |
185 | return r; | |
186 | ||
3742095b | 187 | if (IN_SET(ut_type, LOGIN_PROCESS, USER_PROCESS)) { |
023a4f67 LP |
188 | store.ut_type = LOGIN_PROCESS; |
189 | r = write_entry_both(&store); | |
190 | if (r < 0) | |
191 | return r; | |
192 | } | |
193 | ||
194 | if (ut_type == USER_PROCESS) { | |
195 | store.ut_type = USER_PROCESS; | |
196 | strncpy(store.ut_user, user, sizeof(store.ut_user)-1); | |
197 | r = write_entry_both(&store); | |
198 | if (r < 0) | |
199 | return r; | |
200 | } | |
201 | ||
202 | return 0; | |
169c1bda LP |
203 | } |
204 | ||
205 | int utmp_put_dead_process(const char *id, pid_t pid, int code, int status) { | |
d7ac0952 | 206 | _unused_ _cleanup_(utxent_cleanup) bool utmpx = false; |
863f3ce0 TG |
207 | struct utmpx lookup = { |
208 | .ut_type = INIT_PROCESS /* looks for DEAD_PROCESS, LOGIN_PROCESS, USER_PROCESS, too */ | |
209 | }, store, store_wtmp, *found; | |
169c1bda LP |
210 | |
211 | assert(id); | |
212 | ||
bbd239f6 | 213 | utmpx = utxent_start(); |
169c1bda | 214 | |
f1d553e9 | 215 | /* Copy the whole string if it fits, or just the suffix without the terminating NUL. */ |
1568430c | 216 | copy_suffix(lookup.ut_id, sizeof(lookup.ut_id), id); |
169c1bda | 217 | |
e7fb33ff LP |
218 | found = getutxid(&lookup); |
219 | if (!found) | |
169c1bda LP |
220 | return 0; |
221 | ||
222 | if (found->ut_pid != pid) | |
223 | return 0; | |
224 | ||
fa4ad7ce | 225 | memcpy(&store, found, sizeof(store)); |
169c1bda LP |
226 | store.ut_type = DEAD_PROCESS; |
227 | store.ut_exit.e_termination = code; | |
228 | store.ut_exit.e_exit = status; | |
229 | ||
230 | zero(store.ut_user); | |
231 | zero(store.ut_host); | |
232 | zero(store.ut_tv); | |
233 | ||
4743137a MS |
234 | memcpy(&store_wtmp, &store, sizeof(store_wtmp)); |
235 | /* wtmp wants the current time */ | |
236 | init_timestamp(&store_wtmp, 0); | |
237 | ||
238 | return write_utmp_wtmp(&store, &store_wtmp); | |
169c1bda LP |
239 | } |
240 | ||
0ad26e09 | 241 | int utmp_put_runlevel(int runlevel, int previous) { |
863f3ce0 | 242 | struct utmpx store = {}; |
e537352b LP |
243 | int r; |
244 | ||
245 | assert(runlevel > 0); | |
246 | ||
247 | if (previous <= 0) { | |
248 | /* Find the old runlevel automatically */ | |
249 | ||
e7fb33ff LP |
250 | r = utmp_get_runlevel(&previous, NULL); |
251 | if (r < 0) { | |
d7fc909d LP |
252 | if (r != -ESRCH) |
253 | return r; | |
254 | ||
255 | previous = 0; | |
256 | } | |
e537352b LP |
257 | } |
258 | ||
4927fcae LP |
259 | if (previous == runlevel) |
260 | return 0; | |
261 | ||
0ad26e09 | 262 | init_entry(&store, 0); |
e537352b LP |
263 | |
264 | store.ut_type = RUN_LVL; | |
265 | store.ut_pid = (runlevel & 0xFF) | ((previous & 0xFF) << 8); | |
266 | strncpy(store.ut_user, "runlevel", sizeof(store.ut_user)); | |
267 | ||
268 | return write_entry_both(&store); | |
269 | } |