Skip to content

Commit 8e77ea6

Browse files
service: add optional controlling TTY via tty: stanza
1 parent b0e672b commit 8e77ea6

6 files changed

Lines changed: 130 additions & 2 deletions

File tree

doc/config/service-opts.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ Other run/task/service options are:
4646
* `cgroup.NAME[,opts]` or `cgroup:opts` -- see the [Cgroups](cgroups.md) section
4747
* `env:[-]/path/to/env` -- see the [Service Environment](service-env.md) section
4848
* `log:...` -- see [Redirecting Output](logging.md#redirecting-output)
49+
* `tty:<dev>` -- see [Controlling TTY](tty.md#controlling-tty)
4950
* `nowarn` -- see [Conditional Loading](services.md#conditional-loading)
5051
* `notify:...` -- see [Service Synchronization](service-sync.md)
5152
* `if:...` -- see [Conditional Execution](services.md#conditional-execution)

doc/config/tty.md

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,4 +83,31 @@ board bringup and system debugging it can come in handy.
8383

8484
One can also use the `service` stanza to start a stand-alone shell:
8585

86-
service [12345] /bin/sh -l
86+
service [12345] /bin/sh -l
87+
88+
Controlling TTY for Services
89+
----------------------------
90+
91+
The `tty:<dev>` option gives a `run`, `task`, or `service` a controlling
92+
terminal on the given device. The device is opened, set as the
93+
controlling terminal for the session (after `setsid()`), and connected to
94+
the process's stdin, stdout, and stderr. A default `TERM` environment
95+
variable is set based on the device type: `vt102` for serial lines and
96+
`linux` for virtual terminals.
97+
98+
`<dev>` may be a device node like `/dev/ttyS0`, or the special keyword
99+
`@console` (see above). Note that `@console` expands only to the
100+
first console, not all.
101+
102+
When `tty:` is combined with `log:`, stdout and stderr are redirected
103+
to the log sink instead of the TTY, but stdin remains connected to the
104+
TTY device.
105+
106+
> The `tty:<dev>` option is for `run`, `task`, and `service` stanzas only.
107+
> The `tty` directive itself (for getty/login) has its own syntax, see
108+
> above.
109+
110+
**Example:**
111+
112+
service [2345] tty:/dev/ttyS0 /usr/sbin/foo -- Foo on serial console
113+
task [S] tty:@console my-setup-script -- Board bringup on console

doc/switchroot.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,8 @@ For more complex setups (LUKS, LVM, etc.):
6161

6262
```
6363
# Unlock LUKS volume
64-
run [S] name:cryptsetup /sbin/cryptsetup open /dev/sda2 cryptroot -- Unlocking encrypted root
64+
# The tty:@console stanza is required so cryptsetup can prompt for a passphrase
65+
run [S] name:cryptsetup tty:@console /sbin/cryptsetup open /dev/sda2 cryptroot -- Unlocking encrypted root
6566
6667
# Activate LVM
6768
run [S] name:lvm /sbin/lvm vgchange -ay -- Activating LVM volumes

man/finit.conf.5

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -744,6 +744,37 @@ is
744744
and the default
745745
.Cm tag
746746
identity is the basename of the service or run/task command.
747+
.It Cm tty:\&<dev>
748+
Give the
749+
.Cm run , task ,
750+
or
751+
.Cm service
752+
a controlling terminal on
753+
.Cm <dev> .
754+
The device is opened, set as the controlling terminal for the session
755+
(after
756+
.Fn setsid ) ,
757+
and connected to stdin, stdout, and stderr.
758+
A default
759+
.Ev TERM
760+
variable is set based on the device type:
761+
.Cm vt102
762+
for serial lines and
763+
.Cm linux
764+
for virtual terminals.
765+
.Cm <dev>
766+
may be a device node like
767+
.Pa /dev/ttyS0 ,
768+
or the special keyword
769+
.Cm @console
770+
(see TTYs and Consoles above).
771+
When combined with
772+
.Cm log: ,
773+
stdout and stderr are redirected to the log sink instead, but stdin
774+
remains connected to the TTY device.
775+
.Bd -unfilled -offset indent
776+
service [2345] tty:/dev/ttyS0 /usr/sbin/foo -- Foo on serial console
777+
.Ed
747778
.El
748779
.Sh RESCUE MODE
749780
Finit supports a rescue mode which is activated by the

src/service.c

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,10 +29,12 @@
2929
#include <sched.h> /* sched_yield() */
3030
#include <string.h>
3131
#include <sys/reboot.h>
32+
#include <sys/ioctl.h>
3233
#include <sys/prctl.h>
3334
#include <sys/resource.h>
3435
#include <sys/un.h>
3536
#include <sys/wait.h>
37+
#include <linux/vt.h>
3638
#include <net/if.h>
3739
#ifdef _LIBITE_LITE
3840
# include <libite/lite.h>
@@ -387,6 +389,43 @@ static int lredirect(svc_t *svc)
387389
return close(pipefd[1]);
388390
}
389391

392+
/*
393+
* Set up a controlling TTY for run/task/service.
394+
* Opens the TTY device, dups to 0/1/2, resets termios to
395+
* sane defaults, sets TERM env, and updates procname.
396+
*/
397+
static void svc_prepare_ctty(const char *tty, const char *procname, int log)
398+
{
399+
int fd, dummy;
400+
401+
fd = open(tty, O_RDWR);
402+
if (fd < 0) {
403+
logit(LOG_ERR, "Failed opening %s: %s", tty, strerror(errno));
404+
_exit(1);
405+
}
406+
407+
/* Acquire as controlling terminal for the session */
408+
ioctl(fd, TIOCSCTTY, 0);
409+
410+
dup2(fd, STDIN_FILENO);
411+
if (!log) {
412+
dup2(fd, STDOUT_FILENO);
413+
dup2(fd, STDERR_FILENO);
414+
}
415+
416+
/* Set default TERM */
417+
if (ioctl(fd, VT_OPENQRY, &dummy) == -1)
418+
setenv("TERM", "vt102", 1); /* likely a serial line */
419+
else
420+
setenv("TERM", "linux", 1);
421+
422+
/* Reset to sane defaults */
423+
stty(fd, B0);
424+
close(fd);
425+
426+
prctl(PR_SET_NAME, procname, 0, 0, 0);
427+
}
428+
390429
/*
391430
* Handle redirection of process output, if enabled
392431
*/
@@ -946,6 +985,14 @@ static int service_start(svc_t *svc)
946985

947986
sig_unblock();
948987

988+
/*
989+
* If the stanza declared tty:<dev>, set up a controlling
990+
* terminal on that device now that we're a session leader.
991+
* Must run after setsid() to acquire the controlling TTY.
992+
*/
993+
if (!svc_is_tty(svc) && svc_has_ctty(svc))
994+
svc_prepare_ctty(svc->log.ctty, svc->cmd, !!svc->log.enabled);
995+
949996
if (svc_is_runtask(svc))
950997
status = exec_runtask(args[0], &args[1]);
951998
else if (svc_is_tty(svc))
@@ -1795,6 +1842,7 @@ int service_register(int type, char *cfg, struct rlimit rlimit[], char *file)
17951842
char ident[MAX_IDENT_LEN];
17961843
char *ifstmt = NULL;
17971844
char *notify = NULL;
1845+
char *ctty = NULL;
17981846
struct tty tty = { 0 };
17991847
char *dev = NULL;
18001848
int respawn = 0;
@@ -1911,6 +1959,8 @@ int service_register(int type, char *cfg, struct rlimit rlimit[], char *file)
19111959
env = arg;
19121960
else if (MATCH_CMD(cmd, "caps:", arg))
19131961
caps = arg;
1962+
else if (MATCH_CMD(cmd, "tty:", arg))
1963+
ctty = arg;
19141964
/* catch both cgroup: and cgroup. handled in parse_cgroup() */
19151965
else if (MATCH_CMD(cmd, "cgroup", arg))
19161966
cgroup = arg;
@@ -2160,6 +2210,22 @@ int service_register(int type, char *cfg, struct rlimit rlimit[], char *file)
21602210
parse_caps(svc, caps);
21612211
else
21622212
memset(svc->capabilities, 0, sizeof(svc->capabilities));
2213+
2214+
if (!svc_is_tty(svc) && ctty) {
2215+
char *dev = ctty;
2216+
2217+
/* NOTE: @console expands only to first console, not all */
2218+
if (tty_isatcon(ctty))
2219+
dev = tty_atcon();
2220+
dev = tty_canonicalize(dev);
2221+
if (dev)
2222+
strlcpy(svc->log.ctty, dev, sizeof(svc->log.ctty));
2223+
else
2224+
logit(LOG_WARNING, "%s: tty: %s not found, skipping ctty", svc_ident(svc, NULL, 0), ctty);
2225+
} else if (!svc_is_tty(svc)) {
2226+
memset(svc->log.ctty, 0, sizeof(svc->log.ctty));
2227+
}
2228+
21632229
if (file)
21642230
strlcpy(svc->file, file, sizeof(svc->file));
21652231
else

src/svc.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -176,6 +176,7 @@ typedef struct svc {
176176
char file[64];
177177
char prio[20];
178178
char ident[20];
179+
char ctty[32];
179180
} log;
180181

181182
/* Only for TTY type services */
@@ -291,6 +292,7 @@ static inline int svc_in_runlevel (svc_t *svc, int runlevel) { return svc && IS
291292
static inline int svc_has_pidfile (svc_t *svc) { return svc_is_daemon(svc) && svc->pidfile[0] != 0 && svc->pidfile[0] != '!'; }
292293
static inline int svc_has_pre (svc_t *svc) { return svc->pre_script[0]; }
293294
static inline int svc_has_post (svc_t *svc) { return svc->post_script[0]; }
295+
static inline int svc_has_ctty (svc_t *svc) { return svc && svc->log.ctty[0]; }
294296
static inline int svc_has_ready (svc_t *svc) { return svc->ready_script[0];}
295297
static inline int svc_has_cleanup (svc_t *svc) { return svc->cleanup_script[0]; }
296298

0 commit comments

Comments
 (0)