chiark / gitweb /
tests: add tests for util.c
[elogind.git] / src / run / run.c
1 /*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
2
3 /***
4   This file is part of systemd.
5
6   Copyright 2013 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 <stdio.h>
23 #include <getopt.h>
24
25 #include "sd-bus.h"
26 #include "bus-util.h"
27 #include "strv.h"
28 #include "build.h"
29 #include "unit-name.h"
30 #include "env-util.h"
31 #include "path-util.h"
32 #include "bus-error.h"
33
34 static bool arg_scope = false;
35 static bool arg_remain_after_exit = false;
36 static const char *arg_unit = NULL;
37 static const char *arg_description = NULL;
38 static const char *arg_slice = NULL;
39 static bool arg_send_sighup = false;
40 static BusTransport arg_transport = BUS_TRANSPORT_LOCAL;
41 static const char *arg_host = NULL;
42 static bool arg_user = false;
43 static const char *arg_service_type = NULL;
44 static const char *arg_exec_user = NULL;
45 static const char *arg_exec_group = NULL;
46 static int arg_nice = 0;
47 static bool arg_nice_set = false;
48 static char **arg_environment = NULL;
49 static char **arg_property = NULL;
50
51 static void help(void) {
52         printf("%s [OPTIONS...] COMMAND [ARGS...]\n\n"
53                "Run the specified command in a transient scope or service unit.\n\n"
54                "  -h --help                 Show this help\n"
55                "     --version              Show package version\n"
56                "     --user                 Run as user unit\n"
57                "  -H --host=[USER@]HOST     Operate on remote host\n"
58                "  -M --machine=CONTAINER    Operate on local container\n"
59                "     --scope                Run this as scope rather than service\n"
60                "     --unit=UNIT            Run under the specified unit name\n"
61                "  -p --property=NAME=VALUE  Set unit property\n"
62                "     --description=TEXT     Description for unit\n"
63                "     --slice=SLICE          Run in the specified slice\n"
64                "  -r --remain-after-exit    Leave service around until explicitly stopped\n"
65                "     --send-sighup          Send SIGHUP when terminating\n"
66                "     --service-type=TYPE    Service type\n"
67                "     --uid=USER             Run as system user\n"
68                "     --gid=GROUP            Run as system group\n"
69                "     --nice=NICE            Nice level\n"
70                "     --setenv=NAME=VALUE    Set environment\n",
71                program_invocation_short_name);
72 }
73
74 static int parse_argv(int argc, char *argv[]) {
75
76         enum {
77                 ARG_VERSION = 0x100,
78                 ARG_USER,
79                 ARG_SYSTEM,
80                 ARG_SCOPE,
81                 ARG_UNIT,
82                 ARG_DESCRIPTION,
83                 ARG_SLICE,
84                 ARG_SEND_SIGHUP,
85                 ARG_EXEC_USER,
86                 ARG_EXEC_GROUP,
87                 ARG_SERVICE_TYPE,
88                 ARG_NICE,
89                 ARG_SETENV
90         };
91
92         static const struct option options[] = {
93                 { "help",              no_argument,       NULL, 'h'              },
94                 { "version",           no_argument,       NULL, ARG_VERSION      },
95                 { "user",              no_argument,       NULL, ARG_USER         },
96                 { "system",            no_argument,       NULL, ARG_SYSTEM       },
97                 { "scope",             no_argument,       NULL, ARG_SCOPE        },
98                 { "unit",              required_argument, NULL, ARG_UNIT         },
99                 { "description",       required_argument, NULL, ARG_DESCRIPTION  },
100                 { "slice",             required_argument, NULL, ARG_SLICE        },
101                 { "remain-after-exit", no_argument,       NULL, 'r'              },
102                 { "send-sighup",       no_argument,       NULL, ARG_SEND_SIGHUP  },
103                 { "host",              required_argument, NULL, 'H'              },
104                 { "machine",           required_argument, NULL, 'M'              },
105                 { "service-type",      required_argument, NULL, ARG_SERVICE_TYPE },
106                 { "uid",               required_argument, NULL, ARG_EXEC_USER    },
107                 { "gid",               required_argument, NULL, ARG_EXEC_GROUP   },
108                 { "nice",              required_argument, NULL, ARG_NICE         },
109                 { "setenv",            required_argument, NULL, ARG_SETENV       },
110                 { "property",          required_argument, NULL, 'p'              },
111                 {},
112         };
113
114         int r, c;
115
116         assert(argc >= 0);
117         assert(argv);
118
119         while ((c = getopt_long(argc, argv, "+hrH:M:p:", options, NULL)) >= 0)
120
121                 switch (c) {
122
123                 case 'h':
124                         help();
125                         return 0;
126
127                 case ARG_VERSION:
128                         puts(PACKAGE_STRING);
129                         puts(SYSTEMD_FEATURES);
130                         return 0;
131
132                 case ARG_USER:
133                         arg_user = true;
134                         break;
135
136                 case ARG_SYSTEM:
137                         arg_user = false;
138                         break;
139
140                 case ARG_SCOPE:
141                         arg_scope = true;
142                         break;
143
144                 case ARG_UNIT:
145                         arg_unit = optarg;
146                         break;
147
148                 case ARG_DESCRIPTION:
149                         arg_description = optarg;
150                         break;
151
152                 case ARG_SLICE:
153                         arg_slice = optarg;
154                         break;
155
156                 case ARG_SEND_SIGHUP:
157                         arg_send_sighup = true;
158                         break;
159
160                 case 'r':
161                         arg_remain_after_exit = true;
162                         break;
163
164                 case 'H':
165                         arg_transport = BUS_TRANSPORT_REMOTE;
166                         arg_host = optarg;
167                         break;
168
169                 case 'M':
170                         arg_transport = BUS_TRANSPORT_CONTAINER;
171                         arg_host = optarg;
172                         break;
173
174                 case ARG_SERVICE_TYPE:
175                         arg_service_type = optarg;
176                         break;
177
178                 case ARG_EXEC_USER:
179                         arg_exec_user = optarg;
180                         break;
181
182                 case ARG_EXEC_GROUP:
183                         arg_exec_group = optarg;
184                         break;
185
186                 case ARG_NICE:
187                         r = safe_atoi(optarg, &arg_nice);
188                         if (r < 0 || arg_nice < PRIO_MIN || arg_nice >= PRIO_MAX) {
189                                 log_error("Failed to parse nice value");
190                                 return -EINVAL;
191                         }
192
193                         arg_nice_set = true;
194                         break;
195
196                 case ARG_SETENV:
197
198                         if (strv_extend(&arg_environment, optarg) < 0)
199                                 return log_oom();
200
201                         break;
202
203                 case 'p':
204
205                         if (strv_extend(&arg_property, optarg) < 0)
206                                 return log_oom();
207
208                         break;
209
210                 case '?':
211                         return -EINVAL;
212
213                 default:
214                         assert_not_reached("Unhandled option");
215                 }
216
217         if (optind >= argc) {
218                 log_error("Command line to execute required.");
219                 return -EINVAL;
220         }
221
222         if (arg_user && arg_transport != BUS_TRANSPORT_LOCAL) {
223                 log_error("Execution in user context is not supported on non-local systems.");
224                 return -EINVAL;
225         }
226
227         if (arg_scope && arg_transport != BUS_TRANSPORT_LOCAL) {
228                 log_error("Scope execution is not supported on non-local systems.");
229                 return -EINVAL;
230         }
231
232         if (arg_scope && (arg_remain_after_exit || arg_service_type)) {
233                 log_error("--remain-after-exit and --service-type= are not supported in --scope mode.");
234                 return -EINVAL;
235         }
236
237         return 1;
238 }
239
240 static int message_start_transient_unit_new(sd_bus *bus, const char *name, sd_bus_message **ret) {
241         _cleanup_bus_message_unref_ sd_bus_message *m = NULL;
242         char **i;
243         int r;
244
245         assert(bus);
246         assert(name);
247         assert(ret);
248
249         r = sd_bus_message_new_method_call(
250                         bus,
251                         &m,
252                         "org.freedesktop.systemd1",
253                         "/org/freedesktop/systemd1",
254                         "org.freedesktop.systemd1.Manager",
255                         "StartTransientUnit");
256         if (r < 0)
257                 return r;
258
259         r = sd_bus_message_append(m, "ss", name, "fail");
260         if (r < 0)
261                 return r;
262
263         r = sd_bus_message_open_container(m, 'a', "(sv)");
264         if (r < 0)
265                 return r;
266
267         STRV_FOREACH(i, arg_property) {
268                 r = sd_bus_message_open_container(m, 'r', "sv");
269                 if (r < 0)
270                         return r;
271
272                 r = bus_append_unit_property_assignment(m, *i);
273                 if (r < 0)
274                         return r;
275
276                 r = sd_bus_message_close_container(m);
277                 if (r < 0)
278                         return r;
279         }
280
281         r = sd_bus_message_append(m, "(sv)", "Description", "s", arg_description);
282         if (r < 0)
283                 return r;
284
285         if (!isempty(arg_slice)) {
286                 _cleanup_free_ char *slice;
287
288                 slice = unit_name_mangle_with_suffix(arg_slice, MANGLE_NOGLOB, ".slice");
289                 if (!slice)
290                         return -ENOMEM;
291
292                 r = sd_bus_message_append(m, "(sv)", "Slice", "s", slice);
293                 if (r < 0)
294                         return r;
295         }
296
297         if (arg_send_sighup) {
298                 r = sd_bus_message_append(m, "(sv)", "SendSIGHUP", "b", arg_send_sighup);
299                 if (r < 0)
300                         return r;
301         }
302
303         *ret = m;
304         m = NULL;
305
306         return 0;
307 }
308
309 static int message_start_transient_unit_send(sd_bus *bus, sd_bus_message *m, sd_bus_error *error, sd_bus_message **reply) {
310         int r;
311
312         assert(bus);
313         assert(m);
314
315         r = sd_bus_message_close_container(m);
316         if (r < 0)
317                 return r;
318
319         r = sd_bus_message_append(m, "a(sa(sv))", 0);
320         if (r < 0)
321                 return r;
322
323         return sd_bus_call(bus, m, 0, error, reply);
324 }
325
326 static int start_transient_service(
327                 sd_bus *bus,
328                 char **argv,
329                 sd_bus_error *error) {
330
331         _cleanup_bus_message_unref_ sd_bus_message *m = NULL;
332         _cleanup_free_ char *name = NULL;
333         int r;
334
335         if (arg_unit) {
336                 name = unit_name_mangle_with_suffix(arg_unit, MANGLE_NOGLOB, ".service");
337                 if (!name)
338                         return log_oom();
339         } else if (asprintf(&name, "run-"PID_FMT".service", getpid()) < 0)
340                 return log_oom();
341
342         r = message_start_transient_unit_new(bus, name, &m);
343         if (r < 0)
344                 return bus_log_create_error(r);
345
346         if (arg_remain_after_exit) {
347                 r = sd_bus_message_append(m, "(sv)", "RemainAfterExit", "b", arg_remain_after_exit);
348                 if (r < 0)
349                         return bus_log_create_error(r);
350         }
351
352         if (arg_service_type) {
353                 r = sd_bus_message_append(m, "(sv)", "Type", "s", arg_service_type);
354                 if (r < 0)
355                         return bus_log_create_error(r);
356         }
357
358         if (arg_exec_user) {
359                 r = sd_bus_message_append(m, "(sv)", "User", "s", arg_exec_user);
360                 if (r < 0)
361                         return bus_log_create_error(r);
362         }
363
364         if (arg_exec_group) {
365                 r = sd_bus_message_append(m, "(sv)", "Group", "s", arg_exec_group);
366                 if (r < 0)
367                         return bus_log_create_error(r);
368         }
369
370         if (arg_nice_set) {
371                 r = sd_bus_message_append(m, "(sv)", "Nice", "i", arg_nice);
372                 if (r < 0)
373                         return bus_log_create_error(r);
374         }
375
376         if (!strv_isempty(arg_environment)) {
377                 r = sd_bus_message_open_container(m, 'r', "sv");
378                 if (r < 0)
379                         return bus_log_create_error(r);
380
381                 r = sd_bus_message_append(m, "s", "Environment");
382                 if (r < 0)
383                         return bus_log_create_error(r);
384
385                 r = sd_bus_message_open_container(m, 'v', "as");
386                 if (r < 0)
387                         return bus_log_create_error(r);
388
389                 r = sd_bus_message_append_strv(m, arg_environment);
390                 if (r < 0)
391                         return bus_log_create_error(r);
392
393                 r = sd_bus_message_close_container(m);
394                 if (r < 0)
395                         return bus_log_create_error(r);
396
397                 r = sd_bus_message_close_container(m);
398                 if (r < 0)
399                         return bus_log_create_error(r);
400         }
401
402         r = sd_bus_message_open_container(m, 'r', "sv");
403         if (r < 0)
404                 return bus_log_create_error(r);
405
406         r = sd_bus_message_append(m, "s", "ExecStart");
407         if (r < 0)
408                 return bus_log_create_error(r);
409
410         r = sd_bus_message_open_container(m, 'v', "a(sasb)");
411         if (r < 0)
412                 return bus_log_create_error(r);
413
414         r = sd_bus_message_open_container(m, 'a', "(sasb)");
415         if (r < 0)
416                 return bus_log_create_error(r);
417
418         r = sd_bus_message_open_container(m, 'r', "sasb");
419         if (r < 0)
420                 return bus_log_create_error(r);
421
422         r = sd_bus_message_append(m, "s", argv[0]);
423         if (r < 0)
424                 return bus_log_create_error(r);
425
426         r = sd_bus_message_append_strv(m, argv);
427         if (r < 0)
428                 return bus_log_create_error(r);
429
430         r = sd_bus_message_append(m, "b", false);
431         if (r < 0)
432                 return bus_log_create_error(r);
433
434         r = sd_bus_message_close_container(m);
435         if (r < 0)
436                 return bus_log_create_error(r);
437
438         r = sd_bus_message_close_container(m);
439         if (r < 0)
440                 return bus_log_create_error(r);
441
442         r = sd_bus_message_close_container(m);
443         if (r < 0)
444                 return bus_log_create_error(r);
445
446         r = sd_bus_message_close_container(m);
447         if (r < 0)
448                 return bus_log_create_error(r);
449
450         r = message_start_transient_unit_send(bus, m, error, NULL);
451         if (r < 0)
452                 return bus_log_create_error(r);
453
454         log_info("Running as unit %s.", name);
455
456         return 0;
457 }
458
459 static int start_transient_scope(
460                 sd_bus *bus,
461                 char **argv,
462                 sd_bus_error *error) {
463
464         _cleanup_bus_message_unref_ sd_bus_message *m = NULL;
465         _cleanup_free_ char *name = NULL;
466         _cleanup_strv_free_ char **env = NULL, **user_env = NULL;
467         int r;
468
469         assert(bus);
470
471         if (arg_unit) {
472                 name = unit_name_mangle_with_suffix(arg_unit, MANGLE_NOGLOB, ".scope");
473                 if (!name)
474                         return log_oom();
475         } else if (asprintf(&name, "run-"PID_FMT".scope", getpid()) < 0)
476                 return log_oom();
477
478         r = message_start_transient_unit_new(bus, name, &m);
479         if (r < 0)
480                 return bus_log_create_error(r);
481
482         r = sd_bus_message_append(m, "(sv)", "PIDs", "au", 1, (uint32_t) getpid());
483         if (r < 0)
484                 return bus_log_create_error(r);
485
486         r = message_start_transient_unit_send(bus, m, error, NULL);
487         if (r < 0)
488                 return bus_log_create_error(r);
489
490         if (arg_nice_set) {
491                 if (setpriority(PRIO_PROCESS, 0, arg_nice) < 0) {
492                         log_error("Failed to set nice level: %m");
493                         return -errno;
494                 }
495         }
496
497         if (arg_exec_group) {
498                 gid_t gid;
499
500                 r = get_group_creds(&arg_exec_group, &gid);
501                 if (r < 0) {
502                         log_error("Failed to resolve group %s: %s", arg_exec_group, strerror(-r));
503                         return r;
504                 }
505
506                 if (setresgid(gid, gid, gid) < 0) {
507                         log_error("Failed to change GID to " GID_FMT ": %m", gid);
508                         return -errno;
509                 }
510         }
511
512         if (arg_exec_user) {
513                 const char *home, *shell;
514                 uid_t uid;
515                 gid_t gid;
516
517                 r = get_user_creds(&arg_exec_user, &uid, &gid, &home, &shell);
518                 if (r < 0) {
519                         log_error("Failed to resolve user %s: %s", arg_exec_user, strerror(-r));
520                         return r;
521                 }
522
523                 r = strv_extendf(&user_env, "HOME=%s", home);
524                 if (r < 0)
525                         return log_oom();
526
527                 r = strv_extendf(&user_env, "SHELL=%s", shell);
528                 if (r < 0)
529                         return log_oom();
530
531                 r = strv_extendf(&user_env, "USER=%s", arg_exec_user);
532                 if (r < 0)
533                         return log_oom();
534
535                 r = strv_extendf(&user_env, "LOGNAME=%s", arg_exec_user);
536                 if (r < 0)
537                         return log_oom();
538
539                 if (!arg_exec_group) {
540                         if (setresgid(gid, gid, gid) < 0) {
541                                 log_error("Failed to change GID to " GID_FMT ": %m", gid);
542                                 return -errno;
543                         }
544                 }
545
546                 if (setresuid(uid, uid, uid) < 0) {
547                         log_error("Failed to change UID to " UID_FMT ": %m", uid);
548                         return -errno;
549                 }
550         }
551
552         env = strv_env_merge(3, environ, user_env, arg_environment);
553         if (!env)
554                 return log_oom();
555
556         log_info("Running as unit %s.", name);
557
558         execvpe(argv[0], argv, env);
559         log_error("Failed to execute: %m");
560         return -errno;
561 }
562
563 int main(int argc, char* argv[]) {
564         _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL;
565         _cleanup_bus_close_unref_ sd_bus *bus = NULL;
566         _cleanup_free_ char *description = NULL, *command = NULL;
567         int r;
568
569         log_parse_environment();
570         log_open();
571
572         r = parse_argv(argc, argv);
573         if (r <= 0)
574                 goto finish;
575
576         r = find_binary(argv[optind], &command);
577         if (r < 0) {
578                 log_error("Failed to find executable %s: %s", argv[optind], strerror(-r));
579                 goto finish;
580         }
581         argv[optind] = command;
582
583         if (!arg_description) {
584                 description = strv_join(argv + optind, " ");
585                 if (!description) {
586                         r = log_oom();
587                         goto finish;
588                 }
589
590                 arg_description = description;
591         }
592
593         r = bus_open_transport_systemd(arg_transport, arg_host, arg_user, &bus);
594         if (r < 0) {
595                 log_error("Failed to create bus connection: %s", strerror(-r));
596                 goto finish;
597         }
598
599         if (arg_scope)
600                 r = start_transient_scope(bus, argv + optind, &error);
601         else
602                 r = start_transient_service(bus, argv + optind, &error);
603
604 finish:
605         strv_free(arg_environment);
606         strv_free(arg_property);
607
608         return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
609 }