/* rc-applets.c Handle multicall applets for use in our init scripts. Basically this makes us a lot faster for the most part, and removes any shell incompatabilities we might otherwise encounter. */ /* * Copyright (c) 2007-2009 Roy Marples * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #define SYSLOG_NAMES #include #include #include #include #include #include #include #include #include #include #include #include #include #include "builtins.h" #include "einfo.h" #include "rc-misc.h" /* usecs to wait while we poll the file existance */ #define WAIT_INTERVAL 20000000 #define ONE_SECOND 690000000 /* Applet is first parsed in rc.c - no point in doing it again */ extern const char *applet; static int syslog_decode(char *name, CODE *codetab) { CODE *c; if (isdigit((unsigned char)*name)) return atoi(name); for (c = codetab; c->c_name; c++) if (! strcasecmp(name, c->c_name)) return c->c_val; return -1; } static int do_e(int argc, char **argv) { int retval = EXIT_SUCCESS; int i; size_t l = 0; char *message = NULL; char *p; int level = 0; struct timespec ts; struct timeval stop, now; int (*e) (const char *, ...) EINFO_PRINTF(1, 2) = NULL; int (*ee) (int, const char *, ...) EINFO_PRINTF(2, 3) = NULL; /* Punt applet */ argc--; argv++; if (strcmp(applet, "eval_ecolors") == 0) { printf("GOOD='%s'\nWARN='%s'\nBAD='%s'\nHILITE='%s'\nBRACKET='%s'\nNORMAL='%s'\n", ecolor(ECOLOR_GOOD), ecolor(ECOLOR_WARN), ecolor(ECOLOR_BAD), ecolor(ECOLOR_HILITE), ecolor(ECOLOR_BRACKET), ecolor(ECOLOR_NORMAL)); exit(EXIT_SUCCESS); } if (argc > 0) { if (strcmp(applet, "eend") == 0 || strcmp(applet, "ewend") == 0 || strcmp(applet, "veend") == 0 || strcmp(applet, "vweend") == 0 || strcmp(applet, "ewaitfile") == 0) { errno = 0; retval = (int)strtoimax(argv[0], &p, 0); if (!p || *p != '\0') errno = EINVAL; if (errno) retval = EXIT_FAILURE; else { argc--; argv++; } } else if (strcmp(applet, "esyslog") == 0 || strcmp(applet, "elog") == 0) { p = strchr(argv[0], '.'); if (!p || (level = syslog_decode(p + 1, prioritynames)) == -1) eerrorx("%s: invalid log level `%s'", applet, argv[0]); if (argc < 3) eerrorx("%s: not enough arguments", applet); unsetenv("EINFO_LOG"); setenv("EINFO_LOG", argv[1], 1); argc -= 2; argv += 2; } } if (strcmp(applet, "ewaitfile") == 0) { if (errno) eerrorx("%s: invalid timeout", applet); if (argc == 0) eerrorx("%s: not enough arguments", applet); gettimeofday(&stop, NULL); /* retval stores the timeout */ stop.tv_sec += retval; ts.tv_sec = 0; ts.tv_nsec = WAIT_INTERVAL; for (i = 0; i < argc; i++) { ebeginv("Waiting for %s", argv[i]); for (;;) { if (exists(argv[i])) break; if (nanosleep(&ts, NULL) == -1) return EXIT_FAILURE; gettimeofday(&now, NULL); if (retval <= 0) continue; if (timercmp(&now, &stop, <)) continue; eendv(EXIT_FAILURE, "timed out waiting for %s", argv[i]); return EXIT_FAILURE; } eendv(EXIT_SUCCESS, NULL); } return EXIT_SUCCESS; } if (argc > 0) { for (i = 0; i < argc; i++) l += strlen(argv[i]) + 1; message = xmalloc(l); p = message; for (i = 0; i < argc; i++) { if (i > 0) *p++ = ' '; l = strlen(argv[i]); memcpy(p, argv[i], l); p += l; } *p = 0; } if (strcmp(applet, "einfo") == 0) e = einfo; else if (strcmp(applet, "einfon") == 0) e = einfon; else if (strcmp(applet, "ewarn") == 0) e = ewarn; else if (strcmp(applet, "ewarnn") == 0) e = ewarnn; else if (strcmp(applet, "eerror") == 0) { e = eerror; retval = 1; } else if (strcmp(applet, "eerrorn") == 0) { e = eerrorn; retval = 1; } else if (strcmp(applet, "ebegin") == 0) e = ebegin; else if (strcmp(applet, "eend") == 0) ee = eend; else if (strcmp(applet, "ewend") == 0) ee = ewend; else if (strcmp(applet, "esyslog") == 0) { elog(retval, "%s", message); retval = 0; } else if (strcmp(applet, "veinfo") == 0) e = einfov; else if (strcmp(applet, "veinfon") == 0) e = einfovn; else if (strcmp(applet, "vewarn") == 0) e = ewarnv; else if (strcmp(applet, "vewarnn") == 0) e = ewarnvn; else if (strcmp(applet, "vebegin") == 0) e = ebeginv; else if (strcmp(applet, "veend") == 0) ee = eendv; else if (strcmp(applet, "vewend") == 0) ee = ewendv; else if (strcmp(applet, "eindent") == 0) eindent(); else if (strcmp(applet, "eoutdent") == 0) eoutdent(); else if (strcmp(applet, "veindent") == 0) eindentv(); else if (strcmp(applet, "veoutdent") == 0) eoutdentv(); else { eerror("%s: unknown applet", applet); retval = EXIT_FAILURE; } if (message) { if (e) e("%s", message); else if (ee) ee(retval, "%s", message); } else { if (e) e(NULL); else if (ee) ee(retval, NULL); } free(message); return retval; } static const struct { const char * const name; RC_SERVICE bit; } service_bits[] = { { "service_started", RC_SERVICE_STARTED, }, { "service_stopped", RC_SERVICE_STOPPED, }, { "service_inactive", RC_SERVICE_INACTIVE, }, { "service_starting", RC_SERVICE_STARTING, }, { "service_stopping", RC_SERVICE_STOPPING, }, { "service_hotplugged", RC_SERVICE_HOTPLUGGED, }, { "service_wasinactive", RC_SERVICE_WASINACTIVE, }, { "service_failed", RC_SERVICE_FAILED, }, }; static RC_SERVICE lookup_service_state(const char *service) { size_t i; for (i = 0; i < ARRAY_SIZE(service_bits); ++i) if (!strcmp(service, service_bits[i].name)) return service_bits[i].bit; return 0; } static int do_service(int argc, char **argv) { bool ok = false; char *service; char *exec; int idx; RC_SERVICE state, bit; if (argc > 1) service = argv[1]; else service = getenv("RC_SVCNAME"); if (service == NULL || *service == '\0') eerrorx("%s: no service specified", applet); state = rc_service_state(service); bit = lookup_service_state(applet); if (bit) { ok = (state & bit); } else if (strcmp(applet, "service_started_daemon") == 0) { service = getenv("RC_SVCNAME"); exec = argv[1]; if (argc > 3) { service = argv[1]; exec = argv[2]; sscanf(argv[3], "%d", &idx); } else if (argc == 3) { if (sscanf(argv[2], "%d", &idx) != 1) { service = argv[1]; exec = argv[2]; } } ok = rc_service_started_daemon(service, exec, NULL, idx); } else if (strcmp(applet, "service_crashed") == 0) { ok = (_rc_can_find_pids() && rc_service_daemons_crashed(service) && errno != EACCES); } else eerrorx("%s: unknown applet", applet); return ok ? EXIT_SUCCESS : EXIT_FAILURE; } static int do_mark_service(int argc, char **argv) { bool ok = false; char *svcname = getenv("RC_SVCNAME"); char *service = NULL; char *runscript_pid; /* char *mtime; */ pid_t pid; RC_SERVICE bit; /* size_t l; */ if (argc > 1) service = argv[1]; else service = svcname; if (service == NULL || *service == '\0') eerrorx("%s: no service specified", applet); if (!strncmp(applet, "mark_", 5) && (bit = lookup_service_state(applet + 5))) ok = rc_service_mark(service, bit); else eerrorx("%s: unknown applet", applet); /* If we're marking ourselves then we need to inform our parent runscript process so they do not mark us based on our exit code */ /* * FIXME: svcname and service are almost always equal except called from a * shell with just argv[1] - So that doesn't seem to do what Roy initially * expected. * See 20120424041423.GA23657@odin.qasl.de (Tue, 24 Apr 2012 06:14:23 +0200, * openrc@gentoo.org). */ if (ok && svcname && strcmp(svcname, service) == 0) { runscript_pid = getenv("RC_RUNSCRIPT_PID"); if (runscript_pid && sscanf(runscript_pid, "%d", &pid) == 1) if (kill(pid, SIGHUP) != 0) eerror("%s: failed to signal parent %d: %s", applet, pid, strerror(errno)); /* Remove the exclusive time test. This ensures that it's not in control as well */ /* l = strlen(RC_SVCDIR "/exclusive") + strlen(svcname) + strlen(runscript_pid) + 4; mtime = xmalloc(l); snprintf(mtime, l, RC_SVCDIR "/exclusive/%s.%s", svcname, runscript_pid); if (exists(mtime) && unlink(mtime) != 0) eerror("%s: unlink: %s", applet, strerror(errno)); free(mtime); */ } return ok ? EXIT_SUCCESS : EXIT_FAILURE; } static int do_value(int argc, char **argv) { bool ok = false; char *service = getenv("RC_SVCNAME"); char *option; if (service == NULL) eerrorx("%s: no service specified", applet); if (argc < 2 || ! argv[1] || *argv[1] == '\0') eerrorx("%s: no option specified", applet); if (strcmp(applet, "service_get_value") == 0 || strcmp(applet, "get_options") == 0) { option = rc_service_value_get(service, argv[1]); if (option) { printf("%s", option); free(option); ok = true; } } else if (strcmp(applet, "service_set_value") == 0 || strcmp(applet, "save_options") == 0) ok = rc_service_value_set(service, argv[1], argv[2]); else eerrorx("%s: unknown applet", applet); return ok ? EXIT_SUCCESS : EXIT_FAILURE; } static int shell_var(int argc, char **argv) { int i; char *p; int c; for (i = 1; i < argc; i++) { p = argv[i]; if (i != 1) putchar(' '); while (*p) { c = (unsigned char)*p++; if (! isalnum(c)) c = '_'; putchar(c); } } putchar('\n'); return EXIT_SUCCESS; } static int is_older_than(int argc, char **argv) { int i; if (argc < 3) return EXIT_FAILURE; /* This test is perverted - historically the baselayout function * returns 0 on *failure*, which is plain wrong */ for (i = 2; i < argc; ++i) if (!rc_newer_than(argv[1], argv[i], NULL, NULL)) return EXIT_SUCCESS; return EXIT_FAILURE; } static int is_newer_than(int argc, char **argv) { int i; if (argc < 3) return EXIT_FAILURE; /* This test is correct as it's not present in baselayout */ for (i = 2; i < argc; ++i) if (!rc_newer_than(argv[1], argv[i], NULL, NULL)) return EXIT_FAILURE; return EXIT_SUCCESS; } static int is_runlevel_start(_unused int argc, _unused char **argv) { return rc_runlevel_starting() ? 0 : 1; } static int is_runlevel_stop(_unused int argc, _unused char **argv) { return rc_runlevel_stopping() ? 0 : 1; } static int rc_abort(_unused int argc, _unused char **argv) { const char *p = getenv("RC_PID"); int pid; if (p && sscanf(p, "%d", &pid) == 1) { if (kill(pid, SIGUSR1) != 0) eerrorx("rc-abort: failed to signal parent %d: %s", pid, strerror(errno)); return EXIT_SUCCESS; } return EXIT_FAILURE; } static const struct { const char * const name; int (* const applet)(int argc, char **argv); } applets[] = { #define A(a) { #a, a } A(fstabinfo), A(mountinfo), { "rc-depend", rc_depend, }, { "rc-service", rc_service, }, { "rc-status", rc_status, }, { "rc-update", rc_update, }, { "service", rc_service, }, { "update-rc", rc_update, }, A(runscript), { "start-stop-daemon", start_stop_daemon, }, A(checkpath), A(swclock), A(shell_var), A(is_older_than), A(is_newer_than), A(is_runlevel_start), A(is_runlevel_stop), { "rc-abort", rc_abort, }, /* These are purely for init scripts and do not make sense as * anything else */ { "service_get_value", do_value, }, { "service_set_value", do_value, }, { "get_options", do_value, }, { "save_options", do_value, }, #undef A }; void run_applets(int argc, char **argv) { size_t i; /* Bug 351712: We need an extra way to explicitly select an applet OTHER * than trusting argv[0], as argv[0] is not going to be the applet value if * we are doing SELinux context switching. For this, we allow calls such as * 'rc --applet APPLET', and shift ALL of argv down by two array items. */ if (strcmp(applet, "rc") == 0 && argc >= 3 && (strcmp(argv[1],"--applet") == 0 || strcmp(argv[1], "-a") == 0)) { applet = argv[2]; argv += 2; argc -= 2; } for (i = 0; i < ARRAY_SIZE(applets); ++i) if (!strcmp(applet, applets[i].name)) exit(applets[i].applet(argc, argv)); if (applet[0] == 'e' || (applet[0] == 'v' && applet[1] == 'e')) exit(do_e(argc, argv)); if (strncmp(applet, "service_", strlen("service_")) == 0) exit(do_service(argc, argv)); if (strncmp(applet, "mark_service_", strlen("mark_service_")) == 0) exit(do_mark_service(argc, argv)); if (strcmp(applet, "rc") != 0) eerrorx("%s: unknown applet", applet); }