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
19extern "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
36int32 api_version = B_CUR_DRIVER_API_VERSION;
37tty_module_info *gTTYModule = NULL;
38
39struct mutex gGlobalTTYLock;
40
41static const uint32 kNumTTYs = 64;
42char *gDeviceNames[kNumTTYs * 2 + 3];
43	// reserve space for "pt/" and "tt/" entries, "ptmx", "tty",
44	// and the terminating NULL
45
46struct tty* gMasterTTYs[kNumTTYs];
47struct tty* gSlaveTTYs[kNumTTYs];
48
49extern device_hooks gMasterPTYHooks, gSlavePTYHooks;
50
51
52status_t
53init_hardware(void)
54{
55	TRACE((DRIVER_NAME ": init_hardware()\n"));
56	return B_OK;
57}
58
59
60status_t
61init_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
111void
112uninit_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
125const char **
126publish_devices(void)
127{
128	TRACE((DRIVER_NAME ": publish_devices()\n"));
129	return const_cast<const char **>(gDeviceNames);
130}
131
132
133device_hooks *
134find_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
149static int32
150get_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
164static int32
165get_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
181static bool
182master_service(struct tty *tty, uint32 op, void *buffer, size_t length)
183{
184	// nothing here yet
185	return false;
186}
187
188
189static bool
190slave_service(struct tty *tty, uint32 op, void *buffer, size_t length)
191{
192	// nothing here yet
193	return false;
194}
195
196
197static status_t
198master_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
248static status_t
249slave_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
323static status_t
324pty_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
344static status_t
345pty_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
373static status_t
374pty_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
435static status_t
436pty_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
452static status_t
453pty_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
469static status_t
470pty_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
478static status_t
479pty_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
487device_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
500device_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