mirror of
https://github.com/troglobit/finit.git
synced 2026-10-01 05:22:48 +07:00
1633 lines
36 KiB
C
1633 lines
36 KiB
C
/* New client tool, replaces old /dev/initctl API and telinit tool
|
|
*
|
|
* Copyright (c) 2015-2023 Joachim Wiberg <troglobit@gmail.com>
|
|
*
|
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
* of this software and associated documentation files (the "Software"), to deal
|
|
* in the Software without restriction, including without limitation the rights
|
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
* copies of the Software, and to permit persons to whom the Software is
|
|
* furnished to do so, subject to the following conditions:
|
|
*
|
|
* The above copyright notice and this permission notice shall be included in
|
|
* all copies or substantial portions of the Software.
|
|
*
|
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
* THE SOFTWARE.
|
|
*/
|
|
|
|
#include "config.h"
|
|
|
|
#include <ftw.h>
|
|
#include <ctype.h>
|
|
#include <getopt.h>
|
|
#include <paths.h>
|
|
#include <signal.h>
|
|
#include <stdio.h>
|
|
#include <syslog.h>
|
|
#include <time.h>
|
|
#include <utmp.h>
|
|
#include <arpa/inet.h>
|
|
#ifdef _LIBITE_LITE
|
|
# include <libite/lite.h>
|
|
#else
|
|
# include <lite/lite.h>
|
|
#endif
|
|
|
|
#include "initctl.h"
|
|
#include "client.h"
|
|
#include "cond.h"
|
|
#include "serv.h"
|
|
#include "service.h"
|
|
#include "cgutil.h"
|
|
#include "util.h"
|
|
#include "utmp-api.h"
|
|
|
|
struct cmd {
|
|
char *cmd;
|
|
struct cmd *ctx;
|
|
int (*cb)(char *arg);
|
|
int *cond;
|
|
int (*cb_multiarg)(int argc, char **argv);
|
|
};
|
|
|
|
char *finit_conf = NULL;
|
|
char *finit_rcsd = NULL;
|
|
|
|
int icreate = 0;
|
|
int iforce = 0;
|
|
int ionce = 0;
|
|
int debug = 0;
|
|
int heading = 1;
|
|
int json = 0;
|
|
int noerr = 0;
|
|
int verbose = 0;
|
|
int plain = 0;
|
|
int quiet = 0;
|
|
int runlevel = 0;
|
|
int timeout = 0;
|
|
int cgrp = 0;
|
|
int utmp = 0;
|
|
|
|
int iw, pw;
|
|
|
|
extern int reboot_main(int argc, char *argv[]);
|
|
|
|
|
|
/* figure ut width of IDENT and PID columns */
|
|
static void col_widths(void)
|
|
{
|
|
char ident[MAX_IDENT_LEN];
|
|
char pid[10];
|
|
svc_t *svc;
|
|
|
|
iw = 0;
|
|
pw = 0;
|
|
|
|
for (svc = client_svc_iterator(1); svc; svc = client_svc_iterator(0)) {
|
|
int w, p;
|
|
|
|
svc_ident(svc, ident, sizeof(ident));
|
|
w = (int)strlen(ident);
|
|
if (w > iw)
|
|
iw = w;
|
|
|
|
p = snprintf(pid, sizeof(pid), "%d", svc->pid);
|
|
if (p > pw)
|
|
pw = p;
|
|
}
|
|
|
|
/* adjust for min col width */
|
|
if (iw < 6)
|
|
iw = 6;
|
|
if (pw < 3)
|
|
pw = 3;
|
|
}
|
|
|
|
void print_header(const char *fmt, ...)
|
|
{
|
|
va_list ap;
|
|
|
|
va_start(ap, fmt);
|
|
|
|
if (plain) {
|
|
int len;
|
|
|
|
vfprintf(stdout, fmt, ap);
|
|
fputs("\n", stdout);
|
|
for (len = 0; len < ttcols; len++)
|
|
fputc('=', stdout);
|
|
fputs("\n", stdout);
|
|
} else {
|
|
char buf[ttcols];
|
|
|
|
vsnprintf(buf, sizeof(buf), fmt, ap);
|
|
printheader(stdout, buf, 0);
|
|
}
|
|
|
|
va_end(ap);
|
|
}
|
|
|
|
static int runlevel_get(int *prevlevel)
|
|
{
|
|
struct init_request rq;
|
|
int rc;
|
|
|
|
rq.cmd = INIT_CMD_GET_RUNLEVEL;
|
|
rq.magic = INIT_MAGIC;
|
|
|
|
rc = client_send(&rq, sizeof(rq));
|
|
if (!rc) {
|
|
rc = rq.runlevel;
|
|
if (prevlevel)
|
|
*prevlevel = rq.sleeptime;
|
|
}
|
|
|
|
return rc;
|
|
}
|
|
|
|
static int toggle_debug(char *arg)
|
|
{
|
|
struct init_request rq = {
|
|
.magic = INIT_MAGIC,
|
|
.cmd = INIT_CMD_DEBUG,
|
|
};
|
|
|
|
return client_send(&rq, sizeof(rq));
|
|
}
|
|
|
|
static int do_log(svc_t *svc, char *tail)
|
|
{
|
|
const char *logfile = "/var/log/syslog";
|
|
pid_t pid;
|
|
char *nm;
|
|
|
|
if (svc) {
|
|
nm = svc_ident(svc, NULL, 0);
|
|
pid = svc->pid;
|
|
if (!pid)
|
|
return 0; /* not running */
|
|
} else {
|
|
nm = "finit";
|
|
pid = 1;
|
|
}
|
|
|
|
if (!fexist(logfile)) {
|
|
logfile = "/var/log/messages";
|
|
if (!fexist(logfile))
|
|
return 0; /* bail out, maybe in container */
|
|
}
|
|
|
|
return systemf("cat %s | grep '\\[%d\\]\\|%s' %s", logfile, pid, nm, tail);
|
|
}
|
|
|
|
static int show_log(char *arg)
|
|
{
|
|
svc_t *svc = NULL;
|
|
|
|
if (arg) {
|
|
svc = client_svc_find(arg);
|
|
if (!svc)
|
|
ERRX(noerr ? 0 : 69, "no such task or service(s): %s", arg);
|
|
}
|
|
|
|
return do_log(svc, "");
|
|
}
|
|
|
|
static int do_runlevel(char *arg)
|
|
{
|
|
struct init_request rq = {
|
|
.magic = INIT_MAGIC,
|
|
.cmd = INIT_CMD_RUNLVL,
|
|
};
|
|
|
|
if (!arg) {
|
|
int prevlevel = 0;
|
|
int currlevel;
|
|
char prev;
|
|
|
|
currlevel = runlevel_get(&prevlevel);
|
|
if (255 == currlevel) {
|
|
printf("unknown\n");
|
|
return 0;
|
|
}
|
|
|
|
prev = prevlevel + '0';
|
|
if (prev <= '0' || prev > '9')
|
|
prev = 'N';
|
|
|
|
printf("%c %d\n", prev , currlevel);
|
|
return 0;
|
|
}
|
|
|
|
/* set runlevel */
|
|
rq.runlevel = (int)arg[0];
|
|
|
|
return client_send(&rq, sizeof(rq));
|
|
}
|
|
|
|
static int do_svc(int cmd, char *arg)
|
|
{
|
|
struct init_request rq = {
|
|
.magic = INIT_MAGIC,
|
|
.cmd = cmd,
|
|
.data = "",
|
|
};
|
|
|
|
if (arg)
|
|
strlcpy(rq.data, arg, sizeof(rq.data));
|
|
|
|
return client_send(&rq, sizeof(rq));
|
|
}
|
|
|
|
/*
|
|
* This is a wrapper for do_svc() that adds a simple sanity check of
|
|
* the service(s) provided as argument. If a service does not exist
|
|
* we make sure to return an error code.
|
|
*/
|
|
static int do_startstop(int cmd, char *arg)
|
|
{
|
|
struct init_request rq = {
|
|
.magic = INIT_MAGIC,
|
|
.cmd = INIT_CMD_SVC_QUERY
|
|
};
|
|
|
|
if (!arg || !arg[0])
|
|
ERRX(2, "missing command argument");
|
|
|
|
strlcpy(rq.data, arg, sizeof(rq.data));
|
|
if (client_send(&rq, sizeof(rq)))
|
|
ERRX(noerr ? 0 : 69, "no such task or service(s): %s", arg);
|
|
|
|
return do_svc(cmd, arg);
|
|
}
|
|
|
|
static int do_start (char *arg) { return do_startstop(INIT_CMD_START_SVC, arg); }
|
|
static int do_stop (char *arg) { return do_startstop(INIT_CMD_STOP_SVC, arg); }
|
|
|
|
static int do_reload (char *arg)
|
|
{
|
|
if (!arg || !arg[0])
|
|
return do_svc(INIT_CMD_RELOAD, NULL);
|
|
|
|
return do_startstop(INIT_CMD_RELOAD_SVC, arg);
|
|
}
|
|
|
|
static int do_restart(char *arg)
|
|
{
|
|
if (do_startstop(INIT_CMD_RESTART_SVC, arg))
|
|
ERRX(noerr ? 0 : 7, "failed restarting %s", arg);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* do_signal - Ask finit to send a signal to a service.
|
|
* @argv: must point to an array of strings, containing a service
|
|
* and signal name, in that order.
|
|
* @argc: must be 2.
|
|
*
|
|
* A signal can be a complete signal name such as "SIGHUP", or
|
|
* it can be the shortest unique name, such as "HUP" (no SIG prefix).
|
|
* It can also be a raw signal number, such as "9" (SIGKILL).
|
|
*/
|
|
int do_signal(int argc, char *argv[])
|
|
{
|
|
struct init_request rq = {
|
|
.magic = INIT_MAGIC,
|
|
.cmd = INIT_CMD_SVC_QUERY
|
|
};
|
|
int signo;
|
|
|
|
if (argc != 2)
|
|
ERRX(2, "invalid number of arguments to signal");
|
|
|
|
strlcpy(rq.data, argv[0], sizeof(rq.data));
|
|
if (client_send(&rq, sizeof(rq)))
|
|
ERRX(noerr ? 0 : 69, "no such task or service(s): %s", argv[0]);
|
|
|
|
signo = str2sig(argv[1]);
|
|
if (signo == -1) {
|
|
const char *errstr = NULL;
|
|
|
|
signo = (int)strtonum(argv[1], 1, 31, &errstr);
|
|
if (errstr)
|
|
ERRX(65, "%s signal: %s", errstr, argv[1]);
|
|
}
|
|
|
|
/* Reuse runlevel for signal number. */
|
|
rq.magic = INIT_MAGIC;
|
|
rq.cmd = INIT_CMD_SIGNAL;
|
|
rq.runlevel = signo;
|
|
strlcpy(rq.data, argv[0], sizeof(rq.data));
|
|
|
|
return client_send(&rq, sizeof(rq));
|
|
}
|
|
|
|
int dump_once;
|
|
char *dump_filter;
|
|
static int dump_one_cond(const char *fpath, const struct stat *sb, int tflag, struct FTW *ftwbuf)
|
|
{
|
|
const char *cond, *asserted;
|
|
char *nm = "init";
|
|
pid_t pid = 1;
|
|
|
|
if (tflag != FTW_F)
|
|
return 0;
|
|
|
|
if (!strcmp(fpath, _PATH_RECONF))
|
|
return 0;
|
|
|
|
asserted = condstr(cond_get_path(fpath));
|
|
cond = &fpath[strlen(_PATH_COND)];
|
|
|
|
if (dump_filter && dump_filter[0] && strncmp(cond, dump_filter, strlen(dump_filter)))
|
|
return 0;
|
|
|
|
if (strncmp("pid/", cond, 4) == 0) {
|
|
svc_t *svc;
|
|
|
|
svc = client_svc_find_by_cond(cond);
|
|
if (!svc) {
|
|
nm = "unknown";
|
|
pid = 0;
|
|
} else {
|
|
nm = svc_ident(svc, NULL, 0);
|
|
pid = svc->pid;
|
|
}
|
|
} else if (strncmp("usr/", cond, 4) == 0) {
|
|
nm = "static";
|
|
pid = 0;
|
|
} else if (strncmp("hook/", cond, 4) == 0) {
|
|
nm = "static";
|
|
pid = 1;
|
|
}
|
|
|
|
if (json) {
|
|
if (!dump_once)
|
|
puts("[");
|
|
|
|
printf("%s {\n"
|
|
" \"pid\": %d,\n"
|
|
" \"identity\": \"%s\",\n"
|
|
" \"status\": \"%s\",\n"
|
|
" \"condition\": \"%s\"\n"
|
|
" }",
|
|
dump_once ? ",\n" : "",
|
|
pid, nm, asserted, cond);
|
|
|
|
dump_once++;
|
|
} else
|
|
printf("%-*d %-*s %-6s <%s>\n", pw, pid, iw, nm, asserted, cond);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int do_cond_dump(char *arg)
|
|
{
|
|
col_widths();
|
|
if (heading && !json)
|
|
print_header("%-*s %-*s %-6s %s", pw, "PID", iw, "IDENT",
|
|
"STATUS", "CONDITION");
|
|
|
|
dump_once = 0;
|
|
dump_filter = arg;
|
|
if (nftw(_PATH_COND, dump_one_cond, 20, 0) == -1) {
|
|
WARNX("Failed parsing %s", _PATH_COND);
|
|
return 1;
|
|
}
|
|
if (dump_once)
|
|
puts("\n]");
|
|
|
|
return 0;
|
|
}
|
|
|
|
typedef enum { COND_CLR, COND_SET, COND_GET } condop_t;
|
|
|
|
static cond_state_t cond_read(char *path)
|
|
{
|
|
int now, gen;
|
|
|
|
if (fngetint(path, &gen) == -1)
|
|
return COND_OFF;
|
|
|
|
/*
|
|
* if we cannot read the reconf generation, then either sth is
|
|
* very wrong, or we are called very early/late boot/shutdown.
|
|
*/
|
|
if (fngetint(_PATH_RECONF, &now) == -1)
|
|
return COND_FLUX; /* classify as flux */
|
|
|
|
if (now != gen)
|
|
return COND_FLUX;
|
|
|
|
return COND_ON;
|
|
}
|
|
|
|
/*
|
|
* cond get allows only one argument
|
|
* cond set|clr iterate over multiple args
|
|
*/
|
|
static int do_cond_act(char *args, condop_t op)
|
|
{
|
|
cond_state_t cstate;
|
|
char path[256];
|
|
char *arg;
|
|
|
|
if (!args || !args[0])
|
|
ERRX(2, "Invalid condition (empty)");
|
|
|
|
arg = strtok(args, " \t");
|
|
while (arg) {
|
|
size_t off;
|
|
|
|
if (strncmp(arg, COND_USR, strlen(COND_USR)) == 0)
|
|
arg += strlen(COND_USR);
|
|
|
|
/* allowed to read any condition, but not set/clr */
|
|
if (op != COND_GET) {
|
|
if (strchr(arg, '/'))
|
|
ERRX(2, "Invalid condition (slashes)");
|
|
if (strchr(arg, '.'))
|
|
ERRX(2, "Invalid condition (periods)");
|
|
}
|
|
|
|
if (strchr(arg, '/'))
|
|
snprintf(path, sizeof(path), _PATH_COND "%s", arg);
|
|
else
|
|
snprintf(path, sizeof(path), _PATH_CONDUSR "%s", arg);
|
|
off = strlen(_PATH_COND);
|
|
|
|
switch (op) {
|
|
case COND_GET:
|
|
cstate = cond_read(path);
|
|
if (verbose)
|
|
puts(condstr(cstate));
|
|
|
|
switch (cstate) {
|
|
case COND_ON:
|
|
return 0;
|
|
case COND_OFF:
|
|
return 1;
|
|
case COND_FLUX:
|
|
default:
|
|
break;
|
|
}
|
|
return 255;
|
|
|
|
case COND_SET:
|
|
if (symlink(_PATH_RECONF, path) && errno != EEXIST)
|
|
ERR(73, "Failed asserting condition <%s>", &path[off]);
|
|
break;
|
|
case COND_CLR:
|
|
if (erase(path) && errno != ENOENT)
|
|
ERR(73, "Failed deasserting condition <%s>", &path[off]);
|
|
break;
|
|
}
|
|
|
|
arg = strtok(NULL, " \t");
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int do_cond_get(char *arg) { return do_cond_act(arg, COND_GET); }
|
|
static int do_cond_set(char *arg) { return do_cond_act(arg, COND_SET); }
|
|
static int do_cond_clr(char *arg) { return do_cond_act(arg, COND_CLR); }
|
|
|
|
static char *svc_cond(svc_t *svc, char *buf, size_t len, int ansi)
|
|
{
|
|
char *cond, *conds;
|
|
|
|
buf[0] = 0;
|
|
|
|
if (!svc->cond[0])
|
|
return buf;
|
|
|
|
conds = strdupa(svc->cond);
|
|
if (!conds)
|
|
return buf;
|
|
|
|
if (json)
|
|
strlcat(buf, "[ ", len);
|
|
|
|
for (cond = strtok(conds, ","); cond; cond = strtok(NULL, ",")) {
|
|
if (cond != conds) {
|
|
strlcat(buf, ",", len);
|
|
if (json)
|
|
strlcat(buf, " ", len);
|
|
}
|
|
|
|
if (json)
|
|
strlcat(buf, "\"", len);
|
|
|
|
switch (cond_get(cond)) {
|
|
case COND_ON:
|
|
strlcat(buf, "+", len);
|
|
strlcat(buf, cond, len);
|
|
break;
|
|
|
|
case COND_FLUX:
|
|
if (ansi)
|
|
strlcat(buf, "\e[1m", len);
|
|
strlcat(buf, "~", len);
|
|
strlcat(buf, cond, len);
|
|
if (ansi)
|
|
strlcat(buf, "\e[0m", len);
|
|
break;
|
|
|
|
case COND_OFF:
|
|
if (ansi)
|
|
strlcat(buf, "\e[1m", len);
|
|
strlcat(buf, "-", len);
|
|
strlcat(buf, cond, len);
|
|
if (ansi)
|
|
strlcat(buf, "\e[0m", len);
|
|
break;
|
|
}
|
|
|
|
if (json)
|
|
strlcat(buf, "\"", len);
|
|
}
|
|
|
|
if (json)
|
|
strlcat(buf, " ]", len);
|
|
|
|
return buf;
|
|
}
|
|
|
|
static int do_cond_show(char *arg)
|
|
{
|
|
enum cond_state cond;
|
|
char buf[512];
|
|
int once = 0;
|
|
svc_t *svc;
|
|
|
|
col_widths();
|
|
if (heading && !json)
|
|
print_header("%-*s %-*s %-6s %s", pw, "PID", iw, "IDENT",
|
|
"STATUS", "CONDITION (+ ON, ~ FLUX, - OFF)");
|
|
|
|
for (svc = client_svc_iterator(1); svc; svc = client_svc_iterator(0)) {
|
|
if (!svc->cond[0])
|
|
continue;
|
|
|
|
cond = cond_get_agg(svc->cond);
|
|
svc_ident(svc, buf, sizeof(buf));
|
|
|
|
if (json) {
|
|
if (!once)
|
|
puts("[");
|
|
|
|
printf("%s {\n"
|
|
" \"pid\": %d,\n"
|
|
" \"identity\": \"%s\",\n"
|
|
" \"status\": \"%s\",\n",
|
|
once ? ",\n" : "",
|
|
svc->pid,
|
|
buf,
|
|
condstr(cond));
|
|
svc_cond(svc, buf, sizeof(buf), !plain);
|
|
printf(" \"condition\": %s\n"
|
|
" }", buf);
|
|
|
|
once++;
|
|
continue;
|
|
}
|
|
|
|
printf("%-*d %-*s ", pw, svc->pid, iw, buf);
|
|
|
|
if (cond == COND_ON)
|
|
printf("%-6.6s ", condstr(cond));
|
|
else
|
|
printf("\e[1m%-6.6s\e[0m ", condstr(cond));
|
|
|
|
svc_cond(svc, buf, sizeof(buf), !plain);
|
|
if (buf[0])
|
|
printf("<%s>", buf);
|
|
puts("");
|
|
}
|
|
|
|
if (json && once)
|
|
puts("\n]");
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int do_cmd(int cmd)
|
|
{
|
|
struct init_request rq = {
|
|
.magic = INIT_MAGIC,
|
|
.cmd = cmd,
|
|
.sleeptime = timeout,
|
|
};
|
|
|
|
if (client_send(&rq, sizeof(rq))) {
|
|
if (rq.cmd == INIT_CMD_NACK)
|
|
puts(rq.data);
|
|
|
|
return 1;
|
|
}
|
|
|
|
/* Wait here for systemd to shutdown/reboot */
|
|
sleep(5);
|
|
|
|
return 0;
|
|
}
|
|
|
|
int do_reboot (char *arg) { return do_cmd(INIT_CMD_REBOOT); }
|
|
int do_halt (char *arg) { return do_cmd(INIT_CMD_HALT); }
|
|
int do_poweroff(char *arg) { return do_cmd(INIT_CMD_POWEROFF); }
|
|
int do_suspend (char *arg) { return do_cmd(INIT_CMD_SUSPEND); }
|
|
|
|
int utmp_show(char *file)
|
|
{
|
|
struct utmp *ut;
|
|
time_t sec;
|
|
|
|
if (heading) {
|
|
static int once = 0;
|
|
|
|
print_header("%s%s ", once ? "\n" : "", file);
|
|
once++;
|
|
}
|
|
utmpname(file);
|
|
|
|
setutent();
|
|
while ((ut = getutent())) {
|
|
char user[sizeof(ut->ut_user) + 1];
|
|
char id[sizeof(ut->ut_id) + 1];
|
|
struct tm *sectm;
|
|
char when[80];
|
|
char addr[64];
|
|
int pid;
|
|
|
|
strlcpy(id, ut->ut_id, sizeof(id));
|
|
strlcpy(user, ut->ut_user, sizeof(user));
|
|
|
|
sec = ut->ut_tv.tv_sec;
|
|
sectm = localtime(&sec);
|
|
strftime(when, sizeof(when), "%F %T", sectm);
|
|
|
|
pid = ut->ut_pid;
|
|
if (ut->ut_addr_v6[1] || ut->ut_addr_v6[2] || ut->ut_addr_v6[3])
|
|
inet_ntop(AF_INET6, ut->ut_addr_v6, addr, sizeof(addr));
|
|
else
|
|
inet_ntop(AF_INET, &ut->ut_addr, addr, sizeof(addr));
|
|
|
|
printf("[%d] [%05d] [%-4.4s] [%-8.8s] [%-12.12s] [%-20.20s] [%-15.15s] [%-19.19s]\n",
|
|
ut->ut_type, pid, id, user, ut->ut_line, ut->ut_host, addr, when);
|
|
}
|
|
endutent();
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int do_utmp(char *file)
|
|
{
|
|
if (!utmp)
|
|
return 1;
|
|
|
|
if (fexist(file))
|
|
return utmp_show(file);
|
|
|
|
return utmp_show(_PATH_WTMP) ||
|
|
utmp_show(_PATH_UTMP);
|
|
}
|
|
|
|
static int show_version(char *arg)
|
|
{
|
|
puts(PACKAGE_STRING);
|
|
printf("Bug report address: %-40s\n", PACKAGE_BUGREPORT);
|
|
#ifdef PACKAGE_URL
|
|
printf("Project homepage: %s\n", PACKAGE_URL);
|
|
#endif
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int plugins_list(char *arg)
|
|
{
|
|
struct init_request rq = {
|
|
.magic = INIT_MAGIC,
|
|
.cmd = INIT_CMD_GET_PLUGINS,
|
|
};
|
|
char buf[sizeof(rq.data) + 1];
|
|
char *ptr;
|
|
int rc;
|
|
|
|
rc = client_send(&rq, sizeof(rq));
|
|
if (rc) {
|
|
if (rc == 255)
|
|
return 69;
|
|
else
|
|
ERRX(69, "No such command");
|
|
}
|
|
memcpy(buf, rq.data, sizeof(buf) - 1);
|
|
buf[sizeof(rq.data)] = 0;
|
|
|
|
if (heading)
|
|
print_header("%-18s %s", "PLUGIN", "DEPENDENCIES");
|
|
|
|
ptr = strtok(buf, " ");
|
|
while (ptr) {
|
|
rq.cmd = INIT_CMD_PLUGIN_DEPS;
|
|
strlcpy(rq.data, ptr, sizeof(rq.data));
|
|
rc = client_send(&rq, sizeof(rq));
|
|
if (rc)
|
|
printf("%s\n", ptr);
|
|
else
|
|
printf("%-18s %s\n", ptr, rq.data);
|
|
|
|
ptr = strtok(NULL, " ");
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* runlevel_string - Convert a bit encoded runlevel to .conf syntax
|
|
* @levels: Bit encoded runlevels
|
|
*
|
|
* Returns:
|
|
* Pointer to string on the form [2345], may be up to 12 chars long,
|
|
* plus escape sequences for highlighting current runlevel.
|
|
*/
|
|
char *runlevel_string(int currlevel, int levels)
|
|
{
|
|
static char lvl[21];
|
|
int i = INIT_LEVEL;
|
|
int pos = 1;
|
|
|
|
memset(lvl, 0, sizeof(lvl));
|
|
lvl[0] = '[';
|
|
|
|
do {
|
|
if (ISSET(levels, i)) {
|
|
if (!plain && currlevel == i)
|
|
pos = strlcat(lvl, "\e[1m", sizeof(lvl));
|
|
|
|
if (i == INIT_LEVEL)
|
|
lvl[pos++] = 'S';
|
|
else
|
|
lvl[pos++] = '0' + i;
|
|
|
|
if (!plain && currlevel == i)
|
|
pos = strlcat(lvl, "\e[0m", sizeof(lvl));
|
|
} else {
|
|
lvl[pos++] = '-';
|
|
}
|
|
|
|
/* XXX: ugly hack to get order right: S0123456789 */
|
|
if (i == INIT_LEVEL)
|
|
i = 0;
|
|
else
|
|
i++;
|
|
} while (i < INIT_LEVEL);
|
|
|
|
|
|
lvl[pos++] = ']';
|
|
lvl[pos] = 0;
|
|
|
|
return lvl;
|
|
}
|
|
|
|
char *runlevel_arr(int levels)
|
|
{
|
|
static char lvl[42];
|
|
int i = INIT_LEVEL;
|
|
int p = 2, s = 0;
|
|
|
|
strlcpy(lvl, "[ ", sizeof(lvl));
|
|
do {
|
|
if (ISSET(levels, i)) {
|
|
if (i == INIT_LEVEL)
|
|
p += snprintf(&lvl[p], sizeof(lvl) - p, "\"S\"");
|
|
else
|
|
p += snprintf(&lvl[p], sizeof(lvl) - p, "%s%c", s ? ", " : "", '0' + i);
|
|
s++;
|
|
}
|
|
/* XXX: ugly hack to get order right: S0123456789 */
|
|
if (i == INIT_LEVEL)
|
|
i = 0;
|
|
else
|
|
i++;
|
|
} while (i < INIT_LEVEL);
|
|
strlcat(lvl, " ]", sizeof(lvl));
|
|
|
|
return lvl;
|
|
}
|
|
|
|
static int missing(svc_t *svc)
|
|
{
|
|
if (svc->state == SVC_HALTED_STATE && svc_is_missing(svc))
|
|
return 1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static char *svc_command(svc_t *svc, char *buf, size_t len, int ansi)
|
|
{
|
|
int bold = missing(svc) && ansi;
|
|
|
|
if (whichp(svc->cmd))
|
|
bold = 0;
|
|
|
|
strlcpy(buf, bold ? "\e[1m" : "", len);
|
|
strlcat(buf, svc->cmd, len);
|
|
|
|
for (int i = 1; i < MAX_NUM_SVC_ARGS; i++) {
|
|
if (!svc->args[i][0])
|
|
break;
|
|
|
|
strlcat(buf, " ", len);
|
|
strlcat(buf, svc->args[i], len);
|
|
}
|
|
|
|
if (svc_is_sysv(svc)) {
|
|
char *cmd = svc->state == SVC_HALTED_STATE ? "stop" : "start";
|
|
|
|
strlcat(buf, " ", len);
|
|
strlcat(buf, cmd, len);
|
|
}
|
|
|
|
strlcat(buf, bold ? "\e[0m" : "", len);
|
|
|
|
return buf;
|
|
}
|
|
|
|
static char *svc_environ(svc_t *svc, char *buf, size_t len, int ansi)
|
|
{
|
|
int bold = missing(svc);
|
|
|
|
if (!ansi || svc_checkenv(svc) || svc->env[0] == '-')
|
|
bold = 0;
|
|
|
|
strlcpy(buf, bold ? "\e[1m" : "", len);
|
|
strlcat(buf, svc->env, len);
|
|
strlcat(buf, bold ? "\e[0m" : "", len);
|
|
|
|
return buf;
|
|
}
|
|
|
|
static char *exit_status(svc_t *svc, char *buf, size_t len)
|
|
{
|
|
int rc, sig;
|
|
char *str;
|
|
|
|
rc = WEXITSTATUS(svc->status);
|
|
sig = WTERMSIG(svc->status);
|
|
|
|
if (WIFEXITED(svc->status)) {
|
|
str = code2str(rc);
|
|
snprintf(buf, len, " (code=exited, status=%d%s%s%s)", rc,
|
|
str[0] ? "/" : "", str,
|
|
svc->manual ? ", manual=yes" : "");
|
|
}
|
|
else if (WIFSIGNALED(svc->status)) {
|
|
str = sig2str(sig);
|
|
snprintf(buf, len, " (code=signal, status=%d%s%s)", sig, str[0] ? "/" : "", str);
|
|
}
|
|
|
|
return buf;
|
|
}
|
|
|
|
static char *status(svc_t *svc, int full)
|
|
{
|
|
static char buf[96];
|
|
const char *color;
|
|
char ok[48] = {0};
|
|
char *s;
|
|
|
|
s = svc_status(svc);
|
|
switch (svc->state) {
|
|
case SVC_HALTED_STATE:
|
|
exit_status(svc, ok, sizeof(ok));
|
|
color = "\e[1m";
|
|
break;
|
|
|
|
case SVC_RUNNING_STATE:
|
|
color = "\e[1;32m";
|
|
break;
|
|
|
|
case SVC_DONE_STATE:
|
|
exit_status(svc, ok, sizeof(ok));
|
|
if (WIFEXITED(svc->status)) {
|
|
if (WEXITSTATUS(svc->status))
|
|
color = "\e[1;31m";
|
|
else
|
|
color = "\e[1;32m";
|
|
} else {
|
|
if (full && WIFSIGNALED(svc->status))
|
|
color = "\e[1;31m";
|
|
else
|
|
color = "\e[1;33m";
|
|
}
|
|
break;
|
|
|
|
default:
|
|
exit_status(svc, ok, sizeof(ok));
|
|
color = "\e[1;33m";
|
|
break;
|
|
}
|
|
|
|
if (!full || plain)
|
|
color = NULL;
|
|
|
|
if (!full)
|
|
snprintf(buf, sizeof(buf), "%-8.8s", s);
|
|
else
|
|
snprintf(buf, sizeof(buf), "%s%s%s%s",
|
|
color ? color : "", s, ok, color ? "\e[0m" : "");
|
|
|
|
return buf;
|
|
}
|
|
|
|
static void show_cgroup_tree(char *group, char *pfx)
|
|
{
|
|
char path[256];
|
|
|
|
if (!group) {
|
|
puts("");
|
|
return;
|
|
}
|
|
|
|
strlcpy(path, FINIT_CGPATH, sizeof(path));
|
|
strlcat(path, group, sizeof(path));
|
|
|
|
cgroup_tree(path, pfx, 0, 0);
|
|
}
|
|
|
|
/*
|
|
* arg: 'foo' should match foo:1 foo:2, etc. but not foobar
|
|
* arg: 'foo:1' should only match foo:1
|
|
* arg: 'foo:' is allowed to fail, unsupported syntax atm
|
|
* arg: 'foo:*' is allowed to fail, unsupported syntax atm
|
|
*/
|
|
static int svc_compare(svc_t *svc, char *arg)
|
|
{
|
|
char ident[MAX_IDENT_LEN];
|
|
char *ptr;
|
|
|
|
svc_ident(svc, ident, sizeof(ident));
|
|
ptr = strchr(ident, ':');
|
|
if (ptr && !strchr(arg, ':'))
|
|
*ptr = 0;
|
|
|
|
if (!strcmp(ident, arg))
|
|
return 1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int json_status_one(FILE *fp, svc_t *svc, char *indent, int prev)
|
|
{
|
|
long now = jiffies();
|
|
char *pidfn = NULL;
|
|
char buf[512];
|
|
|
|
pidfn = svc->pidfile;
|
|
if (pidfn[0] == '!')
|
|
pidfn++;
|
|
else if (pidfn[0] == 0)
|
|
pidfn = "none";
|
|
|
|
fprintf(fp,
|
|
"%s"
|
|
"%s{\n"
|
|
"%s \"identity\": \"%s\",\n"
|
|
"%s \"description\": \"%s\",\n"
|
|
"%s \"type\": \"%s\",\n"
|
|
"%s \"forking\": %s,\n"
|
|
"%s \"status\": \"%s\",\n",
|
|
prev ? ",\n" : indent, prev ? indent : "",
|
|
indent, svc_ident(svc, NULL, 0),
|
|
indent, svc->desc,
|
|
indent, svc_typestr(svc),
|
|
indent, svc->forking ? "true" : "false",
|
|
indent, svc_status(svc));
|
|
if (svc->state != SVC_RUNNING_STATE) {
|
|
int rc, sig;
|
|
|
|
rc = WEXITSTATUS(svc->status);
|
|
sig = WTERMSIG(svc->status);
|
|
|
|
if (WIFEXITED(svc->status))
|
|
fprintf(fp,
|
|
"%s \"exit\": { \"%s\": %d },\n",
|
|
indent, "code", rc);
|
|
else if (WIFSIGNALED(svc->status))
|
|
fprintf(fp,
|
|
"%s \"exit\": { \"%s\": %d },\n",
|
|
indent, "signal", sig);
|
|
}
|
|
fprintf(fp,
|
|
"%s \"origin\": \"%s\",\n"
|
|
"%s \"command\": \"%s\",\n",
|
|
indent, svc->file[0] ? svc->file : "built-in",
|
|
indent, svc_command(svc, buf, sizeof(buf), 0));
|
|
svc_environ(svc, buf, sizeof(buf), 0);
|
|
if (buf[0])
|
|
fprintf(fp,
|
|
"%s \"environment\": \"%s\",\n", indent, buf);
|
|
svc_cond(svc, buf, sizeof(buf), 0);
|
|
if (buf[0])
|
|
fprintf(fp,
|
|
"%s \"condition\": %s,\n", indent, buf);
|
|
if (svc->manual)
|
|
fprintf(fp,
|
|
"%s \"starts\": %d,\n", indent, svc->once);
|
|
fprintf(fp,
|
|
"%s \"restarts\": %d,\n", indent, svc->restart_tot); /* XXX: add restart_cnt and restart_max */
|
|
fprintf(fp,
|
|
"%s \"pidfile\": \"%s\",\n"
|
|
"%s \"pid\": %d,\n"
|
|
"%s \"user\": \"%s\",\n"
|
|
"%s \"group\": \"%s\",\n"
|
|
"%s \"uptime\": %ld,\n"
|
|
"%s \"runlevels\": %s\n"
|
|
"%s}",
|
|
indent, pidfn,
|
|
indent, svc->pid,
|
|
indent, svc->username,
|
|
indent, svc->group,
|
|
indent, svc->pid ? now - svc->start_time : 0,
|
|
indent, runlevel_arr(svc->runlevels),
|
|
indent);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int show_status(char *arg)
|
|
{
|
|
char ident[MAX_IDENT_LEN];
|
|
char buf[512];
|
|
int num = 0;
|
|
svc_t *svc;
|
|
|
|
runlevel = runlevel_get(NULL);
|
|
|
|
while (arg && arg[0]) {
|
|
long now = jiffies();
|
|
char uptm[42] = "N/A";
|
|
char *pidfn = NULL;
|
|
|
|
for (svc = client_svc_iterator(1); svc; svc = client_svc_iterator(0))
|
|
num += svc_compare(svc, arg);
|
|
|
|
if (num > 1)
|
|
break;
|
|
|
|
svc = client_svc_find(arg);
|
|
if (!svc)
|
|
ERRX(noerr ? 0 : 69, "no such task or service(s): %s", arg);
|
|
|
|
if (quiet) {
|
|
if (svc_is_runtask(svc)) {
|
|
if (svc->started)
|
|
return 0;
|
|
return 1;
|
|
}
|
|
return svc->state != SVC_RUNNING_STATE;
|
|
}
|
|
|
|
if (json) {
|
|
int rc;
|
|
|
|
rc = json_status_one(stdout, svc, "", 0);
|
|
puts("");
|
|
return rc;
|
|
}
|
|
|
|
pidfn = svc->pidfile;
|
|
if (pidfn[0] == '!')
|
|
pidfn++;
|
|
else if (pidfn[0] == 0)
|
|
pidfn = "none";
|
|
|
|
printf(" Status : %s\n", status(svc, 1));
|
|
printf(" Identity : %s\n", svc_ident(svc, ident, sizeof(ident)));
|
|
printf("Description : %s\n", svc->desc);
|
|
printf(" Origin : %s\n", svc->file[0] ? svc->file : "built-in");
|
|
svc_environ(svc, buf, sizeof(buf), !plain);
|
|
if (buf[0])
|
|
printf("Environment : %s\n", buf);
|
|
svc_cond(svc, buf, sizeof(buf), !plain);
|
|
if (buf[0])
|
|
printf("Condition(s): <%s>\n", buf);
|
|
printf(" Command : %s\n", svc_command(svc, buf, sizeof(buf), !plain));
|
|
printf(" PID file : %s\n", pidfn);
|
|
printf(" PID : %d\n", svc->pid);
|
|
printf(" User : %s\n", svc->username);
|
|
printf(" Group : %s\n", svc->group);
|
|
printf(" Uptime : %s\n", svc->pid ? uptime(now - svc->start_time, uptm, sizeof(uptm)) : uptm);
|
|
if (svc->manual)
|
|
printf(" Starts : %d\n", svc->once);
|
|
printf(" Restarts : %d (%d/%d)\n", svc->restart_tot, svc->restart_cnt, svc->restart_max);
|
|
printf(" Runlevels : %s\n", runlevel_string(runlevel, svc->runlevels));
|
|
if (cgrp && svc->pid > 1) {
|
|
char grbuf[128];
|
|
char path[256];
|
|
struct cg *cg;
|
|
char *group;
|
|
|
|
group = pid_cgroup(svc->pid, grbuf, sizeof(grbuf));
|
|
if (!group)
|
|
goto no_cgroup; /* ... or PID doesn't exist (anymore) */
|
|
snprintf(path, sizeof(path), "%s/%s", FINIT_CGPATH, group);
|
|
cg = cg_conf(path);
|
|
|
|
printf(" Memory : %s\n", memsz(cgroup_memory(group), uptm, sizeof(uptm)));
|
|
printf(" CGroup : %s cpu %s [%s, %s] mem [%s, %s]\n",
|
|
group, cg->cg_cpu.set, cg->cg_cpu.weight, cg->cg_cpu.max,
|
|
cg->cg_mem.min, cg->cg_mem.max);
|
|
show_cgroup_tree(group, " ");
|
|
}
|
|
no_cgroup:
|
|
printf("\n");
|
|
|
|
return do_log(svc, "| tail -10");
|
|
}
|
|
|
|
if (json) {
|
|
int prev = 0;
|
|
|
|
for (svc = client_svc_iterator(1); svc; svc = client_svc_iterator(0)) {
|
|
svc_ident(svc, ident, sizeof(ident));
|
|
if (num && !svc_compare(svc, arg))
|
|
continue;
|
|
|
|
if (!prev)
|
|
fputs("[\n", stdout);
|
|
json_status_one(stdout, svc, " ", prev++);
|
|
}
|
|
if (prev)
|
|
fputs("\n]\n", stdout);
|
|
|
|
return 0;
|
|
}
|
|
|
|
col_widths();
|
|
if (heading) {
|
|
char title[80];
|
|
|
|
snprintf(title, sizeof(title), "%-*s %-*s %-8s %-13s ",
|
|
pw, "PID", iw, "IDENT", "STATUS", "RUNLEVELS");
|
|
if (!verbose)
|
|
strlcat(title, "DESCRIPTION", sizeof(title));
|
|
else
|
|
strlcat(title, "COMMAND", sizeof(title));
|
|
|
|
print_header("%s", title);
|
|
}
|
|
|
|
for (svc = client_svc_iterator(1); svc; svc = client_svc_iterator(0)) {
|
|
char *lvls;
|
|
|
|
svc_ident(svc, ident, sizeof(ident));
|
|
if (num && !svc_compare(svc, arg))
|
|
continue;
|
|
|
|
printf("%-*d ", pw, svc->pid);
|
|
printf("%-*s %s ", iw, ident, status(svc, 0));
|
|
|
|
lvls = runlevel_string(runlevel, svc->runlevels);
|
|
if (strchr(lvls, '\e'))
|
|
printf("%-21.21s ", lvls);
|
|
else
|
|
printf("%-13.13s ", lvls);
|
|
|
|
if (!verbose)
|
|
puts(svc->desc);
|
|
else
|
|
puts(svc_command(svc, buf, sizeof(buf), !plain));
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int show_ident(char *arg)
|
|
{
|
|
svc_t *svc;
|
|
|
|
for (svc = client_svc_iterator(1); svc; svc = client_svc_iterator(0)) {
|
|
char ident[MAX_IDENT_LEN];
|
|
size_t len;
|
|
char *pos;
|
|
|
|
svc_ident(svc, ident, sizeof(ident));
|
|
pos = strchr(ident, ':');
|
|
if (pos)
|
|
len = pos - ident;
|
|
else
|
|
len = strlen(ident);
|
|
if (arg && arg[0] && strncasecmp(ident, arg, len))
|
|
continue;
|
|
|
|
puts(ident);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int transform(char *nm)
|
|
{
|
|
char *names[] = {
|
|
"reboot", "shutdown", "poweroff", "halt", "suspend",
|
|
NULL
|
|
};
|
|
size_t i;
|
|
|
|
for (i = 0; names[i]; i++) {
|
|
if (!strcmp(nm, names[i]))
|
|
return 1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int has_conf(char *path, size_t len, char *name)
|
|
{
|
|
paste(path, len, finit_rcsd, name);
|
|
if (!fisdir(path)) {
|
|
strlcpy(path, finit_rcsd, len);
|
|
return 0;
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
static int usage(int rc)
|
|
{
|
|
int has_rcsd = fisdir(finit_rcsd);
|
|
int has_ena;
|
|
char avail[256];
|
|
char ena[256];
|
|
|
|
has_conf(avail, sizeof(avail), "available");
|
|
has_ena = has_conf(ena, sizeof(ena), "enabled");
|
|
|
|
fprintf(stderr,
|
|
"Usage: %s [OPTIONS] [COMMAND]\n"
|
|
"\n"
|
|
"Options:\n"
|
|
" -b, --batch Batch mode, no screen size probing\n"
|
|
" -c, --create Create missing paths (and files) as needed\n"
|
|
" -f, --force Ignore missing files and arguments, never prompt\n"
|
|
" -h, --help This help text\n"
|
|
" -j, --json JSON output in 'status' and 'cond' commands\n"
|
|
" -n, --noerr Ignore error, e.g., already started/enabled/...\n"
|
|
" -1, --once Only one lap in commands like 'top'\n"
|
|
" -p, --plain Use plain table headings, no ctrl chars\n"
|
|
" -q, --quiet Silent, only return status of command\n"
|
|
" -t, --no-heading Skip table headings\n"
|
|
" -v, --verbose Verbose output\n"
|
|
" -V, --version Show program version\n"
|
|
"\n"
|
|
"Commands:\n"
|
|
" debug Toggle Finit (daemon) debug\n"
|
|
" help This help text\n"
|
|
" version Show program version\n"
|
|
"\n", prognm);
|
|
|
|
if (has_rcsd)
|
|
fprintf(stderr,
|
|
" ls | list List all .conf in %s\n"
|
|
" create <CONF> Create .conf in %s\n"
|
|
" delete <CONF> Delete .conf in %s\n"
|
|
" show <CONF> Show .conf in %s\n"
|
|
" edit <CONF> Edit .conf in %s\n"
|
|
" touch <CONF> Change .conf in %s\n",
|
|
finit_rcsd, avail, avail, avail, avail, avail);
|
|
else
|
|
fprintf(stderr,
|
|
" show Show %s\n", finit_conf);
|
|
|
|
if (has_ena)
|
|
fprintf(stderr,
|
|
" enable <CONF> Enable .conf in %s\n", avail);
|
|
if (has_ena)
|
|
fprintf(stderr,
|
|
" disable <CONF> Disable .conf in %s\n", ena);
|
|
if (has_rcsd)
|
|
fprintf(stderr,
|
|
" reload Reload *.conf in %s (activate changes)\n", finit_rcsd);
|
|
else
|
|
fprintf(stderr,
|
|
" reload Reload %s (activate changes)\n", finit_conf);
|
|
|
|
fprintf(stderr,
|
|
"\n"
|
|
" cond set <COND> Set (assert) user-defined conditions +usr/COND\n"
|
|
" cond get <COND> Get status of user-defined condition, see $? and -v\n"
|
|
" cond clear <COND> Clear (deassert) user-defined conditions -usr/COND\n"
|
|
" cond status Show condition status, default cond command\n"
|
|
" cond dump [TYPE] Dump all, or a type of, conditions and their status\n"
|
|
"\n"
|
|
" log [NAME] Show ten last Finit, or NAME, messages from syslog\n"
|
|
" start <NAME>[:ID] Start service by name, with optional ID\n"
|
|
" stop <NAME>[:ID] Stop/Pause a running service by name\n"
|
|
" reload <NAME>[:ID] Reload service as if .conf changed (SIGHUP or restart)\n"
|
|
" This allows restart of run/tasks that have already run\n"
|
|
" Note: Finit .conf file(s) are *not* reloaded!\n"
|
|
" restart <NAME>[:ID] Restart (stop/start) service by name\n"
|
|
" signal <NAME>[:ID] <S> Send signal S to service by name, with optional ID\n"
|
|
" ident [NAME] Show matching identities for NAME, or all\n"
|
|
" status <NAME>[:ID] Show service status, by name\n"
|
|
" status Show status of services, default command\n");
|
|
if (cgrp)
|
|
fprintf(stderr,
|
|
"\n"
|
|
" cgroup List cgroup config overview\n"
|
|
" ps List processes based on cgroups\n"
|
|
" top Show top-like listing based on cgroups\n");
|
|
fprintf(stderr,
|
|
"\n"
|
|
" plugins List installed plugins\n"
|
|
"\n"
|
|
" runlevel [0-9] Show or set runlevel: 0 halt, 6 reboot\n"
|
|
" reboot Reboot system\n"
|
|
" halt Halt system\n"
|
|
" poweroff Halt and power off system\n"
|
|
" suspend Suspend system\n");
|
|
|
|
if (utmp)
|
|
fprintf(stderr,
|
|
"\n"
|
|
" utmp show Raw dump of UTMP/WTMP db\n");
|
|
fprintf(stderr, "\n");
|
|
|
|
return rc;
|
|
}
|
|
|
|
static int do_devel(char *arg)
|
|
{
|
|
(void)arg;
|
|
|
|
printf("Screen %dx%d\n", ttcols, ttrows);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int do_help(char *arg)
|
|
{
|
|
return usage(0);
|
|
}
|
|
|
|
static int cmd_cond(struct cmd *cmd)
|
|
{
|
|
if (!cmd || !cmd->cond)
|
|
return 1;
|
|
|
|
return *cmd->cond;
|
|
}
|
|
|
|
static int cmd_parse(int argc, char *argv[], struct cmd *command)
|
|
{
|
|
int i, j;
|
|
|
|
for (i = 0; argc > 0 && command[i].cmd; i++) {
|
|
if (!cmd_cond(&command[i]))
|
|
continue;
|
|
|
|
if (!string_compare(command[i].cmd, argv[0]))
|
|
continue;
|
|
|
|
if (command[i].ctx)
|
|
return cmd_parse(argc - 1, &argv[1], command[i].ctx);
|
|
|
|
if (command[i].cb_multiarg)
|
|
return command[i].cb_multiarg(argc - 1, argv + 1);
|
|
|
|
if (command[i].cb) {
|
|
int rc = 0;
|
|
|
|
if (argc == 1)
|
|
return command[i].cb(NULL);
|
|
|
|
for (j = 1; j < argc; j++)
|
|
rc |= command[i].cb(argv[j]);
|
|
|
|
return rc;
|
|
}
|
|
}
|
|
|
|
if (argv[0] && strlen(argv[0]) > 0)
|
|
ERRX(3, "No such command. See 'initctl help' for an overview of available commands.");
|
|
|
|
return command[0].cb(NULL); /* default cmd */
|
|
}
|
|
|
|
static void cleanup(void)
|
|
{
|
|
if (finit_conf)
|
|
free(finit_conf);
|
|
if (finit_rcsd)
|
|
free(finit_rcsd);
|
|
}
|
|
|
|
static void sourcerc(void)
|
|
{
|
|
char *line;
|
|
FILE *fp;
|
|
|
|
fp = fopen(_PATH_VARRUN "finit/.initrc", "r");
|
|
if (!fp)
|
|
goto fallback;
|
|
|
|
while ((line = fparseln(fp, NULL, NULL, NULL, 0))) {
|
|
char *val;
|
|
|
|
if ((val = fgetval(line, "FINIT_CONF", "="))) {
|
|
if (finit_conf)
|
|
free(finit_conf);
|
|
finit_conf = val;
|
|
}
|
|
if ((val = fgetval(line, "FINIT_RCSD", "="))) {
|
|
if (finit_rcsd)
|
|
free(finit_rcsd);
|
|
finit_rcsd = val;
|
|
}
|
|
|
|
free(line);
|
|
}
|
|
|
|
fallback:
|
|
if (!finit_conf)
|
|
finit_conf = strdup(FINIT_CONF);
|
|
if (!finit_rcsd)
|
|
finit_rcsd = strdup(FINIT_RCSD);
|
|
|
|
atexit(cleanup);
|
|
}
|
|
|
|
int main(int argc, char *argv[])
|
|
{
|
|
struct option long_options[] = {
|
|
{ "batch", 0, NULL, 'b' },
|
|
{ "create", 0, NULL, 'c' },
|
|
{ "debug", 0, NULL, 'd' },
|
|
{ "force", 0, NULL, 'f' },
|
|
{ "help", 0, NULL, 'h' },
|
|
{ "json", 0, NULL, 'j' },
|
|
{ "noerr", 0, NULL, 'n' },
|
|
{ "once", 0, NULL, '1' },
|
|
{ "plain", 0, NULL, 'p' },
|
|
{ "quiet", 0, NULL, 'q' },
|
|
{ "no-heading", 0, NULL, 't' },
|
|
{ "verbose", 0, NULL, 'v' },
|
|
{ "version", 0, NULL, 'V' },
|
|
{ NULL, 0, NULL, 0 }
|
|
};
|
|
struct cmd cond[] = {
|
|
{ "status", NULL, do_cond_show, NULL, NULL }, /* default cmd */
|
|
{ "dump", NULL, do_cond_dump, NULL, NULL },
|
|
{ "set", NULL, do_cond_set, NULL, NULL },
|
|
{ "get", NULL, do_cond_get, NULL, NULL },
|
|
{ "clr", NULL, do_cond_clr, NULL, NULL },
|
|
{ "clear", NULL, do_cond_clr, NULL, NULL },
|
|
{ NULL, NULL, NULL, NULL, NULL }
|
|
};
|
|
struct cmd command[] = {
|
|
{ "status", NULL, show_status, NULL, NULL }, /* default cmd */
|
|
{ "ident", NULL, show_ident, NULL, NULL },
|
|
|
|
{ "debug", NULL, toggle_debug, NULL, NULL },
|
|
{ "devel", NULL, do_devel, NULL, NULL },
|
|
{ "help", NULL, do_help, NULL, NULL },
|
|
{ "version", NULL, show_version, NULL, NULL },
|
|
|
|
{ "list", NULL, serv_list, NULL, NULL },
|
|
{ "ls", NULL, serv_list, NULL, NULL },
|
|
{ "enable", NULL, serv_enable, NULL, NULL },
|
|
{ "disable", NULL, serv_disable, NULL, NULL },
|
|
{ "touch", NULL, serv_touch, NULL, NULL },
|
|
{ "show", NULL, serv_show, NULL, NULL },
|
|
{ "cat", NULL, serv_show, NULL, NULL }, /* alias */
|
|
{ "edit", NULL, serv_edit, NULL, NULL },
|
|
{ "create", NULL, serv_creat, NULL, NULL },
|
|
{ "delete", NULL, serv_delete, NULL, NULL },
|
|
{ "reload", NULL, do_reload, NULL, NULL },
|
|
|
|
{ "cond", cond, NULL, NULL, NULL },
|
|
|
|
{ "log", NULL, show_log, NULL, NULL },
|
|
{ "start", NULL, do_start, NULL, NULL },
|
|
{ "stop", NULL, do_stop, NULL, NULL },
|
|
{ "restart", NULL, do_restart, NULL, NULL },
|
|
{ "signal", NULL, NULL, NULL, do_signal },
|
|
{ "kill", NULL, NULL, NULL, do_signal }, /* alias */
|
|
|
|
{ "cgroup", NULL, show_cgroup, &cgrp, NULL },
|
|
{ "ps", NULL, show_cgps, &cgrp, NULL },
|
|
{ "top", NULL, show_cgtop, &cgrp, NULL },
|
|
|
|
{ "plugins", NULL, plugins_list, NULL, NULL },
|
|
|
|
{ "runlevel", NULL, do_runlevel, NULL, NULL },
|
|
{ "reboot", NULL, do_reboot, NULL, NULL },
|
|
{ "halt", NULL, do_halt, NULL, NULL },
|
|
{ "poweroff", NULL, do_poweroff, NULL, NULL },
|
|
{ "suspend", NULL, do_suspend, NULL, NULL },
|
|
|
|
{ "utmp", NULL, do_utmp, &utmp, NULL },
|
|
{ NULL, NULL, NULL, NULL, NULL }
|
|
};
|
|
int interactive = 1, c;
|
|
|
|
if (transform(progname(argv[0])))
|
|
return reboot_main(argc, argv);
|
|
|
|
/* Enable functionality depending on system capabilities */
|
|
sourcerc();
|
|
cgrp = cgroup_avail();
|
|
utmp = has_utmp();
|
|
|
|
while ((c = getopt_long(argc, argv, "1bcdfh?jnpqtvV", long_options, NULL)) != EOF) {
|
|
switch(c) {
|
|
case '1':
|
|
ionce = 1;
|
|
break;
|
|
|
|
case 'b':
|
|
interactive = 0;
|
|
break;
|
|
|
|
case 'c':
|
|
icreate = 1;
|
|
break;
|
|
|
|
case 'd':
|
|
debug = 1;
|
|
break;
|
|
|
|
case 'f':
|
|
iforce = 1;
|
|
break;
|
|
|
|
case 'h':
|
|
case '?':
|
|
return usage(0);
|
|
|
|
case 'j':
|
|
json = 1;
|
|
break;
|
|
|
|
case 'n':
|
|
noerr = 1;
|
|
break;
|
|
|
|
case 'p':
|
|
plain = 1;
|
|
break;
|
|
|
|
case 'q':
|
|
quiet = 1;
|
|
break;
|
|
|
|
case 't':
|
|
heading = 0;
|
|
break;
|
|
|
|
case 'v':
|
|
verbose = 1;
|
|
break;
|
|
|
|
case 'V':
|
|
return show_version(NULL);
|
|
}
|
|
}
|
|
|
|
if (interactive)
|
|
ttinit();
|
|
|
|
return cmd_parse(argc - optind, &argv[optind], command);
|
|
}
|
|
|
|
void logit(int prio, const char *fmt, ...)
|
|
{
|
|
va_list ap;
|
|
|
|
if (quiet)
|
|
return;
|
|
|
|
va_start(ap, fmt);
|
|
if (prio <= LOG_ERR)
|
|
verrx(1, fmt, ap);
|
|
else
|
|
vwarnx(fmt, ap);
|
|
va_end(ap);
|
|
}
|
|
|
|
/**
|
|
* Local Variables:
|
|
* indent-tabs-mode: t
|
|
* c-file-style: "linux"
|
|
* End:
|
|
*/
|