mirror of
https://github.com/troglobit/finit.git
synced 2026-10-02 05:52:48 +07:00
966 lines
20 KiB
C
966 lines
20 KiB
C
/* New client tool, replaces old /dev/initctl API and telinit tool
|
|
*
|
|
* Copyright (c) 2015-2021 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 <err.h>
|
|
#include <ftw.h>
|
|
#include <ctype.h>
|
|
#include <dirent.h>
|
|
#include <getopt.h>
|
|
#include <paths.h>
|
|
#include <signal.h>
|
|
#include <stdio.h>
|
|
#include <time.h>
|
|
#include <utmp.h>
|
|
#include <arpa/inet.h>
|
|
#include <lite/lite.h>
|
|
|
|
#include "client.h"
|
|
#include "cond.h"
|
|
#include "serv.h"
|
|
#include "service.h"
|
|
#include "util.h"
|
|
|
|
#define _PATH_COND _PATH_VARRUN "finit/cond/"
|
|
|
|
#define NONE " "
|
|
#define PIPE plain ? "|" : "│"
|
|
#define FORK plain ? "|-" : "├─"
|
|
#define END plain ? "`-" : "└─"
|
|
|
|
struct cmd {
|
|
char *cmd;
|
|
struct cmd *ctx;
|
|
int (*cb)(char *arg);
|
|
};
|
|
|
|
int icreate = 0;
|
|
int iforce = 0;
|
|
int heading = 1;
|
|
int verbose = 0;
|
|
int plain = 0;
|
|
int runlevel = 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 < 5)
|
|
iw = 5;
|
|
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 < screen_cols; len++)
|
|
fputc('=', stdout);
|
|
fputs("\n", stdout);
|
|
} else {
|
|
char buf[screen_cols];
|
|
|
|
vsnprintf(buf, sizeof(buf), fmt, ap);
|
|
printheader(stdout, buf, 0);
|
|
}
|
|
|
|
va_end(ap);
|
|
}
|
|
|
|
static int runlevel_get(int *prevlevel)
|
|
{
|
|
int result;
|
|
struct init_request rq;
|
|
|
|
memset(&rq, 0, sizeof(rq));
|
|
rq.cmd = INIT_CMD_GET_RUNLEVEL;
|
|
rq.magic = INIT_MAGIC;
|
|
|
|
result = client_send(&rq, sizeof(rq));
|
|
if (!result) {
|
|
result = rq.runlevel;
|
|
if (prevlevel)
|
|
*prevlevel = rq.sleeptime;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
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(char *svc)
|
|
{
|
|
char cmd[128];
|
|
char *logfile = "/var/log/syslog";
|
|
|
|
if (!svc || !svc[0])
|
|
svc = "finit";
|
|
|
|
if (!fexist(logfile))
|
|
logfile = "/var/log/messages";
|
|
|
|
snprintf(cmd, sizeof(cmd), "cat %s | grep %s | tail -10", logfile, svc);
|
|
|
|
return system(cmd);
|
|
}
|
|
|
|
static int do_runlevel(char *arg)
|
|
{
|
|
struct init_request rq = {
|
|
.magic = INIT_MAGIC,
|
|
.cmd = INIT_CMD_RUNLVL,
|
|
};
|
|
|
|
if (!arg) {
|
|
int prevlevel = 0;
|
|
int runlevel;
|
|
char prev;
|
|
|
|
runlevel = runlevel_get(&prevlevel);
|
|
if (255 == runlevel) {
|
|
printf("unknown\n");
|
|
return 0;
|
|
}
|
|
|
|
prev = prevlevel + '0';
|
|
if (prev <= '0' || prev > '9')
|
|
prev = 'N';
|
|
|
|
printf("%c %d\n", prev , runlevel);
|
|
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,
|
|
};
|
|
|
|
if (arg)
|
|
strlcpy(rq.data, arg, sizeof(rq.data));
|
|
|
|
return client_send(&rq, sizeof(rq));
|
|
}
|
|
|
|
static int do_reload (char *arg) { return do_svc(INIT_CMD_RELOAD, arg); }
|
|
|
|
/*
|
|
* 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(1, "missing argument to %s", (cmd == INIT_CMD_START_SVC
|
|
? "start"
|
|
: (cmd == INIT_CMD_STOP_SVC
|
|
? "stop"
|
|
: "restart")));
|
|
|
|
strlcpy(rq.data, arg, sizeof(rq.data));
|
|
if (client_send(&rq, sizeof(rq))) {
|
|
fprintf(stderr, "No such task or service(s): %s\n\n", arg);
|
|
fprintf(stderr, "Usage: initctl %s <NAME>[:ID]\n",
|
|
cmd == INIT_CMD_START_SVC ? "start" :
|
|
(cmd == INIT_CMD_STOP_SVC ? "stop" : "restart"));
|
|
return 1;
|
|
}
|
|
|
|
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_restart(char *arg) { return do_startstop(INIT_CMD_RESTART_SVC, arg); }
|
|
|
|
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_COND "reconf"))
|
|
return 0;
|
|
|
|
asserted = condstr(cond_get_path(fpath));
|
|
cond = &fpath[strlen(_PATH_COND)];
|
|
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;
|
|
}
|
|
}
|
|
|
|
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) {
|
|
char title[80];
|
|
|
|
snprintf(title, sizeof(title), "%-*s %-*s %-6s %s", pw, "PID",
|
|
iw, "IDENT", "STATUS", "CONDITION");
|
|
print_header(title);
|
|
}
|
|
|
|
if (nftw(_PATH_COND, dump_one_cond, 20, 0) == -1) {
|
|
warnx("Failed parsing %s", _PATH_COND);
|
|
return 1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void show_cond_one(const char *_conds)
|
|
{
|
|
static char conds[MAX_COND_LEN];
|
|
char *cond;
|
|
|
|
strlcpy(conds, _conds, sizeof(conds));
|
|
|
|
putchar('<');
|
|
|
|
for (cond = strtok(conds, ","); cond; cond = strtok(NULL, ",")) {
|
|
if (cond != conds)
|
|
putchar(',');
|
|
|
|
switch (cond_get(cond)) {
|
|
case COND_ON:
|
|
printf("+%s", cond);
|
|
break;
|
|
|
|
case COND_FLUX:
|
|
printf("\e[1m~%s\e[0m", cond);
|
|
break;
|
|
|
|
case COND_OFF:
|
|
printf("\e[1m-%s\e[0m", cond);
|
|
break;
|
|
}
|
|
}
|
|
|
|
putchar('>');
|
|
}
|
|
|
|
static int do_cond_show(char *arg)
|
|
{
|
|
char ident[MAX_IDENT_LEN];
|
|
enum cond_state cond;
|
|
svc_t *svc;
|
|
|
|
col_widths();
|
|
if (heading) {
|
|
char title[80];
|
|
|
|
snprintf(title, sizeof(title), "%-*s %-*s %-6s %s", pw, "PID",
|
|
iw, "IDENT", "STATUS", "CONDITION (+ ON, ~ FLUX, - OFF)");
|
|
print_header(title);
|
|
}
|
|
|
|
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, ident, sizeof(ident));
|
|
printf("%-*d %-*s ", pw, svc->pid, iw, ident);
|
|
|
|
if (cond == COND_ON)
|
|
printf("%-6.6s ", condstr(cond));
|
|
else
|
|
printf("\e[1m%-6.6s\e[0m ", condstr(cond));
|
|
|
|
show_cond_one(svc->cond);
|
|
puts("");
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int do_cmd(int cmd)
|
|
{
|
|
struct init_request rq = {
|
|
.magic = INIT_MAGIC,
|
|
.cmd = cmd,
|
|
};
|
|
|
|
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)
|
|
{
|
|
static int once = 0;
|
|
struct tm *sectm;
|
|
struct utmp *ut;
|
|
time_t sec;
|
|
|
|
int pid;
|
|
char id[sizeof(ut->ut_id) + 1], user[sizeof(ut->ut_user) + 1], when[80];
|
|
|
|
if (heading) {
|
|
print_header("%s%s ", once ? "\n" : "", file);
|
|
once++;
|
|
}
|
|
utmpname(file);
|
|
|
|
setutent();
|
|
while ((ut = getutent())) {
|
|
char addr[64];
|
|
|
|
memset(id, 0, sizeof(id));
|
|
strlcpy(id, ut->ut_id, sizeof(id));
|
|
|
|
memset(user, 0, sizeof(user));
|
|
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 (!has_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("v" PACKAGE_VERSION);
|
|
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 runlevel, int levels)
|
|
{
|
|
int i, pos = 1;
|
|
static char lvl[21];
|
|
|
|
memset(lvl, 0, sizeof(lvl));
|
|
lvl[0] = '[';
|
|
|
|
for (i = 0; i < 10; i++) {
|
|
if (ISSET(levels, i)) {
|
|
if (runlevel == i)
|
|
pos = strlcat(lvl, "\e[1m", sizeof(lvl));
|
|
|
|
if (i == 0)
|
|
lvl[pos++] = 'S';
|
|
else
|
|
lvl[pos++] = '0' + i;
|
|
|
|
if (runlevel == i)
|
|
pos = strlcat(lvl, "\e[0m", sizeof(lvl));
|
|
} else {
|
|
lvl[pos++] = '-';
|
|
}
|
|
}
|
|
|
|
lvl[pos++] = ']';
|
|
lvl[pos] = 0;
|
|
|
|
return lvl;
|
|
}
|
|
|
|
static char *svc_command(svc_t *svc, char *cmd, size_t len)
|
|
{
|
|
strlcpy(cmd, svc->cmd, len);
|
|
for (int i = 1; i < MAX_NUM_SVC_ARGS; i++) {
|
|
if (!svc->args[i][0])
|
|
break;
|
|
|
|
strlcat(cmd, " ", len);
|
|
strlcat(cmd, svc->args[i], len);
|
|
}
|
|
|
|
return cmd;
|
|
}
|
|
|
|
static int show_status(char *arg)
|
|
{
|
|
char ident[MAX_IDENT_LEN];
|
|
char cmd[512];
|
|
svc_t *svc;
|
|
|
|
runlevel = runlevel_get(NULL);
|
|
|
|
if (arg && arg[0]) {
|
|
long now = jiffies();
|
|
char buf[42] = "N/A";
|
|
|
|
svc = client_svc_find(arg);
|
|
if (!svc)
|
|
return 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");
|
|
printf("Environment : %s\n", svc->env);
|
|
printf("Command : %s\n", svc_command(svc, cmd, sizeof(cmd)));
|
|
printf("PID : %d\n", svc->pid);
|
|
printf("Uptime : %s\n", svc->pid ? uptime(now - svc->start_time, buf, sizeof(buf)) : buf);
|
|
printf("Runlevels : %s\n", runlevel_string(runlevel, svc->runlevels));
|
|
printf("Status : %s\n", svc_status(svc));
|
|
printf("\n");
|
|
|
|
return do_log(svc->cmd);
|
|
}
|
|
|
|
col_widths();
|
|
if (heading) {
|
|
char title[80];
|
|
|
|
snprintf(title, sizeof(title), "%-*s %-*s %-8s %-12s ",
|
|
pw, "PID", iw, "IDENT", "STATUS", "RUNLEVELS");
|
|
if (!verbose)
|
|
strlcat(title, "DESCRIPTION", sizeof(title));
|
|
else
|
|
strlcat(title, "COMMAND", sizeof(title));
|
|
|
|
print_header(title);
|
|
}
|
|
|
|
for (svc = client_svc_iterator(1); svc; svc = client_svc_iterator(0)) {
|
|
char *lvls;
|
|
|
|
printf("%-*d ", pw, svc->pid);
|
|
|
|
svc_ident(svc, ident, sizeof(ident));
|
|
printf("%-*s %-8s ", iw, ident, svc_status(svc));
|
|
|
|
lvls = runlevel_string(runlevel, svc->runlevels);
|
|
if (strchr(lvls, '\e'))
|
|
printf("%-20.20s ", lvls);
|
|
else
|
|
printf("%-12.12s ", lvls);
|
|
|
|
if (!verbose)
|
|
puts(svc->desc);
|
|
else
|
|
puts(svc_command(svc, cmd, sizeof(cmd)));
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int cgroup_filter(const struct dirent *entry)
|
|
{
|
|
/* Skip current dir ".", and prev dir "..", from list of files */
|
|
if ((1 == strlen(entry->d_name) && entry->d_name[0] == '.') ||
|
|
(2 == strlen(entry->d_name) && !strcmp(entry->d_name, "..")))
|
|
return 0;
|
|
|
|
if (entry->d_name[0] == '.')
|
|
return 0;
|
|
|
|
if (entry->d_type != DT_DIR)
|
|
return 0;
|
|
|
|
return 1;
|
|
}
|
|
|
|
static int dump_cgroup(char *path, char *pfx)
|
|
{
|
|
struct dirent **namelist = NULL;
|
|
struct stat st;
|
|
char buf[512];
|
|
int rc = 0;
|
|
FILE *fp;
|
|
int i, n;
|
|
int num;
|
|
|
|
if (-1 == lstat(path, &st))
|
|
return 1;
|
|
|
|
if ((st.st_mode & S_IFMT) != S_IFDIR) {
|
|
errno = ENOTDIR;
|
|
return -1;
|
|
}
|
|
|
|
fp = fopenf("r", "%s/cgroup.procs", path);
|
|
if (!fp)
|
|
return -1;
|
|
num = 0;
|
|
while (fgets(buf, sizeof(buf), fp))
|
|
num++;
|
|
|
|
if (!pfx) {
|
|
pfx = "";
|
|
puts(path);
|
|
}
|
|
|
|
n = scandir(path, &namelist, cgroup_filter, alphasort);
|
|
if (n > 0) {
|
|
for (i = 0; i < n; i++) {
|
|
char *nm = namelist[i]->d_name;
|
|
char dir[80];
|
|
|
|
snprintf(buf, sizeof(buf), "%s/%s", path, nm);
|
|
|
|
if (i + 1 == n) {
|
|
printf("%s%s ", pfx, END);
|
|
snprintf(dir, sizeof(dir), "%s ", pfx);
|
|
} else {
|
|
printf("%s%s ", pfx, FORK);
|
|
snprintf(dir, sizeof(dir), "%s%s ", pfx, PIPE);
|
|
}
|
|
puts(nm);
|
|
|
|
rc += dump_cgroup(buf, dir);
|
|
|
|
free(namelist[i]);
|
|
}
|
|
|
|
free(namelist);
|
|
}
|
|
|
|
if (num > 0) {
|
|
rewind(fp);
|
|
|
|
i = 0;
|
|
while (fgets(buf, sizeof(buf), fp)) {
|
|
FILE *cfp;
|
|
pid_t pid;
|
|
|
|
pid = atoi(chomp(buf));
|
|
cfp = fopenf("r", "/proc/%d/cmdline", pid);
|
|
if (!cfp)
|
|
continue;
|
|
|
|
if (fgets(buf, sizeof(buf), cfp))
|
|
printf("%s%s %d %s\n", pfx, ++i == num ? END : FORK, pid, buf);
|
|
fclose(cfp);
|
|
}
|
|
|
|
return fclose(fp);
|
|
}
|
|
|
|
fclose(fp);
|
|
|
|
return rc;
|
|
}
|
|
|
|
static int show_cgroup(char *arg)
|
|
{
|
|
char path[512];
|
|
|
|
if (!arg)
|
|
arg = FINIT_CGPATH;
|
|
else if (arg[0] != '/') {
|
|
paste(path, sizeof(path), FINIT_CGPATH, arg);
|
|
arg = path;
|
|
}
|
|
|
|
return dump_cgroup(arg, NULL);
|
|
}
|
|
|
|
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"
|
|
" -p, --plain Use plain table headings, no ctrl chars\n"
|
|
" -t, --no-heading Skip table headings\n"
|
|
" -v, --verbose Verbose output\n"
|
|
" -h, --help This help text\n"
|
|
"\n"
|
|
"Commands:\n"
|
|
" debug Toggle Finit (daemon) debug\n"
|
|
" help This help text\n"
|
|
" version Show Finit version\n"
|
|
"\n", prognm);
|
|
|
|
if (has_rcsd)
|
|
fprintf(stderr,
|
|
" ls | list List all .conf in " FINIT_RCSD "\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",
|
|
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 " FINIT_RCSD " (activate changes)\n");
|
|
else
|
|
fprintf(stderr,
|
|
" reload Reload " FINIT_CONF " (activate changes)\n");
|
|
|
|
fprintf(stderr,
|
|
// " reload <NAME>[:ID] Reload (SIGHUP) service by name\n"
|
|
"\n"
|
|
" cond show Show condition status\n"
|
|
" cond dump Dump all 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"
|
|
" restart <NAME>[:ID] Restart (stop/start) service by name\n"
|
|
" status <NAME>[:ID] Show service status, by name\n"
|
|
" status Show status of services, default command\n"
|
|
"\n"
|
|
" ps List processes based on cgroups\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 (has_utmp())
|
|
fprintf(stderr,
|
|
"\n"
|
|
" utmp show Raw dump of UTMP/WTMP db\n"
|
|
"\n");
|
|
else
|
|
fprintf(stderr, "\n");
|
|
|
|
return rc;
|
|
}
|
|
|
|
static int do_devel(char *arg)
|
|
{
|
|
(void)arg;
|
|
|
|
printf("Screen %dx%d\n", screen_cols, screen_rows);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int do_help(char *arg)
|
|
{
|
|
return usage(0);
|
|
}
|
|
|
|
static int cmd_parse(int argc, char *argv[], struct cmd *command)
|
|
{
|
|
int i, j;
|
|
|
|
for (i = 0; argc > 0 && command[i].cmd; i++) {
|
|
if (!string_match(command[i].cmd, argv[0]))
|
|
continue;
|
|
|
|
if (command[i].ctx)
|
|
return cmd_parse(argc - 1, &argv[1], command[i].ctx);
|
|
|
|
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;
|
|
}
|
|
}
|
|
|
|
return command[0].cb(NULL); /* default cmd */
|
|
}
|
|
|
|
int main(int argc, char *argv[])
|
|
{
|
|
struct option long_options[] = {
|
|
{ "batch", 0, NULL, 'b' },
|
|
{ "create", 0, NULL, 'c' },
|
|
{ "force", 0, NULL, 'f' },
|
|
{ "help", 0, NULL, 'h' },
|
|
{ "plain", 0, NULL, 'p' },
|
|
{ "no-heading", 0, NULL, 't' },
|
|
{ "verbose", 0, NULL, 'v' },
|
|
{ NULL, 0, NULL, 0 }
|
|
};
|
|
struct cmd cond[] = {
|
|
{ "status", NULL, do_cond_show }, /* default cmd */
|
|
{ "dump", NULL, do_cond_dump },
|
|
{ NULL, NULL, NULL }
|
|
};
|
|
struct cmd command[] = {
|
|
{ "status", NULL, show_status }, /* default cmd */
|
|
|
|
{ "debug", NULL, toggle_debug },
|
|
{ "devel", NULL, do_devel },
|
|
{ "help", NULL, do_help },
|
|
{ "version", NULL, show_version },
|
|
|
|
{ "list", NULL, serv_list },
|
|
{ "ls", NULL, serv_list },
|
|
{ "enable", NULL, serv_enable },
|
|
{ "disable", NULL, serv_disable },
|
|
{ "touch", NULL, serv_touch },
|
|
{ "show", NULL, serv_show },
|
|
{ "edit", NULL, serv_edit },
|
|
{ "create", NULL, serv_creat },
|
|
{ "delete", NULL, serv_delete },
|
|
{ "reload", NULL, do_reload },
|
|
|
|
{ "cond", cond, NULL },
|
|
|
|
{ "log", NULL, do_log },
|
|
{ "start", NULL, do_start },
|
|
{ "stop", NULL, do_stop },
|
|
{ "restart", NULL, do_restart },
|
|
|
|
{ "ps", NULL, show_cgroup },
|
|
|
|
{ "runlevel", NULL, do_runlevel },
|
|
{ "reboot", NULL, do_reboot },
|
|
{ "halt", NULL, do_halt },
|
|
{ "poweroff", NULL, do_poweroff },
|
|
{ "suspend", NULL, do_suspend },
|
|
|
|
{ "utmp", NULL, do_utmp },
|
|
{ NULL, NULL, NULL }
|
|
};
|
|
int interactive = 1, c;
|
|
|
|
if (transform(progname(argv[0])))
|
|
return reboot_main(argc, argv);
|
|
|
|
while ((c = getopt_long(argc, argv, "bcfh?ptv", long_options, NULL)) != EOF) {
|
|
switch(c) {
|
|
case 'b':
|
|
interactive = 0;
|
|
break;
|
|
|
|
case 'c':
|
|
icreate = 1;
|
|
break;
|
|
|
|
case 'f':
|
|
iforce = 1;
|
|
break;
|
|
|
|
case 'h':
|
|
case '?':
|
|
return usage(0);
|
|
|
|
case 'p':
|
|
plain = 1;
|
|
break;
|
|
|
|
case 't':
|
|
heading = 0;
|
|
break;
|
|
|
|
case 'v':
|
|
verbose = 1;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (interactive)
|
|
screen_init();
|
|
|
|
if (!has_utmp()) {
|
|
/* system w/o utmp support, disable 'utmp' command */
|
|
for (c = 0; command[c].cmd; c++) {
|
|
if (!string_compare(command[c].cmd, "utmp"))
|
|
continue;
|
|
|
|
command[c].cb = NULL;
|
|
break;
|
|
}
|
|
}
|
|
|
|
return cmd_parse(argc - optind, &argv[optind], command);
|
|
}
|
|
|
|
void logit(int prio, const char *fmt, ...)
|
|
{
|
|
va_list ap;
|
|
|
|
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:
|
|
*/
|