]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/shared/unit-name.c
systemctl: automatically turn paths and unescaped unit names into proper unit names
[thirdparty/systemd.git] / src / shared / unit-name.c
1 /*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
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
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
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
16 Lesser General Public License for more details.
17
18 You should have received a copy of the GNU Lesser General Public License
19 along with systemd; If not, see <http://www.gnu.org/licenses/>.
20 ***/
21
22 #include <errno.h>
23 #include <string.h>
24 #include <assert.h>
25
26 #include "path-util.h"
27 #include "util.h"
28 #include "unit-name.h"
29
30 #define VALID_CHARS \
31 "0123456789" \
32 "abcdefghijklmnopqrstuvwxyz" \
33 "ABCDEFGHIJKLMNOPQRSTUVWXYZ" \
34 ":-_.\\"
35
36 bool unit_name_is_valid_no_type(const char *n, bool template_ok) {
37 const char *e, *i, *at;
38
39 /* Valid formats:
40 *
41 * string@instance.suffix
42 * string.suffix
43 */
44
45 assert(n);
46
47 if (strlen(n) >= UNIT_NAME_MAX)
48 return false;
49
50 e = strrchr(n, '.');
51 if (!e || e == n)
52 return false;
53
54 for (i = n, at = NULL; i < e; i++) {
55
56 if (*i == '@' && !at)
57 at = i;
58
59 if (!strchr("@" VALID_CHARS, *i))
60 return false;
61 }
62
63 if (at) {
64 if (at == n)
65 return false;
66
67 if (!template_ok && at+1 == e)
68 return false;
69 }
70
71 return true;
72 }
73
74 bool unit_instance_is_valid(const char *i) {
75 assert(i);
76
77 /* The max length depends on the length of the string, so we
78 * don't really check this here. */
79
80 if (i[0] == 0)
81 return false;
82
83 /* We allow additional @ in the instance string, we do not
84 * allow them in the prefix! */
85
86 for (; *i; i++)
87 if (!strchr("@" VALID_CHARS, *i))
88 return false;
89
90 return true;
91 }
92
93 bool unit_prefix_is_valid(const char *p) {
94
95 /* We don't allow additional @ in the instance string */
96
97 if (p[0] == 0)
98 return false;
99
100 for (; *p; p++)
101 if (!strchr(VALID_CHARS, *p))
102 return false;
103
104 return true;
105 }
106
107 int unit_name_to_instance(const char *n, char **instance) {
108 const char *p, *d;
109 char *i;
110
111 assert(n);
112 assert(instance);
113
114 /* Everything past the first @ and before the last . is the instance */
115 if (!(p = strchr(n, '@'))) {
116 *instance = NULL;
117 return 0;
118 }
119
120 assert_se(d = strrchr(n, '.'));
121 assert(p < d);
122
123 if (!(i = strndup(p+1, d-p-1)))
124 return -ENOMEM;
125
126 *instance = i;
127 return 0;
128 }
129
130 char *unit_name_to_prefix_and_instance(const char *n) {
131 const char *d;
132
133 assert(n);
134
135 assert_se(d = strrchr(n, '.'));
136
137 return strndup(n, d - n);
138 }
139
140 char *unit_name_to_prefix(const char *n) {
141 const char *p;
142
143 p = strchr(n, '@');
144 if (p)
145 return strndup(n, p - n);
146
147 return unit_name_to_prefix_and_instance(n);
148 }
149
150 char *unit_name_change_suffix(const char *n, const char *suffix) {
151 char *e, *r;
152 size_t a, b;
153
154 assert(n);
155 assert(unit_name_is_valid_no_type(n, true));
156 assert(suffix);
157
158 assert_se(e = strrchr(n, '.'));
159 a = e - n;
160 b = strlen(suffix);
161
162 r = new(char, a + b + 1);
163 if (!r)
164 return NULL;
165
166 memcpy(r, n, a);
167 memcpy(r+a, suffix, b+1);
168
169 return r;
170 }
171
172 char *unit_name_build(const char *prefix, const char *instance, const char *suffix) {
173 assert(prefix);
174 assert(unit_prefix_is_valid(prefix));
175 assert(!instance || unit_instance_is_valid(instance));
176 assert(suffix);
177
178 if (!instance)
179 return strappend(prefix, suffix);
180
181 return join(prefix, "@", instance, suffix, NULL);
182 }
183
184 static char *do_escape_char(char c, char *t) {
185 *(t++) = '\\';
186 *(t++) = 'x';
187 *(t++) = hexchar(c >> 4);
188 *(t++) = hexchar(c);
189 return t;
190 }
191
192 static char *do_escape(const char *f, char *t) {
193 assert(f);
194 assert(t);
195
196 /* do not create units with a leading '.', like for "/.dotdir" mount points */
197 if (*f == '.') {
198 t = do_escape_char(*f, t);
199 f++;
200 }
201
202 for (; *f; f++) {
203 if (*f == '/')
204 *(t++) = '-';
205 else if (*f == '-' || *f == '\\' || !strchr(VALID_CHARS, *f))
206 t = do_escape_char(*f, t);
207 else
208 *(t++) = *f;
209 }
210
211 return t;
212 }
213
214 char *unit_name_build_escape(const char *prefix, const char *instance, const char *suffix) {
215 char *r, *t;
216 size_t a, b, c;
217
218 assert(prefix);
219 assert(suffix);
220
221 /* Takes a arbitrary string for prefix and instance plus a
222 * suffix and makes a nice string suitable as unit name of it,
223 * escaping all weird chars on the way.
224 *
225 * / becomes -, and all chars not allowed in a unit name get
226 * escaped as \xFF, including \ and -, of course. This
227 * escaping is hence reversible.
228 *
229 * This is primarily useful to make nice unit names from
230 * strings, but is actually useful for any kind of string.
231 */
232
233 a = strlen(prefix);
234 c = strlen(suffix);
235
236 if (instance) {
237 b = strlen(instance);
238
239 r = new(char, a*4 + 1 + b*4 + c + 1);
240 if (!r)
241 return NULL;
242
243 t = do_escape(prefix, r);
244 *(t++) = '@';
245 t = do_escape(instance, t);
246 } else {
247
248 if (!(r = new(char, a*4 + c + 1)))
249 return NULL;
250
251 t = do_escape(prefix, r);
252 }
253
254 strcpy(t, suffix);
255 return r;
256 }
257
258 char *unit_name_escape(const char *f) {
259 char *r, *t;
260
261 r = new(char, strlen(f)*4+1);
262 if (!r)
263 return NULL;
264
265 t = do_escape(f, r);
266 *t = 0;
267
268 return r;
269 }
270
271 char *unit_name_unescape(const char *f) {
272 char *r, *t;
273
274 assert(f);
275
276 r = strdup(f);
277 if (!r)
278 return NULL;
279
280 for (t = r; *f; f++) {
281 if (*f == '-')
282 *(t++) = '/';
283 else if (*f == '\\') {
284 int a, b;
285
286 if (f[1] != 'x' ||
287 (a = unhexchar(f[2])) < 0 ||
288 (b = unhexchar(f[3])) < 0) {
289 /* Invalid escape code, let's take it literal then */
290 *(t++) = '\\';
291 } else {
292 *(t++) = (char) ((a << 4) | b);
293 f += 3;
294 }
295 } else
296 *(t++) = *f;
297 }
298
299 *t = 0;
300
301 return r;
302 }
303
304 bool unit_name_is_template(const char *n) {
305 const char *p;
306
307 assert(n);
308
309 if (!(p = strchr(n, '@')))
310 return false;
311
312 return p[1] == '.';
313 }
314
315 char *unit_name_replace_instance(const char *f, const char *i) {
316 const char *p, *e;
317 char *r, *k;
318 size_t a;
319
320 assert(f);
321
322 p = strchr(f, '@');
323 assert_se(e = strrchr(f, '.'));
324
325 a = p - f;
326
327 if (p) {
328 size_t b;
329
330 b = strlen(i);
331
332 if (!(r = new(char, a + 1 + b + strlen(e) + 1)))
333 return NULL;
334
335 k = mempcpy(r, f, a + 1);
336 k = mempcpy(k, i, b);
337 } else {
338
339 if (!(r = new(char, a + strlen(e) + 1)))
340 return NULL;
341
342 k = mempcpy(r, f, a);
343 }
344
345 strcpy(k, e);
346 return r;
347 }
348
349 char *unit_name_template(const char *f) {
350 const char *p, *e;
351 char *r;
352 size_t a;
353
354 p = strchr(f, '@');
355 if (!p)
356 return strdup(f);
357
358 assert_se(e = strrchr(f, '.'));
359 a = p - f + 1;
360
361 r = new(char, a + strlen(e) + 1);
362 if (!r)
363 return NULL;
364
365 strcpy(mempcpy(r, f, a), e);
366 return r;
367
368 }
369
370 char *unit_name_from_path(const char *path, const char *suffix) {
371 char *p, *r;
372
373 assert(path);
374 assert(suffix);
375
376 p = strdup(path);
377 if (!p)
378 return NULL;
379
380 path_kill_slashes(p);
381
382 path = p[0] == '/' ? p + 1 : p;
383
384 if (path[0] == 0) {
385 free(p);
386 return strappend("-", suffix);
387 }
388
389 r = unit_name_build_escape(path, NULL, suffix);
390 free(p);
391
392 return r;
393 }
394
395 char *unit_name_from_path_instance(const char *prefix, const char *path, const char *suffix) {
396 char *p, *r;
397
398 assert(path);
399 assert(suffix);
400
401 if (!(p = strdup(path)))
402 return NULL;
403
404 path_kill_slashes(p);
405
406 path = p[0] == '/' ? p + 1 : p;
407
408 if (path[0] == 0) {
409 free(p);
410 return unit_name_build_escape(prefix, "-", suffix);
411 }
412
413 r = unit_name_build_escape(prefix, path, suffix);
414 free(p);
415
416 return r;
417 }
418
419 char *unit_name_to_path(const char *name) {
420 char *w, *e;
421
422 assert(name);
423
424 w = unit_name_to_prefix(name);
425 if (!w)
426 return NULL;
427
428 e = unit_name_unescape(w);
429 free(w);
430
431 if (!e)
432 return NULL;
433
434 if (e[0] != '/') {
435 w = strappend("/", e);
436 free(e);
437
438 if (!w)
439 return NULL;
440
441 return w;
442 }
443
444 return e;
445 }
446
447 char *unit_name_path_unescape(const char *f) {
448 char *e;
449
450 assert(f);
451
452 e = unit_name_unescape(f);
453 if (!e)
454 return NULL;
455
456 if (e[0] != '/') {
457 char *w;
458
459 w = strappend("/", e);
460 free(e);
461
462 if (!w)
463 return NULL;
464
465 return w;
466 }
467
468 return e;
469 }
470
471 char *unit_dbus_path_from_name(const char *name) {
472 char *e, *p;
473
474 e = bus_path_escape(name);
475 if (!e)
476 return NULL;
477
478 p = strappend("/org/freedesktop/systemd1/unit/", e);
479 free(e);
480
481 return p;
482 }
483
484 char *unit_name_mangle(const char *name) {
485 char *r, *t;
486 const char *f;
487
488 assert(name);
489
490 /* Try to turn a string that might not be a unit name into a
491 * sensible unit name. */
492
493 if (path_startswith(name, "/dev/") ||
494 path_startswith(name, "/sys/"))
495 return unit_name_from_path(name, ".device");
496
497 if (path_is_absolute(name))
498 return unit_name_from_path(name, ".mount");
499
500 /* We'll only escape the obvious characters here, to play
501 * safe. */
502
503 r = new(char, strlen(name) * 4 + 1);
504 if (!r)
505 return NULL;
506
507 for (f = name, t = r; *f; f++) {
508
509 if (*f == '/')
510 *(t++) = '-';
511 else if (!strchr("@" VALID_CHARS, *f))
512 t = do_escape_char(*f, t);
513 else
514 *(t++) = *f;
515 }
516
517 *t = 0;
518
519 return r;
520 }