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 #include <ctype.h>
21 #include <errno.h>
22 #include <fcntl.h>
23 #include <pwd.h>
24 #include <signal.h>
25 #include <stdint.h>
26 #include <stdio.h>
27 #include <stdlib.h>
28 #include <string.h>
29 #include <sys/ioctl.h>
30 #include <sys/stat.h>
31 #include <sys/wait.h>
32 #include <termios.h>
33 #include <unistd.h>
34 #include <utmp.h>
35 
36 #include <sys/fb.h>
37 #include <sys/klog.h>
38 
39 #include "pedigree/native/input/Input.h"
40 
41 // PID of the process we're running
42 pid_t g_RunningPid = -1;
43 
44 // File descriptor for our PTY master.
45 int g_MasterPty;
46 
47 #if defined(LIVECD) && !defined(TRAVIS)
48 #define FIRST_PROGRAM "/applications/live"
49 #else
50 #define FIRST_PROGRAM "/applications/login"
51 #endif
52 
53 #define ALT_KEY (1ULL << 60)
54 #define SHIFT_KEY (1ULL << 61)
55 #define CTRL_KEY (1ULL << 62)
56 #define SPECIAL_KEY (1ULL << 63)
57 
58 enum ActualKey
59 {
60  None,
61  Left,
62  Right,
63  Up,
64  Down,
65 };
66 
67 // Pedigree function, defined in glue.c
68 extern int login(int uid, char *password);
69 
70 // SIGINT handler
71 void sigint(int sig)
72 {
73  // Ignore, but don't log (running program)
74 }
75 
76 void handle_input(Input::InputNotification &note)
77 {
78  pedigree_fb_mode current_mode;
79  int fb = open("/dev/fb", O_RDWR);
80  if (fb >= 0)
81  {
82  int result = ioctl(fb, PEDIGREE_FB_GETMODE, &current_mode);
83  close(fb);
84 
85  if (!result)
86  {
87  if (current_mode.width && current_mode.height && current_mode.depth)
88  {
89  klog(
90  LOG_INFO,
91  "ttyterm: dropping input, currently in graphics mode!");
92  return;
93  }
94  }
95  }
96 
97  klog(LOG_INFO, "ttyterm: system input (type=%d)", note.type);
98 
99  if (note.type & Input::Key)
100  {
101  uint64_t c = note.data.key.key;
102 
103  ActualKey realKey = None;
104  if (c & SPECIAL_KEY)
105  {
106  uint32_t k = c & 0xFFFFFFFFULL;
107  char str[5];
108  memcpy(str, reinterpret_cast<char *>(&k), 4);
109  str[4] = 0;
110 
111  if (!strcmp(str, "left"))
112  {
113  realKey = Left;
114  }
115  else if (!strcmp(str, "righ"))
116  {
117  realKey = Right;
118  }
119  else if (!strcmp(str, "up"))
120  {
121  realKey = Up;
122  }
123  else if (!strcmp(str, "down"))
124  {
125  realKey = Down;
126  }
127  else
128  {
129  // unhandled special key
130  return;
131  }
132  }
133  else if (c & CTRL_KEY)
134  {
135  // CTRL-key = unprintable (ie, CTRL-C, CTRL-U)
136  c &= 0x1F;
137  }
138 
139  if (c == '\n')
140  c = '\r'; // Enter key (ie, return) - CRtoNL.
141 
142  if (realKey != None)
143  {
144  switch (realKey)
145  {
146  case Left:
147  write(g_MasterPty, "\e[D", 3);
148  break;
149  case Right:
150  write(g_MasterPty, "\e[C", 3);
151  break;
152  case Up:
153  write(g_MasterPty, "\e[A", 3);
154  break;
155  case Down:
156  write(g_MasterPty, "\e[B", 3);
157  break;
158  default:
159  break;
160  }
161  }
162  else if (c & ALT_KEY)
163  {
164  // ALT escaped key
165  c &= 0x7F;
166  char buf[2] = {'\e', static_cast<char>(c & 0xFF)};
167  write(g_MasterPty, buf, 2);
168  }
169  else if (c)
170  {
171  uint32_t utf32 = c & 0xFFFFFFFF;
172 
173  // UTF32 -> UTF8
174  char buf[4];
175  size_t nbuf = 0;
176  if (utf32 <= 0x7F)
177  {
178  buf[0] = utf32 & 0x7F;
179  nbuf = 1;
180  }
181  else if (utf32 <= 0x7FF)
182  {
183  buf[0] = 0xC0 | ((utf32 >> 6) & 0x1F);
184  buf[1] = 0x80 | (utf32 & 0x3F);
185  nbuf = 2;
186  }
187  else if (utf32 <= 0xFFFF)
188  {
189  buf[0] = 0xE0 | ((utf32 >> 12) & 0x0F);
190  buf[1] = 0x80 | ((utf32 >> 6) & 0x3F);
191  buf[2] = 0x80 | (utf32 & 0x3F);
192  nbuf = 3;
193  }
194  else if (utf32 <= 0x10FFFF)
195  {
196  buf[0] = 0xF0 | ((utf32 >> 18) & 0x07);
197  buf[1] = 0x80 | ((utf32 >> 12) & 0x3F);
198  buf[2] = 0x80 | ((utf32 >> 6) & 0x3F);
199  buf[3] = 0x80 | (utf32 & 0x3F);
200  nbuf = 4;
201  }
202 
203  // UTF8 conversion complete.
204  write(g_MasterPty, buf, nbuf);
205  }
206  }
207 }
208 
209 int main(int argc, char **argv)
210 {
211  klog(LOG_INFO, "ttyterm: starting up...");
212 
213  // Create ourselves a lock file so we don't end up getting run twice.
214  int fd = open("runtimeĀ»/ttyterm.lck", O_WRONLY | O_EXCL | O_CREAT);
215  if (fd < 0)
216  {
217  fprintf(stderr, "ttyterm: lock file exists, terminating.\n");
218  return 1;
219  }
220  close(fd);
221 
222  // New process group for job control. We'll ignore SIGINT for now.
223  signal(SIGINT, sigint);
224  setsid();
225 
226  // Ensure we are in fact in text mode.
227  int fb = open("/dev/fb", O_RDWR);
228  if (fb >= 0)
229  {
231  klog(LOG_INFO, "ttyterm: forcing text mode");
232  pedigree_fb_modeset mode = {0, 0, 0};
233  int rc = ioctl(fb, PEDIGREE_FB_SETMODE, &mode);
234  close(fb);
235 
236  if (rc < 0)
237  {
238  klog(LOG_INFO, "ttyterm: couldn't force text mode, exiting");
239  return 1;
240  }
241  }
242 
243  // Get a PTY and the main TTY.
244  int tty = open("/dev/textui", O_WRONLY);
245  if (tty < 0)
246  {
247  klog(
248  LOG_ALERT, "ttyterm: couldn't open /dev/textui: %s",
249  strerror(errno));
250  return 1;
251  }
252 
253  g_MasterPty = posix_openpt(O_RDWR);
254  if (g_MasterPty < 0)
255  {
256  close(tty);
257  klog(
258  LOG_ALERT, "ttyterm: couldn't get a pseudo-terminal to use: %s",
259  strerror(errno));
260  return 1;
261  }
262 
264  struct winsize ptySize;
265  ptySize.ws_col = 80;
266  ptySize.ws_row = 25;
267  ioctl(g_MasterPty, TIOCSWINSZ, &ptySize);
268 
269  char slavename[64] = {0};
270  strncpy(slavename, ptsname(g_MasterPty), 64);
271  slavename[15] = 0;
272 
273  // Clear the screen.
274  write(tty, "\e[2J", 5);
275 
276  // Install our input callback so we can hook that in to the pty as well.
277  Input::installCallback(Input::Key, handle_input);
278 
279  // Start up child process.
280  g_RunningPid = fork();
281  if (g_RunningPid == -1)
282  {
283  klog(LOG_ALERT, "ttyterm: couldn't fork: %s", strerror(errno));
284  return EXIT_FAILURE;
285  }
286  else if (g_RunningPid == 0)
287  {
288  close(0);
289  close(1);
290  close(2);
291  close(tty);
292  close(g_MasterPty);
293 
294  // Open the slave ready for the child.
295  int slave = open(slavename, O_RDWR);
296  dup2(slave, 1);
297  dup2(slave, 2);
298 
299  // Text UI has a custom terminfo (it can do a little more than a
300  // traditional vt100 can).
301  setenv("TERM", "pedigree", 1);
302 
303  // Set locale variables (but don't worry about setlocale() itself).
304  setenv("LC_ALL", "en_US.UTF-8", 1);
305 
306  // Add a utmp entry for this new process.
307  setutxent();
308  struct utmpx ut;
309  struct timeval tv;
310  memset(&ut, 0, sizeof(ut));
311  gettimeofday(&tv, NULL);
312  ut.ut_type = LOGIN_PROCESS;
313  ut.ut_pid = getpid();
314  ut.ut_tv = tv;
315  strncpy(ut.ut_id, "/", UT_LINESIZE);
316  strncpy(ut.ut_line, "console", UT_LINESIZE); // ttyterm is the console
317  pututxline(&ut);
318  endutxent();
319 
320  // Enable autowrap before loading the login process.
321  write(slave, "\e[?7h", 5);
322 
323  klog(LOG_INFO, "Starting up '" FIRST_PROGRAM "' on pty %s", slavename);
324  execl(FIRST_PROGRAM, FIRST_PROGRAM, 0);
325  klog(
326  LOG_ALERT, "Launching " FIRST_PROGRAM
327  " failed (next line is the error in errno...)");
328  klog(LOG_ALERT, strerror(errno));
329  exit(1);
330  }
331 
332  // Main loop - read from PTY master, write to TTY.
333  const size_t maxBuffSize = 32768;
334  char buffer[maxBuffSize];
335  while (1)
336  {
337  fd_set fds;
338  FD_ZERO(&fds);
339  FD_SET(g_MasterPty, &fds);
340  FD_SET(tty, &fds);
341 
342  int nReady = select(g_MasterPty + 1, &fds, NULL, NULL, NULL);
343  if (nReady > 0)
344  {
345  // Handle incoming data from the PTY.
346  if (FD_ISSET(g_MasterPty, &fds))
347  {
348  // We need to inhibit any input events while we read from the
349  // master pty, as events must write to it. If we were to
350  // receive an event during this read, we'd deadlock in the
351  // kernel.
352  Input::inhibitEvents();
353  size_t len = read(g_MasterPty, buffer, maxBuffSize);
354  Input::uninhibitEvents();
355  buffer[len] = 0;
356  write(tty, buffer, len);
357  }
358 
359  // Handle incoming data from the TTY.
360  if (FD_ISSET(tty, &fds))
361  {
362  size_t len = read(tty, buffer, maxBuffSize);
363  buffer[len] = 0;
364 
365  // Same problem as above - if we are writing and then an event
366  // fires that triggers another write, we'll deadlock in the
367  // kernel.
368  Input::inhibitEvents();
369  write(g_MasterPty, buffer, len);
370  Input::uninhibitEvents();
371  }
372  }
373  }
374 
375  return 0;
376 }
All zeroes = &#39;revert to text mode&#39;.
Definition: fb.h:37