chiark / gitweb /
unit-name: when escaping a path consider the empty path identical to the root dir
[elogind.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 #include "def.h"
30
31 #define VALID_CHARS                             \
32         DIGITS LETTERS                          \
33         ":-_.\\"
34
35 static const char* const unit_type_table[_UNIT_TYPE_MAX] = {
36         [UNIT_SERVICE] = "service",
37         [UNIT_SOCKET] = "socket",
38         [UNIT_TARGET] = "target",
39         [UNIT_DEVICE] = "device",
40         [UNIT_MOUNT] = "mount",
41         [UNIT_AUTOMOUNT] = "automount",
42         [UNIT_SNAPSHOT] = "snapshot",
43         [UNIT_TIMER] = "timer",
44         [UNIT_SWAP] = "swap",
45         [UNIT_PATH] = "path",
46         [UNIT_SLICE] = "slice",
47         [UNIT_SCOPE] = "scope"
48 };
49
50 DEFINE_STRING_TABLE_LOOKUP(unit_type, UnitType);
51
52 static const char* const unit_load_state_table[_UNIT_LOAD_STATE_MAX] = {
53         [UNIT_STUB] = "stub",
54         [UNIT_LOADED] = "loaded",
55         [UNIT_NOT_FOUND] = "not-found",
56         [UNIT_ERROR] = "error",
57         [UNIT_MERGED] = "merged",
58         [UNIT_MASKED] = "masked"
59 };
60
61 DEFINE_STRING_TABLE_LOOKUP(unit_load_state, UnitLoadState);
62
63 bool unit_name_is_valid(const char *n, bool template_ok) {
64         const char *e, *i, *at;
65
66         /* Valid formats:
67          *
68          *         string@instance.suffix
69          *         string.suffix
70          */
71
72         assert(n);
73
74         if (strlen(n) >= UNIT_NAME_MAX)
75                 return false;
76
77         e = strrchr(n, '.');
78         if (!e || e == n)
79                 return false;
80
81         if (unit_type_from_string(e + 1) < 0)
82                 return false;
83
84         for (i = n, at = NULL; i < e; i++) {
85
86                 if (*i == '@' && !at)
87                         at = i;
88
89                 if (!strchr("@" VALID_CHARS, *i))
90                         return false;
91         }
92
93         if (at) {
94                 if (at == n)
95                         return false;
96
97                 if (!template_ok && at+1 == e)
98                         return false;
99         }
100
101         return true;
102 }
103
104 bool unit_instance_is_valid(const char *i) {
105         assert(i);
106
107         /* The max length depends on the length of the string, so we
108          * don't really check this here. */
109
110         if (i[0] == 0)
111                 return false;
112
113         /* We allow additional @ in the instance string, we do not
114          * allow them in the prefix! */
115
116         for (; *i; i++)
117                 if (!strchr("@" VALID_CHARS, *i))
118                         return false;
119
120         return true;
121 }
122
123 bool unit_prefix_is_valid(const char *p) {
124
125         /* We don't allow additional @ in the instance string */
126
127         if (p[0] == 0)
128                 return false;
129
130         for (; *p; p++)
131                 if (!strchr(VALID_CHARS, *p))
132                         return false;
133
134         return true;
135 }
136
137 int unit_name_to_instance(const char *n, char **instance) {
138         const char *p, *d;
139         char *i;
140
141         assert(n);
142         assert(instance);
143
144         /* Everything past the first @ and before the last . is the instance */
145         p = strchr(n, '@');
146         if (!p) {
147                 *instance = NULL;
148                 return 0;
149         }
150
151         assert_se(d = strrchr(n, '.'));
152         assert(p < d);
153
154         i = strndup(p+1, d-p-1);
155         if (!i)
156                 return -ENOMEM;
157
158         *instance = i;
159         return 0;
160 }
161
162 char *unit_name_to_prefix_and_instance(const char *n) {
163         const char *d;
164
165         assert(n);
166
167         assert_se(d = strrchr(n, '.'));
168
169         return strndup(n, d - n);
170 }
171
172 char *unit_name_to_prefix(const char *n) {
173         const char *p;
174
175         p = strchr(n, '@');
176         if (p)
177                 return strndup(n, p - n);
178
179         return unit_name_to_prefix_and_instance(n);
180 }
181
182 char *unit_name_change_suffix(const char *n, const char *suffix) {
183         char *e, *r;
184         size_t a, b;
185
186         assert(n);
187         assert(unit_name_is_valid(n, true));
188         assert(suffix);
189         assert(suffix[0] == '.');
190
191         assert_se(e = strrchr(n, '.'));
192         a = e - n;
193         b = strlen(suffix);
194
195         r = new(char, a + b + 1);
196         if (!r)
197                 return NULL;
198
199         memcpy(r, n, a);
200         memcpy(r+a, suffix, b+1);
201
202         return r;
203 }
204
205 char *unit_name_build(const char *prefix, const char *instance, const char *suffix) {
206         assert(prefix);
207         assert(unit_prefix_is_valid(prefix));
208         assert(!instance || unit_instance_is_valid(instance));
209         assert(suffix);
210
211         if (!instance)
212                 return strappend(prefix, suffix);
213
214         return strjoin(prefix, "@", instance, suffix, NULL);
215 }
216
217 static char *do_escape_char(char c, char *t) {
218         *(t++) = '\\';
219         *(t++) = 'x';
220         *(t++) = hexchar(c >> 4);
221         *(t++) = hexchar(c);
222         return t;
223 }
224
225 static char *do_escape(const char *f, char *t) {
226         assert(f);
227         assert(t);
228
229         /* do not create units with a leading '.', like for "/.dotdir" mount points */
230         if (*f == '.') {
231                 t = do_escape_char(*f, t);
232                 f++;
233         }
234
235         for (; *f; f++) {
236                 if (*f == '/')
237                         *(t++) = '-';
238                 else if (*f == '-' || *f == '\\' || !strchr(VALID_CHARS, *f))
239                         t = do_escape_char(*f, t);
240                 else
241                         *(t++) = *f;
242         }
243
244         return t;
245 }
246
247 char *unit_name_escape(const char *f) {
248         char *r, *t;
249
250         r = new(char, strlen(f)*4+1);
251         if (!r)
252                 return NULL;
253
254         t = do_escape(f, r);
255         *t = 0;
256
257         return r;
258 }
259
260 char *unit_name_unescape(const char *f) {
261         char *r, *t;
262
263         assert(f);
264
265         r = strdup(f);
266         if (!r)
267                 return NULL;
268
269         for (t = r; *f; f++) {
270                 if (*f == '-')
271                         *(t++) = '/';
272                 else if (*f == '\\') {
273                         int a, b;
274
275                         if (f[1] != 'x' ||
276                             (a = unhexchar(f[2])) < 0 ||
277                             (b = unhexchar(f[3])) < 0) {
278                                 /* Invalid escape code, let's take it literal then */
279                                 *(t++) = '\\';
280                         } else {
281                                 *(t++) = (char) ((a << 4) | b);
282                                 f += 3;
283                         }
284                 } else
285                         *(t++) = *f;
286         }
287
288         *t = 0;
289
290         return r;
291 }
292
293 char *unit_name_path_escape(const char *f) {
294         char *p, *e;
295
296         assert(f);
297
298         p = strdup(f);
299         if (!p)
300                 return NULL;
301
302         path_kill_slashes(p);
303
304         if (streq(p, "/") || streq(p, "")) {
305                 free(p);
306                 return strdup("-");
307         }
308
309         e = unit_name_escape(p[0] == '/' ? p + 1 : p);
310         free(p);
311
312         return e;
313 }
314
315 char *unit_name_path_unescape(const char *f) {
316         char *e;
317
318         assert(f);
319
320         e = unit_name_unescape(f);
321         if (!e)
322                 return NULL;
323
324         if (e[0] != '/') {
325                 char *w;
326
327                 w = strappend("/", e);
328                 free(e);
329
330                 return w;
331         }
332
333         return e;
334 }
335
336 bool unit_name_is_template(const char *n) {
337         const char *p;
338
339         assert(n);
340
341         p = strchr(n, '@');
342         if (!p)
343                 return false;
344
345         return p[1] == '.';
346 }
347
348 bool unit_name_is_instance(const char *n) {
349         const char *p;
350
351         assert(n);
352
353         p = strchr(n, '@');
354         if (!p)
355                 return false;
356
357         return p[1] != '.';
358 }
359
360 char *unit_name_replace_instance(const char *f, const char *i) {
361         const char *p, *e;
362         char *r, *k;
363         size_t a, b;
364
365         assert(f);
366
367         p = strchr(f, '@');
368         if (!p)
369                 return strdup(f);
370
371         e = strrchr(f, '.');
372         if (!e)
373                 assert_se(e = strchr(f, 0));
374
375         a = p - f;
376         b = strlen(i);
377
378         r = new(char, a + 1 + b + strlen(e) + 1);
379         if (!r)
380                 return NULL;
381
382         k = mempcpy(r, f, a + 1);
383         k = mempcpy(k, i, b);
384         strcpy(k, e);
385
386         return r;
387 }
388
389 char *unit_name_template(const char *f) {
390         const char *p, *e;
391         char *r;
392         size_t a;
393
394         p = strchr(f, '@');
395         if (!p)
396                 return strdup(f);
397
398         assert_se(e = strrchr(f, '.'));
399         a = p - f + 1;
400
401         r = new(char, a + strlen(e) + 1);
402         if (!r)
403                 return NULL;
404
405         strcpy(mempcpy(r, f, a), e);
406         return r;
407 }
408
409 char *unit_name_from_path(const char *path, const char *suffix) {
410         char *p, *r;
411
412         assert(path);
413         assert(suffix);
414
415         p = unit_name_path_escape(path);
416         if (!p)
417                 return NULL;
418
419         r = strappend(p, suffix);
420         free(p);
421
422         return r;
423 }
424
425 char *unit_name_from_path_instance(const char *prefix, const char *path, const char *suffix) {
426         char *p, *r;
427
428         assert(prefix);
429         assert(path);
430         assert(suffix);
431
432         p = unit_name_path_escape(path);
433         if (!p)
434                 return NULL;
435
436         r = strjoin(prefix, "@", p, suffix, NULL);
437         free(p);
438
439         return r;
440 }
441
442 char *unit_name_to_path(const char *name) {
443         char *w, *e;
444
445         assert(name);
446
447         w = unit_name_to_prefix(name);
448         if (!w)
449                 return NULL;
450
451         e = unit_name_path_unescape(w);
452         free(w);
453
454         return e;
455 }
456
457 char *unit_dbus_path_from_name(const char *name) {
458         _cleanup_free_ char *e = NULL;
459
460         assert(name);
461
462         e = bus_path_escape(name);
463         if (!e)
464                 return NULL;
465
466         return strappend("/org/freedesktop/systemd1/unit/", e);
467 }
468
469 int unit_name_from_dbus_path(const char *path, char **name) {
470         const char *e;
471         char *n;
472
473         e = startswith(path, "/org/freedesktop/systemd1/unit/");
474         if (!e)
475                 return -EINVAL;
476
477         n = bus_path_unescape(e);
478         if (!n)
479                 return -ENOMEM;
480
481         *name = n;
482         return 0;
483 }
484
485 char *unit_name_mangle(const char *name) {
486         char *r, *t;
487         const char *f;
488
489         assert(name);
490
491         /* Try to turn a string that might not be a unit name into a
492          * sensible unit name. */
493
494         if (is_device_path(name))
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 + sizeof(".service")-1);
504         if (!r)
505                 return NULL;
506
507         for (f = name, t = r; *f; f++) {
508                 if (*f == '/')
509                         *(t++) = '-';
510                 else if (!strchr("@" VALID_CHARS, *f))
511                         t = do_escape_char(*f, t);
512                 else
513                         *(t++) = *f;
514         }
515
516         if (unit_name_to_type(name) < 0)
517                 strcpy(t, ".service");
518         else
519                 *t = 0;
520
521         return r;
522 }
523
524 char *unit_name_mangle_with_suffix(const char *name, const char *suffix) {
525         char *r, *t;
526         const char *f;
527
528         assert(name);
529         assert(suffix);
530         assert(suffix[0] == '.');
531
532         /* Similar to unit_name_mangle(), but is called when we know
533          * that this is about snapshot units. */
534
535         r = new(char, strlen(name) * 4 + strlen(suffix) + 1);
536         if (!r)
537                 return NULL;
538
539         for (f = name, t = r; *f; f++) {
540                 if (*f == '/')
541                         *(t++) = '-';
542                 else if (!strchr(VALID_CHARS, *f))
543                         t = do_escape_char(*f, t);
544                 else
545                         *(t++) = *f;
546         }
547
548         if (!endswith(name, suffix))
549                 strcpy(t, suffix);
550         else
551                 *t = 0;
552
553         return r;
554 }
555
556 UnitType unit_name_to_type(const char *n) {
557         const char *e;
558
559         assert(n);
560
561         e = strrchr(n, '.');
562         if (!e)
563                 return _UNIT_TYPE_INVALID;
564
565         return unit_type_from_string(e + 1);
566 }
567
568 int build_subslice(const char *slice, const char*name, char **subslice) {
569         char *ret;
570
571         assert(slice);
572         assert(name);
573         assert(subslice);
574
575         if (streq(slice, "-.slice"))
576                 ret = strappend(name, ".slice");
577         else {
578                 char *e;
579
580                 e = endswith(slice, ".slice");
581                 if (!e)
582                         return -EINVAL;
583
584                 ret = new(char, (e - slice) + 1 + strlen(name) + 6 + 1);
585                 if (!ret)
586                         return -ENOMEM;
587
588                 stpcpy(stpcpy(stpcpy(mempcpy(ret, slice, e - slice), "-"), name), ".slice");
589         }
590
591         *subslice = ret;
592         return 0;
593 }