chiark / gitweb /
63af43cdbb0de55542e54e54b88f0321a5d8bb70
[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 *generator,
202                 const char *generator_early,
203                 const char *generator_late) {
204
205         const char *e;
206
207         assert(p);
208
209         /* First priority is whatever has been passed to us via env
210          * vars */
211         e = getenv("SYSTEMD_UNIT_PATH");
212         if (e) {
213                 p->unit_path = path_split_and_make_absolute(e);
214                 if (!p->unit_path)
215                         return -ENOMEM;
216         } else
217                 p->unit_path = NULL;
218
219         if (strv_isempty(p->unit_path)) {
220                 /* Nothing is set, so let's figure something out. */
221                 strv_free(p->unit_path);
222
223                 /* For the user units we include share/ in the search
224                  * path in order to comply with the XDG basedir
225                  * spec. For the system stuff we avoid such
226                  * nonsense. OTOH we include /lib in the search path
227                  * for the system stuff but avoid it for user
228                  * stuff. */
229
230                 if (running_as == SYSTEMD_USER) {
231
232                         if (personal)
233                                 p->unit_path = user_dirs(generator, generator_early, generator_late);
234                         else
235                                 p->unit_path = strv_new(
236                                                 /* If you modify this you also want to modify
237                                                  * systemduserunitpath= in systemd.pc.in, and
238                                                  * the arrays in user_dirs() above! */
239                                                 STRV_IFNOTNULL(generator_early),
240                                                 USER_CONFIG_UNIT_PATH,
241                                                 "/etc/systemd/user",
242                                                 "/run/systemd/user",
243                                                 STRV_IFNOTNULL(generator),
244                                                 "/usr/local/lib/systemd/user",
245                                                 "/usr/local/share/systemd/user",
246                                                 USER_DATA_UNIT_PATH,
247                                                 "/usr/lib/systemd/user",
248                                                 "/usr/share/systemd/user",
249                                                 STRV_IFNOTNULL(generator_late),
250                                                 NULL);
251
252                         if (!p->unit_path)
253                                 return -ENOMEM;
254
255                 } else {
256                         p->unit_path = strv_new(
257                                         /* If you modify this you also want to modify
258                                          * systemdsystemunitpath= in systemd.pc.in! */
259                                         STRV_IFNOTNULL(generator_early),
260                                         SYSTEM_CONFIG_UNIT_PATH,
261                                         "/etc/systemd/system",
262                                         "/run/systemd/system",
263                                         STRV_IFNOTNULL(generator),
264                                         "/usr/local/lib/systemd/system",
265                                         SYSTEM_DATA_UNIT_PATH,
266                                         "/usr/lib/systemd/system",
267 #ifdef HAVE_SPLIT_USR
268                                         "/lib/systemd/system",
269 #endif
270                                         STRV_IFNOTNULL(generator_late),
271                                         NULL);
272
273                         if (!p->unit_path)
274                                 return -ENOMEM;
275                 }
276         }
277
278         if (!path_strv_canonicalize_absolute(p->unit_path, NULL))
279                 return -ENOMEM;
280
281         strv_uniq(p->unit_path);
282
283         if (!strv_isempty(p->unit_path)) {
284                 _cleanup_free_ char *t = strv_join(p->unit_path, "\n\t");
285                 if (!t)
286                         return -ENOMEM;
287                 log_debug("Looking for unit files in (higher priority first):\n\t%s", t);
288         } else {
289                 log_debug("Ignoring unit files.");
290                 strv_free(p->unit_path);
291                 p->unit_path = NULL;
292         }
293
294         if (running_as == SYSTEMD_SYSTEM) {
295 #ifdef HAVE_SYSV_COMPAT
296                 /* /etc/init.d/ compatibility does not matter to users */
297
298                 e = getenv("SYSTEMD_SYSVINIT_PATH");
299                 if (e) {
300                         p->sysvinit_path = path_split_and_make_absolute(e);
301                         if (!p->sysvinit_path)
302                                 return -ENOMEM;
303                 } else
304                         p->sysvinit_path = NULL;
305
306                 if (strv_isempty(p->sysvinit_path)) {
307                         strv_free(p->sysvinit_path);
308
309                         p->sysvinit_path = strv_new(
310                                         SYSTEM_SYSVINIT_PATH,     /* /etc/init.d/ */
311                                         NULL);
312                         if (!p->sysvinit_path)
313                                 return -ENOMEM;
314                 }
315
316                 e = getenv("SYSTEMD_SYSVRCND_PATH");
317                 if (e) {
318                         p->sysvrcnd_path = path_split_and_make_absolute(e);
319                         if (!p->sysvrcnd_path)
320                                 return -ENOMEM;
321                 } else
322                         p->sysvrcnd_path = NULL;
323
324                 if (strv_isempty(p->sysvrcnd_path)) {
325                         strv_free(p->sysvrcnd_path);
326
327                         p->sysvrcnd_path = strv_new(
328                                         SYSTEM_SYSVRCND_PATH,     /* /etc/rcN.d/ */
329                                         NULL);
330                         if (!p->sysvrcnd_path)
331                                 return -ENOMEM;
332                 }
333
334                 if (!path_strv_canonicalize_absolute(p->sysvinit_path, NULL))
335                         return -ENOMEM;
336
337                 if (!path_strv_canonicalize_absolute(p->sysvrcnd_path, NULL))
338                         return -ENOMEM;
339
340                 strv_uniq(p->sysvinit_path);
341                 strv_uniq(p->sysvrcnd_path);
342
343                 if (!strv_isempty(p->sysvinit_path)) {
344                         _cleanup_free_ char *t = strv_join(p->sysvinit_path, "\n\t");
345                         if (!t)
346                                 return -ENOMEM;
347                         log_debug("Looking for SysV init scripts in:\n\t%s", t);
348                 } else {
349                         log_debug("Ignoring SysV init scripts.");
350                         strv_free(p->sysvinit_path);
351                         p->sysvinit_path = NULL;
352                 }
353
354                 if (!strv_isempty(p->sysvrcnd_path)) {
355                         _cleanup_free_ char *t =
356                                 strv_join(p->sysvrcnd_path, "\n\t");
357                         if (!t)
358                                 return -ENOMEM;
359
360                         log_debug("Looking for SysV rcN.d links in:\n\t%s", t);
361                 } else {
362                         log_debug("Ignoring SysV rcN.d links.");
363                         strv_free(p->sysvrcnd_path);
364                         p->sysvrcnd_path = NULL;
365                 }
366 #else
367                 log_debug("SysV init scripts and rcN.d links support disabled");
368 #endif
369         }
370
371         return 0;
372 }
373
374 void lookup_paths_free(LookupPaths *p) {
375         assert(p);
376
377         strv_free(p->unit_path);
378         p->unit_path = NULL;
379
380 #ifdef HAVE_SYSV_COMPAT
381         strv_free(p->sysvinit_path);
382         strv_free(p->sysvrcnd_path);
383         p->sysvinit_path = p->sysvrcnd_path = NULL;
384 #endif
385 }