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
init_hardware(void)53 init_hardware(void)
54 {
55 TRACE((DRIVER_NAME ": init_hardware()\n"));
56 return B_OK;
57 }
58
59
60 status_t
init_driver(void)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
uninit_driver(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 **
publish_devices(void)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 *
find_device(const char * name)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
get_tty_index(const char * name)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
get_tty_index(struct tty * tty)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
master_service(struct tty * tty,uint32 op,void * buffer,size_t length)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
slave_service(struct tty * tty,uint32 op,void * buffer,size_t length)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
master_open(const char * name,uint32 flags,void ** _cookie)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
slave_open(const char * name,uint32 flags,void ** _cookie)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
pty_close(void * _cookie)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
pty_free_cookie(void * _cookie)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
pty_ioctl(void * _cookie,uint32 op,void * buffer,size_t length)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
pty_read(void * _cookie,off_t offset,void * buffer,size_t * _length)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
pty_write(void * _cookie,off_t offset,const void * buffer,size_t * _length)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
pty_select(void * _cookie,uint8 event,uint32 ref,selectsync * sync)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
pty_deselect(void * _cookie,uint8 event,selectsync * sync)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