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