]>
Commit | Line | Data |
---|---|---|
d6c9574f | 1 | /*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ |
9e2f7c11 LP |
2 | |
3 | /*** | |
4 | This file is part of systemd. | |
5 | ||
6 | Copyright 2010 Lennart Poettering | |
7 | ||
8 | systemd is free software; you can redistribute it and/or modify it | |
5430f7f2 LP |
9 | under the terms of the GNU Lesser General Public License as published by |
10 | the Free Software Foundation; either version 2.1 of the License, or | |
9e2f7c11 LP |
11 | (at your option) any later version. |
12 | ||
13 | systemd is distributed in the hope that it will be useful, but | |
14 | WITHOUT ANY WARRANTY; without even the implied warranty of | |
15 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
5430f7f2 | 16 | Lesser General Public License for more details. |
9e2f7c11 | 17 | |
5430f7f2 | 18 | You should have received a copy of the GNU Lesser General Public License |
9e2f7c11 LP |
19 | along with systemd; If not, see <http://www.gnu.org/licenses/>. |
20 | ***/ | |
21 | ||
22 | #include <errno.h> | |
23 | #include <string.h> | |
71fad675 | 24 | #include <assert.h> |
9e2f7c11 | 25 | |
9eb977db | 26 | #include "path-util.h" |
f01de965 | 27 | #include "bus-label.h" |
71fad675 | 28 | #include "util.h" |
9e2f7c11 | 29 | #include "unit-name.h" |
4b549144 | 30 | #include "def.h" |
9e2f7c11 LP |
31 | |
32 | #define VALID_CHARS \ | |
4b549144 | 33 | DIGITS LETTERS \ |
4f2d528d | 34 | ":-_.\\" |
9e2f7c11 | 35 | |
830f01f0 | 36 | static const char* const unit_type_table[_UNIT_TYPE_MAX] = { |
0a9f8ed0 ZJS |
37 | [UNIT_SERVICE] = "service", |
38 | [UNIT_SOCKET] = "socket", | |
e821075a | 39 | [UNIT_BUSNAME] = "busname", |
0a9f8ed0 | 40 | [UNIT_TARGET] = "target", |
e821075a | 41 | [UNIT_SNAPSHOT] = "snapshot", |
0a9f8ed0 ZJS |
42 | [UNIT_DEVICE] = "device", |
43 | [UNIT_MOUNT] = "mount", | |
44 | [UNIT_AUTOMOUNT] = "automount", | |
0a9f8ed0 | 45 | [UNIT_SWAP] = "swap", |
e821075a | 46 | [UNIT_TIMER] = "timer", |
0a9f8ed0 | 47 | [UNIT_PATH] = "path", |
6c12b52e LP |
48 | [UNIT_SLICE] = "slice", |
49 | [UNIT_SCOPE] = "scope" | |
0a9f8ed0 ZJS |
50 | }; |
51 | ||
52 | DEFINE_STRING_TABLE_LOOKUP(unit_type, UnitType); | |
53 | ||
830f01f0 | 54 | static const char* const unit_load_state_table[_UNIT_LOAD_STATE_MAX] = { |
f69614f8 ZJS |
55 | [UNIT_STUB] = "stub", |
56 | [UNIT_LOADED] = "loaded", | |
c2756a68 | 57 | [UNIT_NOT_FOUND] = "not-found", |
f69614f8 ZJS |
58 | [UNIT_ERROR] = "error", |
59 | [UNIT_MERGED] = "merged", | |
60 | [UNIT_MASKED] = "masked" | |
61 | }; | |
62 | ||
63 | DEFINE_STRING_TABLE_LOOKUP(unit_load_state, UnitLoadState); | |
64 | ||
f78e6385 | 65 | bool unit_name_is_valid(const char *n, enum template_valid template_ok) { |
9e2f7c11 LP |
66 | const char *e, *i, *at; |
67 | ||
68 | /* Valid formats: | |
69 | * | |
70 | * string@instance.suffix | |
71 | * string.suffix | |
72 | */ | |
73 | ||
74 | assert(n); | |
f78e6385 | 75 | assert(IN_SET(template_ok, TEMPLATE_VALID, TEMPLATE_INVALID)); |
9e2f7c11 LP |
76 | |
77 | if (strlen(n) >= UNIT_NAME_MAX) | |
78 | return false; | |
79 | ||
71fad675 LP |
80 | e = strrchr(n, '.'); |
81 | if (!e || e == n) | |
9e2f7c11 LP |
82 | return false; |
83 | ||
5f739699 LP |
84 | if (unit_type_from_string(e + 1) < 0) |
85 | return false; | |
86 | ||
9e2f7c11 LP |
87 | for (i = n, at = NULL; i < e; i++) { |
88 | ||
89 | if (*i == '@' && !at) | |
90 | at = i; | |
91 | ||
92 | if (!strchr("@" VALID_CHARS, *i)) | |
93 | return false; | |
94 | } | |
95 | ||
96 | if (at) { | |
97 | if (at == n) | |
98 | return false; | |
99 | ||
f78e6385 | 100 | if (!template_ok == TEMPLATE_VALID && at+1 == e) |
9e2f7c11 LP |
101 | return false; |
102 | } | |
103 | ||
104 | return true; | |
105 | } | |
106 | ||
107 | bool unit_instance_is_valid(const char *i) { | |
108 | assert(i); | |
109 | ||
110 | /* The max length depends on the length of the string, so we | |
111 | * don't really check this here. */ | |
112 | ||
113 | if (i[0] == 0) | |
114 | return false; | |
115 | ||
116 | /* We allow additional @ in the instance string, we do not | |
117 | * allow them in the prefix! */ | |
118 | ||
119 | for (; *i; i++) | |
120 | if (!strchr("@" VALID_CHARS, *i)) | |
121 | return false; | |
122 | ||
123 | return true; | |
124 | } | |
125 | ||
126 | bool unit_prefix_is_valid(const char *p) { | |
127 | ||
128 | /* We don't allow additional @ in the instance string */ | |
129 | ||
130 | if (p[0] == 0) | |
131 | return false; | |
132 | ||
133 | for (; *p; p++) | |
134 | if (!strchr(VALID_CHARS, *p)) | |
135 | return false; | |
136 | ||
137 | return true; | |
138 | } | |
139 | ||
140 | int unit_name_to_instance(const char *n, char **instance) { | |
141 | const char *p, *d; | |
142 | char *i; | |
143 | ||
144 | assert(n); | |
145 | assert(instance); | |
146 | ||
147 | /* Everything past the first @ and before the last . is the instance */ | |
35eb6b12 LP |
148 | p = strchr(n, '@'); |
149 | if (!p) { | |
9e2f7c11 LP |
150 | *instance = NULL; |
151 | return 0; | |
152 | } | |
153 | ||
154 | assert_se(d = strrchr(n, '.')); | |
155 | assert(p < d); | |
156 | ||
35eb6b12 LP |
157 | i = strndup(p+1, d-p-1); |
158 | if (!i) | |
9e2f7c11 LP |
159 | return -ENOMEM; |
160 | ||
161 | *instance = i; | |
162 | return 0; | |
163 | } | |
164 | ||
165 | char *unit_name_to_prefix_and_instance(const char *n) { | |
166 | const char *d; | |
167 | ||
168 | assert(n); | |
169 | ||
170 | assert_se(d = strrchr(n, '.')); | |
171 | ||
172 | return strndup(n, d - n); | |
173 | } | |
174 | ||
175 | char *unit_name_to_prefix(const char *n) { | |
176 | const char *p; | |
177 | ||
b0193f1c LP |
178 | p = strchr(n, '@'); |
179 | if (p) | |
9e2f7c11 LP |
180 | return strndup(n, p - n); |
181 | ||
182 | return unit_name_to_prefix_and_instance(n); | |
183 | } | |
184 | ||
185 | char *unit_name_change_suffix(const char *n, const char *suffix) { | |
186 | char *e, *r; | |
187 | size_t a, b; | |
188 | ||
189 | assert(n); | |
f78e6385 | 190 | assert(unit_name_is_valid(n, TEMPLATE_VALID)); |
9e2f7c11 | 191 | assert(suffix); |
a016b922 | 192 | assert(suffix[0] == '.'); |
9e2f7c11 LP |
193 | |
194 | assert_se(e = strrchr(n, '.')); | |
195 | a = e - n; | |
196 | b = strlen(suffix); | |
197 | ||
b0193f1c LP |
198 | r = new(char, a + b + 1); |
199 | if (!r) | |
9e2f7c11 LP |
200 | return NULL; |
201 | ||
202 | memcpy(r, n, a); | |
203 | memcpy(r+a, suffix, b+1); | |
204 | ||
205 | return r; | |
206 | } | |
207 | ||
208 | char *unit_name_build(const char *prefix, const char *instance, const char *suffix) { | |
9e2f7c11 LP |
209 | assert(prefix); |
210 | assert(unit_prefix_is_valid(prefix)); | |
211 | assert(!instance || unit_instance_is_valid(instance)); | |
212 | assert(suffix); | |
9e2f7c11 LP |
213 | |
214 | if (!instance) | |
215 | return strappend(prefix, suffix); | |
216 | ||
b7def684 | 217 | return strjoin(prefix, "@", instance, suffix, NULL); |
9e2f7c11 LP |
218 | } |
219 | ||
4b712653 KS |
220 | static char *do_escape_char(char c, char *t) { |
221 | *(t++) = '\\'; | |
222 | *(t++) = 'x'; | |
223 | *(t++) = hexchar(c >> 4); | |
224 | *(t++) = hexchar(c); | |
225 | return t; | |
226 | } | |
227 | ||
228 | static char *do_escape(const char *f, char *t) { | |
9e2f7c11 LP |
229 | assert(f); |
230 | assert(t); | |
231 | ||
4b712653 KS |
232 | /* do not create units with a leading '.', like for "/.dotdir" mount points */ |
233 | if (*f == '.') { | |
234 | t = do_escape_char(*f, t); | |
235 | f++; | |
236 | } | |
237 | ||
9e2f7c11 LP |
238 | for (; *f; f++) { |
239 | if (*f == '/') | |
8d567588 | 240 | *(t++) = '-'; |
4b712653 KS |
241 | else if (*f == '-' || *f == '\\' || !strchr(VALID_CHARS, *f)) |
242 | t = do_escape_char(*f, t); | |
243 | else | |
9e2f7c11 LP |
244 | *(t++) = *f; |
245 | } | |
246 | ||
247 | return t; | |
248 | } | |
249 | ||
9e2f7c11 LP |
250 | char *unit_name_escape(const char *f) { |
251 | char *r, *t; | |
252 | ||
b0193f1c LP |
253 | r = new(char, strlen(f)*4+1); |
254 | if (!r) | |
9e2f7c11 LP |
255 | return NULL; |
256 | ||
257 | t = do_escape(f, r); | |
258 | *t = 0; | |
259 | ||
260 | return r; | |
9e2f7c11 LP |
261 | } |
262 | ||
263 | char *unit_name_unescape(const char *f) { | |
264 | char *r, *t; | |
265 | ||
266 | assert(f); | |
267 | ||
b0193f1c LP |
268 | r = strdup(f); |
269 | if (!r) | |
9e2f7c11 LP |
270 | return NULL; |
271 | ||
272 | for (t = r; *f; f++) { | |
8d567588 | 273 | if (*f == '-') |
9e2f7c11 LP |
274 | *(t++) = '/'; |
275 | else if (*f == '\\') { | |
276 | int a, b; | |
277 | ||
15e11d81 LP |
278 | if (f[1] != 'x' || |
279 | (a = unhexchar(f[2])) < 0 || | |
280 | (b = unhexchar(f[3])) < 0) { | |
281 | /* Invalid escape code, let's take it literal then */ | |
9e2f7c11 LP |
282 | *(t++) = '\\'; |
283 | } else { | |
284 | *(t++) = (char) ((a << 4) | b); | |
95e501f8 | 285 | f += 3; |
9e2f7c11 LP |
286 | } |
287 | } else | |
288 | *(t++) = *f; | |
289 | } | |
290 | ||
291 | *t = 0; | |
292 | ||
293 | return r; | |
294 | } | |
295 | ||
35eb6b12 | 296 | char *unit_name_path_escape(const char *f) { |
58d08142 | 297 | _cleanup_free_ char *p; |
35eb6b12 LP |
298 | |
299 | assert(f); | |
300 | ||
301 | p = strdup(f); | |
302 | if (!p) | |
303 | return NULL; | |
304 | ||
305 | path_kill_slashes(p); | |
306 | ||
58d08142 | 307 | if (streq(p, "/") || streq(p, "")) |
35eb6b12 | 308 | return strdup("-"); |
35eb6b12 | 309 | |
58d08142 | 310 | return unit_name_escape(p[0] == '/' ? p + 1 : p); |
35eb6b12 LP |
311 | } |
312 | ||
313 | char *unit_name_path_unescape(const char *f) { | |
314 | char *e; | |
315 | ||
316 | assert(f); | |
317 | ||
318 | e = unit_name_unescape(f); | |
319 | if (!e) | |
320 | return NULL; | |
321 | ||
322 | if (e[0] != '/') { | |
323 | char *w; | |
324 | ||
325 | w = strappend("/", e); | |
326 | free(e); | |
327 | ||
328 | return w; | |
329 | } | |
330 | ||
331 | return e; | |
332 | } | |
333 | ||
9e2f7c11 LP |
334 | bool unit_name_is_template(const char *n) { |
335 | const char *p; | |
336 | ||
337 | assert(n); | |
338 | ||
a7b9ecf9 MS |
339 | p = strchr(n, '@'); |
340 | if (!p) | |
9e2f7c11 LP |
341 | return false; |
342 | ||
343 | return p[1] == '.'; | |
344 | } | |
345 | ||
29283ea4 MS |
346 | bool unit_name_is_instance(const char *n) { |
347 | const char *p; | |
348 | ||
349 | assert(n); | |
350 | ||
351 | p = strchr(n, '@'); | |
352 | if (!p) | |
353 | return false; | |
354 | ||
355 | return p[1] != '.'; | |
356 | } | |
357 | ||
9e2f7c11 LP |
358 | char *unit_name_replace_instance(const char *f, const char *i) { |
359 | const char *p, *e; | |
360 | char *r, *k; | |
8556879e | 361 | size_t a, b; |
9e2f7c11 LP |
362 | |
363 | assert(f); | |
364 | ||
365 | p = strchr(f, '@'); | |
8556879e LP |
366 | if (!p) |
367 | return strdup(f); | |
9e2f7c11 | 368 | |
8556879e LP |
369 | e = strrchr(f, '.'); |
370 | if (!e) | |
371 | assert_se(e = strchr(f, 0)); | |
9e2f7c11 | 372 | |
8556879e LP |
373 | a = p - f; |
374 | b = strlen(i); | |
9e2f7c11 | 375 | |
8556879e LP |
376 | r = new(char, a + 1 + b + strlen(e) + 1); |
377 | if (!r) | |
378 | return NULL; | |
9e2f7c11 | 379 | |
8556879e LP |
380 | k = mempcpy(r, f, a + 1); |
381 | k = mempcpy(k, i, b); | |
9e2f7c11 | 382 | strcpy(k, e); |
8556879e | 383 | |
9e2f7c11 LP |
384 | return r; |
385 | } | |
386 | ||
387 | char *unit_name_template(const char *f) { | |
388 | const char *p, *e; | |
389 | char *r; | |
390 | size_t a; | |
391 | ||
b0193f1c LP |
392 | p = strchr(f, '@'); |
393 | if (!p) | |
9e2f7c11 LP |
394 | return strdup(f); |
395 | ||
396 | assert_se(e = strrchr(f, '.')); | |
397 | a = p - f + 1; | |
398 | ||
b0193f1c LP |
399 | r = new(char, a + strlen(e) + 1); |
400 | if (!r) | |
9e2f7c11 LP |
401 | return NULL; |
402 | ||
403 | strcpy(mempcpy(r, f, a), e); | |
404 | return r; | |
9e2f7c11 | 405 | } |
a16e1123 LP |
406 | |
407 | char *unit_name_from_path(const char *path, const char *suffix) { | |
58d08142 | 408 | _cleanup_free_ char *p = NULL; |
5ffceb2f | 409 | |
a16e1123 LP |
410 | assert(path); |
411 | assert(suffix); | |
412 | ||
35eb6b12 | 413 | p = unit_name_path_escape(path); |
b0193f1c | 414 | if (!p) |
5ffceb2f LP |
415 | return NULL; |
416 | ||
58d08142 | 417 | return strappend(p, suffix); |
a16e1123 LP |
418 | } |
419 | ||
9fff8a1f | 420 | char *unit_name_from_path_instance(const char *prefix, const char *path, const char *suffix) { |
58d08142 | 421 | _cleanup_free_ char *p = NULL; |
9fff8a1f | 422 | |
35eb6b12 | 423 | assert(prefix); |
9fff8a1f LP |
424 | assert(path); |
425 | assert(suffix); | |
426 | ||
35eb6b12 LP |
427 | p = unit_name_path_escape(path); |
428 | if (!p) | |
9fff8a1f LP |
429 | return NULL; |
430 | ||
58d08142 | 431 | return strjoin(prefix, "@", p, suffix, NULL); |
9fff8a1f LP |
432 | } |
433 | ||
a16e1123 | 434 | char *unit_name_to_path(const char *name) { |
e821075a | 435 | _cleanup_free_ char *w = NULL; |
a16e1123 LP |
436 | |
437 | assert(name); | |
438 | ||
b0193f1c LP |
439 | w = unit_name_to_prefix(name); |
440 | if (!w) | |
a16e1123 LP |
441 | return NULL; |
442 | ||
e821075a | 443 | return unit_name_path_unescape(w); |
9fc50704 | 444 | } |
48899192 MS |
445 | |
446 | char *unit_dbus_path_from_name(const char *name) { | |
9444b1f2 | 447 | _cleanup_free_ char *e = NULL; |
48899192 | 448 | |
35eb6b12 LP |
449 | assert(name); |
450 | ||
f01de965 | 451 | e = bus_label_escape(name); |
48899192 MS |
452 | if (!e) |
453 | return NULL; | |
454 | ||
9444b1f2 | 455 | return strappend("/org/freedesktop/systemd1/unit/", e); |
48899192 | 456 | } |
b0193f1c | 457 | |
ede3a796 LP |
458 | int unit_name_from_dbus_path(const char *path, char **name) { |
459 | const char *e; | |
460 | char *n; | |
461 | ||
462 | e = startswith(path, "/org/freedesktop/systemd1/unit/"); | |
463 | if (!e) | |
464 | return -EINVAL; | |
465 | ||
f01de965 | 466 | n = bus_label_unescape(e); |
ede3a796 LP |
467 | if (!n) |
468 | return -ENOMEM; | |
469 | ||
470 | *name = n; | |
471 | return 0; | |
472 | } | |
473 | ||
e3e0314b ZJS |
474 | |
475 | /** | |
476 | * Try to turn a string that might not be a unit name into a | |
477 | * sensible unit name. | |
478 | */ | |
f78e6385 | 479 | char *unit_name_mangle(const char *name, enum unit_name_mangle allow_globs) { |
b0193f1c LP |
480 | char *r, *t; |
481 | const char *f; | |
f78e6385 | 482 | const char* valid_chars = allow_globs == MANGLE_GLOB ? "@" VALID_CHARS "[]!-*?" : "@" VALID_CHARS; |
b0193f1c LP |
483 | |
484 | assert(name); | |
f78e6385 | 485 | assert(IN_SET(allow_globs, MANGLE_GLOB, MANGLE_NOGLOB)); |
b0193f1c | 486 | |
a05f97b3 | 487 | if (is_device_path(name)) |
b0193f1c LP |
488 | return unit_name_from_path(name, ".device"); |
489 | ||
490 | if (path_is_absolute(name)) | |
491 | return unit_name_from_path(name, ".mount"); | |
492 | ||
493 | /* We'll only escape the obvious characters here, to play | |
494 | * safe. */ | |
495 | ||
56d4fbf9 | 496 | r = new(char, strlen(name) * 4 + 1 + sizeof(".service")-1); |
b0193f1c LP |
497 | if (!r) |
498 | return NULL; | |
499 | ||
500 | for (f = name, t = r; *f; f++) { | |
b0193f1c LP |
501 | if (*f == '/') |
502 | *(t++) = '-'; | |
e3e0314b | 503 | else if (!strchr(valid_chars, *f)) |
b0193f1c LP |
504 | t = do_escape_char(*f, t); |
505 | else | |
506 | *(t++) = *f; | |
507 | } | |
508 | ||
696c245a | 509 | if (unit_name_to_type(name) < 0) |
56d4fbf9 LP |
510 | strcpy(t, ".service"); |
511 | else | |
512 | *t = 0; | |
b0193f1c LP |
513 | |
514 | return r; | |
515 | } | |
5f739699 | 516 | |
e3e0314b ZJS |
517 | |
518 | /** | |
519 | * Similar to unit_name_mangle(), but is called when we know | |
520 | * that this is about a specific unit type. | |
521 | */ | |
f78e6385 | 522 | char *unit_name_mangle_with_suffix(const char *name, enum unit_name_mangle allow_globs, const char *suffix) { |
1dcf6065 LP |
523 | char *r, *t; |
524 | const char *f; | |
525 | ||
526 | assert(name); | |
a016b922 LP |
527 | assert(suffix); |
528 | assert(suffix[0] == '.'); | |
1dcf6065 | 529 | |
a016b922 | 530 | r = new(char, strlen(name) * 4 + strlen(suffix) + 1); |
1dcf6065 LP |
531 | if (!r) |
532 | return NULL; | |
533 | ||
534 | for (f = name, t = r; *f; f++) { | |
535 | if (*f == '/') | |
536 | *(t++) = '-'; | |
537 | else if (!strchr(VALID_CHARS, *f)) | |
538 | t = do_escape_char(*f, t); | |
539 | else | |
540 | *(t++) = *f; | |
541 | } | |
542 | ||
a016b922 LP |
543 | if (!endswith(name, suffix)) |
544 | strcpy(t, suffix); | |
1dcf6065 LP |
545 | else |
546 | *t = 0; | |
547 | ||
548 | return r; | |
549 | } | |
550 | ||
5f739699 LP |
551 | UnitType unit_name_to_type(const char *n) { |
552 | const char *e; | |
553 | ||
554 | assert(n); | |
555 | ||
556 | e = strrchr(n, '.'); | |
557 | if (!e) | |
558 | return _UNIT_TYPE_INVALID; | |
559 | ||
560 | return unit_type_from_string(e + 1); | |
561 | } | |
fb6becb4 LP |
562 | |
563 | int build_subslice(const char *slice, const char*name, char **subslice) { | |
564 | char *ret; | |
565 | ||
566 | assert(slice); | |
567 | assert(name); | |
568 | assert(subslice); | |
569 | ||
570 | if (streq(slice, "-.slice")) | |
571 | ret = strappend(name, ".slice"); | |
572 | else { | |
573 | char *e; | |
574 | ||
575 | e = endswith(slice, ".slice"); | |
576 | if (!e) | |
577 | return -EINVAL; | |
578 | ||
579 | ret = new(char, (e - slice) + 1 + strlen(name) + 6 + 1); | |
580 | if (!ret) | |
581 | return -ENOMEM; | |
582 | ||
583 | stpcpy(stpcpy(stpcpy(mempcpy(ret, slice, e - slice), "-"), name), ".slice"); | |
584 | } | |
585 | ||
586 | *subslice = ret; | |
587 | return 0; | |
588 | } |