chiark / gitweb /
unit-name: fix detection of unit templates/instances
[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 "bus-label.h"
28 #include "util.h"
29 #include "unit-name.h"
30 #include "def.h"
31
32 #define VALID_CHARS                             \
33         DIGITS LETTERS                          \
34         ":-_.\\"
35
36 static const char* const unit_type_table[_UNIT_TYPE_MAX] = {
37         [UNIT_SERVICE] = "service",
38         [UNIT_SOCKET] = "socket",
39         [UNIT_BUSNAME] = "busname",
40         [UNIT_TARGET] = "target",
41         [UNIT_SNAPSHOT] = "snapshot",
42         [UNIT_DEVICE] = "device",
43         [UNIT_MOUNT] = "mount",
44         [UNIT_AUTOMOUNT] = "automount",
45         [UNIT_SWAP] = "swap",
46         [UNIT_TIMER] = "timer",
47         [UNIT_PATH] = "path",
48         [UNIT_SLICE] = "slice",
49         [UNIT_SCOPE] = "scope"
50 };
51
52 DEFINE_STRING_TABLE_LOOKUP(unit_type, UnitType);
53
54 static const char* const unit_load_state_table[_UNIT_LOAD_STATE_MAX] = {
55         [UNIT_STUB] = "stub",
56         [UNIT_LOADED] = "loaded",
57         [UNIT_NOT_FOUND] = "not-found",
58         [UNIT_ERROR] = "error",
59         [UNIT_MERGED] = "merged",
60         [UNIT_MASKED] = "masked"
61 };
62
63 DEFINE_STRING_TABLE_LOOKUP(unit_load_state, UnitLoadState);
64
65 bool unit_name_is_valid(const char *n, enum template_valid template_ok) {
66         const char *e, *i, *at;
67
68         /* Valid formats:
69          *
70          *         string@instance.suffix
71          *         string.suffix
72          */
73
74         assert(n);
75         assert(IN_SET(template_ok, TEMPLATE_VALID, TEMPLATE_INVALID));
76
77         if (strlen(n) >= UNIT_NAME_MAX)
78                 return false;
79
80         e = strrchr(n, '.');
81         if (!e || e == n)
82                 return false;
83
84         if (unit_type_from_string(e + 1) < 0)
85                 return false;
86
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
100                 if (!template_ok == TEMPLATE_VALID && at+1 == e)
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 */
148         p = strchr(n, '@');
149         if (!p) {
150                 *instance = NULL;
151                 return 0;
152         }
153
154         assert_se(d = strrchr(n, '.'));
155         assert(p < d);
156
157         i = strndup(p+1, d-p-1);
158         if (!i)
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
178         p = strchr(n, '@');
179         if (p)
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);
190         assert(unit_name_is_valid(n, TEMPLATE_VALID));
191         assert(suffix);
192         assert(suffix[0] == '.');
193
194         assert_se(e = strrchr(n, '.'));
195         a = e - n;
196         b = strlen(suffix);
197
198         r = new(char, a + b + 1);
199         if (!r)
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) {
209         assert(prefix);
210         assert(unit_prefix_is_valid(prefix));
211         assert(!instance || unit_instance_is_valid(instance));
212         assert(suffix);
213
214         if (!instance)
215                 return strappend(prefix, suffix);
216
217         return strjoin(prefix, "@", instance, suffix, NULL);
218 }
219
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) {
229         assert(f);
230         assert(t);
231
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
238         for (; *f; f++) {
239                 if (*f == '/')
240                         *(t++) = '-';
241                 else if (*f == '-' || *f == '\\' || !strchr(VALID_CHARS, *f))
242                         t = do_escape_char(*f, t);
243                 else
244                         *(t++) = *f;
245         }
246
247         return t;
248 }
249
250 char *unit_name_escape(const char *f) {
251         char *r, *t;
252
253         r = new(char, strlen(f)*4+1);
254         if (!r)
255                 return NULL;
256
257         t = do_escape(f, r);
258         *t = 0;
259
260         return r;
261 }
262
263 char *unit_name_unescape(const char *f) {
264         char *r, *t;
265
266         assert(f);
267
268         r = strdup(f);
269         if (!r)
270                 return NULL;
271
272         for (t = r; *f; f++) {
273                 if (*f == '-')
274                         *(t++) = '/';
275                 else if (*f == '\\') {
276                         int a, b;
277
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 */
282                                 *(t++) = '\\';
283                         } else {
284                                 *(t++) = (char) ((a << 4) | b);
285                                 f += 3;
286                         }
287                 } else
288                         *(t++) = *f;
289         }
290
291         *t = 0;
292
293         return r;
294 }
295
296 char *unit_name_path_escape(const char *f) {
297         _cleanup_free_ char *p;
298
299         assert(f);
300
301         p = strdup(f);
302         if (!p)
303                 return NULL;
304
305         path_kill_slashes(p);
306
307         if (streq(p, "/") || streq(p, ""))
308                 return strdup("-");
309
310         return unit_name_escape(p[0] == '/' ? p + 1 : p);
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
334 bool unit_name_is_template(const char *n) {
335         const char *p, *e;
336
337         assert(n);
338
339         p = strchr(n, '@');
340         if (!p)
341                 return false;
342
343         e = strrchr(p+1, '.');
344         if (!e)
345                 return false;
346
347         return e == p + 1;
348 }
349
350 bool unit_name_is_instance(const char *n) {
351         const char *p, *e;
352
353         assert(n);
354
355         p = strchr(n, '@');
356         if (!p)
357                 return false;
358
359         e = strrchr(p+1, '.');
360         if (!e)
361                 return false;
362
363         return e > p + 1;
364 }
365
366 char *unit_name_replace_instance(const char *f, const char *i) {
367         const char *p, *e;
368         char *r, *k;
369         size_t a, b;
370
371         assert(f);
372
373         p = strchr(f, '@');
374         if (!p)
375                 return strdup(f);
376
377         e = strrchr(f, '.');
378         if (!e)
379                 assert_se(e = strchr(f, 0));
380
381         a = p - f;
382         b = strlen(i);
383
384         r = new(char, a + 1 + b + strlen(e) + 1);
385         if (!r)
386                 return NULL;
387
388         k = mempcpy(r, f, a + 1);
389         k = mempcpy(k, i, b);
390         strcpy(k, e);
391
392         return r;
393 }
394
395 char *unit_name_template(const char *f) {
396         const char *p, *e;
397         char *r;
398         size_t a;
399
400         p = strchr(f, '@');
401         if (!p)
402                 return strdup(f);
403
404         assert_se(e = strrchr(f, '.'));
405         a = p - f + 1;
406
407         r = new(char, a + strlen(e) + 1);
408         if (!r)
409                 return NULL;
410
411         strcpy(mempcpy(r, f, a), e);
412         return r;
413 }
414
415 char *unit_name_from_path(const char *path, const char *suffix) {
416         _cleanup_free_ char *p = NULL;
417
418         assert(path);
419         assert(suffix);
420
421         p = unit_name_path_escape(path);
422         if (!p)
423                 return NULL;
424
425         return strappend(p, suffix);
426 }
427
428 char *unit_name_from_path_instance(const char *prefix, const char *path, const char *suffix) {
429         _cleanup_free_ char *p = NULL;
430
431         assert(prefix);
432         assert(path);
433         assert(suffix);
434
435         p = unit_name_path_escape(path);
436         if (!p)
437                 return NULL;
438
439         return strjoin(prefix, "@", p, suffix, NULL);
440 }
441
442 char *unit_name_to_path(const char *name) {
443         _cleanup_free_ char *w = NULL;
444
445         assert(name);
446
447         w = unit_name_to_prefix(name);
448         if (!w)
449                 return NULL;
450
451         return unit_name_path_unescape(w);
452 }
453
454 char *unit_dbus_path_from_name(const char *name) {
455         _cleanup_free_ char *e = NULL;
456
457         assert(name);
458
459         e = bus_label_escape(name);
460         if (!e)
461                 return NULL;
462
463         return strappend("/org/freedesktop/systemd1/unit/", e);
464 }
465
466 int unit_name_from_dbus_path(const char *path, char **name) {
467         const char *e;
468         char *n;
469
470         e = startswith(path, "/org/freedesktop/systemd1/unit/");
471         if (!e)
472                 return -EINVAL;
473
474         n = bus_label_unescape(e);
475         if (!n)
476                 return -ENOMEM;
477
478         *name = n;
479         return 0;
480 }
481
482
483 /**
484  *  Try to turn a string that might not be a unit name into a
485  *  sensible unit name.
486  */
487 char *unit_name_mangle(const char *name, enum unit_name_mangle allow_globs) {
488         char *r, *t;
489         const char *f;
490         const char* valid_chars = allow_globs == MANGLE_GLOB ? "@" VALID_CHARS "[]!-*?" : "@" VALID_CHARS;
491
492         assert(name);
493         assert(IN_SET(allow_globs, MANGLE_GLOB, MANGLE_NOGLOB));
494
495         if (is_device_path(name))
496                 return unit_name_from_path(name, ".device");
497
498         if (path_is_absolute(name))
499                 return unit_name_from_path(name, ".mount");
500
501         /* We'll only escape the obvious characters here, to play
502          * safe. */
503
504         r = new(char, strlen(name) * 4 + strlen(".service") + 1);
505         if (!r)
506                 return NULL;
507
508         for (f = name, t = r; *f; f++) {
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         if (unit_name_to_type(name) < 0)
518                 strcpy(t, ".service");
519         else
520                 *t = 0;
521
522         return r;
523 }
524
525
526 /**
527  *  Similar to unit_name_mangle(), but is called when we know
528  *  that this is about a specific unit type.
529  */
530 char *unit_name_mangle_with_suffix(const char *name, enum unit_name_mangle allow_globs, const char *suffix) {
531         char *r, *t;
532         const char *f;
533
534         assert(name);
535         assert(suffix);
536         assert(suffix[0] == '.');
537
538         r = new(char, strlen(name) * 4 + strlen(suffix) + 1);
539         if (!r)
540                 return NULL;
541
542         for (f = name, t = r; *f; f++) {
543                 if (*f == '/')
544                         *(t++) = '-';
545                 else if (!strchr(VALID_CHARS, *f))
546                         t = do_escape_char(*f, t);
547                 else
548                         *(t++) = *f;
549         }
550
551         if (!endswith(name, suffix))
552                 strcpy(t, suffix);
553         else
554                 *t = 0;
555
556         return r;
557 }
558
559 UnitType unit_name_to_type(const char *n) {
560         const char *e;
561
562         assert(n);
563
564         e = strrchr(n, '.');
565         if (!e)
566                 return _UNIT_TYPE_INVALID;
567
568         return unit_type_from_string(e + 1);
569 }
570
571 int build_subslice(const char *slice, const char*name, char **subslice) {
572         char *ret;
573
574         assert(slice);
575         assert(name);
576         assert(subslice);
577
578         if (streq(slice, "-.slice"))
579                 ret = strappend(name, ".slice");
580         else {
581                 char *e;
582
583                 e = endswith(slice, ".slice");
584                 if (!e)
585                         return -EINVAL;
586
587                 ret = new(char, (e - slice) + 1 + strlen(name) + 6 + 1);
588                 if (!ret)
589                         return -ENOMEM;
590
591                 stpcpy(stpcpy(stpcpy(mempcpy(ret, slice, e - slice), "-"), name), ".slice");
592         }
593
594         *subslice = ret;
595         return 0;
596 }