Refactor TTY code to honor runlevels and use I/O plugin to watch /dev

Add support for runlevels to tty command and support for reading baud
rate and TERM variable from finit.conf.

The tty-watcher thread has been converted to a standard I/O plugin.

Signed-off-by: Joachim Nilsson <troglobit@gmail.com>
This commit is contained in:
Joachim Nilsson
2013-06-06 18:19:26 +02:00
parent f97aab4699
commit 0f60aa2289
13 changed files with 391 additions and 144 deletions
+1 -2
View File
@@ -34,8 +34,7 @@ ARCHIVE = $(PKG).tar.xz
EXEC = finit reboot
HEADERS = plugin.h svc.h helpers.h queue.h
DISTFILES = LICENSE README ChangeLog finit.conf services
OBJS = finit.o conf.o helpers.o sig.o svc.o plugin.o
OBJS += tty.o
OBJS = finit.o conf.o helpers.o sig.o svc.o plugin.o tty.o
SRCS = $(OBJS:.o=.c)
DEPS = $(addprefix .,$(SRCS:.c=.d))
+39 -5
View File
@@ -26,6 +26,7 @@
#include "finit.h"
#include "svc.h"
#include "tty.h"
#include "lite.h"
#include "helpers.h"
@@ -49,6 +50,42 @@ static char *strip_line(char *line)
return line;
}
/* Convert optional "[!123456789S]" string into a bitmask */
int parse_runlevels(char *runlevels)
{
int i, not = 0, bitmask = 0;
if (!runlevels)
runlevels = "[234]";
i = 1;
while (i) {
int level;
char lvl = runlevels[i++];
if (']' == lvl || 0 == lvl)
break;
if ('!' == lvl) {
not = 1;
bitmask = 0x3FE;
continue;
}
if ('s' == lvl || 'S' == lvl)
lvl = ':'; /* RUNLEVEL_BOOT */
level = lvl - '0';
if (level > RUNLEVEL_BOOT || level < 0)
continue;
if (not)
CLRBIT(bitmask, level);
else
SETBIT(bitmask, level);
}
return bitmask;
}
void parse_finit_conf(char *file)
{
FILE *fp;
@@ -67,9 +104,8 @@ void parse_finit_conf(char *file)
while (!feof(fp)) {
if (!fgets(line, sizeof(line), fp))
continue;
_d("conf: %s", line);
chomp(line);
_d("conf: %s", line);
/* Skip comments. */
if (MATCH_CMD(line, "#", x))
@@ -179,9 +215,7 @@ void parse_finit_conf(char *file)
continue;
}
if (MATCH_CMD(line, "tty ", x)) {
char *tty = strdup(strip_line(x));
int baud = 115200; /* XXX - Read from config file */
tty_add(tty, baud);
tty_register(strip_line(x));
continue;
}
}
+2 -1
View File
@@ -34,6 +34,7 @@
#include "plugin.h"
#include "svc.h"
#include "sig.h"
#include "tty.h"
#include "lite.h"
int debug = 0;
@@ -273,7 +274,7 @@ int main(int argc, char* argv[])
plugin_run_hooks(HOOK_SYSTEM_UP);
/* Start GETTY on console(s) */
tty_start();
tty_runlevel(runlevel);
/*
* Enter main loop to monior /dev/initctl and services
+8 -7
View File
@@ -37,12 +37,16 @@
#if defined EMBEDDED_SYSTEM
# define CONSOLE "/dev/console"
# define SETUP_DEVFS "/sbin/mdev -s"
# define GETTY "/sbin/getty -L 115200 %s vt100"
# define GETTY "/sbin/getty -L"
# define GETTY_BUSYBOX
# define BAUDRATE 115200
# define RUNLEVEL 2
#else /* Debian/Ubuntu based distributions */
# define CONSOLE "/dev/tty1"
# define SETUP_DEVFS "/sbin/udevd --daemon"
# define GETTY "/sbin/getty -8 38400 %s"
# define GETTY "/sbin/getty -8"
# define GETTY_AGETTY
# define BAUDRATE 38400
# define RUNLEVEL 2
# define RANDOMSEED "/var/lib/urandom/random-seed"
# define REMOUNT_ROOTFS_RW
@@ -84,11 +88,8 @@ extern char *console;
extern char *__progname;
/* conf.c */
void parse_finit_conf(char *file);
/* tty.c */
void tty_start(void);
int tty_add(char *tty, int baud);
int parse_runlevels (char *runlevels);
void parse_finit_conf (char *file);
#endif /* FINIT_H_ */
+9 -2
View File
@@ -505,7 +505,7 @@ int run_interactive(char *cmd, char *fmt, ...)
return status;
}
pid_t run_getty(char *cmd, int console)
pid_t run_getty(char *cmd, char *args[], int console)
{
pid_t pid = fork();
@@ -557,7 +557,7 @@ pid_t run_getty(char *cmd, int console)
if (fexist(SYNC_STOPPED))
continue;
run(cmd);
execv(cmd, args);
}
exit(0);
@@ -657,6 +657,13 @@ void chomp(char *str)
*x = 0;
}
/* Signal safe sleep ... we get a lot of SIGCHLD at reboot */
void do_sleep(unsigned int sec)
{
while ((sec = sleep(sec)))
;
}
void set_hostname(char *hostname)
{
FILE *fp;
+6 -1
View File
@@ -74,6 +74,10 @@
#define _e(fmt, args...) do { fprintf(stderr, "finit:%s:%s() - " fmt "\n", __FILE__, __func__, ##args); } while (0)
#define _pe(fmt, args...) do { fprintf(stderr, "finit:%s:%s() - " fmt ". Error %d: %s\n", __FILE__, __func__, ##args, errno, strerror(errno)); } while (0)
#define ISSET(a,i) ((a & (1 << (i)) ? 1 : 0))
#define SETBIT(a,i) (a |= (1 << (i)))
#define CLRBIT(a,i) (a &= ~(1 << (i)))
extern int debug;
extern int verbose;
@@ -93,6 +97,7 @@ void print_desc (char *action, char *desc);
int print_result (int fail);
int start_process (char *cmd, char *args[], int console);
void chomp (char *str);
void do_sleep (unsigned int sec);
int getuser (char *username);
int getgroup (char *group);
void set_hostname (char *hostname);
@@ -100,7 +105,7 @@ void set_hostname (char *hostname);
int complete (char *cmd, int pid);
int run (char *cmd);
int run_interactive (char *cmd, char *fmt, ...);
pid_t run_getty (char *cmd, int console);
pid_t run_getty (char *cmd, char *args[], int console);
int run_parts (char *dir, char *cmd);
#endif /* FINIT_HELPERS_H_ */
+3 -3
View File
@@ -124,9 +124,9 @@ int plugin_unregister(plugin_t *plugin)
}
// _d("Comparing %s against plugin %s", str, p->name);
#define SEARCH_PLUGIN(str) \
PLUGIN_ITERATOR(p, tmp) { \
_d("Comparing %s against plugin %s", str, p->name); \
if (!strcmp(p->name, str)) \
return p; \
}
@@ -305,9 +305,9 @@ static void check_plugin_depends(plugin_t *plugin)
int i;
for (i = 0; i < PLUGIN_DEP_MAX && plugin->depends[i]; i++) {
_d("Plugin %s depends on %s ...", plugin->name, plugin->depends[i]);
// _d("Plugin %s depends on %s ...", plugin->name, plugin->depends[i]);
if (plugin_find(plugin->depends[i])) {
_d("OK plugin %s was already loaded.", plugin->depends[i]);
// _d("OK plugin %s was already loaded.", plugin->depends[i]);
continue;
}
+1 -1
View File
@@ -4,7 +4,7 @@ CPPFLAGS += -I..
LDFLAGS := $(filter-out -rdynamic, $(LDFLAGS))
LDFLAGS += -shared
PLUGINS ?= initctl.so alsa-utils.so bootmisc.so dbus.so hwclock.so \
resolvconf.so urandom.so x11-common.so
resolvconf.so urandom.so x11-common.so tty.so
DEPS = $(addprefix .,$(PLUGINS:.so=.d))
include ../common.mk
+94
View File
@@ -0,0 +1,94 @@
/* Optional TTY Watcher, used to catch new TTYs that are discovered, e.g., USB
*
* Copyright (c) 2013 Mattias Walström <lazzer@gmail.com>
* Copyright (c) 2013 Joachim Nilsson <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 <errno.h>
#include <fcntl.h> /* O_RDONLY et al */
#include <unistd.h> /* read() */
#include <sys/inotify.h>
#include <sys/stat.h>
#include "finit.h"
#include "helpers.h"
#include "plugin.h"
#include "tty.h"
static void watcher(void *arg, int fd, int events);
static plugin_t plugin = {
.io = {
.cb = watcher,
.flags = PLUGIN_IO_READ,
},
};
static void setup(void)
{
plugin.io.fd = inotify_init1(IN_NONBLOCK);
if (-1 == plugin.io.fd || inotify_add_watch(plugin.io.fd, "/dev", IN_CREATE | IN_DELETE) < 0)
_e("Failed starting TTY watcher: %s", strerror(errno));
}
static void watcher(void *UNUSED(arg), int fd, int UNUSED(events))
{
int len = 0;
char buf[EVENT_SIZE], name[42];
tty_node_t *entry;
struct inotify_event *notified = (struct inotify_event *)buf;
while ((len = read(fd, buf, sizeof(buf)))) {
if (-1 == len) {
if (errno == EINTR)
continue;
break;
}
snprintf(name, sizeof(name), "/dev/%s", notified->name);
entry = tty_find(name);
if (entry && tty_enabled(&entry->data, runlevel)) {
if (notified->mask & IN_CREATE)
tty_start(&entry->data);
else if (entry->data.pid)
tty_stop(&entry->data);
}
}
}
PLUGIN_INIT(plugin_init)
{
setup();
plugin_register(&plugin);
}
PLUGIN_EXIT(plugin_exit)
{
plugin_unregister(&plugin);
}
/**
* Local Variables:
* version-control: t
* indent-tabs-mode: t
* c-file-style: "linux"
* End:
*/
-7
View File
@@ -49,13 +49,6 @@
static int stopped = 0;
/* Signal safe sleep ... we get a lot of SIGCHLD at reboot */
void do_sleep(unsigned int sec)
{
while ((sec = sleep(sec)))
;
}
void do_shutdown (int sig)
{
touch(SYNC_SHUTDOWN);
+20 -35
View File
@@ -29,13 +29,10 @@
#include "helpers.h"
#include "private.h"
#include "sig.h"
#include "tty.h"
#include "lite.h"
#include "svc.h"
#define ISSET(a,i) ((a & (1 << (i)) ? 1 : 0))
#define SETBIT(a,i) (a |= (1 << (i)))
#define CLRBIT(a,i) (a &= ~(1 << (i)))
/* The registered services are kept in shared memory for easy read-only access
* by 3rd party APIs. Mostly because of the ability to, from the outside, query
* liveness state of all daemons that should run. */
@@ -151,14 +148,14 @@ void svc_bootstrap(void)
* Stops all services not in @newlevel and starts, or lets continue to run,
* those in @newlevel. Also updates @prevlevel and active @runlevel.
*/
void svc_runlevel (int newlevel)
void svc_runlevel(int newlevel)
{
svc_t *svc;
if (runlevel == newlevel)
return;
if (newlevel > 5 || newlevel < 1)
if (newlevel < 0 || newlevel > 9)
return;
prevlevel = runlevel;
@@ -176,10 +173,22 @@ void svc_runlevel (int newlevel)
}
}
if (0 == runlevel) {
do_shutdown(SIGUSR2);
return;
}
if (6 == runlevel) {
do_shutdown(SIGUSR1);
return;
}
if (runlevel == 1)
touch("/etc/nologin"); /* Disable login in single-user mode */
else
remove("/etc/nologin");
if (RUNLEVEL_BOOT != prevlevel)
tty_runlevel(runlevel);
}
/**
@@ -214,7 +223,7 @@ void svc_runlevel (int newlevel)
*/
int svc_register(int type, char *line, char *username)
{
int i = 0, not = 0;
int i = 0;
char *cmd, *desc, *runlevels = NULL;
svc_t *svc;
@@ -269,34 +278,7 @@ int svc_register(int type, char *line, char *username)
strlcpy(svc->args[i++], cmd, sizeof(svc->args[0]));
svc->args[i][0] = 0;
if (!runlevels)
runlevels = "[234]";
i = 1;
while (i) {
int level;
char lvl = runlevels[i++];
if (']' == lvl || 0 == lvl)
break;
if ('!' == lvl) {
not = 1;
svc->runlevels = 0x3FE;
continue;
}
if ('s' == lvl || 'S' == lvl)
lvl = ':'; /* RUNLEVEL_BOOT */
level = lvl - '0';
if (level > RUNLEVEL_BOOT || level < 0)
continue;
if (not)
CLRBIT(svc->runlevels, level);
else
SETBIT(svc->runlevels, level);
}
svc->runlevels = parse_runlevels(runlevels);
_d("Service %s runlevel 0x%2x", svc->cmd, svc->runlevels);
return 0;
@@ -358,6 +340,9 @@ void svc_monitor(void)
if (is_norespawn())
return;
if (tty_respawn(lost))
return;
for (svc = svc_iterator(1); svc; svc = svc_iterator(0)) {
if (lost != svc->pid)
continue;
+143 -80
View File
@@ -1,6 +1,7 @@
/* TTY handling in finit
/* Finit TTY handling
*
* Copyright (c) 2013 Mattias Walström <lazzer@gmail.com>
* Copyright (c) 2013 Mattias Walström <lazzer@gmail.com>
* Copyright (c) 2013 Joachim Nilsson <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
@@ -21,119 +22,181 @@
* THE SOFTWARE.
*/
#include <sys/inotify.h>
#include <signal.h>
#include <sys/prctl.h>
#include <sys/wait.h>
#include "queue.h"
#include "finit.h"
#include "helpers.h"
#include "lite.h"
#include "tty.h"
typedef struct finit_tty_t {
char *name;
int baud;
int pid; /* Runtime information*/
} finit_tty_t;
typedef struct node {
LIST_ENTRY(node) link;
finit_tty_t data;
} node_t;
LIST_HEAD(, tty_node) tty_list = LIST_HEAD_INITIALIZER();
/* We need to make room for the filename also, which is sent after inotify_event. */
#define EVENT_SIZE ((sizeof(struct inotify_event) + FILENAME_MAX))
LIST_HEAD(, node) node_list = LIST_HEAD_INITIALIZER();
int tty_add(char *tty, int baud)
/* tty [!1-9,S] <DEV> [BAUD[,BAUD,...]] [TERM] */
int tty_register(char *line)
{
node_t *entry;
tty_node_t *entry;
int insert = 0, baud = 0;
char *cmd, *dev;
char *runlevels = NULL, *term = NULL;
if (!tty)
if (!line) {
_e("Invalid input argument.");
return errno = EINVAL;
}
entry = malloc(sizeof(*entry));
if (!entry)
return errno = ENOMEM;
cmd = strtok(line, " ");
if (!cmd) {
incomplete:
_e("Incomplete tty, cannot register.");
return errno = EINVAL;
}
entry->data.name = tty;
if (cmd[0] == '[') { /* [runlevels] */
runlevels = &cmd[0];
dev = strtok(NULL, " ");
if (!dev)
goto incomplete;
} else {
dev = cmd;
}
cmd = strtok(NULL, " ");
if (cmd) {
baud = strtonum(cmd, 50, 4000000, NULL);
term = strtok(NULL, " ");
}
entry = tty_find(dev);
if (!entry) {
insert = 1;
entry = malloc(sizeof(*entry));
if (!entry)
return errno = ENOMEM;
}
entry->data.name = strdup(dev);
if (!baud)
baud = BAUDRATE;
entry->data.baud = baud;
entry->data.term = term ? strdup(term) : NULL;
entry->data.runlevels = parse_runlevels(runlevels);
_d("Registering tty %s at %d baud with term=%s on runlevels %s", dev, baud, term ?: "N/A", runlevels);
LIST_INSERT_HEAD(&node_list, entry, link);
if (insert)
LIST_INSERT_HEAD(&tty_list, entry, link);
return 0;
}
static void tty_startstop_one(finit_tty_t *tty, uint32_t mask)
tty_node_t *tty_find(char *dev)
{
int is_console = 0;
char cmd[strlen(GETTY) + FILENAME_MAX];
tty_node_t *entry;
LIST_FOREACH(entry, &tty_list, link) {
if (!strcmp(dev, entry->data.name))
return entry;
}
return NULL;
}
tty_node_t *tty_find_by_pid(pid_t pid)
{
tty_node_t *entry;
LIST_FOREACH(entry, &tty_list, link) {
if (entry->data.pid == pid)
return entry;
}
return NULL;
}
void tty_start(finit_tty_t *tty)
{
int i = 0, is_console = 0;
char *cmd, *arg;
char line[FILENAME_MAX];
char *args[42];
if (tty->pid)
return;
if (console && !strcmp(tty->name, console))
is_console = 1;
if (mask & IN_CREATE) {
_d("Starting %s as %s", tty->name, is_console ? "console" : "TTY");
snprintf(cmd, sizeof(cmd), GETTY, tty->name);
tty->pid = run_getty(cmd, is_console);
}
#if defined(GETTY_AGETTY)
snprintf(line, sizeof(line), "%s %s %d %s", GETTY, tty->name, tty->baud, tty->term ?: "");
#elif defined(GETTY_BUSYBOX)
snprintf(line, sizeof(line), "%s %d %s %s", GETTY, tty->baud, tty->name, tty->term ?: "");
#else
#error No GETTY defined!
#endif
cmd = strtok(line, " ");
args[i++] = basename(cmd);
while ((arg = strtok(NULL, " ")))
args[i++] = arg;
args[i] = NULL;
/* Kill the spawned child, and recollect it. */
if ((mask & IN_DELETE) && tty->pid) {
_d("Stopping %s", tty->name);
kill(tty->pid, SIGKILL);
waitpid(tty->pid, NULL, 0);
tty->pid = 0;
}
_d("Starting %s: %s on %s", is_console ? "console" : "TTY", cmd, tty->name);
tty->pid = run_getty(cmd, args, is_console);
}
static void tty_watcher(node_t *entry)
void tty_stop(finit_tty_t *tty)
{
int fd = inotify_init();
if (!tty->pid)
return;
prctl(PR_SET_NAME, "tty-watcher", 0, 0, 0);
if (-1 == fd || inotify_add_watch(fd, "/dev", IN_CREATE | IN_DELETE) < 0) {
fprintf(stderr, "finit: Failed starting TTY watcher: %s\n", strerror(errno));
exit(1);
}
while (1) {
int len = 0;
char buf[EVENT_SIZE];
struct inotify_event *notified = (struct inotify_event *)buf;
while ((len = read(fd, buf, EVENT_SIZE))) {
if (len == -1) {
if (errno == EAGAIN || errno == EINTR)
continue;
}
LIST_FOREACH(entry, &node_list, link) {
if (!strcmp(notified->name, entry->data.name))
tty_startstop_one(&entry->data, notified->mask);
}
}
}
_d("Stopping TTY %s", tty->name);
kill(tty->pid, SIGTERM);
do_sleep(2);
kill(tty->pid, SIGKILL);
waitpid(tty->pid, NULL, 0);
tty->pid = 0;
}
void tty_start(void)
int tty_enabled(finit_tty_t *tty, int runlevel)
{
node_t *entry;
if (!tty)
return 0;
/* Start all TTYs that already exist in the system */
LIST_FOREACH(entry, &node_list, link) {
chdir("/dev");
if (fexist (entry->data.name))
tty_startstop_one(&entry->data, IN_CREATE);
if (!ISSET(tty->runlevels, runlevel))
return 0;
else if (fexist(tty->name))
return 1;
return 0;
}
/* TTY monitor, called by svc_monitor() */
int tty_respawn(pid_t pid)
{
tty_node_t *entry = tty_find_by_pid(pid);
if (!entry)
return 0;
if (!tty_enabled(&entry->data, runlevel))
tty_stop(&entry->data);
else
tty_start(&entry->data);
return 1;
}
/* Start all TTYs that exist in the system and are allowed at this runlevel */
void tty_runlevel(int runlevel)
{
tty_node_t *entry;
LIST_FOREACH(entry, &tty_list, link) {
if (!tty_enabled(&entry->data, runlevel))
tty_stop(&entry->data);
else
tty_start(&entry->data);
}
/* Start a watcher to catch new TTYs that is discovered, e.g., USB */
if (!fork())
tty_watcher(entry);
}
/**
+65
View File
@@ -0,0 +1,65 @@
/* Finit TTY handling
*
* Copyright (c) 2013 Mattias Walström <lazzer@gmail.com>
* Copyright (c) 2013 Joachim Nilsson <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.
*/
#ifndef FINIT_TTY_H_
#define FINIT_TTY_H_
#include "queue.h" /* BSD sys/queue.h API */
#define EVENT_SIZE ((sizeof(struct inotify_event) + 20))
typedef struct {
char *name;
int baud;
char *term;
int runlevels;
int pid;
} finit_tty_t;
typedef struct tty_node {
LIST_ENTRY(tty_node) link;
finit_tty_t data;
} tty_node_t;
//extern LIST_HEAD(, tty_node) tty_list;
int tty_register (char *line);
tty_node_t *tty_find (char *dev);
tty_node_t *tty_find_by_pid (pid_t pid);
void tty_start (finit_tty_t *tty);
void tty_stop (finit_tty_t *tty);
int tty_enabled (finit_tty_t *tty, int runlevel);
int tty_respawn (pid_t pid);
void tty_runlevel (int runlevel);
#endif /* FINIT_TTY_H_ */
/**
* Local Variables:
* version-control: t
* indent-tabs-mode: t
* c-file-style: "linux"
* End:
*/