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