diff --git a/doc/config/service-opts.md b/doc/config/service-opts.md index 81da55ef..add707bb 100644 --- a/doc/config/service-opts.md +++ b/doc/config/service-opts.md @@ -46,6 +46,7 @@ Other run/task/service options are: * `cgroup.NAME[,opts]` or `cgroup:opts` -- see the [Cgroups](cgroups.md) section * `env:[-]/path/to/env` -- see the [Service Environment](service-env.md) section * `log:...` -- see [Redirecting Output](logging.md#redirecting-output) + * `tty:` -- see [Controlling TTY](tty.md#controlling-tty) * `nowarn` -- see [Conditional Loading](services.md#conditional-loading) * `notify:...` -- see [Service Synchronization](service-sync.md) * `if:...` -- see [Conditional Execution](services.md#conditional-execution) diff --git a/doc/config/tty.md b/doc/config/tty.md index 59bc7e86..1ffaae07 100644 --- a/doc/config/tty.md +++ b/doc/config/tty.md @@ -83,4 +83,31 @@ board bringup and system debugging it can come in handy. One can also use the `service` stanza to start a stand-alone shell: - service [12345] /bin/sh -l \ No newline at end of file + service [12345] /bin/sh -l + +Controlling TTY for Services +---------------------------- + +The `tty:` option gives a `run`, `task`, or `service` a controlling +terminal on the given device. The device is opened, set as the +controlling terminal for the session (after `setsid()`), and connected to +the process's stdin, stdout, and stderr. A default `TERM` environment +variable is set based on the device type: `vt102` for serial lines and +`linux` for virtual terminals. + +`` may be a device node like `/dev/ttyS0`, or the special keyword +`@console` (see above). Note that `@console` expands only to the +first console, not all. + +When `tty:` is combined with `log:`, stdout and stderr are redirected +to the log sink instead of the TTY, but stdin remains connected to the +TTY device. + +> The `tty:` option is for `run`, `task`, and `service` stanzas only. +> The `tty` directive itself (for getty/login) has its own syntax, see +> above. + +**Example:** + + service [2345] tty:/dev/ttyS0 /usr/sbin/foo -- Foo on serial console + task [S] tty:@console my-setup-script -- Board bringup on console \ No newline at end of file diff --git a/doc/switchroot.md b/doc/switchroot.md index 89ae08b6..c342b487 100644 --- a/doc/switchroot.md +++ b/doc/switchroot.md @@ -61,7 +61,8 @@ For more complex setups (LUKS, LVM, etc.): ``` # Unlock LUKS volume -run [S] name:cryptsetup /sbin/cryptsetup open /dev/sda2 cryptroot -- Unlocking encrypted root +# The tty:@console stanza is required so cryptsetup can prompt for a passphrase +run [S] name:cryptsetup tty:@console /sbin/cryptsetup open /dev/sda2 cryptroot -- Unlocking encrypted root # Activate LVM run [S] name:lvm /sbin/lvm vgchange -ay -- Activating LVM volumes diff --git a/man/finit.conf.5 b/man/finit.conf.5 index eb74eb88..a1c2df19 100644 --- a/man/finit.conf.5 +++ b/man/finit.conf.5 @@ -744,6 +744,37 @@ is and the default .Cm tag identity is the basename of the service or run/task command. +.It Cm tty:\& +Give the +.Cm run , task , +or +.Cm service +a controlling terminal on +.Cm . +The device is opened, set as the controlling terminal for the session +(after +.Fn setsid ) , +and connected to stdin, stdout, and stderr. +A default +.Ev TERM +variable is set based on the device type: +.Cm vt102 +for serial lines and +.Cm linux +for virtual terminals. +.Cm +may be a device node like +.Pa /dev/ttyS0 , +or the special keyword +.Cm @console +(see TTYs and Consoles above). +When combined with +.Cm log: , +stdout and stderr are redirected to the log sink instead, but stdin +remains connected to the TTY device. +.Bd -unfilled -offset indent +service [2345] tty:/dev/ttyS0 /usr/sbin/foo -- Foo on serial console +.Ed .El .Sh RESCUE MODE Finit supports a rescue mode which is activated by the diff --git a/src/service.c b/src/service.c index 016b5d2f..7ed4fceb 100644 --- a/src/service.c +++ b/src/service.c @@ -29,10 +29,12 @@ #include /* sched_yield() */ #include #include +#include #include #include #include #include +#include #include #ifdef _LIBITE_LITE # include @@ -387,6 +389,43 @@ static int lredirect(svc_t *svc) return close(pipefd[1]); } +/* + * Set up a controlling TTY for run/task/service. + * Opens the TTY device, dups to 0/1/2, resets termios to + * sane defaults, sets TERM env, and updates procname. + */ +static void svc_prepare_ctty(const char *tty, const char *procname, int log) +{ + int fd, dummy; + + fd = open(tty, O_RDWR); + if (fd < 0) { + logit(LOG_ERR, "Failed opening %s: %s", tty, strerror(errno)); + _exit(1); + } + + /* Acquire as controlling terminal for the session */ + ioctl(fd, TIOCSCTTY, 0); + + dup2(fd, STDIN_FILENO); + if (!log) { + dup2(fd, STDOUT_FILENO); + dup2(fd, STDERR_FILENO); + } + + /* Set default TERM */ + if (ioctl(fd, VT_OPENQRY, &dummy) == -1) + setenv("TERM", "vt102", 1); /* likely a serial line */ + else + setenv("TERM", "linux", 1); + + /* Reset to sane defaults */ + stty(fd, B0); + close(fd); + + prctl(PR_SET_NAME, procname, 0, 0, 0); +} + /* * Handle redirection of process output, if enabled */ @@ -946,6 +985,14 @@ static int service_start(svc_t *svc) sig_unblock(); + /* + * If the stanza declared tty:, set up a controlling + * terminal on that device now that we're a session leader. + * Must run after setsid() to acquire the controlling TTY. + */ + if (!svc_is_tty(svc) && svc_has_ctty(svc)) + svc_prepare_ctty(svc->log.ctty, svc->cmd, !!svc->log.enabled); + if (svc_is_runtask(svc)) status = exec_runtask(args[0], &args[1]); else if (svc_is_tty(svc)) @@ -1795,6 +1842,7 @@ int service_register(int type, char *cfg, struct rlimit rlimit[], char *file) char ident[MAX_IDENT_LEN]; char *ifstmt = NULL; char *notify = NULL; + char *ctty = NULL; struct tty tty = { 0 }; char *dev = NULL; int respawn = 0; @@ -1911,6 +1959,8 @@ int service_register(int type, char *cfg, struct rlimit rlimit[], char *file) env = arg; else if (MATCH_CMD(cmd, "caps:", arg)) caps = arg; + else if (MATCH_CMD(cmd, "tty:", arg)) + ctty = arg; /* catch both cgroup: and cgroup. handled in parse_cgroup() */ else if (MATCH_CMD(cmd, "cgroup", arg)) cgroup = arg; @@ -2160,6 +2210,22 @@ int service_register(int type, char *cfg, struct rlimit rlimit[], char *file) parse_caps(svc, caps); else memset(svc->capabilities, 0, sizeof(svc->capabilities)); + + if (!svc_is_tty(svc) && ctty) { + char *dev = ctty; + + /* NOTE: @console expands only to first console, not all */ + if (tty_isatcon(ctty)) + dev = tty_atcon(); + dev = tty_canonicalize(dev); + if (dev) + strlcpy(svc->log.ctty, dev, sizeof(svc->log.ctty)); + else + logit(LOG_WARNING, "%s: tty: %s not found, skipping ctty", svc_ident(svc, NULL, 0), ctty); + } else if (!svc_is_tty(svc)) { + memset(svc->log.ctty, 0, sizeof(svc->log.ctty)); + } + if (file) strlcpy(svc->file, file, sizeof(svc->file)); else diff --git a/src/svc.h b/src/svc.h index f21a8ee6..1d4b46e3 100644 --- a/src/svc.h +++ b/src/svc.h @@ -176,6 +176,7 @@ typedef struct svc { char file[64]; char prio[20]; char ident[20]; + char ctty[32]; } log; /* Only for TTY type services */ @@ -291,6 +292,7 @@ static inline int svc_in_runlevel (svc_t *svc, int runlevel) { return svc && IS static inline int svc_has_pidfile (svc_t *svc) { return svc_is_daemon(svc) && svc->pidfile[0] != 0 && svc->pidfile[0] != '!'; } static inline int svc_has_pre (svc_t *svc) { return svc->pre_script[0]; } static inline int svc_has_post (svc_t *svc) { return svc->post_script[0]; } +static inline int svc_has_ctty (svc_t *svc) { return svc && svc->log.ctty[0]; } static inline int svc_has_ready (svc_t *svc) { return svc->ready_script[0];} static inline int svc_has_cleanup (svc_t *svc) { return svc->cleanup_script[0]; }