chiark / gitweb /
systemctl: automatically turn paths and unescaped unit names into proper unit names
[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
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 }