chiark / gitweb /
Prep v226: Mask all unneeded functions
[elogind.git] / src / basic / 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 /// UNNEEDED by elogind
146 #if 0
147 int unit_name_to_instance(const char *n, char **instance) {
148         const char *p, *d;
149         char *i;
150
151         assert(n);
152         assert(instance);
153
154         if (!unit_name_is_valid(n, UNIT_NAME_ANY))
155                 return -EINVAL;
156
157         /* Everything past the first @ and before the last . is the instance */
158         p = strchr(n, '@');
159         if (!p) {
160                 *instance = NULL;
161                 return 0;
162         }
163
164         p++;
165
166         d = strrchr(p, '.');
167         if (!d)
168                 return -EINVAL;
169
170         i = strndup(p, d-p);
171         if (!i)
172                 return -ENOMEM;
173
174         *instance = i;
175         return 1;
176 }
177
178 int unit_name_to_prefix_and_instance(const char *n, char **ret) {
179         const char *d;
180         char *s;
181
182         assert(n);
183         assert(ret);
184
185         if (!unit_name_is_valid(n, UNIT_NAME_ANY))
186                 return -EINVAL;
187
188         d = strrchr(n, '.');
189         if (!d)
190                 return -EINVAL;
191
192         s = strndup(n, d - n);
193         if (!s)
194                 return -ENOMEM;
195
196         *ret = s;
197         return 0;
198 }
199 #endif // 0
200
201 UnitType unit_name_to_type(const char *n) {
202         const char *e;
203
204         assert(n);
205
206         if (!unit_name_is_valid(n, UNIT_NAME_ANY))
207                 return _UNIT_TYPE_INVALID;
208
209         assert_se(e = strrchr(n, '.'));
210
211         return unit_type_from_string(e + 1);
212 }
213
214 /// UNNEEDED by elogind
215 #if 0
216 int unit_name_change_suffix(const char *n, const char *suffix, char **ret) {
217         char *e, *s;
218         size_t a, b;
219
220         assert(n);
221         assert(suffix);
222         assert(ret);
223
224         if (!unit_name_is_valid(n, UNIT_NAME_ANY))
225                 return -EINVAL;
226
227         if (!unit_suffix_is_valid(suffix))
228                 return -EINVAL;
229
230         assert_se(e = strrchr(n, '.'));
231
232         a = e - n;
233         b = strlen(suffix);
234
235         s = new(char, a + b + 1);
236         if (!s)
237                 return -ENOMEM;
238
239         strcpy(mempcpy(s, n, a), suffix);
240         *ret = s;
241
242         return 0;
243 }
244 #endif // 0
245
246 int unit_name_build(const char *prefix, const char *instance, const char *suffix, char **ret) {
247         char *s;
248
249         assert(prefix);
250         assert(suffix);
251         assert(ret);
252
253         if (!unit_prefix_is_valid(prefix))
254                 return -EINVAL;
255
256         if (instance && !unit_instance_is_valid(instance))
257                 return -EINVAL;
258
259         if (!unit_suffix_is_valid(suffix))
260                 return -EINVAL;
261
262         if (!instance)
263                 s = strappend(prefix, suffix);
264         else
265                 s = strjoin(prefix, "@", instance, suffix, NULL);
266         if (!s)
267                 return -ENOMEM;
268
269         *ret = s;
270         return 0;
271 }
272
273 static char *do_escape_char(char c, char *t) {
274         assert(t);
275
276         *(t++) = '\\';
277         *(t++) = 'x';
278         *(t++) = hexchar(c >> 4);
279         *(t++) = hexchar(c);
280
281         return t;
282 }
283
284 static char *do_escape(const char *f, char *t) {
285         assert(f);
286         assert(t);
287
288         /* do not create units with a leading '.', like for "/.dotdir" mount points */
289         if (*f == '.') {
290                 t = do_escape_char(*f, t);
291                 f++;
292         }
293
294         for (; *f; f++) {
295                 if (*f == '/')
296                         *(t++) = '-';
297                 else if (*f == '-' || *f == '\\' || !strchr(VALID_CHARS, *f))
298                         t = do_escape_char(*f, t);
299                 else
300                         *(t++) = *f;
301         }
302
303         return t;
304 }
305
306 char *unit_name_escape(const char *f) {
307         char *r, *t;
308
309         assert(f);
310
311         r = new(char, strlen(f)*4+1);
312         if (!r)
313                 return NULL;
314
315         t = do_escape(f, r);
316         *t = 0;
317
318         return r;
319 }
320
321 int unit_name_unescape(const char *f, char **ret) {
322         _cleanup_free_ char *r = NULL;
323         char *t;
324
325         assert(f);
326
327         r = strdup(f);
328         if (!r)
329                 return -ENOMEM;
330
331         for (t = r; *f; f++) {
332                 if (*f == '-')
333                         *(t++) = '/';
334                 else if (*f == '\\') {
335                         int a, b;
336
337                         if (f[1] != 'x')
338                                 return -EINVAL;
339
340                         a = unhexchar(f[2]);
341                         if (a < 0)
342                                 return -EINVAL;
343
344                         b = unhexchar(f[3]);
345                         if (b < 0)
346                                 return -EINVAL;
347
348                         *(t++) = (char) (((uint8_t) a << 4U) | (uint8_t) b);
349                         f += 3;
350                 } else
351                         *(t++) = *f;
352         }
353
354         *t = 0;
355
356         *ret = r;
357         r = NULL;
358
359         return 0;
360 }
361
362 int unit_name_path_escape(const char *f, char **ret) {
363         char *p, *s;
364
365         assert(f);
366         assert(ret);
367
368         p = strdupa(f);
369         if (!p)
370                 return -ENOMEM;
371
372         path_kill_slashes(p);
373
374         if (STR_IN_SET(p, "/", ""))
375                 s = strdup("-");
376         else {
377                 char *e;
378
379                 if (!path_is_safe(p))
380                         return -EINVAL;
381
382                 /* Truncate trailing slashes */
383                 e = endswith(p, "/");
384                 if (e)
385                         *e = 0;
386
387                 /* Truncate leading slashes */
388                 if (p[0] == '/')
389                         p++;
390
391                 s = unit_name_escape(p);
392         }
393         if (!s)
394                 return -ENOMEM;
395
396         *ret = s;
397         return 0;
398 }
399
400 int unit_name_path_unescape(const char *f, char **ret) {
401         char *s;
402         int r;
403
404         assert(f);
405
406         if (isempty(f))
407                 return -EINVAL;
408
409         if (streq(f, "-")) {
410                 s = strdup("/");
411                 if (!s)
412                         return -ENOMEM;
413         } else {
414                 char *w;
415
416                 r = unit_name_unescape(f, &w);
417                 if (r < 0)
418                         return r;
419
420                 /* Don't accept trailing or leading slashes */
421                 if (startswith(w, "/") || endswith(w, "/")) {
422                         free(w);
423                         return -EINVAL;
424                 }
425
426                 /* Prefix a slash again */
427                 s = strappend("/", w);
428                 free(w);
429                 if (!s)
430                         return -ENOMEM;
431
432                 if (!path_is_safe(s)) {
433                         free(s);
434                         return -EINVAL;
435                 }
436         }
437
438         if (ret)
439                 *ret = s;
440         else
441                 free(s);
442
443         return 0;
444 }
445
446 /// UNNEEDED by elogind
447 #if 0
448 int unit_name_replace_instance(const char *f, const char *i, char **ret) {
449         const char *p, *e;
450         char *s;
451         size_t a, b;
452
453         assert(f);
454         assert(i);
455         assert(ret);
456
457         if (!unit_name_is_valid(f, UNIT_NAME_INSTANCE|UNIT_NAME_TEMPLATE))
458                 return -EINVAL;
459         if (!unit_instance_is_valid(i))
460                 return -EINVAL;
461
462         assert_se(p = strchr(f, '@'));
463         assert_se(e = strrchr(f, '.'));
464
465         a = p - f;
466         b = strlen(i);
467
468         s = new(char, a + 1 + b + strlen(e) + 1);
469         if (!s)
470                 return -ENOMEM;
471
472         strcpy(mempcpy(mempcpy(s, f, a + 1), i, b), e);
473
474         *ret = s;
475         return 0;
476 }
477
478 int unit_name_template(const char *f, char **ret) {
479         const char *p, *e;
480         char *s;
481         size_t a;
482
483         assert(f);
484         assert(ret);
485
486         if (!unit_name_is_valid(f, UNIT_NAME_INSTANCE|UNIT_NAME_TEMPLATE))
487                 return -EINVAL;
488
489         assert_se(p = strchr(f, '@'));
490         assert_se(e = strrchr(f, '.'));
491
492         a = p - f;
493
494         s = new(char, a + 1 + strlen(e) + 1);
495         if (!s)
496                 return -ENOMEM;
497
498         strcpy(mempcpy(s, f, a + 1), e);
499
500         *ret = s;
501         return 0;
502 }
503 #endif // 0
504
505 int unit_name_from_path(const char *path, const char *suffix, char **ret) {
506         _cleanup_free_ char *p = NULL;
507         char *s = NULL;
508         int r;
509
510         assert(path);
511         assert(suffix);
512         assert(ret);
513
514         if (!unit_suffix_is_valid(suffix))
515                 return -EINVAL;
516
517         r = unit_name_path_escape(path, &p);
518         if (r < 0)
519                 return r;
520
521         s = strappend(p, suffix);
522         if (!s)
523                 return -ENOMEM;
524
525         *ret = s;
526         return 0;
527 }
528
529 /// UNNEEDED by elogind
530 #if 0
531 int unit_name_from_path_instance(const char *prefix, const char *path, const char *suffix, char **ret) {
532         _cleanup_free_ char *p = NULL;
533         char *s;
534         int r;
535
536         assert(prefix);
537         assert(path);
538         assert(suffix);
539         assert(ret);
540
541         if (!unit_prefix_is_valid(prefix))
542                 return -EINVAL;
543
544         if (!unit_suffix_is_valid(suffix))
545                 return -EINVAL;
546
547         r = unit_name_path_escape(path, &p);
548         if (r < 0)
549                 return r;
550
551         s = strjoin(prefix, "@", p, suffix, NULL);
552         if (!s)
553                 return -ENOMEM;
554
555         *ret = s;
556         return 0;
557 }
558
559 int unit_name_to_path(const char *name, char **ret) {
560         _cleanup_free_ char *prefix = NULL;
561         int r;
562
563         assert(name);
564
565         r = unit_name_to_prefix(name, &prefix);
566         if (r < 0)
567                 return r;
568
569         return unit_name_path_unescape(prefix, ret);
570 }
571 #endif // 0
572
573 char *unit_dbus_path_from_name(const char *name) {
574         _cleanup_free_ char *e = NULL;
575
576         assert(name);
577
578         e = bus_label_escape(name);
579         if (!e)
580                 return NULL;
581
582         return strappend("/org/freedesktop/systemd1/unit/", e);
583 }
584
585 int unit_name_from_dbus_path(const char *path, char **name) {
586         const char *e;
587         char *n;
588
589         e = startswith(path, "/org/freedesktop/systemd1/unit/");
590         if (!e)
591                 return -EINVAL;
592
593         n = bus_label_unescape(e);
594         if (!n)
595                 return -ENOMEM;
596
597         *name = n;
598         return 0;
599 }
600
601 const char* unit_dbus_interface_from_type(UnitType t) {
602
603         static const char *const table[_UNIT_TYPE_MAX] = {
604                 [UNIT_SERVICE] = "org.freedesktop.systemd1.Service",
605                 [UNIT_SOCKET] = "org.freedesktop.systemd1.Socket",
606                 [UNIT_BUSNAME] = "org.freedesktop.systemd1.BusName",
607                 [UNIT_TARGET] = "org.freedesktop.systemd1.Target",
608                 [UNIT_SNAPSHOT] = "org.freedesktop.systemd1.Snapshot",
609                 [UNIT_DEVICE] = "org.freedesktop.systemd1.Device",
610                 [UNIT_MOUNT] = "org.freedesktop.systemd1.Mount",
611                 [UNIT_AUTOMOUNT] = "org.freedesktop.systemd1.Automount",
612                 [UNIT_SWAP] = "org.freedesktop.systemd1.Swap",
613                 [UNIT_TIMER] = "org.freedesktop.systemd1.Timer",
614                 [UNIT_PATH] = "org.freedesktop.systemd1.Path",
615                 [UNIT_SLICE] = "org.freedesktop.systemd1.Slice",
616                 [UNIT_SCOPE] = "org.freedesktop.systemd1.Scope",
617         };
618
619         if (t < 0)
620                 return NULL;
621         if (t >= _UNIT_TYPE_MAX)
622                 return NULL;
623
624         return table[t];
625 }
626
627 /// UNNEEDED by elogind
628 #if 0
629 const char *unit_dbus_interface_from_name(const char *name) {
630         UnitType t;
631
632         t = unit_name_to_type(name);
633         if (t < 0)
634                 return NULL;
635
636         return unit_dbus_interface_from_type(t);
637 }
638 #endif // 0
639
640 static char *do_escape_mangle(const char *f, UnitNameMangle allow_globs, char *t) {
641         const char *valid_chars;
642
643         assert(f);
644         assert(IN_SET(allow_globs, UNIT_NAME_GLOB, UNIT_NAME_NOGLOB));
645         assert(t);
646
647         /* We'll only escape the obvious characters here, to play
648          * safe. */
649
650         valid_chars = allow_globs == UNIT_NAME_GLOB ? "@" VALID_CHARS "[]!-*?" : "@" VALID_CHARS;
651
652         for (; *f; f++) {
653                 if (*f == '/')
654                         *(t++) = '-';
655                 else if (!strchr(valid_chars, *f))
656                         t = do_escape_char(*f, t);
657                 else
658                         *(t++) = *f;
659         }
660
661         return t;
662 }
663
664 /**
665  *  Convert a string to a unit name. /dev/blah is converted to dev-blah.device,
666  *  /blah/blah is converted to blah-blah.mount, anything else is left alone,
667  *  except that @suffix is appended if a valid unit suffix is not present.
668  *
669  *  If @allow_globs, globs characters are preserved. Otherwise they are escaped.
670  */
671 int unit_name_mangle_with_suffix(const char *name, UnitNameMangle allow_globs, const char *suffix, char **ret) {
672         char *s, *t;
673         int r;
674
675         assert(name);
676         assert(suffix);
677         assert(ret);
678
679         if (isempty(name)) /* We cannot mangle empty unit names to become valid, sorry. */
680                 return -EINVAL;
681
682         if (!unit_suffix_is_valid(suffix))
683                 return -EINVAL;
684
685         if (unit_name_is_valid(name, UNIT_NAME_ANY)) {
686                 /* No mangling necessary... */
687                 s = strdup(name);
688                 if (!s)
689                         return -ENOMEM;
690
691                 *ret = s;
692                 return 0;
693         }
694
695         if (is_device_path(name)) {
696                 r = unit_name_from_path(name, ".device", ret);
697                 if (r >= 0)
698                         return 1;
699                 if (r != -EINVAL)
700                         return r;
701         }
702
703         if (path_is_absolute(name)) {
704                 r = unit_name_from_path(name, ".mount", ret);
705                 if (r >= 0)
706                         return 1;
707                 if (r != -EINVAL)
708                         return r;
709         }
710
711         s = new(char, strlen(name) * 4 + strlen(suffix) + 1);
712         if (!s)
713                 return -ENOMEM;
714
715         t = do_escape_mangle(name, allow_globs, s);
716         *t = 0;
717
718         if (unit_name_to_type(s) < 0)
719                 strcpy(t, suffix);
720
721         *ret = s;
722         return 1;
723 }
724
725 /// UNNEEDED by elogind
726 #if 0
727 int slice_build_parent_slice(const char *slice, char **ret) {
728         char *s, *dash;
729         int r;
730
731         assert(slice);
732         assert(ret);
733
734         if (!slice_name_is_valid(slice))
735                 return -EINVAL;
736
737         if (streq(slice, "-.slice")) {
738                 *ret = NULL;
739                 return 0;
740         }
741
742         s = strdup(slice);
743         if (!s)
744                 return -ENOMEM;
745
746         dash = strrchr(s, '-');
747         if (dash)
748                 strcpy(dash, ".slice");
749         else {
750                 r = free_and_strdup(&s, "-.slice");
751                 if (r < 0) {
752                 free(s);
753                         return r;
754                 }
755         }
756
757         *ret = s;
758         return 1;
759 }
760 #endif // 0
761
762 int slice_build_subslice(const char *slice, const char*name, char **ret) {
763         char *subslice;
764
765         assert(slice);
766         assert(name);
767         assert(ret);
768
769         if (!slice_name_is_valid(slice))
770                 return -EINVAL;
771
772         if (!unit_prefix_is_valid(name))
773                 return -EINVAL;
774
775         if (streq(slice, "-.slice"))
776                 subslice = strappend(name, ".slice");
777         else {
778                 char *e;
779
780                 assert_se(e = endswith(slice, ".slice"));
781
782                 subslice = new(char, (e - slice) + 1 + strlen(name) + 6 + 1);
783                 if (!subslice)
784                         return -ENOMEM;
785
786                 stpcpy(stpcpy(stpcpy(mempcpy(subslice, slice, e - slice), "-"), name), ".slice");
787         }
788
789         *ret = subslice;
790         return 0;
791 }
792
793 bool slice_name_is_valid(const char *name) {
794         const char *p, *e;
795         bool dash = false;
796
797         if (!unit_name_is_valid(name, UNIT_NAME_PLAIN))
798                 return false;
799
800         if (streq(name, "-.slice"))
801                 return true;
802
803         e = endswith(name, ".slice");
804         if (!e)
805                 return false;
806
807         for (p = name; p < e; p++) {
808
809                 if (*p == '-') {
810
811                         /* Don't allow initial dash */
812                         if (p == name)
813                                 return false;
814
815                         /* Don't allow multiple dashes */
816                         if (dash)
817                                 return false;
818
819                         dash = true;
820                 } else
821                         dash = false;
822         }
823
824         /* Don't allow trailing hash */
825         if (dash)
826                 return false;
827
828         return true;
829 }
830
831 static const char* const unit_type_table[_UNIT_TYPE_MAX] = {
832         [UNIT_SERVICE] = "service",
833         [UNIT_SOCKET] = "socket",
834         [UNIT_BUSNAME] = "busname",
835         [UNIT_TARGET] = "target",
836         [UNIT_SNAPSHOT] = "snapshot",
837         [UNIT_DEVICE] = "device",
838         [UNIT_MOUNT] = "mount",
839         [UNIT_AUTOMOUNT] = "automount",
840         [UNIT_SWAP] = "swap",
841         [UNIT_TIMER] = "timer",
842         [UNIT_PATH] = "path",
843         [UNIT_SLICE] = "slice",
844         [UNIT_SCOPE] = "scope",
845 };
846
847 DEFINE_STRING_TABLE_LOOKUP(unit_type, UnitType);
848
849 static const char* const unit_load_state_table[_UNIT_LOAD_STATE_MAX] = {
850         [UNIT_STUB] = "stub",
851         [UNIT_LOADED] = "loaded",
852         [UNIT_NOT_FOUND] = "not-found",
853         [UNIT_ERROR] = "error",
854         [UNIT_MERGED] = "merged",
855         [UNIT_MASKED] = "masked"
856 };
857
858 DEFINE_STRING_TABLE_LOOKUP(unit_load_state, UnitLoadState);
859
860 static const char* const unit_dependency_table[_UNIT_DEPENDENCY_MAX] = {
861         [UNIT_REQUIRES] = "Requires",
862         [UNIT_REQUIRES_OVERRIDABLE] = "RequiresOverridable",
863         [UNIT_REQUISITE] = "Requisite",
864         [UNIT_REQUISITE_OVERRIDABLE] = "RequisiteOverridable",
865         [UNIT_WANTS] = "Wants",
866         [UNIT_BINDS_TO] = "BindsTo",
867         [UNIT_PART_OF] = "PartOf",
868         [UNIT_REQUIRED_BY] = "RequiredBy",
869         [UNIT_REQUIRED_BY_OVERRIDABLE] = "RequiredByOverridable",
870         [UNIT_REQUISITE_OF] = "RequisiteOf",
871         [UNIT_REQUISITE_OF_OVERRIDABLE] = "RequisiteOfOverridable",
872         [UNIT_WANTED_BY] = "WantedBy",
873         [UNIT_BOUND_BY] = "BoundBy",
874         [UNIT_CONSISTS_OF] = "ConsistsOf",
875         [UNIT_CONFLICTS] = "Conflicts",
876         [UNIT_CONFLICTED_BY] = "ConflictedBy",
877         [UNIT_BEFORE] = "Before",
878         [UNIT_AFTER] = "After",
879         [UNIT_ON_FAILURE] = "OnFailure",
880         [UNIT_TRIGGERS] = "Triggers",
881         [UNIT_TRIGGERED_BY] = "TriggeredBy",
882         [UNIT_PROPAGATES_RELOAD_TO] = "PropagatesReloadTo",
883         [UNIT_RELOAD_PROPAGATED_FROM] = "ReloadPropagatedFrom",
884         [UNIT_JOINS_NAMESPACE_OF] = "JoinsNamespaceOf",
885         [UNIT_REFERENCES] = "References",
886         [UNIT_REFERENCED_BY] = "ReferencedBy",
887 };
888
889 DEFINE_STRING_TABLE_LOOKUP(unit_dependency, UnitDependency);