]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/shared/specifier.c
Merge pull request #20030 from keszybz/exec_fd-event-source
[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 "fd-util.h"
14 #include "format-util.h"
15 #include "fs-util.h"
16 #include "hostname-util.h"
17 #include "id128-util.h"
18 #include "macro.h"
19 #include "os-util.h"
20 #include "specifier.h"
21 #include "string-util.h"
22 #include "strv.h"
23 #include "user-util.h"
24
25 /*
26 * Generic infrastructure for replacing %x style specifiers in
27 * strings. Will call a callback for each replacement.
28 */
29
30 /* Any ASCII character or digit: our pool of potential specifiers,
31 * and "%" used for escaping. */
32 #define POSSIBLE_SPECIFIERS ALPHANUMERICAL "%"
33
34 int specifier_printf(const char *text, size_t max_length, const Specifier table[], const char *root, const void *userdata, char **ret) {
35 _cleanup_free_ char *result = NULL;
36 bool percent = false;
37 const char *f;
38 size_t l;
39 char *t;
40 int r;
41
42 assert(text);
43 assert(table);
44
45 l = strlen(text);
46 if (!GREEDY_REALLOC(result, l + 1))
47 return -ENOMEM;
48 t = result;
49
50 for (f = text; *f != '\0'; f++, l--) {
51 if (percent) {
52 if (*f == '%')
53 *(t++) = '%';
54 else {
55 const Specifier *i;
56
57 for (i = table; i->specifier; i++)
58 if (i->specifier == *f)
59 break;
60
61 if (i->lookup) {
62 _cleanup_free_ char *w = NULL;
63 size_t k, j;
64
65 r = i->lookup(i->specifier, i->data, root, userdata, &w);
66 if (r < 0)
67 return r;
68
69 j = t - result;
70 k = strlen(w);
71
72 if (!GREEDY_REALLOC(result, j + k + l + 1))
73 return -ENOMEM;
74 memcpy(result + j, w, k);
75 t = result + j + k;
76 } else if (strchr(POSSIBLE_SPECIFIERS, *f))
77 /* Oops, an unknown specifier. */
78 return -EBADSLT;
79 else {
80 *(t++) = '%';
81 *(t++) = *f;
82 }
83 }
84
85 percent = false;
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 char *n;
111
112 n = strdup(strempty(data));
113 if (!n)
114 return -ENOMEM;
115
116 *ret = n;
117 return 0;
118 }
119
120 int specifier_machine_id(char specifier, const void *data, const char *root, const void *userdata, char **ret) {
121 sd_id128_t id;
122 char *n;
123 int r;
124
125 if (root) {
126 _cleanup_close_ int fd = -1;
127
128 fd = chase_symlinks_and_open("/etc/machine-id", root, CHASE_PREFIX_ROOT, O_RDONLY|O_CLOEXEC|O_NOCTTY, NULL);
129 if (fd < 0)
130 return fd;
131
132 r = id128_read_fd(fd, ID128_PLAIN, &id);
133 } else
134 r = sd_id128_get_machine(&id);
135 if (r < 0)
136 return r;
137
138 n = new(char, SD_ID128_STRING_MAX);
139 if (!n)
140 return -ENOMEM;
141
142 *ret = sd_id128_to_string(id, n);
143 return 0;
144 }
145
146 int specifier_boot_id(char specifier, const void *data, const char *root, const void *userdata, char **ret) {
147 sd_id128_t id;
148 char *n;
149 int r;
150
151 r = sd_id128_get_boot(&id);
152 if (r < 0)
153 return r;
154
155 n = new(char, SD_ID128_STRING_MAX);
156 if (!n)
157 return -ENOMEM;
158
159 *ret = sd_id128_to_string(id, n);
160 return 0;
161 }
162
163 int specifier_host_name(char specifier, const void *data, const char *root, const void *userdata, char **ret) {
164 char *n;
165
166 n = gethostname_malloc();
167 if (!n)
168 return -ENOMEM;
169
170 *ret = n;
171 return 0;
172 }
173
174 int specifier_short_host_name(char specifier, const void *data, const char *root, const void *userdata, char **ret) {
175 char *n;
176
177 n = gethostname_short_malloc();
178 if (!n)
179 return -ENOMEM;
180
181 *ret = n;
182 return 0;
183 }
184
185 int specifier_kernel_release(char specifier, const void *data, const char *root, const void *userdata, char **ret) {
186 struct utsname uts;
187 char *n;
188 int r;
189
190 r = uname(&uts);
191 if (r < 0)
192 return -errno;
193
194 n = strdup(uts.release);
195 if (!n)
196 return -ENOMEM;
197
198 *ret = n;
199 return 0;
200 }
201
202 int specifier_architecture(char specifier, const void *data, const char *root, const void *userdata, char **ret) {
203 char *t;
204
205 t = strdup(architecture_to_string(uname_architecture()));
206 if (!t)
207 return -ENOMEM;
208
209 *ret = t;
210 return 0;
211 }
212
213 static int specifier_os_release_common(const char *field, const char *root, char **ret) {
214 char *t = NULL;
215 int r;
216
217 r = parse_os_release(root, field, &t);
218 if (r < 0)
219 return r;
220 if (!t) {
221 /* fields in /etc/os-release might quite possibly be missing, even if everything is entirely
222 * valid otherwise. Let's hence return "" in that case. */
223 t = strdup("");
224 if (!t)
225 return -ENOMEM;
226 }
227
228 *ret = t;
229 return 0;
230 }
231
232 int specifier_os_id(char specifier, const void *data, const char *root, const void *userdata, char **ret) {
233 return specifier_os_release_common("ID", root, ret);
234 }
235
236 int specifier_os_version_id(char specifier, const void *data, const char *root, const void *userdata, char **ret) {
237 return specifier_os_release_common("VERSION_ID", root, ret);
238 }
239
240 int specifier_os_build_id(char specifier, const void *data, const char *root, const void *userdata, char **ret) {
241 return specifier_os_release_common("BUILD_ID", root, ret);
242 }
243
244 int specifier_os_variant_id(char specifier, const void *data, const char *root, const void *userdata, char **ret) {
245 return specifier_os_release_common("VARIANT_ID", root, ret);
246 }
247
248 int specifier_os_image_id(char specifier, const void *data, const char *root, const void *userdata, char **ret) {
249 return specifier_os_release_common("IMAGE_ID", root, ret);
250 }
251
252 int specifier_os_image_version(char specifier, const void *data, const char *root, const void *userdata, char **ret) {
253 return specifier_os_release_common("IMAGE_VERSION", root, ret);
254 }
255
256 int specifier_group_name(char specifier, const void *data, const char *root, const void *userdata, char **ret) {
257 char *t;
258
259 t = gid_to_name(getgid());
260 if (!t)
261 return -ENOMEM;
262
263 *ret = t;
264 return 0;
265 }
266
267 int specifier_group_id(char specifier, const void *data, const char *root, const void *userdata, char **ret) {
268 if (asprintf(ret, UID_FMT, getgid()) < 0)
269 return -ENOMEM;
270
271 return 0;
272 }
273
274 int specifier_user_name(char specifier, const void *data, const char *root, const void *userdata, char **ret) {
275 char *t;
276
277 /* If we are UID 0 (root), this will not result in NSS, otherwise it might. This is good, as we want to be able
278 * to run this in PID 1, where our user ID is 0, but where NSS lookups are not allowed.
279
280 * We don't use getusername_malloc() here, because we don't want to look at $USER, to remain consistent with
281 * specifer_user_id() below.
282 */
283
284 t = uid_to_name(getuid());
285 if (!t)
286 return -ENOMEM;
287
288 *ret = t;
289 return 0;
290 }
291
292 int specifier_user_id(char specifier, const void *data, const char *root, const void *userdata, char **ret) {
293
294 if (asprintf(ret, UID_FMT, getuid()) < 0)
295 return -ENOMEM;
296
297 return 0;
298 }
299
300 int specifier_user_home(char specifier, const void *data, const char *root, const void *userdata, char **ret) {
301
302 /* On PID 1 (which runs as root) this will not result in NSS,
303 * which is good. See above */
304
305 return get_home_dir(ret);
306 }
307
308 int specifier_user_shell(char specifier, const void *data, const char *root, const void *userdata, char **ret) {
309
310 /* On PID 1 (which runs as root) this will not result in NSS,
311 * which is good. See above */
312
313 return get_shell(ret);
314 }
315
316 int specifier_tmp_dir(char specifier, const void *data, const char *root, const void *userdata, char **ret) {
317 const char *p;
318 char *copy;
319 int r;
320
321 if (root) /* If root dir is set, don't honour $TMP or similar */
322 p = "/tmp";
323 else {
324 r = tmp_dir(&p);
325 if (r < 0)
326 return r;
327 }
328 copy = strdup(p);
329 if (!copy)
330 return -ENOMEM;
331
332 *ret = copy;
333 return 0;
334 }
335
336 int specifier_var_tmp_dir(char specifier, const void *data, const char *root, const void *userdata, char **ret) {
337 const char *p;
338 char *copy;
339 int r;
340
341 if (root)
342 p = "/var/tmp";
343 else {
344 r = var_tmp_dir(&p);
345 if (r < 0)
346 return r;
347 }
348 copy = strdup(p);
349 if (!copy)
350 return -ENOMEM;
351
352 *ret = copy;
353 return 0;
354 }
355
356 int specifier_escape_strv(char **l, char ***ret) {
357 char **z, **p, **q;
358
359 assert(ret);
360
361 if (strv_isempty(l)) {
362 *ret = NULL;
363 return 0;
364 }
365
366 z = new(char*, strv_length(l)+1);
367 if (!z)
368 return -ENOMEM;
369
370 for (p = l, q = z; *p; p++, q++) {
371
372 *q = specifier_escape(*p);
373 if (!*q) {
374 strv_free(z);
375 return -ENOMEM;
376 }
377 }
378
379 *q = NULL;
380 *ret = z;
381
382 return 0;
383 }
384
385 const Specifier system_and_tmp_specifier_table[] = {
386 COMMON_SYSTEM_SPECIFIERS,
387 COMMON_TMP_SPECIFIERS,
388 {}
389 };