chiark / gitweb /
shared: rename path_strv_canonicalize_absolute functions
[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                 _cleanup_free_ char *data_home_parent = NULL;
129
130                 if (asprintf(&data_home, "%s/.local/share/systemd/user", home) < 0)
131                         goto fail;
132
133                 /* There is really no need for two unit dirs in $HOME,
134                  * except to be fully compliant with the XDG spec. We
135                  * now try to link the two dirs, so that we can
136                  * minimize disk seeks a little. Further down we'll
137                  * then filter out this link, if it is actually is
138                  * one. */
139
140                 if (path_get_parent(data_home, &data_home_parent) >= 0) {
141                         _cleanup_free_ char *config_home_relative = NULL;
142
143                         if (path_make_relative(data_home_parent, config_home, &config_home_relative) >= 0) {
144                                 mkdir_parents_label(data_home, 0777);
145                                 (void) symlink(config_home_relative, data_home);
146                         }
147                 }
148         }
149
150         e = getenv("XDG_DATA_DIRS");
151         if (e)
152                 data_dirs = strv_split(e, ":");
153         else
154                 data_dirs = strv_new("/usr/local/share",
155                                      "/usr/share",
156                                      NULL);
157         if (!data_dirs)
158                 goto fail;
159
160         /* Now merge everything we found. */
161         if (generator_early)
162                 if (strv_extend(&r, generator_early) < 0)
163                         goto fail;
164
165         if (config_home)
166                 if (strv_extend(&r, config_home) < 0)
167                         goto fail;
168
169         if (!strv_isempty(config_dirs))
170                 if (strv_extend_strv_concat(&r, config_dirs, "/systemd/user") < 0)
171                         goto fail;
172
173         if (strv_extend_strv(&r, (char**) config_unit_paths) < 0)
174                 goto fail;
175
176         if (generator)
177                 if (strv_extend(&r, generator) < 0)
178                         goto fail;
179
180         if (data_home)
181                 if (strv_extend(&r, data_home) < 0)
182                         goto fail;
183
184         if (!strv_isempty(data_dirs))
185                 if (strv_extend_strv_concat(&r, data_dirs, "/systemd/user") < 0)
186                         goto fail;
187
188         if (strv_extend_strv(&r, (char**) data_unit_paths) < 0)
189                 goto fail;
190
191         if (generator_late)
192                 if (strv_extend(&r, generator_late) < 0)
193                         goto fail;
194
195         if (!path_strv_make_absolute_cwd(r))
196                 goto fail;
197
198         return r;
199
200 fail:
201         strv_free(r);
202         return NULL;
203 }
204
205 int lookup_paths_init(
206                 LookupPaths *p,
207                 SystemdRunningAs running_as,
208                 bool personal,
209                 const char *root_dir,
210                 const char *generator,
211                 const char *generator_early,
212                 const char *generator_late) {
213
214         const char *e;
215
216         assert(p);
217
218         /* First priority is whatever has been passed to us via env
219          * vars */
220         e = getenv("SYSTEMD_UNIT_PATH");
221         if (e) {
222                 p->unit_path = path_split_and_make_absolute(e);
223                 if (!p->unit_path)
224                         return -ENOMEM;
225         } else
226                 p->unit_path = NULL;
227
228         if (strv_isempty(p->unit_path)) {
229                 /* Nothing is set, so let's figure something out. */
230                 strv_free(p->unit_path);
231
232                 /* For the user units we include share/ in the search
233                  * path in order to comply with the XDG basedir
234                  * spec. For the system stuff we avoid such
235                  * nonsense. OTOH we include /lib in the search path
236                  * for the system stuff but avoid it for user
237                  * stuff. */
238
239                 if (running_as == SYSTEMD_USER) {
240
241                         if (personal)
242                                 p->unit_path = user_dirs(generator, generator_early, generator_late);
243                         else
244                                 p->unit_path = strv_new(
245                                                 /* If you modify this you also want to modify
246                                                  * systemduserunitpath= in systemd.pc.in, and
247                                                  * the arrays in user_dirs() above! */
248                                                 STRV_IFNOTNULL(generator_early),
249                                                 USER_CONFIG_UNIT_PATH,
250                                                 "/etc/systemd/user",
251                                                 "/run/systemd/user",
252                                                 STRV_IFNOTNULL(generator),
253                                                 "/usr/local/lib/systemd/user",
254                                                 "/usr/local/share/systemd/user",
255                                                 USER_DATA_UNIT_PATH,
256                                                 "/usr/lib/systemd/user",
257                                                 "/usr/share/systemd/user",
258                                                 STRV_IFNOTNULL(generator_late),
259                                                 NULL);
260
261                         if (!p->unit_path)
262                                 return -ENOMEM;
263
264                 } else {
265                         p->unit_path = strv_new(
266                                         /* If you modify this you also want to modify
267                                          * systemdsystemunitpath= in systemd.pc.in! */
268                                         STRV_IFNOTNULL(generator_early),
269                                         SYSTEM_CONFIG_UNIT_PATH,
270                                         "/etc/systemd/system",
271                                         "/run/systemd/system",
272                                         STRV_IFNOTNULL(generator),
273                                         "/usr/local/lib/systemd/system",
274                                         SYSTEM_DATA_UNIT_PATH,
275                                         "/usr/lib/systemd/system",
276 #ifdef HAVE_SPLIT_USR
277                                         "/lib/systemd/system",
278 #endif
279                                         STRV_IFNOTNULL(generator_late),
280                                         NULL);
281
282                         if (!p->unit_path)
283                                 return -ENOMEM;
284                 }
285         }
286
287         if (!path_strv_resolve_uniq(p->unit_path, root_dir))
288                 return -ENOMEM;
289
290         if (!strv_isempty(p->unit_path)) {
291                 _cleanup_free_ char *t = strv_join(p->unit_path, "\n\t");
292                 if (!t)
293                         return -ENOMEM;
294                 log_debug("Looking for unit files in (higher priority first):\n\t%s", t);
295         } else {
296                 log_debug("Ignoring unit files.");
297                 strv_free(p->unit_path);
298                 p->unit_path = NULL;
299         }
300
301         if (running_as == SYSTEMD_SYSTEM) {
302 #ifdef HAVE_SYSV_COMPAT
303                 /* /etc/init.d/ compatibility does not matter to users */
304
305                 e = getenv("SYSTEMD_SYSVINIT_PATH");
306                 if (e) {
307                         p->sysvinit_path = path_split_and_make_absolute(e);
308                         if (!p->sysvinit_path)
309                                 return -ENOMEM;
310                 } else
311                         p->sysvinit_path = NULL;
312
313                 if (strv_isempty(p->sysvinit_path)) {
314                         strv_free(p->sysvinit_path);
315
316                         p->sysvinit_path = strv_new(
317                                         SYSTEM_SYSVINIT_PATH,     /* /etc/init.d/ */
318                                         NULL);
319                         if (!p->sysvinit_path)
320                                 return -ENOMEM;
321                 }
322
323                 e = getenv("SYSTEMD_SYSVRCND_PATH");
324                 if (e) {
325                         p->sysvrcnd_path = path_split_and_make_absolute(e);
326                         if (!p->sysvrcnd_path)
327                                 return -ENOMEM;
328                 } else
329                         p->sysvrcnd_path = NULL;
330
331                 if (strv_isempty(p->sysvrcnd_path)) {
332                         strv_free(p->sysvrcnd_path);
333
334                         p->sysvrcnd_path = strv_new(
335                                         SYSTEM_SYSVRCND_PATH,     /* /etc/rcN.d/ */
336                                         NULL);
337                         if (!p->sysvrcnd_path)
338                                 return -ENOMEM;
339                 }
340
341                 if (!path_strv_resolve_uniq(p->sysvinit_path, root_dir))
342                         return -ENOMEM;
343
344                 if (!path_strv_resolve_uniq(p->sysvrcnd_path, root_dir))
345                         return -ENOMEM;
346
347                 if (!strv_isempty(p->sysvinit_path)) {
348                         _cleanup_free_ char *t = strv_join(p->sysvinit_path, "\n\t");
349                         if (!t)
350                                 return -ENOMEM;
351                         log_debug("Looking for SysV init scripts in:\n\t%s", t);
352                 } else {
353                         log_debug("Ignoring SysV init scripts.");
354                         strv_free(p->sysvinit_path);
355                         p->sysvinit_path = NULL;
356                 }
357
358                 if (!strv_isempty(p->sysvrcnd_path)) {
359                         _cleanup_free_ char *t =
360                                 strv_join(p->sysvrcnd_path, "\n\t");
361                         if (!t)
362                                 return -ENOMEM;
363
364                         log_debug("Looking for SysV rcN.d links in:\n\t%s", t);
365                 } else {
366                         log_debug("Ignoring SysV rcN.d links.");
367                         strv_free(p->sysvrcnd_path);
368                         p->sysvrcnd_path = NULL;
369                 }
370 #else
371                 log_debug("SysV init scripts and rcN.d links support disabled");
372 #endif
373         }
374
375         return 0;
376 }
377
378 void lookup_paths_free(LookupPaths *p) {
379         assert(p);
380
381         strv_free(p->unit_path);
382         p->unit_path = NULL;
383
384 #ifdef HAVE_SYSV_COMPAT
385         strv_free(p->sysvinit_path);
386         strv_free(p->sysvrcnd_path);
387         p->sysvinit_path = p->sysvrcnd_path = NULL;
388 #endif
389 }