chiark / gitweb /
Assume that /proc/meminfo can be missing
[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         char *config_home = NULL, *data_home = NULL;
94         char **config_dirs = NULL, **data_dirs = NULL;
95         char **r = NULL, **t;
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                 t = strv_append(r, generator_early);
155                 if (!t)
156                         goto fail;
157                 strv_free(r);
158                 r = t;
159         }
160
161         if (config_home) {
162                 t = strv_append(r, config_home);
163                 if (!t)
164                         goto fail;
165                 strv_free(r);
166                 r = t;
167         }
168
169         if (!strv_isempty(config_dirs)) {
170                 t = strv_merge_concat(r, config_dirs, "/systemd/user");
171                 if (!t)
172                         goto finish;
173                 strv_free(r);
174                 r = t;
175         }
176
177         t = strv_merge(r, (char**) config_unit_paths);
178         if (!t)
179                 goto fail;
180         strv_free(r);
181         r = t;
182
183         if (generator) {
184                 t = strv_append(r, generator);
185                 if (!t)
186                         goto fail;
187                 strv_free(r);
188                 r = t;
189         }
190
191         if (data_home) {
192                 t = strv_append(r, data_home);
193                 if (!t)
194                         goto fail;
195                 strv_free(r);
196                 r = t;
197         }
198
199         if (!strv_isempty(data_dirs)) {
200                 t = strv_merge_concat(r, data_dirs, "/systemd/user");
201                 if (!t)
202                         goto fail;
203                 strv_free(r);
204                 r = t;
205         }
206
207         t = strv_merge(r, (char**) data_unit_paths);
208         if (!t)
209                 goto fail;
210         strv_free(r);
211         r = t;
212
213         if (generator_late) {
214                 t = strv_append(r, generator_late);
215                 if (!t)
216                         goto fail;
217                 strv_free(r);
218                 r = t;
219         }
220
221         if (!path_strv_make_absolute_cwd(r))
222                 goto fail;
223
224 finish:
225         free(config_home);
226         strv_free(config_dirs);
227         free(data_home);
228         strv_free(data_dirs);
229
230         return r;
231
232 fail:
233         strv_free(r);
234         r = NULL;
235         goto finish;
236 }
237
238 int lookup_paths_init(
239                 LookupPaths *p,
240                 SystemdRunningAs running_as,
241                 bool personal,
242                 const char *generator,
243                 const char *generator_early,
244                 const char *generator_late) {
245
246         const char *e;
247
248         assert(p);
249
250         /* First priority is whatever has been passed to us via env
251          * vars */
252         e = getenv("SYSTEMD_UNIT_PATH");
253         if (e) {
254                 p->unit_path = path_split_and_make_absolute(e);
255                 if (!p->unit_path)
256                         return -ENOMEM;
257         } else
258                 p->unit_path = NULL;
259
260         if (strv_isempty(p->unit_path)) {
261                 /* Nothing is set, so let's figure something out. */
262                 strv_free(p->unit_path);
263
264                 /* For the user units we include share/ in the search
265                  * path in order to comply with the XDG basedir
266                  * spec. For the system stuff we avoid such
267                  * nonsense. OTOH we include /lib in the search path
268                  * for the system stuff but avoid it for user
269                  * stuff. */
270
271                 if (running_as == SYSTEMD_USER) {
272
273                         if (personal)
274                                 p->unit_path = user_dirs(generator, generator_early, generator_late);
275                         else
276                                 p->unit_path = strv_new(
277                                                 /* If you modify this you also want to modify
278                                                  * systemduserunitpath= in systemd.pc.in, and
279                                                  * the arrays in user_dirs() above! */
280                                                 STRV_IFNOTNULL(generator_early),
281                                                 USER_CONFIG_UNIT_PATH,
282                                                 "/etc/systemd/user",
283                                                 "/run/systemd/user",
284                                                 STRV_IFNOTNULL(generator),
285                                                 "/usr/local/lib/systemd/user",
286                                                 "/usr/local/share/systemd/user",
287                                                 USER_DATA_UNIT_PATH,
288                                                 "/usr/lib/systemd/user",
289                                                 "/usr/share/systemd/user",
290                                                 STRV_IFNOTNULL(generator_late),
291                                                 NULL);
292
293                         if (!p->unit_path)
294                                 return -ENOMEM;
295
296                 } else {
297                         p->unit_path = strv_new(
298                                         /* If you modify this you also want to modify
299                                          * systemdsystemunitpath= in systemd.pc.in! */
300                                         STRV_IFNOTNULL(generator_early),
301                                         SYSTEM_CONFIG_UNIT_PATH,
302                                         "/etc/systemd/system",
303                                         "/run/systemd/system",
304                                         STRV_IFNOTNULL(generator),
305                                         "/usr/local/lib/systemd/system",
306                                         SYSTEM_DATA_UNIT_PATH,
307                                         "/usr/lib/systemd/system",
308 #ifdef HAVE_SPLIT_USR
309                                         "/lib/systemd/system",
310 #endif
311                                         STRV_IFNOTNULL(generator_late),
312                                         NULL);
313
314                         if (!p->unit_path)
315                                 return -ENOMEM;
316                 }
317         }
318
319         if (!path_strv_canonicalize(p->unit_path))
320                 return -ENOMEM;
321
322         strv_uniq(p->unit_path);
323
324         if (!strv_isempty(p->unit_path)) {
325                 _cleanup_free_ char *t = strv_join(p->unit_path, "\n\t");
326                 if (!t)
327                         return -ENOMEM;
328                 log_debug("Looking for unit files in (higher priority first):\n\t%s", t);
329         } else {
330                 log_debug("Ignoring unit files.");
331                 strv_free(p->unit_path);
332                 p->unit_path = NULL;
333         }
334
335         if (running_as == SYSTEMD_SYSTEM) {
336 #ifdef HAVE_SYSV_COMPAT
337                 /* /etc/init.d/ compatibility does not matter to users */
338
339                 e = getenv("SYSTEMD_SYSVINIT_PATH");
340                 if (e) {
341                         p->sysvinit_path = path_split_and_make_absolute(e);
342                         if (!p->sysvinit_path)
343                                 return -ENOMEM;
344                 } else
345                         p->sysvinit_path = NULL;
346
347                 if (strv_isempty(p->sysvinit_path)) {
348                         strv_free(p->sysvinit_path);
349
350                         p->sysvinit_path = strv_new(
351                                         SYSTEM_SYSVINIT_PATH,     /* /etc/init.d/ */
352                                         NULL);
353                         if (!p->sysvinit_path)
354                                 return -ENOMEM;
355                 }
356
357                 e = getenv("SYSTEMD_SYSVRCND_PATH");
358                 if (e) {
359                         p->sysvrcnd_path = path_split_and_make_absolute(e);
360                         if (!p->sysvrcnd_path)
361                                 return -ENOMEM;
362                 } else
363                         p->sysvrcnd_path = NULL;
364
365                 if (strv_isempty(p->sysvrcnd_path)) {
366                         strv_free(p->sysvrcnd_path);
367
368                         p->sysvrcnd_path = strv_new(
369                                         SYSTEM_SYSVRCND_PATH,     /* /etc/rcN.d/ */
370                                         NULL);
371                         if (!p->sysvrcnd_path)
372                                 return -ENOMEM;
373                 }
374
375                 if (!path_strv_canonicalize(p->sysvinit_path))
376                         return -ENOMEM;
377
378                 if (!path_strv_canonicalize(p->sysvrcnd_path))
379                         return -ENOMEM;
380
381                 strv_uniq(p->sysvinit_path);
382                 strv_uniq(p->sysvrcnd_path);
383
384                 if (!strv_isempty(p->sysvinit_path)) {
385                         _cleanup_free_ char *t = strv_join(p->sysvinit_path, "\n\t");
386                         if (!t)
387                                 return -ENOMEM;
388                         log_debug("Looking for SysV init scripts in:\n\t%s", 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                         _cleanup_free_ char *t =
397                                 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                 } else {
403                         log_debug("Ignoring SysV rcN.d links.");
404                         strv_free(p->sysvrcnd_path);
405                         p->sysvrcnd_path = NULL;
406                 }
407 #else
408                 log_debug("SysV init scripts and rcN.d links support disabled");
409 #endif
410         }
411
412         return 0;
413 }
414
415 void lookup_paths_free(LookupPaths *p) {
416         assert(p);
417
418         strv_free(p->unit_path);
419         p->unit_path = NULL;
420
421 #ifdef HAVE_SYSV_COMPAT
422         strv_free(p->sysvinit_path);
423         strv_free(p->sysvrcnd_path);
424         p->sysvinit_path = p->sysvrcnd_path = NULL;
425 #endif
426 }