xref: /haiku/src/bin/consoled/consoled.cpp (revision 13581b3d2a71545960b98fefebc5225b5bf29072)
1 /*
2  * Copyright 2004-2010, Haiku. All rights reserved.
3  * Distributed under the terms of the MIT License.
4  *
5  * Copyright 2002, Travis Geiselbrecht. All rights reserved.
6  * Distributed under the terms of the NewOS License.
7  */
8 
9 
10 #include <ctype.h>
11 #include <dirent.h>
12 #include <errno.h>
13 #include <stdio.h>
14 #include <stdlib.h>
15 #include <string.h>
16 #include <termios.h>
17 #include <unistd.h>
18 
19 #include <FindDirectory.h>
20 #include <image.h>
21 #include <InterfaceDefs.h>
22 #include <OS.h>
23 
24 #include <keyboard_mouse_driver.h>
25 #include <Keymap.h>
26 
27 
28 struct console;
29 
30 struct keyboard {
31 	struct keyboard*	next;
32 	int					device;
33 	int					target;
34 	thread_id			thread;
35 };
36 
37 struct console {
38 	int					console_fd;
39 	thread_id			console_writer;
40 
41 	struct keyboard*	keyboards;
42 
43 	int					tty_master_fd;
44 	int					tty_slave_fd;
45 	int					tty_num;
46 };
47 
48 
49 struct console gConsole;
50 
51 
52 void
53 error(const char* message, ...)
54 {
55 	char buffer[2048];
56 
57 	va_list args;
58 	va_start(args, message);
59 
60 	vsnprintf(buffer, sizeof(buffer), message, args);
61 
62 	va_end(args);
63 
64 	// put it out on stderr as well as to serial/syslog
65 	fputs(buffer, stderr);
66 	debug_printf("%s", buffer);
67 }
68 
69 
70 void
71 update_leds(int fd, uint32 modifiers)
72 {
73 	char lockIO[3] = {0, 0, 0};
74 
75 	if ((modifiers & B_NUM_LOCK) != 0)
76 		lockIO[0] = 1;
77 	if ((modifiers & B_CAPS_LOCK) != 0)
78 		lockIO[1] = 1;
79 	if ((modifiers & B_SCROLL_LOCK) != 0)
80 		lockIO[2] = 1;
81 
82 	ioctl(fd, KB_SET_LEDS, &lockIO, sizeof(lockIO));
83 }
84 
85 
86 static int32
87 keyboard_reader(void* arg)
88 {
89 	struct keyboard* keyboard = (struct keyboard*)arg;
90 	uint8 activeDeadKey = 0;
91 	uint32 modifiers = 0;
92 
93 	BKeymap keymap;
94 	// Load current keymap from disk (we can't talk to the input server)
95 	// TODO: find a better way (we shouldn't have to care about the on-disk
96 	// location)
97 	char path[PATH_MAX];
98 	status_t status = find_directory(B_USER_SETTINGS_DIRECTORY, -1, false,
99 		path, sizeof(path));
100 	if (status == B_OK) {
101 		strlcat(path, "/Key_map", sizeof(path));
102 		status = keymap.SetTo(path);
103 	}
104 	if (status != B_OK)
105 		keymap.SetToDefault();
106 
107 	for (;;) {
108 		raw_key_info rawKeyInfo;
109 		if (ioctl(keyboard->device, KB_READ, &rawKeyInfo,
110 				sizeof(rawKeyInfo)) != 0)
111 			break;
112 
113 		uint32 keycode = rawKeyInfo.keycode;
114 		bool isKeyDown = rawKeyInfo.is_keydown;
115 
116 		if (keycode == 0)
117 			continue;
118 
119 		uint32 changedModifiers = keymap.Modifier(keycode);
120 		bool isLock = (changedModifiers
121 			& (B_CAPS_LOCK | B_NUM_LOCK | B_SCROLL_LOCK)) != 0;
122 		if (changedModifiers != 0 && (!isLock || isKeyDown)) {
123 			uint32 oldModifiers = modifiers;
124 
125 			if ((isKeyDown && !isLock)
126 				|| (isKeyDown && !(modifiers & changedModifiers)))
127 				modifiers |= changedModifiers;
128 			else {
129 				modifiers &= ~changedModifiers;
130 
131 				// ensure that we don't clear a combined B_*_KEY when still
132 				// one of the individual B_{LEFT|RIGHT}_*_KEY is pressed
133 				if (modifiers & (B_LEFT_SHIFT_KEY | B_RIGHT_SHIFT_KEY))
134 					modifiers |= B_SHIFT_KEY;
135 				if (modifiers & (B_LEFT_COMMAND_KEY | B_RIGHT_COMMAND_KEY))
136 					modifiers |= B_COMMAND_KEY;
137 				if (modifiers & (B_LEFT_CONTROL_KEY | B_RIGHT_CONTROL_KEY))
138 					modifiers |= B_CONTROL_KEY;
139 				if (modifiers & (B_LEFT_OPTION_KEY | B_RIGHT_OPTION_KEY))
140 					modifiers |= B_OPTION_KEY;
141 			}
142 
143 			if (modifiers != oldModifiers) {
144 				if (isLock)
145 					update_leds(keyboard->device, modifiers);
146 			}
147 		}
148 
149 		uint8 newDeadKey = 0;
150 		if (activeDeadKey == 0 || !isKeyDown)
151 			newDeadKey = keymap.ActiveDeadKey(keycode, modifiers);
152 
153 		char* string = NULL;
154 		int32 numBytes = 0;
155 		if (newDeadKey == 0 && isKeyDown) {
156 			keymap.GetChars(keycode, modifiers, activeDeadKey, &string,
157 				&numBytes);
158 			if (numBytes > 0)
159 				write(keyboard->target, string, numBytes);
160 
161 			delete[] string;
162 		}
163 
164 		if (newDeadKey == 0) {
165 			if (isKeyDown && !modifiers && activeDeadKey != 0) {
166 				// a dead key was completed
167 				activeDeadKey = 0;
168 			}
169 		} else if (isKeyDown) {
170 			// start of a dead key
171 			activeDeadKey = newDeadKey;
172 		}
173 	}
174 
175 	return 0;
176 }
177 
178 
179 static int32
180 console_writer(void* arg)
181 {
182 	struct console* con = (struct console*)arg;
183 
184 	for (;;) {
185 		char buffer[1024];
186 		ssize_t length = read(con->tty_master_fd, buffer, sizeof(buffer));
187 		if (length < 0)
188 			break;
189 
190 		write(con->console_fd, buffer, length);
191 	}
192 
193 	return 0;
194 }
195 
196 
197 static void
198 stop_keyboards(struct console* con)
199 {
200 	// close devices
201 
202 	for (struct keyboard* keyboard = con->keyboards; keyboard != NULL;
203 			keyboard = keyboard->next) {
204 		close(keyboard->device);
205 	}
206 
207 	// wait for the threads
208 
209 	for (struct keyboard* keyboard = con->keyboards; keyboard != NULL;) {
210 		struct keyboard* next = keyboard->next;
211 		wait_for_thread(keyboard->thread, NULL);
212 
213 		delete keyboard;
214 		keyboard = next;
215 	}
216 
217 	con->keyboards = NULL;
218 }
219 
220 
221 /*!	Opens the all keyboard drivers it finds starting from the given
222 	location \a start that support the debugger extension.
223 */
224 static struct keyboard*
225 open_keyboards(int target, const char* start, struct keyboard* previous)
226 {
227 	// Wait for the directory to appear, if we're loaded early in boot
228 	// it may take a while for it to appear while the drivers load.
229 	DIR* dir;
230 	int32 tries = 0;
231 	while (true) {
232 		dir = opendir(start);
233 		if (dir != NULL)
234 			break;
235 		if(++tries == 10)
236 			return NULL;
237 		sleep(1);
238 	}
239 
240 	struct keyboard* keyboard = previous;
241 
242 	while (true) {
243 		dirent* entry = readdir(dir);
244 		if (entry == NULL)
245 			break;
246 		if (!strcmp(entry->d_name, ".") || !strcmp(entry->d_name, ".."))
247 			continue;
248 
249 		char path[PATH_MAX];
250 		strlcpy(path, start, sizeof(path));
251 		strlcat(path, "/", sizeof(path));
252 		strlcat(path, entry->d_name, sizeof(path));
253 
254 		struct stat stat;
255 		if (::stat(path, &stat) != 0)
256 			continue;
257 
258 		if (S_ISDIR(stat.st_mode)) {
259 			keyboard = open_keyboards(target, path, keyboard);
260 			continue;
261 		}
262 
263 		// Try to open it as a device
264 		int fd = open(path, O_RDONLY);
265 		if (fd >= 0) {
266 			// Turn on debugger mode
267 			if (ioctl(fd, KB_SET_DEBUG_READER, NULL, 0) == 0) {
268 				keyboard = new ::keyboard();
269 				keyboard->device = fd;
270 				keyboard->target = target;
271 				keyboard->thread = spawn_thread(&keyboard_reader, path,
272 					B_URGENT_DISPLAY_PRIORITY, keyboard);
273 				if (keyboard->thread < 0) {
274 					close(fd);
275 					closedir(dir);
276 					delete keyboard;
277 					return NULL;
278 				}
279 
280 				if (previous != NULL)
281 					previous->next = keyboard;
282 
283 				resume_thread(keyboard->thread);
284 			} else
285 				close(fd);
286 		}
287 	}
288 
289 	closedir(dir);
290 	return keyboard;
291 }
292 
293 
294 static int
295 start_console(struct console* con)
296 {
297 	memset(con, 0, sizeof(struct console));
298 	con->console_fd = -1;
299 	con->tty_master_fd = -1;
300 	con->tty_slave_fd = -1;
301 	con->console_writer = -1;
302 
303 	con->console_fd = open("/dev/console", O_WRONLY);
304 	if (con->console_fd < 0)
305 		return -2;
306 
307 	DIR* dir = opendir("/dev/pt");
308 	if (dir != NULL) {
309 		struct dirent* entry;
310 		char name[64];
311 
312 		while ((entry = readdir(dir)) != NULL) {
313 			if (entry->d_name[0] == '.')
314 				continue;
315 
316 			snprintf(name, sizeof(name), "/dev/pt/%s", entry->d_name);
317 
318 			con->tty_master_fd = open(name, O_RDWR);
319 			if (con->tty_master_fd >= 0) {
320 				snprintf(name, sizeof(name), "/dev/tt/%s", entry->d_name);
321 
322 				con->tty_slave_fd = open(name, O_RDWR);
323 				if (con->tty_slave_fd < 0) {
324 					error("Could not open tty %s: %s!\n", name,
325 						strerror(errno));
326 					close(con->tty_master_fd);
327 				} else {
328 					// set default mode
329 					struct termios termios;
330 					struct winsize size;
331 
332 					if (tcgetattr(con->tty_slave_fd, &termios) == 0) {
333 						termios.c_iflag = ICRNL;
334 						termios.c_oflag = OPOST | ONLCR;
335 						termios.c_lflag = ISIG | ICANON | ECHO | ECHOE | ECHONL;
336 
337 						tcsetattr(con->tty_slave_fd, TCSANOW, &termios);
338 					}
339 
340 					if (ioctl(con->console_fd, TIOCGWINSZ, &size,
341 							sizeof(struct winsize)) == 0) {
342 						// we got the window size from the console
343 						ioctl(con->tty_slave_fd, TIOCSWINSZ, &size,
344 							sizeof(struct winsize));
345 					}
346 				}
347 				break;
348 			}
349 		}
350 
351 		setenv("TTY", name, true);
352 	}
353 
354 	if (con->tty_master_fd < 0 || con->tty_slave_fd < 0)
355 		return -3;
356 
357 	con->keyboards
358 		= open_keyboards(con->tty_master_fd, "/dev/input/keyboard", NULL);
359 	if (con->keyboards == NULL)
360 		return -4;
361 
362 	con->console_writer = spawn_thread(&console_writer, "console writer",
363 		B_URGENT_DISPLAY_PRIORITY, con);
364 	if (con->console_writer < 0)
365 		return -5;
366 
367 	resume_thread(con->console_writer);
368 	setenv("TERM", "xterm", true);
369 
370 	return 0;
371 }
372 
373 
374 static void
375 stop_console(struct console* con)
376 {
377 	// close TTY FDs; this will also unblock the threads
378 	close(con->tty_master_fd);
379 	close(con->tty_slave_fd);
380 
381 	// close console and keyboards
382 	close(con->console_fd);
383 	wait_for_thread(con->console_writer, NULL);
384 
385 	stop_keyboards(con);
386 }
387 
388 
389 static pid_t
390 start_process(int argc, const char** argv, struct console* con)
391 {
392 	int savedInput = dup(0);
393 	int savedOutput = dup(1);
394 	int savedError = dup(2);
395 
396 	dup2(con->tty_slave_fd, 0);
397 	dup2(con->tty_slave_fd, 1);
398 	dup2(con->tty_slave_fd, 2);
399 
400 	pid_t pid = load_image(argc, argv, (const char**)environ);
401 	resume_thread(pid);
402 	setpgid(pid, 0);
403 	tcsetpgrp(con->tty_slave_fd, pid);
404 
405 	dup2(savedInput, 0);
406 	dup2(savedOutput, 1);
407 	dup2(savedError, 2);
408 	close(savedInput);
409 	close(savedOutput);
410 	close(savedError);
411 
412 	return pid;
413 }
414 
415 
416 int
417 main(int argc, char** argv)
418 {
419 	// we're a session leader
420 	setsid();
421 
422 	int err = start_console(&gConsole);
423 	if (err < 0) {
424 		error("consoled: error %d starting console.\n", err);
425 		return err;
426 	}
427 
428 	// move our stdin and stdout to the console
429 	dup2(gConsole.tty_slave_fd, 0);
430 	dup2(gConsole.tty_slave_fd, 1);
431 	dup2(gConsole.tty_slave_fd, 2);
432 
433 	if (argc > 1) {
434 		// a command was given: we run it only once
435 
436 		// get the command argument vector
437 		int commandArgc = argc - 1;
438 		const char** commandArgv = new const char*[commandArgc + 1];
439 		for (int i = 0; i < commandArgc; i++)
440 			commandArgv[i] = argv[i + 1];
441 
442 		commandArgv[commandArgc] = NULL;
443 
444 		// start the process
445 		pid_t process = start_process(commandArgc, commandArgv, &gConsole);
446 
447 		status_t returnCode;
448 		wait_for_thread(process, &returnCode);
449 	} else {
450 		// no command given: start a shell in an endless loop
451 		for (;;) {
452 			pid_t shellProcess;
453 			status_t returnCode;
454 			const char* shellArgv[] = { "/bin/sh", "--login", NULL };
455 
456 			shellProcess = start_process(2, shellArgv, &gConsole);
457 
458 			wait_for_thread(shellProcess, &returnCode);
459 
460 			puts("Restart shell");
461 		}
462 	}
463 
464 	stop_console(&gConsole);
465 
466 	return 0;
467 }
468 
469