The Pedigree Project 0.1
ttyterm.cc
1/*
2 * Copyright (c) 2008-2014, Pedigree Developers
3 *
4 * Please see the CONTRIB file in the root of the source tree for a full
5 * list of contributors.
6 *
7 * Permission to use, copy, modify, and distribute this software for any
8 * purpose with or without fee is hereby granted, provided that the above
9 * copyright notice and this permission notice appear in all copies.
10 *
11 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
12 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
13 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
14 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
15 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
16 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
17 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
18 */
19
20#define _GNU_SOURCE 1
21
22#include "pedigree/native/input/Input.h"
23
24#include <config.h>
25#include <ctype.h>
26#include <errno.h>
27#include <fcntl.h>
28#include <pwd.h>
29#include <signal.h>
30#include <stdint.h>
31#include <stdio.h>
32#include <stdlib.h>
33#include <string.h>
34#include <termios.h>
35#include <unistd.h>
36#include <utmp.h>
37
38#include <pedigree/fb.h>
39#include <pedigree/log.h>
40#include <sys/ioctl.h>
41#include <sys/select.h>
42#include <sys/stat.h>
43#include <sys/time.h>
44#include <sys/wait.h>
45
46// PID of the process we're running
47pid_t g_RunningPid = -1;
48
49// File descriptor for our PTY master.
50int g_MasterPty;
51
52#define FIRST_PROGRAM "/usr/bin/login"
53
54// SIGINT handler
55void sigint(int sig) {
56 // Ignore, but don't log (running program)
57}
58
59int main(int argc, char** argv) {
60 pedigree_log(LOG_INFO, "ttyterm: starting up...");
61
62 // Create ourselves a lock file so we don't end up getting run twice.
63 int fd = open("/run/ttyterm.lck", O_WRONLY | O_EXCL | O_CREAT);
64 if (fd < 0) {
65 fprintf(stderr, "ttyterm: lock file exists, terminating.\n");
66 return 1;
67 }
68 close(fd);
69
70 // New process group for job control. We'll ignore SIGINT for now.
71 signal(SIGINT, sigint);
72 setsid();
73
74 // Ensure we are in fact in text mode.
75 int fb = open("/dev/fb", O_RDWR);
76 if (fb >= 0) {
78 pedigree_log(LOG_INFO, "ttyterm: forcing text mode");
79 pedigree_fb_modeset mode = {0, 0, 0};
80 int rc = ioctl(fb, PEDIGREE_FB_SETMODE, &mode);
81 close(fb);
82
83 if (rc < 0) {
84 pedigree_log(LOG_INFO, "ttyterm: couldn't force text mode, exiting");
85 return 1;
86 }
87 }
88
89 // Get a PTY and the main TTY.
90 int tty = open("/dev/textui", O_RDWR);
91 if (tty < 0) {
92 pedigree_log(LOG_ALERT, "ttyterm: couldn't open /dev/textui: %s", strerror(errno));
93 return 1;
94 }
95
96 // Mirror the terminal to COM1 so QEMU can run without a graphical display.
97 // The serial endpoint is optional for targets without a serial device.
98 int serial = open("/dev/ttyS0", O_RDWR);
99 if (serial < 0) {
100 pedigree_log(LOG_WARNING, "ttyterm: couldn't open /dev/ttyS0: %s", strerror(errno));
101 }
102
103 g_MasterPty = posix_openpt(O_RDWR);
104 if (g_MasterPty < 0) {
105 close(tty);
106 pedigree_log(LOG_ALERT, "ttyterm: couldn't get a pseudo-terminal to use: %s", strerror(errno));
107 return 1;
108 }
109
111 struct winsize ptySize;
112 ptySize.ws_col = 80;
113 ptySize.ws_row = 25;
114 ioctl(g_MasterPty, TIOCSWINSZ, &ptySize);
115
116 char slavename[64] = {0};
117 strncpy(slavename, ptsname(g_MasterPty), 64);
118 slavename[15] = 0;
119
120 // Clear the screen.
121 write(tty, "\e[2J", 5);
122
123 // Start up child process.
124 g_RunningPid = fork();
125 if (g_RunningPid == -1) {
126 pedigree_log(LOG_ALERT, "ttyterm: couldn't fork: %s", strerror(errno));
127 return EXIT_FAILURE;
128 } else if (g_RunningPid == 0) {
129 close(0);
130 close(1);
131 close(2);
132 close(tty);
133 if (serial >= 0) {
134 close(serial);
135 }
136 close(g_MasterPty);
137
138 // Open the slave ready for the child.
139 int slave = open(slavename, O_RDWR);
140 if (slave < 0) {
141 pedigree_log(LOG_ALERT, "ttyterm: couldn't open pty slave: %s", strerror(errno));
142 exit(1);
143 }
144
145 if (dup2(slave, STDIN_FILENO) < 0) {
146 pedigree_log(LOG_ALERT, "ttyterm: couldn't attach pty slave to stdin: %s", strerror(errno));
147 exit(1);
148 }
149 dup2(slave, 1);
150 dup2(slave, 2);
151 if (slave > STDERR_FILENO) {
152 close(slave);
153 }
154
155 // Text UI has a custom terminfo (it can do a little more than a
156 // traditional vt100 can).
157 setenv("TERM", "pedigree", 1);
158
159 // Set locale variables (but don't worry about setlocale() itself).
160 setenv("LC_ALL", "en_US.UTF-8", 1);
161
162 // Add a utmp entry for this new process.
163 setutxent();
164 struct utmpx ut;
165 struct timeval tv;
166 memset(&ut, 0, sizeof(ut));
167 gettimeofday(&tv, NULL);
168 ut.ut_type = LOGIN_PROCESS;
169 ut.ut_pid = getpid();
170 ut.ut_tv = tv;
171 strncpy(ut.ut_id, "/", sizeof(ut.ut_id));
172 strncpy(ut.ut_line, "console", UT_LINESIZE); // ttyterm is the console
173 pututxline(&ut);
174 endutxent();
175
176 // Enable autowrap before loading the login process.
177 write(slave, "\e[?7h", 5);
178
179 pedigree_log(LOG_INFO, "Starting up '" FIRST_PROGRAM "' on pty %s", slavename);
180 execl(FIRST_PROGRAM, FIRST_PROGRAM, 0);
181 pedigree_log(LOG_ALERT,
182 "Launching " FIRST_PROGRAM " failed (next line is the error in errno...)");
183 pedigree_log(LOG_ALERT, "%s", strerror(errno));
184 exit(1);
185 }
186
187 // Main loop - read from PTY master, write to TTY.
188 const size_t maxBuffSize = 32768;
189 char buffer[maxBuffSize];
190 while (1) {
191 fd_set fds;
192 FD_ZERO(&fds);
193 FD_SET(g_MasterPty, &fds);
194 FD_SET(tty, &fds);
195 int maxFd = g_MasterPty > tty ? g_MasterPty : tty;
196 if (serial >= 0) {
197 FD_SET(serial, &fds);
198 if (serial > maxFd) {
199 maxFd = serial;
200 }
201 }
202
203 // SerialFile polls the UART, so bound the wait to make serial input
204 // visible without requiring a separate kernel reader thread.
205 struct timeval timeout = {0, 100000};
206 int nReady = select(maxFd + 1, &fds, NULL, NULL, &timeout);
207 if (nReady > 0) {
208 // Handle incoming data from the PTY.
209 if (FD_ISSET(g_MasterPty, &fds)) {
210 // We need to inhibit any input events while we read from the
211 // master pty, as events must write to it. If we were to
212 // receive an event during this read, we'd deadlock in the
213 // kernel.
214 Input::inhibitEvents();
215 ssize_t len = read(g_MasterPty, buffer, maxBuffSize);
216 Input::uninhibitEvents();
217 if (len > 0) {
218 write(tty, buffer, len);
219 if (serial >= 0) {
220 write(serial, buffer, len);
221 }
222 }
223 }
224
225 // Handle incoming data from the TTY.
226 if (FD_ISSET(tty, &fds)) {
227 ssize_t len = read(tty, buffer, maxBuffSize);
228
229 // Same problem as above - if we are writing and then an event
230 // fires that triggers another write, we'll deadlock in the
231 // kernel.
232 Input::inhibitEvents();
233 if (len > 0)
234 write(g_MasterPty, buffer, len);
235 Input::uninhibitEvents();
236 }
237
238 // Handle incoming data from the serial terminal.
239 if (serial >= 0 && FD_ISSET(serial, &fds)) {
240 ssize_t len = read(serial, buffer, maxBuffSize);
241
242 Input::inhibitEvents();
243 if (len > 0)
244 write(g_MasterPty, buffer, len);
245 Input::uninhibitEvents();
246 }
247 }
248 }
249
250 return 0;
251}
All zeroes = 'revert to text mode'.
Definition fb.h:51