xref: /haiku/src/add-ons/kernel/drivers/pty/driver.cpp (revision e1c4049fed1047bdb957b0529e1921e97ef94770)
1 /*
2  * Copyright 2004, Axel Dörfler, axeld@pinc-software.de. All rights reserved.
3  * Copyright 2023, Haiku, Inc. All rights reserved.
4  * Distributed under the terms of the MIT License.
5  */
6 
7 #include <new>
8 
9 #include <stdio.h>
10 #include <stdlib.h>
11 #include <string.h>
12 #include <errno.h>
13 
14 #include <util/AutoLock.h>
15 #include <Drivers.h>
16 
17 #include <team.h>
18 
19 extern "C" {
20 #include <drivers/tty.h>
21 #include <tty_module.h>
22 }
23 #include "tty_private.h"
24 
25 
26 //#define PTY_TRACE
27 #ifdef PTY_TRACE
28 #	define TRACE(x) dprintf x
29 #else
30 #	define TRACE(x)
31 #endif
32 
33 #define DRIVER_NAME "pty"
34 
35 
36 int32 api_version = B_CUR_DRIVER_API_VERSION;
37 tty_module_info *gTTYModule = NULL;
38 
39 struct mutex gGlobalTTYLock;
40 
41 static const uint32 kNumTTYs = 64;
42 char *gDeviceNames[kNumTTYs * 2 + 3];
43 	// reserve space for "pt/" and "tt/" entries, "ptmx", "tty",
44 	// and the terminating NULL
45 
46 struct tty* gMasterTTYs[kNumTTYs];
47 struct tty* gSlaveTTYs[kNumTTYs];
48 
49 extern device_hooks gMasterPTYHooks, gSlavePTYHooks;
50 
51 
52 status_t
53 init_hardware(void)
54 {
55 	TRACE((DRIVER_NAME ": init_hardware()\n"));
56 	return B_OK;
57 }
58 
59 
60 status_t
61 init_driver(void)
62 {
63 	status_t status = get_module(B_TTY_MODULE_NAME, (module_info **)&gTTYModule);
64 	if (status < B_OK)
65 		return status;
66 
67 	TRACE((DRIVER_NAME ": init_driver()\n"));
68 
69 	mutex_init(&gGlobalTTYLock, "tty global");
70 	memset(gDeviceNames, 0, sizeof(gDeviceNames));
71 	memset(gMasterTTYs, 0, sizeof(gMasterTTYs));
72 	memset(gSlaveTTYs, 0, sizeof(gSlaveTTYs));
73 
74 	// create driver name array
75 
76 	char letter = 'p';
77 	int8 digit = 0;
78 
79 	for (uint32 i = 0; i < kNumTTYs; i++) {
80 		// For compatibility, we have to create the same mess in /dev/pt and
81 		// /dev/tt as BeOS does: we publish devices p0, p1, ..., pf, r1, ...,
82 		// sf. It would be nice if we could drop compatibility and create
83 		// something better. In fact we already don't need the master devices
84 		// anymore, since "/dev/ptmx" does the job. The slaves entries could
85 		// be published on the fly when a master is opened (e.g via
86 		// vfs_create_special_node()).
87 		char buffer[64];
88 
89 		snprintf(buffer, sizeof(buffer), "pt/%c%x", letter, digit);
90 		gDeviceNames[i] = strdup(buffer);
91 
92 		snprintf(buffer, sizeof(buffer), "tt/%c%x", letter, digit);
93 		gDeviceNames[i + kNumTTYs] = strdup(buffer);
94 
95 		if (++digit > 15)
96 			digit = 0, letter++;
97 
98 		if (!gDeviceNames[i] || !gDeviceNames[i + kNumTTYs]) {
99 			uninit_driver();
100 			return B_NO_MEMORY;
101 		}
102 	}
103 
104 	gDeviceNames[2 * kNumTTYs] = (char *)"ptmx";
105 	gDeviceNames[2 * kNumTTYs + 1] = (char *)"tty";
106 
107 	return B_OK;
108 }
109 
110 
111 void
112 uninit_driver(void)
113 {
114 	TRACE((DRIVER_NAME ": uninit_driver()\n"));
115 
116 	for (int32 i = 0; i < (int32)kNumTTYs * 2; i++)
117 		free(gDeviceNames[i]);
118 
119 	mutex_destroy(&gGlobalTTYLock);
120 
121 	put_module(B_TTY_MODULE_NAME);
122 }
123 
124 
125 const char **
126 publish_devices(void)
127 {
128 	TRACE((DRIVER_NAME ": publish_devices()\n"));
129 	return const_cast<const char **>(gDeviceNames);
130 }
131 
132 
133 device_hooks *
134 find_device(const char *name)
135 {
136 	TRACE((DRIVER_NAME ": find_device(\"%s\")\n", name));
137 
138 	for (uint32 i = 0; gDeviceNames[i] != NULL; i++) {
139 		if (!strcmp(name, gDeviceNames[i])) {
140 			return i < kNumTTYs || i == (2 * kNumTTYs)
141 				? &gMasterPTYHooks : &gSlavePTYHooks;
142 		}
143 	}
144 
145 	return NULL;
146 }
147 
148 
149 static int32
150 get_tty_index(const char *name)
151 {
152 	// device names follow this form: "pt/%c%x"
153 	int8 digit = name[4];
154 	if (digit >= 'a') {
155 		// hexadecimal digits
156 		digit -= 'a' - 10;
157 	} else
158 		digit -= '0';
159 
160 	return (name[3] - 'p') * 16 + digit;
161 }
162 
163 
164 static int32
165 get_tty_index(struct tty *tty)
166 {
167 	int32 index = -1;
168 	for (uint32 i = 0; i < kNumTTYs; i++) {
169 		if (tty == gMasterTTYs[i] || tty == gSlaveTTYs[i]) {
170 			index = i;
171 			break;
172 		}
173 	}
174 	return index;
175 }
176 
177 
178 //	#pragma mark - device hooks
179 
180 
181 static bool
182 master_service(struct tty *tty, uint32 op, void *buffer, size_t length)
183 {
184 	// nothing here yet
185 	return false;
186 }
187 
188 
189 static bool
190 slave_service(struct tty *tty, uint32 op, void *buffer, size_t length)
191 {
192 	// nothing here yet
193 	return false;
194 }
195 
196 
197 static status_t
198 master_open(const char *name, uint32 flags, void **_cookie)
199 {
200 	bool findUnusedTTY = strcmp(name, "ptmx") == 0;
201 
202 	int32 index = -1;
203 	if (!findUnusedTTY) {
204 		index = get_tty_index(name);
205 		if (index >= (int32)kNumTTYs)
206 			return B_ERROR;
207 	}
208 
209 	TRACE(("pty_open: TTY index = %" B_PRId32 " (name = %s)\n", index, name));
210 
211 	MutexLocker globalLocker(gGlobalTTYLock);
212 
213 	if (findUnusedTTY) {
214 		for (index = 0; index < (int32)kNumTTYs; index++) {
215 			if (gMasterTTYs[index] == NULL)
216 				break;
217 		}
218 		if (index >= (int32)kNumTTYs)
219 			return ENOENT;
220 	} else if (gMasterTTYs[index] != NULL && gMasterTTYs[index]->ref_count != 0) {
221 		// we're already open!
222 		return B_BUSY;
223 	}
224 
225 	status_t status = B_OK;
226 
227 	if (gMasterTTYs[index] == NULL) {
228 		status = gTTYModule->tty_create(master_service, NULL, &gMasterTTYs[index]);
229 		if (status != B_OK)
230 			return status;
231 	}
232 	if (gSlaveTTYs[index] == NULL) {
233 		status = gTTYModule->tty_create(slave_service, gMasterTTYs[index], &gSlaveTTYs[index]);
234 		if (status != B_OK)
235 			return status;
236 	}
237 
238 	tty_cookie *cookie;
239 	status = gTTYModule->tty_create_cookie(gMasterTTYs[index], gSlaveTTYs[index], flags, &cookie);
240 	if (status != B_OK)
241 		return status;
242 
243 	*_cookie = cookie;
244 	return B_OK;
245 }
246 
247 
248 static status_t
249 slave_open(const char *name, uint32 flags, void **_cookie)
250 {
251 	// Get the tty index: Opening "/dev/tty" means opening the process'
252 	// controlling tty.
253 	int32 index = get_tty_index(name);
254 	if (strcmp(name, "tty") == 0) {
255 		struct tty *controllingTTY = (struct tty *)team_get_controlling_tty();
256 		if (controllingTTY == NULL)
257 			return B_NOT_ALLOWED;
258 
259 		index = get_tty_index(controllingTTY);
260 		if (index < 0)
261 			return B_NOT_ALLOWED;
262 	} else {
263 		index = get_tty_index(name);
264 		if (index >= (int32)kNumTTYs)
265 			return B_ERROR;
266 	}
267 
268 	TRACE(("slave_open: TTY index = %" B_PRId32 " (name = %s)\n", index,
269 		name));
270 
271 	MutexLocker globalLocker(gGlobalTTYLock);
272 
273 	// we may only be used if our master has already been opened
274 	if (gMasterTTYs[index] == NULL || gMasterTTYs[index]->open_count == 0
275 			|| gSlaveTTYs[index] == NULL) {
276 		return B_IO_ERROR;
277 	}
278 
279 	bool makeControllingTTY = (flags & O_NOCTTY) == 0;
280 	pid_t processID = getpid();
281 	pid_t sessionID = getsid(processID);
282 
283 	if (gSlaveTTYs[index]->open_count == 0) {
284 		// We only allow session leaders to open the tty initially.
285 		if (makeControllingTTY && processID != sessionID)
286 			return B_NOT_ALLOWED;
287 	} else if (makeControllingTTY) {
288 		// If already open, we allow only processes from the same session
289 		// to open the tty again while becoming controlling tty
290 		pid_t ttySession = gSlaveTTYs[index]->settings->session_id;
291 		if (ttySession >= 0) {
292 			makeControllingTTY = false;
293 		} else {
294 			// The tty is not associated with a session yet. The process needs
295 			// to be a session leader.
296 			if (makeControllingTTY && processID != sessionID)
297 				return B_NOT_ALLOWED;
298 		}
299 	}
300 
301 	if (gSlaveTTYs[index]->open_count == 0) {
302 		gSlaveTTYs[index]->settings->session_id = -1;
303 		gSlaveTTYs[index]->settings->pgrp_id = -1;
304 	}
305 
306 	tty_cookie *cookie;
307 	status_t status = gTTYModule->tty_create_cookie(gSlaveTTYs[index], gMasterTTYs[index], flags,
308 		&cookie);
309 	if (status != B_OK)
310 		return status;
311 
312 	if (makeControllingTTY) {
313 		gSlaveTTYs[index]->settings->session_id = sessionID;
314 		gSlaveTTYs[index]->settings->pgrp_id = sessionID;
315 		team_set_controlling_tty(gSlaveTTYs[index]);
316 	}
317 
318 	*_cookie = cookie;
319 	return B_OK;
320 }
321 
322 
323 static status_t
324 pty_close(void *_cookie)
325 {
326 	tty_cookie *cookie = (tty_cookie *)_cookie;
327 
328 	TRACE(("pty_close: cookie %p\n", _cookie));
329 
330 	MutexLocker globalLocker(gGlobalTTYLock);
331 
332 	if (cookie->tty->is_master) {
333 		// close all connected slave cookies first
334 		while (tty_cookie *slave = cookie->other_tty->cookies.Head())
335 			gTTYModule->tty_close_cookie(slave);
336 	}
337 
338 	gTTYModule->tty_close_cookie(cookie);
339 
340 	return B_OK;
341 }
342 
343 
344 static status_t
345 pty_free_cookie(void *_cookie)
346 {
347 	// The TTY is already closed. We only have to free the cookie.
348 	tty_cookie *cookie = (tty_cookie *)_cookie;
349 	struct tty *tty = cookie->tty;
350 
351 	MutexLocker globalLocker(gGlobalTTYLock);
352 
353 	gTTYModule->tty_destroy_cookie(cookie);
354 
355 	if (tty->ref_count == 0) {
356 		// We need to destroy both master and slave TTYs at the same time,
357 		// and in the proper order.
358 		int32 index = get_tty_index(tty);
359 		if (index < 0)
360 			return B_OK;
361 
362 		if (gMasterTTYs[index]->ref_count == 0 && gSlaveTTYs[index]->ref_count == 0) {
363 			gTTYModule->tty_destroy(gSlaveTTYs[index]);
364 			gTTYModule->tty_destroy(gMasterTTYs[index]);
365 			gMasterTTYs[index] = gSlaveTTYs[index] = NULL;
366 		}
367 	}
368 
369 	return B_OK;
370 }
371 
372 
373 static status_t
374 pty_ioctl(void *_cookie, uint32 op, void *buffer, size_t length)
375 {
376 	tty_cookie *cookie = (tty_cookie *)_cookie;
377 
378 	struct tty* tty = cookie->tty;
379 	RecursiveLocker locker(tty->lock);
380 
381 	TRACE(("pty_ioctl: cookie %p, op %" B_PRIu32 ", buffer %p, length %lu"
382 		"\n", _cookie, op, buffer, length));
383 
384 	switch (op) {
385 		case B_IOCTL_GET_TTY_INDEX:
386 		{
387 			int32 ptyIndex = get_tty_index(cookie->tty);
388 			if (ptyIndex < 0)
389 				return B_BAD_VALUE;
390 
391 			if (user_memcpy(buffer, &ptyIndex, sizeof(int32)) < B_OK)
392 				return B_BAD_ADDRESS;
393 
394 			return B_OK;
395 		}
396 
397 		case B_IOCTL_GRANT_TTY:
398 		{
399 			if (!cookie->tty->is_master)
400 				return B_BAD_VALUE;
401 
402 			int32 ptyIndex = get_tty_index(cookie->tty);
403 			if (ptyIndex < 0)
404 				return B_BAD_VALUE;
405 
406 			// get slave path
407 			char path[64];
408 			snprintf(path, sizeof(path), "/dev/%s",
409 				gDeviceNames[kNumTTYs + ptyIndex]);
410 
411 			// set owner and permissions respectively
412 			if (chown(path, getuid(), getgid()) != 0
413 				|| chmod(path, S_IRUSR | S_IWUSR | S_IWGRP) != 0) {
414 				return errno;
415 			}
416 
417 			return B_OK;
418 		}
419 
420 		case 'pgid':				// BeOS
421 			op = TIOCSPGRP;
422 
423 		case 'wsiz':				// BeOS
424 			op = TIOCSWINSZ;
425 			break;
426 
427 		default:
428 			break;
429 	}
430 
431 	return gTTYModule->tty_control(cookie, op, buffer, length);
432 }
433 
434 
435 static status_t
436 pty_read(void *_cookie, off_t offset, void *buffer, size_t *_length)
437 {
438 	tty_cookie *cookie = (tty_cookie *)_cookie;
439 
440 	TRACE(("pty_read: cookie %p, offset %" B_PRIdOFF ", buffer %p, length "
441 		"%lu\n", _cookie, offset, buffer, *_length));
442 
443 	status_t result = gTTYModule->tty_read(cookie, buffer, _length);
444 
445 	TRACE(("pty_read done: cookie %p, result: %" B_PRIx32 ", length %lu\n",
446 		_cookie, result, *_length));
447 
448 	return result;
449 }
450 
451 
452 static status_t
453 pty_write(void *_cookie, off_t offset, const void *buffer, size_t *_length)
454 {
455 	tty_cookie *cookie = (tty_cookie *)_cookie;
456 
457 	TRACE(("pty_write: cookie %p, offset %" B_PRIdOFF ", buffer %p, length "
458 		"%lu\n", _cookie, offset, buffer, *_length));
459 
460 	status_t result = gTTYModule->tty_write(cookie, buffer, _length);
461 
462 	TRACE(("pty_write done: cookie %p, result: %" B_PRIx32 ", length %lu\n",
463 		_cookie, result, *_length));
464 
465 	return result;
466 }
467 
468 
469 static status_t
470 pty_select(void *_cookie, uint8 event, uint32 ref, selectsync *sync)
471 {
472 	tty_cookie *cookie = (tty_cookie *)_cookie;
473 
474 	return gTTYModule->tty_select(cookie, event, ref, sync);
475 }
476 
477 
478 static status_t
479 pty_deselect(void *_cookie, uint8 event, selectsync *sync)
480 {
481 	tty_cookie *cookie = (tty_cookie *)_cookie;
482 
483 	return gTTYModule->tty_deselect(cookie, event, sync);
484 }
485 
486 
487 device_hooks gMasterPTYHooks = {
488 	&master_open,
489 	&pty_close,
490 	&pty_free_cookie,
491 	&pty_ioctl,
492 	&pty_read,
493 	&pty_write,
494 	&pty_select,
495 	&pty_deselect,
496 	NULL,	// read_pages()
497 	NULL	// write_pages()
498 };
499 
500 device_hooks gSlavePTYHooks = {
501 	&slave_open,
502 	&pty_close,
503 	&pty_free_cookie,
504 	&pty_ioctl,
505 	&pty_read,
506 	&pty_write,
507 	&pty_select,
508 	&pty_deselect,
509 	NULL,	// read_pages()
510 	NULL	// write_pages()
511 };
512