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