chiark / gitweb /
core: rework unit name validation and manipulation logic
[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
25 #include "path-util.h"
26 #include "bus-label.h"
27 #include "util.h"
28 #include "unit-name.h"
29 #include "def.h"
30 #include "strv.h"
31
32 #define VALID_CHARS                             \
33         DIGITS LETTERS                          \
34         ":-_.\\"
35
36 bool unit_name_is_valid(const char *n, UnitNameFlags flags) {
37         const char *e, *i, *at;
38
39         assert((flags & ~(UNIT_NAME_PLAIN|UNIT_NAME_INSTANCE|UNIT_NAME_TEMPLATE)) == 0);
40
41         if (_unlikely_(flags == 0))
42                 return false;
43
44         if (isempty(n))
45                 return false;
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         if (unit_type_from_string(e + 1) < 0)
55                 return false;
56
57         for (i = n, at = NULL; i < e; i++) {
58
59                 if (*i == '@' && !at)
60                         at = i;
61
62                 if (!strchr("@" VALID_CHARS, *i))
63                         return false;
64         }
65
66         if (at == n)
67                 return false;
68
69         if (flags & UNIT_NAME_PLAIN)
70                 if (!at)
71                         return true;
72
73         if (flags & UNIT_NAME_INSTANCE)
74                 if (at && e > at + 1)
75                         return true;
76
77         if (flags & UNIT_NAME_TEMPLATE)
78                 if (at && e == at + 1)
79                         return true;
80
81         return false;
82 }
83
84 bool unit_prefix_is_valid(const char *p) {
85
86         /* We don't allow additional @ in the prefix string */
87
88         if (isempty(p))
89                 return false;
90
91         return in_charset(p, VALID_CHARS);
92 }
93
94 bool unit_instance_is_valid(const char *i) {
95
96         /* The max length depends on the length of the string, so we
97          * don't really check this here. */
98
99         if (isempty(i))
100                 return false;
101
102         /* We allow additional @ in the instance string, we do not
103          * allow them in the prefix! */
104
105         return in_charset(i, "@" VALID_CHARS);
106 }
107
108 bool unit_suffix_is_valid(const char *s) {
109         if (isempty(s))
110                 return false;
111
112         if (s[0] != '.')
113                 return false;
114
115         if (unit_type_from_string(s + 1) < 0)
116                 return false;
117
118         return true;
119 }
120
121 int unit_name_to_prefix(const char *n, char **ret) {
122         const char *p;
123         char *s;
124
125         assert(n);
126         assert(ret);
127
128         if (!unit_name_is_valid(n, UNIT_NAME_ANY))
129                 return -EINVAL;
130
131         p = strchr(n, '@');
132         if (!p)
133                 p = strrchr(n, '.');
134
135         assert_se(p);
136
137         s = strndup(n, p - n);
138         if (!s)
139                 return -ENOMEM;
140
141         *ret = s;
142         return 0;
143 }
144
145 int unit_name_to_instance(const char *n, char **instance) {
146         const char *p, *d;
147         char *i;
148
149         assert(n);
150         assert(instance);
151
152         if (!unit_name_is_valid(n, UNIT_NAME_ANY))
153                 return -EINVAL;
154
155         /* Everything past the first @ and before the last . is the instance */
156         p = strchr(n, '@');
157         if (!p) {
158                 *instance = NULL;
159                 return 0;
160         }
161
162         p++;
163
164         d = strrchr(p, '.');
165         if (!d)
166                 return -EINVAL;
167
168         i = strndup(p, d-p);
169         if (!i)
170                 return -ENOMEM;
171
172         *instance = i;
173         return 1;
174 }
175
176 int unit_name_to_prefix_and_instance(const char *n, char **ret) {
177         const char *d;
178         char *s;
179
180         assert(n);
181         assert(ret);
182
183         if (!unit_name_is_valid(n, UNIT_NAME_ANY))
184                 return -EINVAL;
185
186         d = strrchr(n, '.');
187         if (!d)
188                 return -EINVAL;
189
190         s = strndup(n, d - n);
191         if (!s)
192                 return -ENOMEM;
193
194         *ret = s;
195         return 0;
196 }
197
198 UnitType unit_name_to_type(const char *n) {
199         const char *e;
200
201         assert(n);
202
203         if (!unit_name_is_valid(n, UNIT_NAME_ANY))
204                 return _UNIT_TYPE_INVALID;
205
206         assert_se(e = strrchr(n, '.'));
207
208         return unit_type_from_string(e + 1);
209 }
210
211 int unit_name_change_suffix(const char *n, const char *suffix, char **ret) {
212         char *e, *s;
213         size_t a, b;
214
215         assert(n);
216         assert(suffix);
217         assert(ret);
218
219         if (!unit_name_is_valid(n, UNIT_NAME_ANY))
220                 return -EINVAL;
221
222         if (!unit_suffix_is_valid(suffix))
223                 return -EINVAL;
224
225         assert_se(e = strrchr(n, '.'));
226
227         a = e - n;
228         b = strlen(suffix);
229
230         s = new(char, a + b + 1);
231         if (!s)
232                 return -ENOMEM;
233
234         strcpy(mempcpy(s, n, a), suffix);
235         *ret = s;
236
237         return 0;
238 }
239
240 int unit_name_build(const char *prefix, const char *instance, const char *suffix, char **ret) {
241         char *s;
242
243         assert(prefix);
244         assert(suffix);
245         assert(ret);
246
247         if (!unit_prefix_is_valid(prefix))
248                 return -EINVAL;
249
250         if (instance && !unit_instance_is_valid(instance))
251                 return -EINVAL;
252
253         if (!unit_suffix_is_valid(suffix))
254                 return -EINVAL;
255
256         if (!instance)
257                 s = strappend(prefix, suffix);
258         else
259                 s = strjoin(prefix, "@", instance, suffix, NULL);
260         if (!s)
261                 return -ENOMEM;
262
263         *ret = s;
264         return 0;
265 }
266
267 static char *do_escape_char(char c, char *t) {
268         assert(t);
269
270         *(t++) = '\\';
271         *(t++) = 'x';
272         *(t++) = hexchar(c >> 4);
273         *(t++) = hexchar(c);
274
275         return t;
276 }
277
278 static char *do_escape(const char *f, char *t) {
279         assert(f);
280         assert(t);
281
282         /* do not create units with a leading '.', like for "/.dotdir" mount points */
283         if (*f == '.') {
284                 t = do_escape_char(*f, t);
285                 f++;
286         }
287
288         for (; *f; f++) {
289                 if (*f == '/')
290                         *(t++) = '-';
291                 else if (*f == '-' || *f == '\\' || !strchr(VALID_CHARS, *f))
292                         t = do_escape_char(*f, t);
293                 else
294                         *(t++) = *f;
295         }
296
297         return t;
298 }
299
300 char *unit_name_escape(const char *f) {
301         char *r, *t;
302
303         assert(f);
304
305         r = new(char, strlen(f)*4+1);
306         if (!r)
307                 return NULL;
308
309         t = do_escape(f, r);
310         *t = 0;
311
312         return r;
313 }
314
315 int unit_name_unescape(const char *f, char **ret) {
316         _cleanup_free_ char *r = NULL;
317         char *t;
318
319         assert(f);
320
321         r = strdup(f);
322         if (!r)
323                 return -ENOMEM;
324
325         for (t = r; *f; f++) {
326                 if (*f == '-')
327                         *(t++) = '/';
328                 else if (*f == '\\') {
329                         int a, b;
330
331                         if (f[1] != 'x')
332                                 return -EINVAL;
333
334                         a = unhexchar(f[2]);
335                         if (a < 0)
336                                 return -EINVAL;
337
338                         b = unhexchar(f[3]);
339                         if (b < 0)
340                                 return -EINVAL;
341
342                         *(t++) = (char) ((a << 4) | b);
343                         f += 3;
344                 } else
345                         *(t++) = *f;
346         }
347
348         *t = 0;
349
350         *ret = r;
351         r = NULL;
352
353         return 0;
354 }
355
356 int unit_name_path_escape(const char *f, char **ret) {
357         char *p, *s;
358
359         assert(f);
360         assert(ret);
361
362         p = strdupa(f);
363         if (!p)
364                 return -ENOMEM;
365
366         path_kill_slashes(p);
367
368         if (STR_IN_SET(p, "/", ""))
369                 s = strdup("-");
370         else {
371                 char *e;
372
373                 if (!path_is_safe(p))
374                         return -EINVAL;
375
376                 /* Truncate trailing slashes */
377                 e = endswith(p, "/");
378                 if (e)
379                         *e = 0;
380
381                 /* Truncate leading slashes */
382                 if (p[0] == '/')
383                         p++;
384
385                 s = unit_name_escape(p);
386         }
387         if (!s)
388                 return -ENOMEM;
389
390         *ret = s;
391         return 0;
392 }
393
394 int unit_name_path_unescape(const char *f, char **ret) {
395         char *s, *w;
396         int r;
397
398         assert(f);
399
400         if (streq(f, "-")) {
401                 s = strdup("/");
402                 if (!s)
403                         return -ENOMEM;
404
405                 *ret = s;
406                 return 0;
407         }
408
409         r = unit_name_unescape(f, &s);
410         if (r < 0)
411                 return r;
412
413         /* Don't accept trailing or leading slashes */
414         if (startswith(s, "/") || endswith(s, "/")) {
415                 free(s);
416                 return -EINVAL;
417         }
418
419         /* Prefix a slash again */
420         w = strappend("/", s);
421         free(s);
422         if (!w)
423                 return -ENOMEM;
424
425         if (!path_is_safe(w)) {
426                 free(w);
427                 return -EINVAL;
428         }
429
430         *ret = w;
431         return 0;
432 }
433
434 int unit_name_replace_instance(const char *f, const char *i, char **ret) {
435         const char *p, *e;
436         char *s;
437         size_t a, b;
438
439         assert(f);
440         assert(i);
441         assert(ret);
442
443         if (!unit_name_is_valid(f, UNIT_NAME_INSTANCE|UNIT_NAME_TEMPLATE))
444                 return -EINVAL;
445         if (!unit_instance_is_valid(i))
446                 return -EINVAL;
447
448         assert_se(p = strchr(f, '@'));
449         assert_se(e = strrchr(f, '.'));
450
451         a = p - f;
452         b = strlen(i);
453
454         s = new(char, a + 1 + b + strlen(e) + 1);
455         if (!s)
456                 return -ENOMEM;
457
458         strcpy(mempcpy(mempcpy(s, f, a + 1), i, b), e);
459
460         *ret = s;
461         return 0;
462 }
463
464 int unit_name_template(const char *f, char **ret) {
465         const char *p, *e;
466         char *s;
467         size_t a;
468
469         assert(f);
470         assert(ret);
471
472         if (!unit_name_is_valid(f, UNIT_NAME_INSTANCE|UNIT_NAME_TEMPLATE))
473                 return -EINVAL;
474
475         assert_se(p = strchr(f, '@'));
476         assert_se(e = strrchr(f, '.'));
477
478         a = p - f;
479
480         s = new(char, a + 1 + strlen(e) + 1);
481         if (!s)
482                 return -ENOMEM;
483
484         strcpy(mempcpy(s, f, a + 1), e);
485
486         *ret = s;
487         return 0;
488 }
489
490 int unit_name_from_path(const char *path, const char *suffix, char **ret) {
491         _cleanup_free_ char *p = NULL;
492         char *s = NULL;
493         int r;
494
495         assert(path);
496         assert(suffix);
497         assert(ret);
498
499         if (!unit_suffix_is_valid(suffix))
500                 return -EINVAL;
501
502         r = unit_name_path_escape(path, &p);
503         if (r < 0)
504                 return r;
505
506         s = strappend(p, suffix);
507         if (!s)
508                 return -ENOMEM;
509
510         *ret = s;
511         return 0;
512 }
513
514 int unit_name_from_path_instance(const char *prefix, const char *path, const char *suffix, char **ret) {
515         _cleanup_free_ char *p = NULL;
516         char *s;
517         int r;
518
519         assert(prefix);
520         assert(path);
521         assert(suffix);
522         assert(ret);
523
524         if (!unit_prefix_is_valid(prefix))
525                 return -EINVAL;
526
527         if (!unit_suffix_is_valid(suffix))
528                 return -EINVAL;
529
530         r = unit_name_path_escape(path, &p);
531         if (r < 0)
532                 return r;
533
534         s = strjoin(prefix, "@", p, suffix, NULL);
535         if (!s)
536                 return -ENOMEM;
537
538         *ret = s;
539         return 0;
540 }
541
542 int unit_name_to_path(const char *name, char **ret) {
543         _cleanup_free_ char *prefix = NULL;
544         int r;
545
546         assert(name);
547
548         r = unit_name_to_prefix(name, &prefix);
549         if (r < 0)
550                 return r;
551
552         return unit_name_path_unescape(prefix, ret);
553 }
554
555 char *unit_dbus_path_from_name(const char *name) {
556         _cleanup_free_ char *e = NULL;
557
558         assert(name);
559
560         e = bus_label_escape(name);
561         if (!e)
562                 return NULL;
563
564         return strappend("/org/freedesktop/systemd1/unit/", e);
565 }
566
567 int unit_name_from_dbus_path(const char *path, char **name) {
568         const char *e;
569         char *n;
570
571         e = startswith(path, "/org/freedesktop/systemd1/unit/");
572         if (!e)
573                 return -EINVAL;
574
575         n = bus_label_unescape(e);
576         if (!n)
577                 return -ENOMEM;
578
579         *name = n;
580         return 0;
581 }
582
583 static char *do_escape_mangle(const char *f, UnitNameMangle allow_globs, char *t) {
584         const char *valid_chars;
585
586         assert(f);
587         assert(IN_SET(allow_globs, UNIT_NAME_GLOB, UNIT_NAME_NOGLOB));
588         assert(t);
589
590         /* We'll only escape the obvious characters here, to play
591          * safe. */
592
593         valid_chars = allow_globs == UNIT_NAME_GLOB ? "@" VALID_CHARS "[]!-*?" : "@" VALID_CHARS;
594
595         for (; *f; f++) {
596                 if (*f == '/')
597                         *(t++) = '-';
598                 else if (!strchr(valid_chars, *f))
599                         t = do_escape_char(*f, t);
600                 else
601                         *(t++) = *f;
602         }
603
604         return t;
605 }
606
607 /**
608  *  Convert a string to a unit name. /dev/blah is converted to dev-blah.device,
609  *  /blah/blah is converted to blah-blah.mount, anything else is left alone,
610  *  except that @suffix is appended if a valid unit suffix is not present.
611  *
612  *  If @allow_globs, globs characters are preserved. Otherwise they are escaped.
613  */
614 int unit_name_mangle_with_suffix(const char *name, UnitNameMangle allow_globs, const char *suffix, char **ret) {
615         char *s, *t;
616         int r;
617
618         assert(name);
619         assert(suffix);
620         assert(ret);
621
622         if (isempty(name)) /* We cannot mangle empty unit names to become valid, sorry. */
623                 return -EINVAL;
624
625         if (!unit_suffix_is_valid(suffix))
626                 return -EINVAL;
627
628         if (unit_name_is_valid(name, UNIT_NAME_ANY)) {
629                 /* No mangling necessary... */
630                 s = strdup(name);
631                 if (!s)
632                         return -ENOMEM;
633
634                 *ret = s;
635                 return 0;
636         }
637
638         if (is_device_path(name)) {
639                 r = unit_name_from_path(name, ".device", ret);
640                 if (r >= 0)
641                         return 1;
642                 if (r != -EINVAL)
643                         return r;
644         }
645
646         if (path_is_absolute(name)) {
647                 r = unit_name_from_path(name, ".mount", ret);
648                 if (r >= 0)
649                         return 1;
650                 if (r != -EINVAL)
651                         return r;
652         }
653
654         s = new(char, strlen(name) * 4 + strlen(suffix) + 1);
655         if (!s)
656                 return -ENOMEM;
657
658         t = do_escape_mangle(name, allow_globs, s);
659         *t = 0;
660
661         if (unit_name_to_type(s) < 0)
662                 strcpy(t, suffix);
663
664         *ret = s;
665         return 1;
666 }
667
668 int slice_build_subslice(const char *slice, const char*name, char **ret) {
669         char *subslice;
670
671         assert(slice);
672         assert(name);
673         assert(ret);
674
675         if (!unit_name_is_valid(slice, UNIT_NAME_PLAIN))
676                 return -EINVAL;
677
678         if (!unit_prefix_is_valid(name))
679                 return -EINVAL;
680
681         if (streq(slice, "-.slice"))
682                 subslice = strappend(name, ".slice");
683         else {
684                 char *e;
685
686                 e = endswith(slice, ".slice");
687                 if (!e)
688                         return -EINVAL;
689
690                 subslice = new(char, (e - slice) + 1 + strlen(name) + 6 + 1);
691                 if (!subslice)
692                         return -ENOMEM;
693
694                 stpcpy(stpcpy(stpcpy(mempcpy(subslice, slice, e - slice), "-"), name), ".slice");
695         }
696
697         *ret = subslice;
698         return 0;
699 }
700
701 static const char* const unit_type_table[_UNIT_TYPE_MAX] = {
702         [UNIT_SERVICE] = "service",
703         [UNIT_SOCKET] = "socket",
704         [UNIT_BUSNAME] = "busname",
705         [UNIT_TARGET] = "target",
706         [UNIT_SNAPSHOT] = "snapshot",
707         [UNIT_DEVICE] = "device",
708         [UNIT_MOUNT] = "mount",
709         [UNIT_AUTOMOUNT] = "automount",
710         [UNIT_SWAP] = "swap",
711         [UNIT_TIMER] = "timer",
712         [UNIT_PATH] = "path",
713         [UNIT_SLICE] = "slice",
714         [UNIT_SCOPE] = "scope"
715 };
716
717 DEFINE_STRING_TABLE_LOOKUP(unit_type, UnitType);
718
719 static const char* const unit_load_state_table[_UNIT_LOAD_STATE_MAX] = {
720         [UNIT_STUB] = "stub",
721         [UNIT_LOADED] = "loaded",
722         [UNIT_NOT_FOUND] = "not-found",
723         [UNIT_ERROR] = "error",
724         [UNIT_MERGED] = "merged",
725         [UNIT_MASKED] = "masked"
726 };
727
728 DEFINE_STRING_TABLE_LOOKUP(unit_load_state, UnitLoadState);
729
730 static const char* const unit_dependency_table[_UNIT_DEPENDENCY_MAX] = {
731         [UNIT_REQUIRES] = "Requires",
732         [UNIT_REQUIRES_OVERRIDABLE] = "RequiresOverridable",
733         [UNIT_REQUISITE] = "Requisite",
734         [UNIT_REQUISITE_OVERRIDABLE] = "RequisiteOverridable",
735         [UNIT_WANTS] = "Wants",
736         [UNIT_BINDS_TO] = "BindsTo",
737         [UNIT_PART_OF] = "PartOf",
738         [UNIT_REQUIRED_BY] = "RequiredBy",
739         [UNIT_REQUIRED_BY_OVERRIDABLE] = "RequiredByOverridable",
740         [UNIT_WANTED_BY] = "WantedBy",
741         [UNIT_BOUND_BY] = "BoundBy",
742         [UNIT_CONSISTS_OF] = "ConsistsOf",
743         [UNIT_CONFLICTS] = "Conflicts",
744         [UNIT_CONFLICTED_BY] = "ConflictedBy",
745         [UNIT_BEFORE] = "Before",
746         [UNIT_AFTER] = "After",
747         [UNIT_ON_FAILURE] = "OnFailure",
748         [UNIT_TRIGGERS] = "Triggers",
749         [UNIT_TRIGGERED_BY] = "TriggeredBy",
750         [UNIT_PROPAGATES_RELOAD_TO] = "PropagatesReloadTo",
751         [UNIT_RELOAD_PROPAGATED_FROM] = "ReloadPropagatedFrom",
752         [UNIT_JOINS_NAMESPACE_OF] = "JoinsNamespaceOf",
753         [UNIT_REFERENCES] = "References",
754         [UNIT_REFERENCED_BY] = "ReferencedBy",
755 };
756
757 DEFINE_STRING_TABLE_LOOKUP(unit_dependency, UnitDependency);