1 /**
2 Copyright: Copyright (c) 2020, Joakim Brännström. All rights reserved.
3 License: $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost Software License 1.0)
4 Author: Joakim Brännström (joakim.brannstrom@gmx.com)
5 */
6 module proc.tty;
7 
8 import std.stdio : File;
9 static import std.process;
10 
11 import proc;
12 
13 /// Spawn a process where stdin/stdout/stderr is pseudo-terminals.
14 PipeProcess ttyProcess(scope const(char[])[] args, const string[string] env = null,
15         std.process.Config config = std.process.Config.none, scope const(char)[] workDir = null) @trusted {
16     import proc.libc;
17 
18     int master;
19     int slave;
20     auto res = openpty(&master, &slave, null, null, null);
21     if (res != 0) {
22         throw new Exception("failed to open a pseudo terminal");
23     }
24 
25     File ioSlave;
26     ioSlave.fdopen(slave, "rw");
27 
28     File ioMaster;
29     ioMaster.fdopen(master, "rw");
30 
31     auto p = std.process.spawnProcess(args, ioSlave, ioSlave, ioSlave, env, config, workDir);
32 
33     return PipeProcess(p, ioMaster, ioMaster, ioMaster);
34 }
35 
36 @("stdin, stdout and stderr shall be pseudo terminals")
37 unittest {
38     import core.sys.posix.unistd;
39 
40     auto p = ttyProcess(["sleep", "1s"]);
41     auto res = p.wait;
42     assert(p.wait == 0);
43 
44     assert(isatty(p.stdin.file.fileno) == 1);
45     assert(isatty(p.stdout.file.fileno) == 1);
46     assert(isatty(p.stderr.file.fileno) == 1);
47 }
48 
49 @("shall open the command read in a fake tty")
50 unittest {
51     import std.stdio;
52 
53     auto p = ttyProcess(["/bin/bash", "-c", "read && echo $REPLY"]);
54     p.stdin.write("foo\n");
55 
56     auto res = p.stdout.read(1024);
57     assert(res.length >= 4);
58     assert(res[0 .. 3] == cast(const(ubyte)[]) "foo");
59 
60     assert(p.wait == 0);
61 }
62 
63 @("the execute command shall detect stdin as a fake tty")
64 unittest {
65     auto p = ttyProcess([
66             "/bin/bash", "-c", "if [ -t 0 ]; then exit 0; fi; exit 1"
67             ]);
68     auto res = p.wait;
69     assert(p.wait == 0);
70 }
71 
72 @("the execute command shall detect stdout as a fake tty")
73 unittest {
74     auto p = ttyProcess([
75             "/bin/bash", "-c", "if [ -t 1 ]; then exit 0; fi; exit 1"
76             ]);
77     auto res = p.wait;
78     assert(p.wait == 0);
79 }
80 
81 @("the execute command shall detect stderr as a fake tty")
82 unittest {
83     auto p = ttyProcess([
84             "/bin/bash", "-c", "if [ -t 2 ]; then exit 0; fi; exit 1"
85             ]);
86     auto res = p.wait;
87     assert(p.wait == 0);
88 }