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