]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/shared/specifier.c
man/systemd-sysext: list ephemeral/ephemeral-import in the list of options
[thirdparty/systemd.git] / src / shared / specifier.c
1 /* SPDX-License-Identifier: LGPL-2.1-or-later */
2
3 #include <sys/utsname.h>
4 #include <unistd.h>
5
6 #include "sd-id128.h"
7
8 #include "alloc-util.h"
9 #include "architecture.h"
10 #include "chase.h"
11 #include "format-util.h"
12 #include "fs-util.h"
13 #include "hostname-setup.h"
14 #include "hostname-util.h"
15 #include "id128-util.h"
16 #include "os-util.h"
17 #include "path-util.h"
18 #include "runtime-scope.h"
19 #include "specifier.h"
20 #include "string-util.h"
21 #include "strv.h"
22 #include "user-util.h"
23
24 /*
25 * Generic infrastructure for replacing %x style specifiers in
26 * strings. Will call a callback for each replacement.
27 */
28
29 /* Any ASCII character or digit: our pool of potential specifiers,
30 * and "%" used for escaping. */
31 #define POSSIBLE_SPECIFIERS ALPHANUMERICAL "%"
32
33 int specifier_printf(const char *text, size_t max_length, const Specifier table[], const char *root, const void *userdata, char **ret) {
34 _cleanup_free_ char *result = NULL;
35 bool percent = false;
36 size_t l;
37 char *t;
38 int r;
39
40 assert(ret);
41 assert(text);
42 assert(table);
43
44 l = strlen(text);
45 if (!GREEDY_REALLOC(result, l + 1))
46 return -ENOMEM;
47 t = result;
48
49 for (const char *f = text; *f != '\0'; f++, l--) {
50 if (percent) {
51 percent = false;
52
53 if (*f == '%')
54 *(t++) = '%';
55 else {
56 const Specifier *i;
57
58 for (i = table; i->specifier; i++)
59 if (i->specifier == *f)
60 break;
61
62 if (i->lookup) {
63 _cleanup_free_ char *w = NULL;
64 size_t k, j;
65
66 r = i->lookup(i->specifier, i->data, root, userdata, &w);
67 if (r < 0)
68 return r;
69 if (isempty(w))
70 continue;
71
72 j = t - result;
73 k = strlen(w);
74
75 if (!GREEDY_REALLOC(result, j + k + l + 1))
76 return -ENOMEM;
77 t = mempcpy(result + j, w, k);
78 } else if (strchr(POSSIBLE_SPECIFIERS, *f))
79 /* Oops, an unknown specifier. */
80 return -EBADSLT;
81 else {
82 *(t++) = '%';
83 *(t++) = *f;
84 }
85 }
86 } else if (*f == '%')
87 percent = true;
88 else
89 *(t++) = *f;
90
91 if ((size_t) (t - result) > max_length)
92 return -ENAMETOOLONG;
93 }
94
95 /* If string ended with a stray %, also end with % */
96 if (percent) {
97 *(t++) = '%';
98 if ((size_t) (t - result) > max_length)
99 return -ENAMETOOLONG;
100 }
101 *(t++) = 0;
102
103 *ret = TAKE_PTR(result);
104 return 0;
105 }
106
107 /* Generic handler for simple string replacements */
108
109 int specifier_string(char specifier, const void *data, const char *root, const void *userdata, char **ret) {
110 return strdup_to(ASSERT_PTR(ret), empty_to_null(data));
111 }
112
113 int specifier_real_path(char specifier, const void *data, const char *root, const void *userdata, char **ret) {
114 const char *path = data;
115
116 assert(ret);
117
118 if (!path)
119 return -ENOENT;
120
121 return chase(path, root, 0, ret, NULL);
122 }
123
124 int specifier_real_directory(char specifier, const void *data, const char *root, const void *userdata, char **ret) {
125 _cleanup_free_ char *path = NULL;
126 int r;
127
128 assert(ret);
129
130 r = specifier_real_path(specifier, data, root, userdata, &path);
131 if (r < 0)
132 return r;
133
134 assert(path);
135 return path_extract_directory(path, ret);
136 }
137
138 int specifier_id128(char specifier, const void *data, const char *root, const void *userdata, char **ret) {
139 const sd_id128_t *id = ASSERT_PTR(data);
140 char *n;
141
142 n = new(char, SD_ID128_STRING_MAX);
143 if (!n)
144 return -ENOMEM;
145
146 *ret = sd_id128_to_string(*id, n);
147 return 0;
148 }
149
150 int specifier_uuid(char specifier, const void *data, const char *root, const void *userdata, char **ret) {
151 const sd_id128_t *id = ASSERT_PTR(data);
152 char *n;
153
154 n = new(char, SD_ID128_UUID_STRING_MAX);
155 if (!n)
156 return -ENOMEM;
157
158 *ret = sd_id128_to_uuid_string(*id, n);
159 return 0;
160 }
161
162 int specifier_uint64(char specifier, const void *data, const char *root, const void *userdata, char **ret) {
163 const uint64_t *n = ASSERT_PTR(data);
164
165 return asprintf(ret, "%" PRIu64, *n) < 0 ? -ENOMEM : 0;
166 }
167
168 int specifier_machine_id(char specifier, const void *data, const char *root, const void *userdata, char **ret) {
169 sd_id128_t id;
170 int r;
171
172 assert(ret);
173
174 r = id128_get_machine(root, &id);
175 if (r < 0) /* Translate error for missing /etc/machine-id file to EUNATCH. */
176 return r == -ENOENT ? -EUNATCH : r;
177
178 return specifier_id128(specifier, &id, root, userdata, ret);
179 }
180
181 int specifier_boot_id(char specifier, const void *data, const char *root, const void *userdata, char **ret) {
182 sd_id128_t id;
183 int r;
184
185 assert(ret);
186
187 r = sd_id128_get_boot(&id);
188 if (r < 0)
189 return r;
190
191 return specifier_id128(specifier, &id, root, userdata, ret);
192 }
193
194 int specifier_hostname(char specifier, const void *data, const char *root, const void *userdata, char **ret) {
195 char *n;
196
197 assert(ret);
198
199 n = gethostname_malloc();
200 if (!n)
201 return -ENOMEM;
202
203 *ret = n;
204 return 0;
205 }
206
207 int specifier_short_hostname(char specifier, const void *data, const char *root, const void *userdata, char **ret) {
208 char *n;
209
210 assert(ret);
211
212 n = gethostname_short_malloc();
213 if (!n)
214 return -ENOMEM;
215
216 *ret = n;
217 return 0;
218 }
219
220 int specifier_pretty_hostname(char specifier, const void *data, const char *root, const void *userdata, char **ret) {
221 char *n = NULL;
222
223 assert(ret);
224
225 if (get_pretty_hostname(&n) < 0) {
226 n = gethostname_short_malloc();
227 if (!n)
228 return -ENOMEM;
229 }
230
231 *ret = n;
232 return 0;
233 }
234
235 int specifier_kernel_release(char specifier, const void *data, const char *root, const void *userdata, char **ret) {
236 struct utsname uts;
237
238 assert(ret);
239
240 if (uname(&uts) < 0)
241 return -errno;
242
243 return strdup_to(ret, uts.release);
244 }
245
246 int specifier_architecture(char specifier, const void *data, const char *root, const void *userdata, char **ret) {
247 return strdup_to(ASSERT_PTR(ret),
248 architecture_to_string(uname_architecture()));
249 }
250
251 /* Note: fields in /etc/os-release might quite possibly be missing, even if everything is entirely valid
252 * otherwise. We'll return an empty value or NULL in that case from the functions below. But if the
253 * os-release file is missing, we'll return -EUNATCH. This means that something is seriously wrong with the
254 * installation. */
255
256 static int parse_os_release_specifier(const char *root, const char *id, char **ret) {
257 _cleanup_free_ char *v = NULL;
258 int r;
259
260 assert(ret);
261
262 r = parse_os_release(root, id, &v);
263 if (r >= 0)
264 /* parse_os_release() calls parse_env_file() which only sets the return value for
265 * entries found. Let's make sure we set the return value in all cases. */
266 *ret = TAKE_PTR(v);
267
268 /* Translate error for missing os-release file to EUNATCH. */
269 return r == -ENOENT ? -EUNATCH : r;
270 }
271
272 int specifier_os_id(char specifier, const void *data, const char *root, const void *userdata, char **ret) {
273 return parse_os_release_specifier(root, "ID", ret);
274 }
275
276 int specifier_os_version_id(char specifier, const void *data, const char *root, const void *userdata, char **ret) {
277 return parse_os_release_specifier(root, "VERSION_ID", ret);
278 }
279
280 int specifier_os_build_id(char specifier, const void *data, const char *root, const void *userdata, char **ret) {
281 return parse_os_release_specifier(root, "BUILD_ID", ret);
282 }
283
284 int specifier_os_variant_id(char specifier, const void *data, const char *root, const void *userdata, char **ret) {
285 return parse_os_release_specifier(root, "VARIANT_ID", ret);
286 }
287
288 int specifier_os_image_id(char specifier, const void *data, const char *root, const void *userdata, char **ret) {
289 return parse_os_release_specifier(root, "IMAGE_ID", ret);
290 }
291
292 int specifier_os_image_version(char specifier, const void *data, const char *root, const void *userdata, char **ret) {
293 return parse_os_release_specifier(root, "IMAGE_VERSION", ret);
294 }
295
296 int specifier_group_name(char specifier, const void *data, const char *root, const void *userdata, char **ret) {
297 RuntimeScope scope = PTR_TO_INT(data);
298 char *t;
299
300 assert(ret);
301
302 if (scope == RUNTIME_SCOPE_GLOBAL)
303 return -EINVAL;
304
305 t = gid_to_name(scope == RUNTIME_SCOPE_USER ? getgid() : 0);
306 if (!t)
307 return -ENOMEM;
308
309 *ret = t;
310 return 0;
311 }
312
313 int specifier_group_id(char specifier, const void *data, const char *root, const void *userdata, char **ret) {
314 RuntimeScope scope = PTR_TO_INT(data);
315 gid_t gid;
316
317 assert(ret);
318
319 if (scope == RUNTIME_SCOPE_GLOBAL)
320 return -EINVAL;
321
322 gid = scope == RUNTIME_SCOPE_USER ? getgid() : 0;
323
324 if (asprintf(ret, UID_FMT, gid) < 0)
325 return -ENOMEM;
326
327 return 0;
328 }
329
330 int specifier_user_name(char specifier, const void *data, const char *root, const void *userdata, char **ret) {
331 RuntimeScope scope = PTR_TO_INT(data);
332 uid_t uid;
333 char *t;
334
335 assert(ret);
336
337 if (scope == RUNTIME_SCOPE_GLOBAL)
338 return -EINVAL;
339
340 uid = scope == RUNTIME_SCOPE_USER ? getuid() : 0;
341
342 /* If we are UID 0 (root), this will not result in NSS, otherwise it might. This is good, as we want
343 * to be able to run this in PID 1, where our user ID is 0, but where NSS lookups are not allowed.
344
345 * We don't use getusername_malloc() here, because we don't want to look at $USER, to remain
346 * consistent with specifer_user_id() below.
347 */
348
349 t = uid_to_name(uid);
350 if (!t)
351 return -ENOMEM;
352
353 *ret = t;
354 return 0;
355 }
356
357 int specifier_user_id(char specifier, const void *data, const char *root, const void *userdata, char **ret) {
358 RuntimeScope scope = PTR_TO_INT(data);
359 uid_t uid;
360
361 assert(ret);
362
363 if (scope == RUNTIME_SCOPE_GLOBAL)
364 return -EINVAL;
365
366 uid = scope == RUNTIME_SCOPE_USER ? getuid() : 0;
367
368 if (asprintf(ret, UID_FMT, uid) < 0)
369 return -ENOMEM;
370
371 return 0;
372 }
373
374 int specifier_user_home(char specifier, const void *data, const char *root, const void *userdata, char **ret) {
375 assert(ret);
376
377 /* On PID 1 (which runs as root) this will not result in NSS,
378 * which is good. See above */
379
380 return get_home_dir(ret);
381 }
382
383 int specifier_user_shell(char specifier, const void *data, const char *root, const void *userdata, char **ret) {
384 assert(ret);
385
386 /* On PID 1 (which runs as root) this will not result in NSS,
387 * which is good. See above */
388
389 return get_shell(ret);
390 }
391
392 int specifier_tmp_dir(char specifier, const void *data, const char *root, const void *userdata, char **ret) {
393 const char *p;
394 int r;
395
396 assert(ret);
397
398 if (root) /* If root dir is set, don't honour $TMP or similar */
399 p = "/tmp";
400 else {
401 r = tmp_dir(&p);
402 if (r < 0)
403 return r;
404 }
405
406 return strdup_to(ret, p);
407 }
408
409 int specifier_var_tmp_dir(char specifier, const void *data, const char *root, const void *userdata, char **ret) {
410 const char *p;
411 int r;
412
413 assert(ret);
414
415 if (root)
416 p = "/var/tmp";
417 else {
418 r = var_tmp_dir(&p);
419 if (r < 0)
420 return r;
421 }
422
423 return strdup_to(ret, p);
424 }
425
426 char* specifier_escape(const char *string) {
427 return strreplace(string, "%", "%%");
428 }
429
430 int specifier_escape_strv(char **l, char ***ret) {
431 _cleanup_strv_free_ char **z = NULL;
432 char **p, **q;
433
434 assert(ret);
435
436 if (strv_isempty(l)) {
437 *ret = NULL;
438 return 0;
439 }
440
441 z = new(char*, strv_length(l)+1);
442 if (!z)
443 return -ENOMEM;
444
445 for (p = l, q = z; *p; p++, q++) {
446
447 *q = specifier_escape(*p);
448 if (!*q)
449 return -ENOMEM;
450 }
451
452 *q = NULL;
453 *ret = TAKE_PTR(z);
454
455 return 0;
456 }
457
458 const Specifier system_and_tmp_specifier_table[] = {
459 COMMON_SYSTEM_SPECIFIERS,
460 COMMON_TMP_SPECIFIERS,
461 {}
462 };