chiark / gitweb /
0f5b7163e40ac6329f87e061c39a5575b868b02a
[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 const char *unit_dbus_interface_from_name(const char *name) {
628         UnitType t;
629
630         t = unit_name_to_type(name);
631         if (t < 0)
632                 return NULL;
633
634         return unit_dbus_interface_from_type(t);
635 }
636
637 static char *do_escape_mangle(const char *f, UnitNameMangle allow_globs, char *t) {
638         const char *valid_chars;
639
640         assert(f);
641         assert(IN_SET(allow_globs, UNIT_NAME_GLOB, UNIT_NAME_NOGLOB));
642         assert(t);
643
644         /* We'll only escape the obvious characters here, to play
645          * safe. */
646
647         valid_chars = allow_globs == UNIT_NAME_GLOB ? "@" VALID_CHARS "[]!-*?" : "@" VALID_CHARS;
648
649         for (; *f; f++) {
650                 if (*f == '/')
651                         *(t++) = '-';
652                 else if (!strchr(valid_chars, *f))
653                         t = do_escape_char(*f, t);
654                 else
655                         *(t++) = *f;
656         }
657
658         return t;
659 }
660
661 /**
662  *  Convert a string to a unit name. /dev/blah is converted to dev-blah.device,
663  *  /blah/blah is converted to blah-blah.mount, anything else is left alone,
664  *  except that @suffix is appended if a valid unit suffix is not present.
665  *
666  *  If @allow_globs, globs characters are preserved. Otherwise they are escaped.
667  */
668 int unit_name_mangle_with_suffix(const char *name, UnitNameMangle allow_globs, const char *suffix, char **ret) {
669         char *s, *t;
670         int r;
671
672         assert(name);
673         assert(suffix);
674         assert(ret);
675
676         if (isempty(name)) /* We cannot mangle empty unit names to become valid, sorry. */
677                 return -EINVAL;
678
679         if (!unit_suffix_is_valid(suffix))
680                 return -EINVAL;
681
682         if (unit_name_is_valid(name, UNIT_NAME_ANY)) {
683                 /* No mangling necessary... */
684                 s = strdup(name);
685                 if (!s)
686                         return -ENOMEM;
687
688                 *ret = s;
689                 return 0;
690         }
691
692         if (is_device_path(name)) {
693                 r = unit_name_from_path(name, ".device", ret);
694                 if (r >= 0)
695                         return 1;
696                 if (r != -EINVAL)
697                         return r;
698         }
699
700         if (path_is_absolute(name)) {
701                 r = unit_name_from_path(name, ".mount", ret);
702                 if (r >= 0)
703                         return 1;
704                 if (r != -EINVAL)
705                         return r;
706         }
707
708         s = new(char, strlen(name) * 4 + strlen(suffix) + 1);
709         if (!s)
710                 return -ENOMEM;
711
712         t = do_escape_mangle(name, allow_globs, s);
713         *t = 0;
714
715         if (unit_name_to_type(s) < 0)
716                 strcpy(t, suffix);
717
718         *ret = s;
719         return 1;
720 }
721
722 /// UNNEEDED by elogind
723 #if 0
724 int slice_build_parent_slice(const char *slice, char **ret) {
725         char *s, *dash;
726         int r;
727
728         assert(slice);
729         assert(ret);
730
731         if (!slice_name_is_valid(slice))
732                 return -EINVAL;
733
734         if (streq(slice, "-.slice")) {
735                 *ret = NULL;
736                 return 0;
737         }
738
739         s = strdup(slice);
740         if (!s)
741                 return -ENOMEM;
742
743         dash = strrchr(s, '-');
744         if (dash)
745                 strcpy(dash, ".slice");
746         else {
747                 r = free_and_strdup(&s, "-.slice");
748                 if (r < 0) {
749                 free(s);
750                         return r;
751                 }
752         }
753
754         *ret = s;
755         return 1;
756 }
757 #endif // 0
758
759 int slice_build_subslice(const char *slice, const char*name, char **ret) {
760         char *subslice;
761
762         assert(slice);
763         assert(name);
764         assert(ret);
765
766         if (!slice_name_is_valid(slice))
767                 return -EINVAL;
768
769         if (!unit_prefix_is_valid(name))
770                 return -EINVAL;
771
772         if (streq(slice, "-.slice"))
773                 subslice = strappend(name, ".slice");
774         else {
775                 char *e;
776
777                 assert_se(e = endswith(slice, ".slice"));
778
779                 subslice = new(char, (e - slice) + 1 + strlen(name) + 6 + 1);
780                 if (!subslice)
781                         return -ENOMEM;
782
783                 stpcpy(stpcpy(stpcpy(mempcpy(subslice, slice, e - slice), "-"), name), ".slice");
784         }
785
786         *ret = subslice;
787         return 0;
788 }
789
790 bool slice_name_is_valid(const char *name) {
791         const char *p, *e;
792         bool dash = false;
793
794         if (!unit_name_is_valid(name, UNIT_NAME_PLAIN))
795                 return false;
796
797         if (streq(name, "-.slice"))
798                 return true;
799
800         e = endswith(name, ".slice");
801         if (!e)
802                 return false;
803
804         for (p = name; p < e; p++) {
805
806                 if (*p == '-') {
807
808                         /* Don't allow initial dash */
809                         if (p == name)
810                                 return false;
811
812                         /* Don't allow multiple dashes */
813                         if (dash)
814                                 return false;
815
816                         dash = true;
817                 } else
818                         dash = false;
819         }
820
821         /* Don't allow trailing hash */
822         if (dash)
823                 return false;
824
825         return true;
826 }
827
828 static const char* const unit_type_table[_UNIT_TYPE_MAX] = {
829         [UNIT_SERVICE] = "service",
830         [UNIT_SOCKET] = "socket",
831         [UNIT_BUSNAME] = "busname",
832         [UNIT_TARGET] = "target",
833         [UNIT_SNAPSHOT] = "snapshot",
834         [UNIT_DEVICE] = "device",
835         [UNIT_MOUNT] = "mount",
836         [UNIT_AUTOMOUNT] = "automount",
837         [UNIT_SWAP] = "swap",
838         [UNIT_TIMER] = "timer",
839         [UNIT_PATH] = "path",
840         [UNIT_SLICE] = "slice",
841         [UNIT_SCOPE] = "scope",
842 };
843
844 DEFINE_STRING_TABLE_LOOKUP(unit_type, UnitType);
845
846 static const char* const unit_load_state_table[_UNIT_LOAD_STATE_MAX] = {
847         [UNIT_STUB] = "stub",
848         [UNIT_LOADED] = "loaded",
849         [UNIT_NOT_FOUND] = "not-found",
850         [UNIT_ERROR] = "error",
851         [UNIT_MERGED] = "merged",
852         [UNIT_MASKED] = "masked"
853 };
854
855 DEFINE_STRING_TABLE_LOOKUP(unit_load_state, UnitLoadState);
856
857 static const char* const unit_dependency_table[_UNIT_DEPENDENCY_MAX] = {
858         [UNIT_REQUIRES] = "Requires",
859         [UNIT_REQUIRES_OVERRIDABLE] = "RequiresOverridable",
860         [UNIT_REQUISITE] = "Requisite",
861         [UNIT_REQUISITE_OVERRIDABLE] = "RequisiteOverridable",
862         [UNIT_WANTS] = "Wants",
863         [UNIT_BINDS_TO] = "BindsTo",
864         [UNIT_PART_OF] = "PartOf",
865         [UNIT_REQUIRED_BY] = "RequiredBy",
866         [UNIT_REQUIRED_BY_OVERRIDABLE] = "RequiredByOverridable",
867         [UNIT_REQUISITE_OF] = "RequisiteOf",
868         [UNIT_REQUISITE_OF_OVERRIDABLE] = "RequisiteOfOverridable",
869         [UNIT_WANTED_BY] = "WantedBy",
870         [UNIT_BOUND_BY] = "BoundBy",
871         [UNIT_CONSISTS_OF] = "ConsistsOf",
872         [UNIT_CONFLICTS] = "Conflicts",
873         [UNIT_CONFLICTED_BY] = "ConflictedBy",
874         [UNIT_BEFORE] = "Before",
875         [UNIT_AFTER] = "After",
876         [UNIT_ON_FAILURE] = "OnFailure",
877         [UNIT_TRIGGERS] = "Triggers",
878         [UNIT_TRIGGERED_BY] = "TriggeredBy",
879         [UNIT_PROPAGATES_RELOAD_TO] = "PropagatesReloadTo",
880         [UNIT_RELOAD_PROPAGATED_FROM] = "ReloadPropagatedFrom",
881         [UNIT_JOINS_NAMESPACE_OF] = "JoinsNamespaceOf",
882         [UNIT_REFERENCES] = "References",
883         [UNIT_REFERENCED_BY] = "ReferencedBy",
884 };
885
886 DEFINE_STRING_TABLE_LOOKUP(unit_dependency, UnitDependency);