]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/xdg-autostart-generator/xdg-autostart-service.c
Merge pull request #16112 from poettering/nss-systemd-block-fix
[thirdparty/systemd.git] / src / xdg-autostart-generator / xdg-autostart-service.c
CommitLineData
8feca247
BB
1/* SPDX-License-Identifier: LGPL-2.1+ */
2
3#include <errno.h>
4#include <stdio.h>
5#include <unistd.h>
6
7#include "xdg-autostart-service.h"
8
9#include "conf-parser.h"
10#include "escape.h"
11#include "unit-name.h"
12#include "path-util.h"
13#include "fd-util.h"
14#include "generator.h"
15#include "log.h"
16#include "specifier.h"
17#include "string-util.h"
18#include "nulstr-util.h"
19#include "strv.h"
20
21XdgAutostartService* xdg_autostart_service_free(XdgAutostartService *s) {
22 if (!s)
23 return NULL;
24
25 free(s->name);
26 free(s->path);
27 free(s->description);
28
29 free(s->type);
30 free(s->exec_string);
31
32 strv_free(s->only_show_in);
33 strv_free(s->not_show_in);
34
35 free(s->try_exec);
36 free(s->autostart_condition);
37 free(s->kde_autostart_condition);
38
39 free(s->gnome_autostart_phase);
40
41 return mfree(s);
42}
43
44char *xdg_autostart_service_translate_name(const char *name) {
45 _cleanup_free_ char *c = NULL, *escaped = NULL;
46 char *res;
47
48 c = strdup(name);
49 if (!c)
50 return NULL;
51
52 res = endswith(c, ".desktop");
53 if (res)
54 *res = '\0';
55
56 escaped = unit_name_escape(c);
57 if (!escaped)
58 return NULL;
59
60 return strjoin("app-", escaped, "-autostart.service");
61}
62
63static int xdg_config_parse_bool(
64 const char *unit,
65 const char *filename,
66 unsigned line,
67 const char *section,
68 unsigned section_line,
69 const char *lvalue,
70 int ltype,
71 const char *rvalue,
72 void *data,
73 void *userdata) {
74
75 bool *b = data;
8feca247
BB
76
77 assert(filename);
78 assert(lvalue);
79 assert(rvalue);
80 assert(data);
81
82 if (streq(rvalue, "true"))
83 *b = true;
84 else if (streq(rvalue, "false"))
85 *b = false;
86 else
6527b019 87 return log_syntax(unit, LOG_ERR, filename, line, SYNTHETIC_ERRNO(EINVAL), "Invalid value for boolean: %s", rvalue);
8feca247
BB
88
89 return 0;
90}
91
92/* Unescapes the string in-place, returns non-zero status on error. */
93static int xdg_unescape_string(
94 const char *unit,
95 const char *filename,
96 int line,
97 char *str) {
98
99 char *in;
100 char *out;
101
102 assert(str);
103
104 in = out = str;
105
106 for (; *in; in++, out++) {
107 if (*in == '\\') {
108 /* Move forward, and ensure it is a valid escape. */
109 in++;
110
111 switch (*in) {
112 case 's':
113 *out = ' ';
114 break;
115 case 'n':
116 *out = '\n';
117 break;
118 case 't':
119 *out = '\t';
120 break;
121 case 'r':
122 *out = '\r';
123 break;
124 case '\\':
125 *out = '\\';
126 break;
127 case ';':
128 /* Technically only permitted for strv. */
129 *out = ';';
130 break;
131 default:
132 return log_syntax(unit, LOG_ERR, filename, line, SYNTHETIC_ERRNO(EINVAL), "Undefined escape sequence \\%c.", *in);
133 }
134
135 continue;
136 }
137
138 *out = *in;
139 }
140 *out = '\0';
141
142 return 0;
143}
144
145/* Note: We do not bother with unescaping the strings, hence the _raw postfix. */
146static int xdg_config_parse_string(
147 const char *unit,
148 const char *filename,
149 unsigned line,
150 const char *section,
151 unsigned section_line,
152 const char *lvalue,
153 int ltype,
154 const char *rvalue,
155 void *data,
156 void *userdata) {
157
158 _cleanup_free_ char *res = NULL;
159 char **out = data;
160 int r;
161
162 assert(filename);
163 assert(lvalue);
164 assert(rvalue);
165 assert(data);
166
167 /* XDG does not allow duplicate definitions. */
168 if (*out) {
169 log_syntax(unit, LOG_ERR, filename, line, 0, "Key %s was defined multiple times, ignoring.", lvalue);
170 return 0;
171 }
172
173 res = strdup(rvalue);
174 if (!res)
175 return log_oom();
176
177 r = xdg_unescape_string(unit, filename, line, res);
178 if (r < 0)
179 return r;
180
181 *out = TAKE_PTR(res);
182 return 0;
183}
184
185static int xdg_config_parse_strv(
186 const char *unit,
187 const char *filename,
188 unsigned line,
189 const char *section,
190 unsigned section_line,
191 const char *lvalue,
192 int ltype,
193 const char *rvalue,
194 void *data,
195 void *userdata) {
196
197 char ***sv = data;
198 const char *start;
199 const char *end;
200 int r;
201
202 assert(filename);
203 assert(lvalue);
204 assert(rvalue);
205 assert(data);
206
207 /* XDG does not allow duplicate definitions. */
208 if (*sv) {
209 log_syntax(unit, LOG_ERR, filename, line, 0, "Key %s was defined multiple times, ignoring.", lvalue);
210 return 0;
211 }
212
213 *sv = strv_new(NULL);
214 if (!*sv)
215 return log_oom();
216
217 /* We cannot use strv_split because it does not handle escaping correctly. */
218 start = rvalue;
219
220 for (end = start; *end; end++) {
221 if (*end == '\\') {
222 /* Move forward, and ensure it is a valid escape. */
223 end++;
224 if (strchr("sntr\\;", *end) == NULL) {
225 log_syntax(unit, LOG_ERR, filename, line, 0, "Undefined escape sequence \\%c.", *end);
226 return 0;
227 }
228 continue;
229 }
230
231 if (*end == ';') {
232 _cleanup_free_ char *copy = NULL;
233
234 copy = strndup(start, end - start);
235 if (!copy)
236 return log_oom();
237 r = xdg_unescape_string(unit, filename, line, copy);
238 if (r < 0)
239 return r;
240 r = strv_consume(sv, TAKE_PTR(copy));
241 if (r < 0)
242 return log_oom();
243
244 start = end + 1;
245 }
246 }
247
248 /* Any trailing entry should be ignored if it is empty. */
249 if (end > start) {
250 r = strv_extend(sv, start);
251 if (r < 0)
252 return log_oom();
253 }
254
255 return 0;
256}
257
258static int xdg_config_item_table_lookup(
259 const void *table,
260 const char *section,
261 const char *lvalue,
262 ConfigParserCallback *func,
263 int *ltype,
264 void **data,
265 void *userdata) {
266
267 assert(lvalue);
268
269 /* Ignore any keys with [] as those are translations. */
270 if (strchr(lvalue, '[')) {
271 *func = NULL;
272 *ltype = 0;
273 *data = NULL;
274 return 1;
275 }
276
277 return config_item_table_lookup(table, section, lvalue, func, ltype, data, userdata);
278}
279
280XdgAutostartService *xdg_autostart_service_parse_desktop(const char *path) {
281 _cleanup_(xdg_autostart_service_freep) XdgAutostartService *service = NULL;
282 int r;
283
284 service = new0(XdgAutostartService, 1);
285 if (!service)
286 return NULL;
287
288 service->path = strdup(path);
289 if (!service->path)
290 return NULL;
291
292 const ConfigTableItem items[] = {
293 { "Desktop Entry", "Name", xdg_config_parse_string, 0, &service->description},
294 { "Desktop Entry", "Exec", xdg_config_parse_string, 0, &service->exec_string},
295 { "Desktop Entry", "TryExec", xdg_config_parse_string, 0, &service->try_exec},
296 { "Desktop Entry", "Type", xdg_config_parse_string, 0, &service->type},
297 { "Desktop Entry", "OnlyShowIn", xdg_config_parse_strv, 0, &service->only_show_in},
298 { "Desktop Entry", "NotShowIn", xdg_config_parse_strv, 0, &service->not_show_in},
299 { "Desktop Entry", "Hidden", xdg_config_parse_bool, 0, &service->hidden},
300 { "Desktop Entry", "AutostartCondition", xdg_config_parse_string, 0, &service->autostart_condition},
301 { "Desktop Entry", "X-KDE-autostart-condition", xdg_config_parse_string, 0, &service->kde_autostart_condition},
302 { "Desktop Entry", "X-GNOME-Autostart-Phase", xdg_config_parse_string, 0, &service->gnome_autostart_phase},
303 { "Desktop Entry", "X-systemd-skip", xdg_config_parse_bool, 0, &service->systemd_skip},
304
305 /* Common entries that we do not use currently. */
306 { "Desktop Entry", "Categories", NULL, 0, NULL},
307 { "Desktop Entry", "Comment", NULL, 0, NULL},
308 { "Desktop Entry", "Encoding", NULL, 0, NULL},
309 { "Desktop Entry", "GenericName", NULL, 0, NULL},
310 { "Desktop Entry", "Icon", NULL, 0, NULL},
311 { "Desktop Entry", "Keywords", NULL, 0, NULL},
312 { "Desktop Entry", "NoDisplay", NULL, 0, NULL},
313 { "Desktop Entry", "StartupNotify", NULL, 0, NULL},
314 { "Desktop Entry", "Terminal", NULL, 0, NULL},
315 { "Desktop Entry", "Version", NULL, 0, NULL},
316 {}
317 };
318
319 r = config_parse(NULL, service->path, NULL,
320 "Desktop Entry\0",
321 xdg_config_item_table_lookup, items,
4f9ff96a
LP
322 CONFIG_PARSE_WARN, service,
323 NULL);
8feca247
BB
324 /* If parsing failed, only hide the file so it will still mask others. */
325 if (r < 0) {
326 log_warning_errno(r, "Failed to parse %s, ignoring it", service->path);
327 service->hidden = true;
328 }
329
330 return TAKE_PTR(service);
331}
332
333int xdg_autostart_format_exec_start(
334 const char *exec,
335 char **ret_exec_start) {
336
337 _cleanup_strv_free_ char **exec_split = NULL;
338 char *res;
339 size_t n, i;
340 bool first_arg;
341 int r;
342
343 /*
344 * Unfortunately, there is a mismatch between systemd's idea of $PATH
345 * and XDGs. i.e. we need to ensure that we have an absolute path to
346 * support cases where $PATH has been modified from the default set.
347 *
348 * Note that this is only needed for development environments though;
349 * so while it is important, this should have no effect in production
350 * environments.
351 *
352 * To be compliant with the XDG specification, we also need to strip
353 * certain parameters and such. Doing so properly makes parsing the
354 * command line unavoidable.
355 *
356 * NOTE: Technically, XDG only specifies " as quotes, while this also
357 * accepts '.
358 */
359 exec_split = strv_split_full(exec, WHITESPACE, SPLIT_QUOTES | SPLIT_RELAX);
360 if (!exec_split)
361 return -ENOMEM;
362
363 if (strv_isempty(exec_split))
364 return log_warning_errno(SYNTHETIC_ERRNO(EINVAL), "Exec line is empty");
365
366 first_arg = true;
367 for (i = n = 0; exec_split[i]; i++) {
368 _cleanup_free_ char *c = NULL, *raw = NULL, *p = NULL, *escaped = NULL, *quoted = NULL;
369
370 r = cunescape(exec_split[i], 0, &c);
371 if (r < 0)
372 return log_debug_errno(r, "Failed to unescape '%s': %m", exec_split[i]);
373
374 if (first_arg) {
375 _cleanup_free_ char *executable = NULL;
376
377 /* This is the executable, find it in $PATH */
378 first_arg = false;
379 r = find_binary(c, &executable);
380 if (r < 0)
381 return log_info_errno(r, "Exec binary '%s' does not exist: %m", c);
382
383 escaped = cescape(executable);
384 if (!escaped)
385 return log_oom();
386
387 free(exec_split[n]);
388 exec_split[n++] = TAKE_PTR(escaped);
389 continue;
390 }
391
392 /*
393 * Remove any standardised XDG fields; we assume they never appear as
394 * part of another argument as that just does not make any sense as
395 * they can be empty (GLib will e.g. turn "%f" into an empty argument).
396 * Other implementations may handle this differently.
397 */
398 if (STR_IN_SET(c,
399 "%f", "%F",
400 "%u", "%U",
401 "%d", "%D",
402 "%n", "%N",
403 "%i", /* Location of icon, could be implemented. */
404 "%c", /* Translated application name, could be implemented. */
405 "%k", /* Location of desktop file, could be implemented. */
406 "%v",
407 "%m"
408 ))
409 continue;
410
411 /*
412 * %% -> % and then % -> %% means that we correctly quote any %
413 * and also quote any left over (and invalid) % specifier from
414 * the desktop file.
415 */
416 raw = strreplace(c, "%%", "%");
417 if (!raw)
418 return log_oom();
419 p = strreplace(raw, "%", "%%");
420 if (!p)
421 return log_oom();
422 escaped = cescape(p);
423 if (!escaped)
424 return log_oom();
425
426 quoted = strjoin("\"", escaped, "\"");
427 if (!quoted)
428 return log_oom();
429
430 free(exec_split[n]);
431 exec_split[n++] = TAKE_PTR(quoted);
432 }
433 for (; exec_split[n]; n++)
434 exec_split[n] = mfree(exec_split[n]);
435
436 res = strv_join(exec_split, " ");
437 if (!res)
438 return log_oom();
439
440 *ret_exec_start = res;
441 return 0;
442}
443
444static int xdg_autostart_generate_desktop_condition(
445 FILE *f,
446 const char *test_binary,
447 const char *condition) {
448
449 int r;
450
451 /* Generate an ExecCondition for GNOME autostart condition */
452 if (!isempty(condition)) {
453 _cleanup_free_ char *gnome_autostart_condition_path = NULL, *e_autostart_condition = NULL;
454
455 r = find_binary(test_binary, &gnome_autostart_condition_path);
456 if (r < 0) {
457 log_full_errno(r == -ENOENT ? LOG_INFO : LOG_WARNING, r,
458 "%s not found: %m", test_binary);
459 fprintf(f, "# ExecCondition using %s skipped due to missing binary.\n", test_binary);
460 return r;
461 }
462
463 e_autostart_condition = cescape(condition);
464 if (!e_autostart_condition)
465 return log_oom();
466
467 fprintf(f,
468 "ExecCondition=%s --condition \"%s\"\n",
469 gnome_autostart_condition_path,
470 e_autostart_condition);
471 }
472
473 return 0;
474}
475
476int xdg_autostart_service_generate_unit(
477 XdgAutostartService *service,
478 const char *dest) {
479
480 _cleanup_free_ char *path_escaped = NULL, *exec_start = NULL, *unit = NULL;
481 _cleanup_fclose_ FILE *f = NULL;
482 int r;
483
484 assert(service);
485
486 /* Nothing to do for hidden services. */
487 if (service->hidden) {
488 log_info("Not generating service for XDG autostart %s, it is hidden.", service->name);
489 return 0;
490 }
491
492 if (service->systemd_skip) {
493 log_info("Not generating service for XDG autostart %s, should be skipped by generator.", service->name);
494 return 0;
495 }
496
497 /* Nothing to do if type is not Application. */
498 if (!streq_ptr(service->type, "Application")) {
566cb7e2 499 log_info("Not generating service for XDG autostart %s, only Type=Application is supported.", service->name);
8feca247
BB
500 return 0;
501 }
502
503 if (!service->exec_string) {
504 log_warning("Not generating service for XDG autostart %s, it is has no Exec= line.", service->name);
505 return 0;
506 }
507
508 /*
509 * The TryExec key cannot be checked properly from the systemd unit,
510 * it is trivial to check using find_binary though.
511 */
512 if (service->try_exec) {
513 r = find_binary(service->try_exec, NULL);
514 if (r < 0) {
515 log_full_errno(r == -ENOENT ? LOG_INFO : LOG_WARNING, r,
516 "Not generating service for XDG autostart %s, could not find TryExec= binary %s: %m",
517 service->name, service->try_exec);
518 return 0;
519 }
520 }
521
522 r = xdg_autostart_format_exec_start(service->exec_string, &exec_start);
523 if (r < 0) {
524 log_warning_errno(r,
525 "Not generating service for XDG autostart %s, error parsing Exec= line: %m",
526 service->name);
527 return 0;
528 }
529
9d9a9500
BB
530 if (service->gnome_autostart_phase) {
531 /* There is no explicit value for the "Application" phase. */
532 log_info("Not generating service for XDG autostart %s, startup phases are not supported.",
8feca247
BB
533 service->name);
534 return 0;
535 }
536
537 path_escaped = specifier_escape(service->path);
538 if (!path_escaped)
539 return log_oom();
540
541 unit = path_join(dest, service->name);
542 if (!unit)
543 return log_oom();
544
545 f = fopen(unit, "wxe");
546 if (!f)
547 return log_error_errno(errno, "Failed to create unit file %s: %m", unit);
548
549 fprintf(f,
550 "# Automatically generated by systemd-xdg-autostart-generator\n\n"
551 "[Unit]\n"
552 "Documentation=man:systemd-xdg-autostart-generator(8)\n"
553 "SourcePath=%s\n"
554 "PartOf=graphical-session.target\n\n",
555 path_escaped);
556
557 if (service->description) {
558 _cleanup_free_ char *t = NULL;
559
560 t = specifier_escape(service->description);
561 if (!t)
562 return log_oom();
563
564 fprintf(f, "Description=%s\n", t);
565 }
566
9d9a9500 567 /* Only start after the session is ready. */
8feca247
BB
568 fprintf(f,
569 "After=graphical-session.target\n");
570
571 fprintf(f,
572 "\n[Service]\n"
573 "Type=simple\n"
574 "ExecStart=:%s\n"
575 "Restart=no\n"
576 "TimeoutSec=5s\n"
577 "Slice=app.slice\n",
578 exec_start);
579
580 /* Generate an ExecCondition to check $XDG_CURRENT_DESKTOP */
581 if (!strv_isempty(service->only_show_in) || !strv_isempty(service->not_show_in)) {
582 _cleanup_free_ char *only_show_in = NULL, *not_show_in = NULL, *e_only_show_in = NULL, *e_not_show_in = NULL;
583
584 only_show_in = strv_join(service->only_show_in, ":");
585 not_show_in = strv_join(service->not_show_in, ":");
586 if (!only_show_in || !not_show_in)
587 return log_oom();
588
589 e_only_show_in = cescape(only_show_in);
590 e_not_show_in = cescape(not_show_in);
591 if (!e_only_show_in || !e_not_show_in)
592 return log_oom();
593
594 /* Just assume the values are reasonably sane */
595 fprintf(f,
596 "ExecCondition=" ROOTLIBEXECDIR "/systemd-xdg-autostart-condition \"%s\" \"%s\"\n",
597 e_only_show_in,
598 e_not_show_in);
599 }
600
601 r = xdg_autostart_generate_desktop_condition(f,
602 "gnome-systemd-autostart-condition",
603 service->autostart_condition);
604 if (r < 0)
605 return r;
606
607 r = xdg_autostart_generate_desktop_condition(f,
608 "kde-systemd-start-condition",
609 service->kde_autostart_condition);
610 if (r < 0)
611 return r;
612
613 (void) generator_add_symlink(dest, "xdg-desktop-autostart.target", "wants", service->name);
614
615 return 0;
616}