]>
Commit | Line | Data |
---|---|---|
1 | /* SPDX-License-Identifier: LGPL-2.1-or-later */ | |
2 | ||
3 | #include <stdio.h> | |
4 | #include <stdlib.h> | |
5 | #include <sys/utsname.h> | |
6 | #include <utmpx.h> | |
7 | ||
8 | #include "log.h" | |
9 | #include "memory-util.h" | |
10 | #include "string-util.h" | |
11 | #include "time-util.h" | |
12 | #include "utmp-wtmp.h" | |
13 | ||
14 | int utmp_get_runlevel(int *runlevel, int *previous) { | |
15 | _unused_ _cleanup_(utxent_cleanup) bool utmpx = false; | |
16 | struct utmpx *found, lookup = { .ut_type = RUN_LVL }; | |
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 | |
25 | * very new and not apply to the current script being executed. */ | |
26 | ||
27 | e = getenv("RUNLEVEL"); | |
28 | if (!isempty(e)) { | |
29 | *runlevel = e[0]; | |
30 | if (previous) | |
31 | *previous = 0; | |
32 | ||
33 | return 0; | |
34 | } | |
35 | ||
36 | if (utmpxname(UTMPX_FILE) < 0) | |
37 | return -errno; | |
38 | ||
39 | utmpx = utxent_start(); | |
40 | ||
41 | found = getutxid(&lookup); | |
42 | if (!found) | |
43 | return -errno; | |
44 | ||
45 | *runlevel = found->ut_pid & 0xFF; | |
46 | if (previous) | |
47 | *previous = (found->ut_pid >> 8) & 0xFF; | |
48 | ||
49 | return 0; | |
50 | } | |
51 | ||
52 | static void init_timestamp(struct utmpx *store, usec_t t) { | |
53 | assert(store); | |
54 | ||
55 | if (t <= 0) | |
56 | t = now(CLOCK_REALTIME); | |
57 | ||
58 | store->ut_tv.tv_sec = t / USEC_PER_SEC; | |
59 | store->ut_tv.tv_usec = t % USEC_PER_SEC; | |
60 | } | |
61 | ||
62 | static void init_entry(struct utmpx *store, usec_t t) { | |
63 | struct utsname uts = {}; | |
64 | ||
65 | assert(store); | |
66 | ||
67 | init_timestamp(store, t); | |
68 | ||
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) { | |
77 | _unused_ _cleanup_(utxent_cleanup) bool utmpx = false; | |
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 | ||
85 | if (utmpxname(UTMPX_FILE) < 0) | |
86 | return -errno; | |
87 | ||
88 | utmpx = utxent_start(); | |
89 | ||
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; | |
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 | |
104 | * simply appended to the end; i.e. basically a log. */ | |
105 | ||
106 | errno = 0; | |
107 | updwtmpx(WTMPX_FILE, store); | |
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 | } | |
117 | return -errno; | |
118 | } | |
119 | ||
120 | static int write_utmp_wtmp(const struct utmpx *store_utmp, const struct utmpx *store_wtmp) { | |
121 | int r, s; | |
122 | ||
123 | r = write_entry_utmp(store_utmp); | |
124 | s = write_entry_wtmp(store_wtmp); | |
125 | return r < 0 ? r : s; | |
126 | } | |
127 | ||
128 | static int write_entry_both(const struct utmpx *store) { | |
129 | return write_utmp_wtmp(store, store); | |
130 | } | |
131 | ||
132 | int utmp_put_shutdown(void) { | |
133 | struct utmpx store = {}; | |
134 | ||
135 | init_entry(&store, 0); | |
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 | ||
143 | int utmp_put_reboot(usec_t t) { | |
144 | struct utmpx store = {}; | |
145 | ||
146 | init_entry(&store, t); | |
147 | ||
148 | store.ut_type = BOOT_TIME; | |
149 | strncpy(store.ut_user, "reboot", sizeof(store.ut_user)); | |
150 | ||
151 | return write_entry_both(&store); | |
152 | } | |
153 | ||
154 | static void copy_suffix(char *buf, size_t buf_size, const char *src) { | |
155 | size_t l; | |
156 | ||
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); | |
162 | } | |
163 | ||
164 | int utmp_put_init_process(const char *id, pid_t pid, pid_t sid, const char *line, int ut_type, const char *user) { | |
165 | struct utmpx store = { | |
166 | .ut_type = INIT_PROCESS, | |
167 | .ut_pid = pid, | |
168 | .ut_session = sid, | |
169 | }; | |
170 | int r; | |
171 | ||
172 | assert(id); | |
173 | assert(ut_type != USER_PROCESS || user); | |
174 | ||
175 | init_timestamp(&store, 0); | |
176 | ||
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); | |
179 | ||
180 | if (line) | |
181 | strncpy_exact(store.ut_line, line, sizeof(store.ut_line)); | |
182 | ||
183 | r = write_entry_both(&store); | |
184 | if (r < 0) | |
185 | return r; | |
186 | ||
187 | if (IN_SET(ut_type, LOGIN_PROCESS, USER_PROCESS)) { | |
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; | |
203 | } | |
204 | ||
205 | int utmp_put_dead_process(const char *id, pid_t pid, int code, int status) { | |
206 | _unused_ _cleanup_(utxent_cleanup) bool utmpx = false; | |
207 | struct utmpx lookup = { | |
208 | .ut_type = INIT_PROCESS /* looks for DEAD_PROCESS, LOGIN_PROCESS, USER_PROCESS, too */ | |
209 | }, store, store_wtmp, *found; | |
210 | ||
211 | assert(id); | |
212 | ||
213 | utmpx = utxent_start(); | |
214 | ||
215 | /* Copy the whole string if it fits, or just the suffix without the terminating NUL. */ | |
216 | copy_suffix(lookup.ut_id, sizeof(lookup.ut_id), id); | |
217 | ||
218 | found = getutxid(&lookup); | |
219 | if (!found) | |
220 | return 0; | |
221 | ||
222 | if (found->ut_pid != pid) | |
223 | return 0; | |
224 | ||
225 | memcpy(&store, found, sizeof(store)); | |
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 | ||
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); | |
239 | } | |
240 | ||
241 | int utmp_put_runlevel(int runlevel, int previous) { | |
242 | struct utmpx store = {}; | |
243 | int r; | |
244 | ||
245 | assert(runlevel > 0); | |
246 | ||
247 | if (previous <= 0) { | |
248 | /* Find the old runlevel automatically */ | |
249 | ||
250 | r = utmp_get_runlevel(&previous, NULL); | |
251 | if (r < 0) { | |
252 | if (r != -ESRCH) | |
253 | return r; | |
254 | ||
255 | previous = 0; | |
256 | } | |
257 | } | |
258 | ||
259 | if (previous == runlevel) | |
260 | return 0; | |
261 | ||
262 | init_entry(&store, 0); | |
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 | } |