chiark / gitweb /
8ee9ddc9c6e65662560481a28a927f6207e2eb98
[elogind.git] / src / shared / path-lookup.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 <assert.h>
23 #include <stdlib.h>
24 #include <stdio.h>
25 #include <string.h>
26 #include <unistd.h>
27 #include <errno.h>
28
29 #include "util.h"
30 #include "mkdir.h"
31 #include "strv.h"
32 #include "path-util.h"
33 #include "path-lookup.h"
34
35 static const char* const systemd_running_as_table[_SYSTEMD_RUNNING_AS_MAX] = {
36         [SYSTEMD_SYSTEM] = "system",
37         [SYSTEMD_USER] = "user"
38 };
39
40 DEFINE_STRING_TABLE_LOOKUP(systemd_running_as, SystemdRunningAs);
41
42 int user_config_home(char **config_home) {
43         const char *e;
44
45         e = getenv("XDG_CONFIG_HOME");
46         if (e) {
47                 if (asprintf(config_home, "%s/systemd/user", e) < 0)
48                         return -ENOMEM;
49
50                 return 1;
51         } else {
52                 const char *home;
53
54                 home = getenv("HOME");
55                 if (home) {
56                         if (asprintf(config_home, "%s/.config/systemd/user", home) < 0)
57                                 return -ENOMEM;
58
59                         return 1;
60                 }
61         }
62
63         return 0;
64 }
65
66 static char** user_dirs(
67                 const char *generator,
68                 const char *generator_early,
69                 const char *generator_late) {
70
71         const char * const config_unit_paths[] = {
72                 USER_CONFIG_UNIT_PATH,
73                 "/etc/systemd/user",
74                 "/run/systemd/user",
75                 NULL
76         };
77
78         const char * const data_unit_paths[] = {
79                 "/usr/local/lib/systemd/user",
80                 "/usr/local/share/systemd/user",
81                 USER_DATA_UNIT_PATH,
82                 "/usr/lib/systemd/user",
83                 "/usr/share/systemd/user",
84                 NULL
85         };
86
87         const char *home, *e;
88         char *config_home = NULL, *data_home = NULL;
89         char **config_dirs = NULL, **data_dirs = NULL;
90         char **r = NULL, **t;
91
92         /* Implement the mechanisms defined in
93          *
94          * http://standards.freedesktop.org/basedir-spec/basedir-spec-0.6.html
95          *
96          * We look in both the config and the data dirs because we
97          * want to encourage that distributors ship their unit files
98          * as data, and allow overriding as configuration.
99          */
100
101         if (user_config_home(&config_home) < 0)
102                 goto fail;
103
104         home = getenv("HOME");
105
106         e = getenv("XDG_CONFIG_DIRS");
107         if (e) {
108                 config_dirs = strv_split(e, ":");
109                 if (!config_dirs)
110                         goto fail;
111         }
112
113         /* We don't treat /etc/xdg/systemd here as the spec
114          * suggests because we assume that that is a link to
115          * /etc/systemd/ anyway. */
116
117         e = getenv("XDG_DATA_HOME");
118         if (e) {
119                 if (asprintf(&data_home, "%s/systemd/user", e) < 0)
120                         goto fail;
121
122         } else if (home) {
123                 if (asprintf(&data_home, "%s/.local/share/systemd/user", home) < 0)
124                         goto fail;
125
126                 /* There is really no need for two unit dirs in $HOME,
127                  * except to be fully compliant with the XDG spec. We
128                  * now try to link the two dirs, so that we can
129                  * minimize disk seeks a little. Further down we'll
130                  * then filter out this link, if it is actually is
131                  * one. */
132
133                 mkdir_parents_label(data_home, 0777);
134                 (void) symlink("../../../.config/systemd/user", data_home);
135         }
136
137         e = getenv("XDG_DATA_DIRS");
138         if (e)
139                 data_dirs = strv_split(e, ":");
140         else
141                 data_dirs = strv_new("/usr/local/share",
142                                      "/usr/share",
143                                      NULL);
144         if (!data_dirs)
145                 goto fail;
146
147         /* Now merge everything we found. */
148         if (generator_early) {
149                 t = strv_append(r, generator_early);
150                 if (!t)
151                         goto fail;
152                 strv_free(r);
153                 r = t;
154         }
155
156         if (config_home) {
157                 t = strv_append(r, config_home);
158                 if (!t)
159                         goto fail;
160                 strv_free(r);
161                 r = t;
162         }
163
164         if (!strv_isempty(config_dirs)) {
165                 t = strv_merge_concat(r, config_dirs, "/systemd/user");
166                 if (!t)
167                         goto finish;
168                 strv_free(r);
169                 r = t;
170         }
171
172         t = strv_merge(r, (char**) config_unit_paths);
173         if (!t)
174                 goto fail;
175         strv_free(r);
176         r = t;
177
178         if (generator) {
179                 t = strv_append(r, generator);
180                 if (!t)
181                         goto fail;
182                 strv_free(r);
183                 r = t;
184         }
185
186         if (data_home) {
187                 t = strv_append(r, data_home);
188                 if (!t)
189                         goto fail;
190                 strv_free(r);
191                 r = t;
192         }
193
194         if (!strv_isempty(data_dirs)) {
195                 t = strv_merge_concat(r, data_dirs, "/systemd/user");
196                 if (!t)
197                         goto fail;
198                 strv_free(r);
199                 r = t;
200         }
201
202         t = strv_merge(r, (char**) data_unit_paths);
203         if (!t)
204                 goto fail;
205         strv_free(r);
206         r = t;
207
208         if (generator_late) {
209                 t = strv_append(r, generator_late);
210                 if (!t)
211                         goto fail;
212                 strv_free(r);
213                 r = t;
214         }
215
216         if (!path_strv_make_absolute_cwd(r))
217                 goto fail;
218
219 finish:
220         free(config_home);
221         strv_free(config_dirs);
222         free(data_home);
223         strv_free(data_dirs);
224
225         return r;
226
227 fail:
228         strv_free(r);
229         r = NULL;
230         goto finish;
231 }
232
233 int lookup_paths_init(
234                 LookupPaths *p,
235                 SystemdRunningAs running_as,
236                 bool personal,
237                 const char *generator,
238                 const char *generator_early,
239                 const char *generator_late) {
240
241         const char *e;
242         char *t;
243
244         assert(p);
245
246         /* First priority is whatever has been passed to us via env
247          * vars */
248         e = getenv("SYSTEMD_UNIT_PATH");
249         if (e) {
250                 p->unit_path = path_split_and_make_absolute(e);
251                 if (!p->unit_path)
252                         return -ENOMEM;
253         } else
254                 p->unit_path = NULL;
255
256         if (strv_isempty(p->unit_path)) {
257                 /* Nothing is set, so let's figure something out. */
258                 strv_free(p->unit_path);
259
260                 /* For the user units we include share/ in the search
261                  * path in order to comply with the XDG basedir
262                  * spec. For the system stuff we avoid such
263                  * nonsense. OTOH we include /lib in the search path
264                  * for the system stuff but avoid it for user
265                  * stuff. */
266
267                 if (running_as == SYSTEMD_USER) {
268
269                         if (personal)
270                                 p->unit_path = user_dirs(generator, generator_early, generator_late);
271                         else
272                                 p->unit_path = strv_new(
273                                                 /* If you modify this you also want to modify
274                                                  * systemduserunitpath= in systemd.pc.in, and
275                                                  * the arrays in user_dirs() above! */
276                                                 STRV_IFNOTNULL(generator_early),
277                                                 USER_CONFIG_UNIT_PATH,
278                                                 "/etc/systemd/user",
279                                                 "/run/systemd/user",
280                                                 STRV_IFNOTNULL(generator),
281                                                 "/usr/local/lib/systemd/user",
282                                                 "/usr/local/share/systemd/user",
283                                                 USER_DATA_UNIT_PATH,
284                                                 "/usr/lib/systemd/user",
285                                                 "/usr/share/systemd/user",
286                                                 STRV_IFNOTNULL(generator_late),
287                                                 NULL);
288
289                         if (!p->unit_path)
290                                 return -ENOMEM;
291
292                 } else {
293                         p->unit_path = strv_new(
294                                         /* If you modify this you also want to modify
295                                          * systemdsystemunitpath= in systemd.pc.in! */
296                                         STRV_IFNOTNULL(generator_early),
297                                         SYSTEM_CONFIG_UNIT_PATH,
298                                         "/etc/systemd/system",
299                                         "/run/systemd/system",
300                                         STRV_IFNOTNULL(generator),
301                                         "/usr/local/lib/systemd/system",
302                                         SYSTEM_DATA_UNIT_PATH,
303                                         "/usr/lib/systemd/system",
304 #ifdef HAVE_SPLIT_USR
305                                         "/lib/systemd/system",
306 #endif
307                                         STRV_IFNOTNULL(generator_late),
308                                         NULL);
309
310                         if (!p->unit_path)
311                                 return -ENOMEM;
312                 }
313         }
314
315         if (!path_strv_canonicalize(p->unit_path))
316                 return -ENOMEM;
317
318         strv_uniq(p->unit_path);
319
320         if (!strv_isempty(p->unit_path)) {
321
322                 t = strv_join(p->unit_path, "\n\t");
323                 if (!t)
324                         return -ENOMEM;
325                 log_debug("Looking for unit files in:\n\t%s", t);
326                 free(t);
327         } else {
328                 log_debug("Ignoring unit files.");
329                 strv_free(p->unit_path);
330                 p->unit_path = NULL;
331         }
332
333         if (running_as == SYSTEMD_SYSTEM) {
334 #ifdef HAVE_SYSV_COMPAT
335                 /* /etc/init.d/ compatibility does not matter to users */
336
337                 e = getenv("SYSTEMD_SYSVINIT_PATH");
338                 if (e) {
339                         p->sysvinit_path = path_split_and_make_absolute(e);
340                         if (!p->sysvinit_path)
341                                 return -ENOMEM;
342                 } else
343                         p->sysvinit_path = NULL;
344
345                 if (strv_isempty(p->sysvinit_path)) {
346                         strv_free(p->sysvinit_path);
347
348                         p->sysvinit_path = strv_new(
349                                         SYSTEM_SYSVINIT_PATH,     /* /etc/init.d/ */
350                                         NULL);
351                         if (!p->sysvinit_path)
352                                 return -ENOMEM;
353                 }
354
355                 e = getenv("SYSTEMD_SYSVRCND_PATH");
356                 if (e) {
357                         p->sysvrcnd_path = path_split_and_make_absolute(e);
358                         if (!p->sysvrcnd_path)
359                                 return -ENOMEM;
360                 } else
361                         p->sysvrcnd_path = NULL;
362
363                 if (strv_isempty(p->sysvrcnd_path)) {
364                         strv_free(p->sysvrcnd_path);
365
366                         p->sysvrcnd_path = strv_new(
367                                         SYSTEM_SYSVRCND_PATH,     /* /etc/rcN.d/ */
368                                         NULL);
369                         if (!p->sysvrcnd_path)
370                                 return -ENOMEM;
371                 }
372
373                 if (!path_strv_canonicalize(p->sysvinit_path))
374                         return -ENOMEM;
375
376                 if (!path_strv_canonicalize(p->sysvrcnd_path))
377                         return -ENOMEM;
378
379                 strv_uniq(p->sysvinit_path);
380                 strv_uniq(p->sysvrcnd_path);
381
382                 if (!strv_isempty(p->sysvinit_path)) {
383
384                         t = strv_join(p->sysvinit_path, "\n\t");
385                         if (!t)
386                                 return -ENOMEM;
387                         log_debug("Looking for SysV init scripts in:\n\t%s", t);
388                         free(t);
389                 } else {
390                         log_debug("Ignoring SysV init scripts.");
391                         strv_free(p->sysvinit_path);
392                         p->sysvinit_path = NULL;
393                 }
394
395                 if (!strv_isempty(p->sysvrcnd_path)) {
396
397                         t = strv_join(p->sysvrcnd_path, "\n\t");
398                         if (!t)
399                                 return -ENOMEM;
400
401                         log_debug("Looking for SysV rcN.d links in:\n\t%s", t);
402                         free(t);
403                 } else {
404                         log_debug("Ignoring SysV rcN.d links.");
405                         strv_free(p->sysvrcnd_path);
406                         p->sysvrcnd_path = NULL;
407                 }
408 #else
409                 log_debug("Disabled SysV init scripts and rcN.d links support");
410 #endif
411         }
412
413         return 0;
414 }
415
416 void lookup_paths_free(LookupPaths *p) {
417         assert(p);
418
419         strv_free(p->unit_path);
420         p->unit_path = NULL;
421
422 #ifdef HAVE_SYSV_COMPAT
423         strv_free(p->sysvinit_path);
424         strv_free(p->sysvrcnd_path);
425         p->sysvinit_path = p->sysvrcnd_path = NULL;
426 #endif
427 }