WvStreams
wvpty.cc
1/* -*- Mode: C++ -*-
2 * Worldvisions Weaver Software:
3 * Copyright (C) 1997-2004 Net Integration Technologies, Inc.
4 *
5 * WvStreams implementation of ptys under Linux.
6 *
7 * For more information on programming ptys, see chapter 19 of
8 * Stevens' "Advanced Programming in the UNIX Environment"
9 */
10
11#include "wvpty.h"
12
13#include <grp.h>
14#include <termios.h>
15#include <fcntl.h>
16#include <sys/ioctl.h>
17#include <sys/signal.h>
18#include <sys/types.h>
19#include <sys/wait.h>
20#include <sys/stat.h>
21
22#define DPRINTF(format, args...)
23//#define DPRINTF(format, args...) fprintf(stderr, "WvPty:" format, ##args)
24
25bool WvPty::open_pty(WvString &master, int &master_fd,
26 WvString &slave, int &slave_fd)
27{
28 const char *xvals = "pqrstuvwxyzPQRST";
29 const char *yvals = "0123456789abcdef";
30 char pty[] = "/dev/ptyXY";
31 char tty[] = "/dev/ttyXY";
32
33 for (int i=0; xvals[i]; ++i)
34 {
35 pty[8] = tty[8] = xvals[i];
36
37 for (int j=0; yvals[j]; ++j)
38 {
39 pty[9] = tty[9] = yvals[j];
40
41 master_fd = ::open(pty, O_RDWR);
42 if (master_fd >= 0)
43 slave_fd = ::open(tty, O_RDWR);
44 else slave_fd = -1;
45 if (master_fd < 0 || slave_fd < 0)
46 {
47 int saved_errno = errno;
48 if (master_fd >= 0) ::close(master_fd);
49 if (slave_fd >= 0) ::close(slave_fd);
50 if (saved_errno == ENOENT)
51 {
52 DPRINTF("No more PTYs (ENOENT)\n");
53 return false; // no more ptys
54 }
55 }
56 else
57 {
58 DPRINTF("PTY is %s\n", (master = WvString(pty)).edit());
59 DPRINTF("TTY is %s\n", (slave = WvString(tty)).edit());
60
61 // try to change owner and permissions of slave.
62 // this will only work if we
63 // are root; if we're not root, we don't care.
64 struct group *gr = ::getgrnam("tty");
65 ::fchown(slave_fd, ::getuid(), gr? gr->gr_gid: (gid_t)-1);
66 ::fchmod(slave_fd, S_IRUSR | S_IWUSR | S_IWGRP);
67
68 return true;
69 }
70 }
71 }
72
73 DPRINTF("No more PTYs\n");
74 return false;
75}
76
77WvPty::WvPty(const char *program, const char * const *argv,
78 Callback _pre_exec_cb, Callback _post_exec_cb)
79 : _pid(-1), _exit_status(242),
80 pre_exec_cb(_pre_exec_cb), post_exec_cb(_post_exec_cb)
81{
82 int master_fd, slave_fd;
83 if (!open_pty(_master, master_fd, _slave, slave_fd)
84 || (_pid = ::fork()) < 0)
85 {
86 // error
87 _pid = -1;
88 setfd(-1);
89 }
90 else if (_pid == 0)
91 {
92 // child
93 static const int std_fds[] = {
94 STDIN_FILENO, STDOUT_FILENO, STDERR_FILENO, -1
95 };
96 const int *std_fd;
97
98 if (::close(master_fd) < 0)
99 {
100 DPRINTF("close(master_fd) failed: %s\n", strerror(errno));
101 goto _error;
102 }
103 if (::setsid() < 0)
104 {
105 DPRINTF("setsid() failed: %s\n", strerror(errno));
106 goto _error;
107 }
108 ::ioctl(slave_fd, TIOCSCTTY, NULL); // This may fail in case opening the
109 // ptys in open_slave proactively gave us a
110 // controling terminal
111 for (std_fd = std_fds; *std_fd != -1; ++std_fd)
112 {
113 if (::dup2(slave_fd, *std_fd) < 0)
114 {
115 DPRINTF("dup2(slave_fd, %s) failed: %s\n", *std_fd,
116 strerror(errno));
117 goto _error;
118 }
119 }
120 if (slave_fd > STDERR_FILENO && ::close(slave_fd) < 0)
121 {
122 DPRINTF("close(slave_fd) failed: %s\n", strerror(errno));
123 goto _error;
124 }
125
126 for (std_fd = std_fds; *std_fd != -1; ++std_fd)
127 {
128 if (::fcntl(*std_fd, F_SETFL,
129 fcntl(*std_fd, F_GETFL) & (O_APPEND|O_ASYNC)))
130 {
131 DPRINTF("fcntl(%s, F_SETFL) failed: %s\n", *std_fd,
132 strerror(errno));
133 goto _error;
134 }
135 }
136
137 if (pre_exec_cb && !pre_exec_cb(*this)) goto _error;
138 execvp(program, (char * const *)argv);
139 if (post_exec_cb) post_exec_cb(*this);
140
141_error:
142 _exit(242);
143 }
144 else
145 {
146 // parent
147 if (::close(slave_fd) < 0)
148 {
149 DPRINTF("close(slave_fd) failed: %s\n", strerror(errno));
150 goto _error;
151 }
152 setfd(master_fd);
153 }
154}
155
156void WvPty::kill(int signum)
157{
158 if (_pid != -1)
159 ::kill(_pid, signum);
160}
161
162void WvPty::monitor_child(bool wait)
163{
164 if (_pid != -1)
165 {
166 int status;
167 if (::waitpid(_pid, &status, wait? 0: WNOHANG) == _pid)
168 {
169 _pid = -1;
170 _exit_status = status;
171 }
172 }
173}
174
175bool WvPty::child_exited()
176{
177 monitor_child(false);
178 return _pid == -1;
179}
180
181bool WvPty::child_killed()
182{
183 monitor_child(false);
184 return _pid == -1 && WIFSIGNALED(_exit_status);
185}
186
187int WvPty::finish()
188{
189 monitor_child(true);
190 return WEXITSTATUS(_exit_status);
191}
192
193int WvPty::exit_status()
194{
195 monitor_child(false);
196 if (_pid == -1)
197 {
198 if (child_killed())
199 return WTERMSIG(_exit_status);
200 else
201 return WEXITSTATUS(_exit_status);
202 }
203 else
204 return 242;
205}
206
virtual void close()
Closes the file descriptors.
WvString is an implementation of a simple and efficient printable-string class.