mirror of
https://github.com/troglobit/finit.git
synced 2026-10-02 14:02:52 +07:00
The setfsent() family of APIs was never standardized and on Linux only GLIBC implement them. We've tried to overcome this limitation, and to support uClibc/uClibc-ng and musl libc, by providing replacements APIs in helpers.c. However, and since we want to support alternative /etc/fstab files, the setmntent() family of APIs is more widespread and supports reading from any fstab or mtab file. Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
596 lines
12 KiB
C
596 lines
12 KiB
C
/* Misc. utility functions for finit and its plugins
|
|
*
|
|
* Copyright (c) 2008-2010 Claudio Matsuoka <cmatsuoka@gmail.com>
|
|
* Copyright (c) 2008-2022 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 <ctype.h> /* isblank() */
|
|
#include <errno.h>
|
|
#include <grp.h>
|
|
#include <pwd.h>
|
|
#include <limits.h>
|
|
#include <net/if.h>
|
|
#include <netinet/in.h>
|
|
#include <stdarg.h>
|
|
#include <sysexits.h>
|
|
#include <sys/ioctl.h>
|
|
#ifdef _LIBITE_LITE
|
|
# include <libite/lite.h>
|
|
#else
|
|
# include <lite/lite.h>
|
|
#endif
|
|
|
|
#include "finit.h"
|
|
#include "helpers.h"
|
|
#include "private.h"
|
|
#include "sig.h"
|
|
#include "util.h"
|
|
#include "utmp-api.h"
|
|
|
|
static pstyle_t progress_onoff = PROGRESS_DEFAULT;
|
|
static pstyle_t progress_style = PROGRESS_DEFAULT;
|
|
|
|
#ifndef HOSTNAME_PATH
|
|
#define HOSTNAME_PATH "/etc/hostname"
|
|
#endif
|
|
|
|
/*
|
|
* Note: the pending status (⋯) must be last item. Also, ⋯ is not
|
|
* off-by-one, it's a multi-byte character.
|
|
*/
|
|
#define STATUS_CLASS { \
|
|
CHOOSE(" OK ", " OK ", "\e[1;32m"), /* Green */ \
|
|
CHOOSE("FAIL", "FAIL", "\e[1;31m"), /* Red */ \
|
|
CHOOSE("WARN", "WARN", "\e[1;33m"), /* Yellow */ \
|
|
CHOOSE(" \\/ ", " ⋯ ", "\e[1;33m"), /* Yellow */ \
|
|
}
|
|
|
|
#define CHOOSE(x,y,z) x
|
|
static const char *status1[] = STATUS_CLASS;
|
|
#undef CHOOSE
|
|
|
|
#define CHOOSE(x,y,z) y
|
|
static const char *status2[] = STATUS_CLASS;
|
|
#undef CHOOSE
|
|
|
|
#define CHOOSE(x,y,z) z
|
|
static const char *color[] = STATUS_CLASS;
|
|
|
|
char *console(void)
|
|
{
|
|
return _PATH_CONSOLE;
|
|
}
|
|
|
|
void console_init(void)
|
|
{
|
|
log_init();
|
|
}
|
|
|
|
ssize_t cprintf(const char *fmt, ...)
|
|
{
|
|
const size_t len = strlen(fmt) * 2;
|
|
char buf[len < 256 ? 256 : len];
|
|
size_t size;
|
|
va_list ap;
|
|
|
|
va_start(ap, fmt);
|
|
size = vsnprintf(buf, sizeof(buf), fmt, ap);
|
|
va_end(ap);
|
|
|
|
dprint(STDERR_FILENO, buf, size);
|
|
|
|
return size;
|
|
}
|
|
|
|
char *strip_line(char *line)
|
|
{
|
|
char *ptr;
|
|
|
|
/* Trim leading whitespace */
|
|
while (*line && isblank(*line))
|
|
line++;
|
|
|
|
/* Strip any comment at end of line */
|
|
ptr = line;
|
|
while (*ptr && *ptr != '#')
|
|
ptr++;
|
|
*ptr = 0;
|
|
|
|
return line;
|
|
}
|
|
|
|
void enable_progress(int onoff)
|
|
{
|
|
if (onoff)
|
|
progress_style = progress_onoff;
|
|
else
|
|
progress_style = PROGRESS_SILENT;
|
|
}
|
|
|
|
void show_progress(pstyle_t style)
|
|
{
|
|
progress_onoff = style;
|
|
}
|
|
|
|
/*
|
|
* Find PRETTY_NAME in /etc/os-release to use as heading at boot
|
|
* and on runlevel changes. Fallback: "Finit vX.YY"
|
|
*/
|
|
char *release_heading(void)
|
|
{
|
|
char *name = NULL;
|
|
char buf[256];
|
|
FILE *fp;
|
|
|
|
fp = fopen("/etc/os-release", "r");
|
|
if (fp) {
|
|
while (fgets(buf, sizeof(buf), fp)) {
|
|
char *ptr = &buf[12];
|
|
|
|
if (strncmp(buf, "PRETTY_NAME=", 12))
|
|
continue;
|
|
|
|
if (*ptr == '"' || *ptr == '\'') {
|
|
char *end = &ptr[strlen(ptr) - 1];
|
|
char q = *ptr;
|
|
|
|
while (end > ptr && isspace(*end))
|
|
*end-- = 0;
|
|
|
|
if (*end == q) {
|
|
*end = 0;
|
|
ptr++;
|
|
}
|
|
}
|
|
|
|
name = strdup(ptr);
|
|
break;
|
|
}
|
|
fclose(fp);
|
|
}
|
|
|
|
if (!name)
|
|
name = strdup(PACKAGE_NAME " v" PACKAGE_VERSION);
|
|
|
|
return name;
|
|
}
|
|
|
|
/*
|
|
* Return screen length of string, not counting escape chars, and
|
|
* accounting for unicode characters as only one screen byte wide
|
|
*/
|
|
size_t slen(char *string)
|
|
{
|
|
unsigned char *buf = (unsigned char *)string;
|
|
size_t len = 0;
|
|
|
|
while (*buf) {
|
|
/* Skip ANSI escape sequences */
|
|
if (*buf == '\e') {
|
|
while (*buf && !isalpha(*buf))
|
|
buf++;
|
|
continue;
|
|
}
|
|
/* Skip 3-byte unicode */
|
|
if (*buf == 0xe2) {
|
|
for (int cnt = 3; *buf && cnt >= 0; cnt--)
|
|
buf++;
|
|
continue;
|
|
}
|
|
|
|
len++;
|
|
buf++;
|
|
}
|
|
|
|
return len;
|
|
}
|
|
|
|
/*
|
|
* ch may be an ascii or unicode character
|
|
*/
|
|
static char *pad(char *buf, size_t len, char *ch, size_t width)
|
|
{
|
|
size_t i = slen(buf);
|
|
|
|
strlcat(buf, " ", len);
|
|
|
|
width -= 8; /* Skip leading '[ STAT ]' */
|
|
while (i < width) {
|
|
strlcat(buf, ch, len);
|
|
i++;
|
|
}
|
|
|
|
return buf;
|
|
}
|
|
|
|
void print_banner(const char *heading)
|
|
{
|
|
char buf[4 * ttcols];
|
|
|
|
if (progress_style == PROGRESS_SILENT)
|
|
return;
|
|
|
|
buf[0] = 0;
|
|
strlcat(buf, "\r\e[K", sizeof(buf));
|
|
|
|
if (progress_style == PROGRESS_CLASSIC) {
|
|
strlcat(buf, "\e[1m", sizeof(buf));
|
|
strlcat(buf, heading, sizeof(buf));
|
|
pad(buf, sizeof(buf), "=", ttcols + 10);
|
|
} else {
|
|
size_t wmax = 80 <= ttcols ? 80 : ttcols;
|
|
|
|
/* • • • Foo System ═══════════════════════════════════════════════ */
|
|
/* ⬤ ⬤ ⬤ Foo System ═══════════════════════════════════════════════ */
|
|
/* ⚉ ⚉ ⚉ Foo System ═══════════════════════════════════════════════ */
|
|
/* o o o Foo System ═══════════════════════════════════════════════ */
|
|
/* ◉ ◉ ◉ Foo System ═══════════════════════════════════════════════ */
|
|
/* ⦿⦿⦿ Foo System ═══════════════════════════════════════════════ */
|
|
strlcat(buf, "\e[1;31m● \e[1;33m● \e[1;32m● \e[0m\e[1m ", sizeof(buf));
|
|
strlcat(buf, heading, sizeof(buf));
|
|
|
|
/*
|
|
* Padding with full-width '═' sign from unicode,
|
|
* we could also use '―' or something else.
|
|
*/
|
|
pad(buf, sizeof(buf), "═", wmax);
|
|
}
|
|
strlcat(buf, "\e[0m\n", sizeof(buf));
|
|
cprintf("%s", buf);
|
|
}
|
|
|
|
static size_t print_timestamp(char *buf, size_t len)
|
|
{
|
|
#if defined(CONFIG_PRINTK_TIME)
|
|
FILE *fp;
|
|
float stamp, dummy;
|
|
|
|
fp = fopen("/proc/uptime", "r");
|
|
if (!fp)
|
|
return;
|
|
|
|
fgets(buf, len, fp);
|
|
fclose(fp);
|
|
|
|
sscanf(buf, "%f %f", &stamp, &dummy);
|
|
return snprintf(buf, len, "[ %.6f ]", stamp);
|
|
#else
|
|
return 0;
|
|
#endif
|
|
}
|
|
|
|
static char *status(int rc)
|
|
{
|
|
static char buf[64];
|
|
|
|
if (rc < 0 || rc >= (int)NELEMS(status1))
|
|
rc = NELEMS(status1) - 1; /* Default to "⋯" (pending) */
|
|
|
|
if (progress_style == PROGRESS_CLASSIC) {
|
|
int hl = 1;
|
|
|
|
if (rc == 1 || rc == 2)
|
|
hl = 7;
|
|
|
|
snprintf(buf, sizeof(buf), "\e[%dm[%s]\e[0m", hl, status1[rc]);
|
|
} else
|
|
snprintf(buf, sizeof(buf), "\e[1m[%s%s\e[0m\e[1m]\e[0m ", color[rc], status2[rc]);
|
|
|
|
return buf;
|
|
}
|
|
|
|
void printv(const char *fmt, va_list ap)
|
|
{
|
|
char buf[ttcols];
|
|
size_t len;
|
|
|
|
if (!fmt || progress_style == PROGRESS_SILENT)
|
|
return;
|
|
|
|
delline();
|
|
|
|
buf[0] = 0;
|
|
len = print_timestamp(buf, sizeof(buf));
|
|
vsnprintf(&buf[len], sizeof(buf) - len, fmt, ap);
|
|
|
|
if (progress_style == PROGRESS_CLASSIC)
|
|
cprintf("\r%s ", pad(buf, sizeof(buf), ".", sizeof(buf)));
|
|
else
|
|
cprintf("\r\e[K%s%s", status(3), buf);
|
|
}
|
|
|
|
void print(int rc, const char *fmt, ...)
|
|
{
|
|
if (progress_style == PROGRESS_SILENT)
|
|
return;
|
|
|
|
if (fmt) {
|
|
va_list ap;
|
|
|
|
va_start(ap, fmt);
|
|
printv(fmt, ap);
|
|
va_end(ap);
|
|
}
|
|
|
|
if (rc < 0)
|
|
return;
|
|
|
|
if (progress_style == PROGRESS_CLASSIC)
|
|
cprintf("%s\n", status(rc));
|
|
else
|
|
cprintf("\r%s\n", status(rc));
|
|
}
|
|
|
|
void print_desc(char *action, char *desc)
|
|
{
|
|
print(-1, "%s%s", action ?: "", desc ?: "");
|
|
}
|
|
|
|
int print_result(int fail)
|
|
{
|
|
print(!!fail, NULL);
|
|
return fail;
|
|
}
|
|
|
|
int getuser(char *username, char **home)
|
|
{
|
|
#ifdef ENABLE_STATIC
|
|
*home = "/"; /* XXX: Fixme */
|
|
return fgetint("/etc/passwd", "x:\n", username);
|
|
#else
|
|
struct passwd *usr;
|
|
|
|
if (!username || (usr = getpwnam(username)) == NULL)
|
|
return -1;
|
|
|
|
if (home)
|
|
*home = usr->pw_dir;
|
|
return usr->pw_uid;
|
|
#endif
|
|
}
|
|
|
|
int getgroup(char *group)
|
|
{
|
|
#ifdef ENABLE_STATIC
|
|
return fgetint("/etc/group", "x:\n", group);
|
|
#else
|
|
struct group *grp;
|
|
|
|
if ((grp = getgrnam(group)) == NULL)
|
|
return -1;
|
|
|
|
return grp->gr_gid;
|
|
#endif
|
|
}
|
|
|
|
/* get current user */
|
|
int getcuser(char *buf, size_t len)
|
|
{
|
|
char *ptr = "root";
|
|
|
|
#ifndef ENABLE_STATIC
|
|
struct passwd *pw = getpwuid(getuid());
|
|
|
|
if (pw)
|
|
ptr = pw->pw_name;
|
|
#endif
|
|
if (strlcpy(buf, ptr, len) > len)
|
|
return -1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* get current group */
|
|
int getcgroup(char *buf, size_t len)
|
|
{
|
|
char *ptr = "root";
|
|
|
|
#ifndef ENABLE_STATIC
|
|
struct group *gr = getgrgid(getgid());
|
|
|
|
if (gr)
|
|
ptr = gr->gr_name;
|
|
#endif
|
|
if (strlcpy(buf, ptr, len) > len)
|
|
return -1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
int mksubsys(const char *dir, mode_t mode, char *user, char *group)
|
|
{
|
|
mode_t omask;
|
|
int uid, gid;
|
|
int rc = 0;
|
|
|
|
omask = umask(0);
|
|
|
|
uid = getuser(user, NULL);
|
|
if (uid >= 0) {
|
|
gid = getgroup(group);
|
|
if (gid < 0)
|
|
gid = 0;
|
|
|
|
rc = makedir(dir, mode);
|
|
if (chown(dir, uid, gid))
|
|
_pe("Failed chown(%s, %d, %d)", dir, uid, gid);
|
|
}
|
|
|
|
umask(omask);
|
|
|
|
return rc;
|
|
}
|
|
|
|
void set_hostname(char **hostname)
|
|
{
|
|
FILE *fp;
|
|
|
|
if (rescue)
|
|
goto done;
|
|
|
|
fp = fopen(HOSTNAME_PATH, "r");
|
|
if (fp) {
|
|
struct stat st;
|
|
|
|
if (fstat(fileno(fp), &st)) {
|
|
fclose(fp);
|
|
return;
|
|
}
|
|
|
|
*hostname = realloc(*hostname, st.st_size);
|
|
if (!*hostname) {
|
|
fclose(fp);
|
|
return;
|
|
}
|
|
|
|
if (fgets(*hostname, st.st_size, fp))
|
|
chomp(*hostname);
|
|
fclose(fp);
|
|
}
|
|
|
|
done:
|
|
if (*hostname) {
|
|
if (sethostname(*hostname, strlen(*hostname)))
|
|
_pe("Failed sethostnaem(%s)", *hostname);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Bring up networking, but only if not single-user or rescue mode
|
|
*/
|
|
void networking(int updown)
|
|
{
|
|
/* No need to report errors if network is already down */
|
|
if (!prevlevel && !updown)
|
|
return;
|
|
|
|
if (updown)
|
|
_d("Setting up networking ...");
|
|
else
|
|
_d("Taking down networking ...");
|
|
|
|
/* Run user network start script if enabled */
|
|
if (updown && network) {
|
|
run_interactive(network, "Starting networking: %s", network);
|
|
goto done;
|
|
}
|
|
|
|
/* Debian/Ubuntu/Busybox/RH/Suse */
|
|
if (!whichp("ifup"))
|
|
goto fallback;
|
|
|
|
if (fexist("/etc/network/interfaces")) {
|
|
pid_t pid;
|
|
|
|
pid = fork();
|
|
if (pid == 0) {
|
|
int rc = EX_OSERR;
|
|
const char *cmd;
|
|
FILE *pp;
|
|
|
|
setsid();
|
|
sig_unblock();
|
|
|
|
if (updown)
|
|
cmd = "ifup -a 2>&1";
|
|
else if (whichp("ifquery"))
|
|
/* Regular ifodwn supports --force but not -f */
|
|
cmd = "ifdown -a --force 2>&1";
|
|
else
|
|
/* Busybox ifdown support -f, but not --force */
|
|
cmd = "ifdown -a -f 2>&1";
|
|
|
|
pp = popen(cmd, "r");
|
|
if (pp) {
|
|
char buf[256];
|
|
|
|
while (fgets(buf, sizeof(buf), pp))
|
|
logit(LOG_NOTICE, "network: %s", buf);
|
|
|
|
rc = pclose(pp);
|
|
}
|
|
|
|
_exit(rc);
|
|
}
|
|
cgroup_service("network", pid, NULL);
|
|
print(pid > 0 ? 0 : 1, "%s network interfaces ...",
|
|
updown ? "Bringing up" : "Taking down");
|
|
|
|
goto done;
|
|
}
|
|
|
|
fallback:
|
|
/* Fall back to bring up at least loopback */
|
|
ifconfig("lo", "127.0.0.1", "255.0.0.0", updown);
|
|
done:
|
|
|
|
/* Hooks that rely on loopback, or basic networking being up. */
|
|
if (updown) {
|
|
_d("Calling all network up hooks ...");
|
|
plugin_run_hooks(HOOK_NETWORK_UP);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Very basic, and incomplete, check if we're running in a container.
|
|
*/
|
|
int in_container(void)
|
|
{
|
|
const char *containers[] = {
|
|
"lxc",
|
|
"docker",
|
|
"kubepod",
|
|
"unshare"
|
|
};
|
|
const char *files[] = {
|
|
"/run/.containerenv",
|
|
"/.dockerenv"
|
|
};
|
|
static int incont = -1;
|
|
size_t i;
|
|
char *c;
|
|
|
|
if (incont > 0)
|
|
return incont;
|
|
|
|
c = getenv("container");
|
|
if (c) {
|
|
for (i = 0; i < NELEMS(containers); i++) {
|
|
if (!strcmp(containers[i], c))
|
|
return incont = 1;
|
|
}
|
|
}
|
|
|
|
for (i = 0; i < NELEMS(files); i++) {
|
|
if (!access(files[i], F_OK))
|
|
return incont = 1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* Local Variables:
|
|
* indent-tabs-mode: t
|
|
* c-file-style: "linux"
|
|
* End:
|
|
*/
|