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