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