Skip to content

Commit c438384

Browse files
committed
ccon: Add support for a 'console' config property
For container processes like systemd which need a writable terminal at /dev/console but don't need special handling for their standard streams. The grep removes a: execute: sh -c echo hello >>/dev/console && echo goodbye line due to --verbose logging to stderr, which after the set_terminal call, is going to the pseudoterminal slave. The printf's add carriage returns due to the pseudoterminal's default ONLCR setting.
1 parent 0aea07a commit c438384

File tree

5 files changed

+204
-48
lines changed

5 files changed

+204
-48
lines changed

README.md

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ less opinionated than [LXC][lxc.container.conf.5]).
2020
* [Network namespace](#network-namespace)
2121
* [IPC namespace](#ipc-namespace)
2222
* [UTS namespace](#uts-namespace)
23+
* [Console](#console)
2324
* [Process](#process)
2425
* [Terminal](#terminal)
2526
* [User](#user)
@@ -61,6 +62,8 @@ synchronize the container setup. Here's an outline of the lifecycle:
6162
| sends exec-message → | |
6263
| | opens the local ptmx |
6364
| | ← sends pseudoterminal master |
65+
| | bind mounts `/dev/console` |
66+
| | ← sends pseudoterminal slave |
6467
| waits on child death | executes user process |
6568
| splicing standard streams ||
6669
| onto the pseduoterminal master | |
@@ -372,6 +375,28 @@ for [`sethostname`][gethostname.2].
372375
* **`path`** (optional, string) the absolute path to a UTS namespace
373376
which the container process should join.
374377

378+
### Console
379+
380+
* **`console`** (optional, boolean) if true, the container process
381+
will [open its local `/dev/ptmx`][pts.4] (e.g. with
382+
[`posix_openpt`][posix_openpt.3p]), grant access to the slave with
383+
[`grantpt`][grantpt.3p], [bind `mount`][mount.2] the pseudoterminal
384+
slave to `/dev/console`, and send both the pseudoterminal master and
385+
slave back to the host process. The host process will continually
386+
copy its [standard input][stdin.3] to that pseudoterminal master and
387+
the pseudoterminal master to its [standard output][stdin.3]. If
388+
[**`process.terminal`**](#terminal) is also true, the same
389+
pseudoterminal will be used for both `/dev/console` and the
390+
container process's [standard streams][stdin.3].
391+
392+
Some applications (including [systemd][systemd-container-interface])
393+
require a TTY at `/dev/console`. This setting allows you to provide
394+
that console without [`dup`][dup.2]ing over the container process's
395+
standard streams.
396+
397+
For more details on why using the container's `/dev/ptmx` is
398+
important, see the [**`process.terminal`** documentation](#terminal).
399+
375400
### Process
376401

377402
After the container setup is finished, the container process can
@@ -424,7 +449,10 @@ consider][devpts].
424449
its standard streams, and send the pseudoterminal master back to the
425450
host process. The host process will continually copy its
426451
[standard input][stdin.3] to that pseudoterminal master and the
427-
pseudoterminal master to its [standard output][stdin.3].
452+
pseudoterminal master to its [standard output][stdin.3]. If
453+
[**`console`**](#console) is also true, the same pseudoterminal will
454+
be used for both `/dev/console` and the container process's standard
455+
streams.
428456

429457
Before [77356912][glibc-77356912] (included in version 2.23, released
430458
2016-02-19), [glibc][]'s [`grantpt`][grantpt.3p] was more agressive
@@ -796,6 +824,7 @@ be distributed under the GPLv3+.
796824
[Nginx]: http://nginx.org/
797825
[pkg-config]: http://www.freedesktop.org/wiki/Software/pkg-config/
798826
[semver]: http://semver.org/spec/v2.0.0.html
827+
[systemd-container-interface]: https://www.freedesktop.org/wiki/Software/systemd/ContainerInterface/
799828

800829
[GPL-compatible]: https://www.gnu.org/licenses/license-list.html#GPLCompatibleLicenses
801830
[mit]: https://www.gnu.org/licenses/license-list.html#Expat

ccon.c

Lines changed: 92 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -94,14 +94,15 @@ static int handle_parent(json_t * config, pid_t cpid, int *socket);
9494
static int child_func(void *arg);
9595
static int handle_child(json_t * config, int *socket, int *exec_fd,
9696
namespace_fd_t ** namespace_fds);
97-
static int set_terminal(json_t * process, int dup_stdin, int *socket);
97+
static int set_terminal(json_t * process, int console, int dup_stdin,
98+
int *socket);
9899
static int set_working_directory(json_t * process);
99100
static int set_user_group(json_t * process);
100101
static int _capng_name_to_capability(const char *name);
101102
static int set_capabilities(json_t * process);
102103
static void exec_container_process(json_t * config, int *socket, int *exec_fd);
103-
static void exec_process(json_t * process, int dup_stdin, int *socket,
104-
int *exec_fd);
104+
static void exec_process(json_t * process, int console, int dup_stdin,
105+
int *socket, int *exec_fd);
105106
static int get_host_exec_fd(json_t * config, int *exec_fd);
106107
static int get_namespace_fds(json_t * config, namespace_fd_t ** namespace_fds);
107108
static int run_hooks(json_t * config, const char *name, pid_t cpid);
@@ -121,9 +122,9 @@ static int open_in_path(const char *name, int flags);
121122
static int _wait(pid_t pid, const char *name);
122123
static char **json_array_of_strings_value(json_t * array);
123124
static int close_pipe(int pipe_fd[]);
124-
static int sendfd(int socket, int *fd);
125+
static int sendfd(int socket, int *fd, int close_fd);
125126
static int recvfd(int socket, int *fd);
126-
static int splice_pseudoterminal_master(int *master);
127+
static int splice_pseudoterminal_master(int *master, int *slave);
127128
static int mkdir_all(const char *path, mode_t mode);
128129

129130
int main(int argc, char **argv)
@@ -321,6 +322,7 @@ static int validate_config(json_t * config)
321322
"s?s," /* "path": "/proc/123/ns/uts */
322323
"}," /* } (uts) */
323324
"}," /* } (namespaces) */
325+
"s?b," /* "console": { */
324326
"s?{" /* "process": { */
325327
"s?b," /* "terminal": true */
326328
"s?{" /* "user": { */
@@ -358,6 +360,7 @@ static int validate_config(json_t * config)
358360
"path",
359361
"uts",
360362
"path",
363+
"console",
361364
"process",
362365
"terminal",
363366
"user",
@@ -555,13 +558,13 @@ static int run_container(json_t * config)
555558

556559
static int handle_parent(json_t * config, pid_t cpid, int *socket)
557560
{
558-
json_t *process, *terminal = NULL;
561+
json_t *process, *console = NULL, *terminal = NULL;
559562
char buf[MESSAGE_SIZE];
560563
struct iovec iov;
561564
struct msghdr msg = { NULL, 0, &iov, 1, NULL, 0, 0 };
562565
size_t len;
563566
ssize_t n;
564-
int master = -1, err = 0, exit = 0;
567+
int master = -1, slave = -1, err = 0, exit = 0;
565568

566569
if (set_user_namespace_mappings(config, cpid)) {
567570
err = 1;
@@ -618,15 +621,24 @@ static int handle_parent(json_t * config, pid_t cpid, int *socket)
618621
goto wait;
619622
}
620623

624+
console = json_object_get(config, "console");
621625
process = json_object_get(config, "process");
622626
if (process) {
623627
terminal = json_object_get(process, "terminal");
624-
if (json_boolean_value(terminal)) {
628+
if (json_boolean_value(terminal)
629+
|| (console && json_boolean_value(console))) {
625630
if (recvfd(*socket, &master)) {
626631
err = 1;
627632
goto wait;
628633
}
629634
}
635+
636+
if (console && json_boolean_value(console)) {
637+
if (recvfd(*socket, &slave)) {
638+
err = 1;
639+
goto wait;
640+
}
641+
}
630642
}
631643

632644
wait:
@@ -643,7 +655,7 @@ static int handle_parent(json_t * config, pid_t cpid, int *socket)
643655
*socket = -1;
644656

645657
if (!err && master >= 0) {
646-
if (splice_pseudoterminal_master(&master)) {
658+
if (splice_pseudoterminal_master(&master, &slave)) {
647659
err = 1;
648660
kill_child(0, NULL, NULL);
649661
}
@@ -657,6 +669,14 @@ static int handle_parent(json_t * config, pid_t cpid, int *socket)
657669
}
658670
}
659671

672+
if (slave >= 0) {
673+
if (close(slave) == -1) {
674+
PERROR("close pseudoterminal slave");
675+
err = 1;
676+
kill_child(0, NULL, NULL);
677+
}
678+
}
679+
660680
exit = _wait(cpid, "container");
661681

662682
(void)run_hooks(config, "post-stop", 0);
@@ -777,18 +797,19 @@ static int handle_child(json_t * config, int *socket, int *exec_fd,
777797
return 1;
778798
}
779799

780-
static int set_terminal(json_t * process, int dup_stdin, int *socket)
800+
static int set_terminal(json_t * process, int console, int dup_stdin,
801+
int *socket)
781802
{
782803
json_t *terminal;
783804
char *slave_name;
784805
int err = 0, master = -1, slave = -1;
785806

786807
terminal = json_object_get(process, "terminal");
787-
if (!terminal) {
808+
if (!terminal && !console) {
788809
return 0;
789810
}
790811

791-
if (json_boolean_value(terminal)) {
812+
if (json_boolean_value(terminal) || console) {
792813
if (socket == NULL) {
793814
LOG("cannot create a pseudoterminal without a socket for master\n");
794815
return 1;
@@ -832,38 +853,47 @@ static int set_terminal(json_t * process, int dup_stdin, int *socket)
832853
goto cleanup;
833854
}
834855

835-
if (sendfd(*socket, &master)) {
856+
if (sendfd(*socket, &master, 1)) {
836857
err = 1;
837858
goto cleanup;
838859
}
839860

840-
if (dup_stdin) {
841-
if (dup2(slave, STDIN_FILENO) == -1) {
842-
PERROR("dup2");
861+
if (console) {
862+
LOG("bind mount %s to /dev/console\n", slave_name);
863+
if (mount
864+
(slave_name, "/dev/console", NULL, MS_BIND,
865+
NULL) == -1) {
866+
PERROR("mount");
867+
return 1;
868+
}
869+
870+
if (sendfd(*socket, &slave, 0)) {
843871
err = 1;
844872
goto cleanup;
845873
}
846874
}
847875

848-
if (dup2(slave, STDOUT_FILENO) == -1) {
849-
PERROR("dup2");
850-
err = 1;
851-
goto cleanup;
852-
}
876+
if (json_boolean_value(terminal)) {
877+
if (dup_stdin) {
878+
if (dup2(slave, STDIN_FILENO) == -1) {
879+
PERROR("dup2");
880+
err = 1;
881+
goto cleanup;
882+
}
883+
}
853884

854-
if (dup2(slave, STDERR_FILENO) == -1) {
855-
PERROR("dup2");
856-
err = 1;
857-
goto cleanup;
858-
}
885+
if (dup2(slave, STDOUT_FILENO) == -1) {
886+
PERROR("dup2");
887+
err = 1;
888+
goto cleanup;
889+
}
859890

860-
if (close(slave)) {
861-
PERROR("closing pseudoterminal slave");
862-
slave = -1;
863-
err = 1;
864-
goto cleanup;
891+
if (dup2(slave, STDERR_FILENO) == -1) {
892+
PERROR("dup2");
893+
err = 1;
894+
goto cleanup;
895+
}
865896
}
866-
slave = -1;
867897
}
868898

869899
cleanup:
@@ -1034,20 +1064,22 @@ static int set_capabilities(json_t * process)
10341064

10351065
static void exec_container_process(json_t * config, int *socket, int *exec_fd)
10361066
{
1037-
json_t *process;
1067+
json_t *process, *console;
10381068

10391069
process = json_object_get(config, "process");
10401070
if (!process) {
10411071
LOG("process not defined, exiting\n");
10421072
exit(0);
10431073
}
10441074

1045-
exec_process(process, 1, socket, exec_fd);
1075+
console = json_object_get(config, "console");
1076+
exec_process(process, console
1077+
&& json_boolean_value(console), 1, socket, exec_fd);
10461078
return;
10471079
}
10481080

1049-
static void exec_process(json_t * process, int dup_stdin, int *socket,
1050-
int *exec_fd)
1081+
static void exec_process(json_t * process, int console, int dup_stdin,
1082+
int *socket, int *exec_fd)
10511083
{
10521084
char *path = NULL;
10531085
char **argv = NULL, **env = NULL;
@@ -1060,7 +1092,7 @@ static void exec_process(json_t * process, int dup_stdin, int *socket,
10601092
exit(0);
10611093
}
10621094

1063-
if (set_terminal(process, dup_stdin, socket)) {
1095+
if (set_terminal(process, console, dup_stdin, socket)) {
10641096
goto cleanup;
10651097
}
10661098

@@ -1343,7 +1375,7 @@ static int run_hooks(json_t * config, const char *name, pid_t cpid)
13431375
pipe_fd[0] = -1;
13441376
}
13451377

1346-
exec_process(hook, cpid == 0, &sockets[1], NULL);
1378+
exec_process(hook, 0, cpid == 0, &sockets[1], NULL);
13471379
err = 1;
13481380
goto cleanup;
13491381
}
@@ -1373,7 +1405,7 @@ static int run_hooks(json_t * config, const char *name, pid_t cpid)
13731405
}
13741406

13751407
if (master >= 0) {
1376-
if (splice_pseudoterminal_master(&master)) {
1408+
if (splice_pseudoterminal_master(&master, NULL)) {
13771409
err = 1;
13781410
goto cleanup;
13791411
}
@@ -2149,7 +2181,7 @@ static int close_pipe(int pipe_fd[])
21492181
return err;
21502182
}
21512183

2152-
static int sendfd(int socket, int *fd)
2184+
static int sendfd(int socket, int *fd, int close_fd)
21532185
{
21542186
struct cmsghdr *cmsg;
21552187
union {
@@ -2171,12 +2203,14 @@ static int sendfd(int socket, int *fd)
21712203
return 1;
21722204
}
21732205

2174-
if (close(*fd)) {
2175-
PERROR("close");
2206+
if (close_fd) {
2207+
if (close(*fd)) {
2208+
PERROR("close");
2209+
*fd = -1;
2210+
return 1;
2211+
}
21762212
*fd = -1;
2177-
return 1;
21782213
}
2179-
*fd = -1;
21802214

21812215
return 0;
21822216
}
@@ -2208,7 +2242,7 @@ int recvfd(int socket, int *fd)
22082242
return 0;
22092243
}
22102244

2211-
static int splice_pseudoterminal_master(int *master)
2245+
static int splice_pseudoterminal_master(int *master, int *slave)
22122246
{
22132247
fd_set rfds, wfds, efds;
22142248
char in_buf[1024];
@@ -2226,6 +2260,12 @@ static int splice_pseudoterminal_master(int *master)
22262260

22272261
if (child_pid < 0) { /* don't bother piping to a dead process */
22282262
in_open = in_len = 0;
2263+
if (slave && *slave >= 0) { /* don't hold the slave open either */
2264+
if (close(*slave)) {
2265+
PERROR("close pseudoterminal slave");
2266+
}
2267+
*slave = -1;
2268+
}
22292269
}
22302270

22312271
if (in_open && in_len == 0) { /* wait for new data */
@@ -2369,6 +2409,13 @@ static int splice_pseudoterminal_master(int *master)
23692409
*master = -1;
23702410
}
23712411

2412+
if (slave && *slave >= 0) {
2413+
if (close(*slave)) {
2414+
PERROR("close pseudoterminal slave");
2415+
}
2416+
*slave = -1;
2417+
}
2418+
23722419
return err;
23732420
}
23742421

0 commit comments

Comments
 (0)