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