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 || gMasterTTYs[index]->ref_count == 0) 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 gTTYModule->tty_close_cookie(cookie); 333 334 return B_OK; 335 } 336 337 338 static status_t 339 pty_free_cookie(void *_cookie) 340 { 341 // The TTY is already closed. We only have to free the cookie. 342 tty_cookie *cookie = (tty_cookie *)_cookie; 343 344 gTTYModule->tty_destroy_cookie(cookie); 345 346 return B_OK; 347 } 348 349 350 static status_t 351 pty_ioctl(void *_cookie, uint32 op, void *buffer, size_t length) 352 { 353 tty_cookie *cookie = (tty_cookie *)_cookie; 354 355 struct tty* tty = cookie->tty; 356 RecursiveLocker locker(tty->lock); 357 358 TRACE(("pty_ioctl: cookie %p, op %" B_PRIu32 ", buffer %p, length %lu" 359 "\n", _cookie, op, buffer, length)); 360 361 switch (op) { 362 case B_IOCTL_GET_TTY_INDEX: 363 { 364 int32 ptyIndex = get_tty_index(cookie->tty); 365 if (ptyIndex < 0) 366 return B_BAD_VALUE; 367 368 if (user_memcpy(buffer, &ptyIndex, sizeof(int32)) < B_OK) 369 return B_BAD_ADDRESS; 370 371 return B_OK; 372 } 373 374 case B_IOCTL_GRANT_TTY: 375 { 376 if (!cookie->tty->is_master) 377 return B_BAD_VALUE; 378 379 int32 ptyIndex = get_tty_index(cookie->tty); 380 if (ptyIndex < 0) 381 return B_BAD_VALUE; 382 383 // get slave path 384 char path[64]; 385 snprintf(path, sizeof(path), "/dev/%s", 386 gDeviceNames[kNumTTYs + ptyIndex]); 387 388 // set owner and permissions respectively 389 if (chown(path, getuid(), getgid()) != 0 390 || chmod(path, S_IRUSR | S_IWUSR | S_IWGRP) != 0) { 391 return errno; 392 } 393 394 return B_OK; 395 } 396 397 case 'pgid': // BeOS 398 op = TIOCSPGRP; 399 400 case 'wsiz': // BeOS 401 op = TIOCSWINSZ; 402 break; 403 404 default: 405 break; 406 } 407 408 return gTTYModule->tty_control(cookie, op, buffer, length); 409 } 410 411 412 static status_t 413 pty_read(void *_cookie, off_t offset, void *buffer, size_t *_length) 414 { 415 tty_cookie *cookie = (tty_cookie *)_cookie; 416 417 TRACE(("pty_read: cookie %p, offset %" B_PRIdOFF ", buffer %p, length " 418 "%lu\n", _cookie, offset, buffer, *_length)); 419 420 status_t result = gTTYModule->tty_read(cookie, buffer, _length); 421 422 TRACE(("pty_read done: cookie %p, result: %" B_PRIx32 ", length %lu\n", 423 _cookie, result, *_length)); 424 425 return result; 426 } 427 428 429 static status_t 430 pty_write(void *_cookie, off_t offset, const void *buffer, size_t *_length) 431 { 432 tty_cookie *cookie = (tty_cookie *)_cookie; 433 434 TRACE(("pty_write: cookie %p, offset %" B_PRIdOFF ", buffer %p, length " 435 "%lu\n", _cookie, offset, buffer, *_length)); 436 437 status_t result = gTTYModule->tty_write(cookie, buffer, _length); 438 439 TRACE(("pty_write done: cookie %p, result: %" B_PRIx32 ", length %lu\n", 440 _cookie, result, *_length)); 441 442 return result; 443 } 444 445 446 static status_t 447 pty_select(void *_cookie, uint8 event, uint32 ref, selectsync *sync) 448 { 449 tty_cookie *cookie = (tty_cookie *)_cookie; 450 451 return gTTYModule->tty_select(cookie, event, ref, sync); 452 } 453 454 455 static status_t 456 pty_deselect(void *_cookie, uint8 event, selectsync *sync) 457 { 458 tty_cookie *cookie = (tty_cookie *)_cookie; 459 460 return gTTYModule->tty_deselect(cookie, event, sync); 461 } 462 463 464 device_hooks gMasterPTYHooks = { 465 &master_open, 466 &pty_close, 467 &pty_free_cookie, 468 &pty_ioctl, 469 &pty_read, 470 &pty_write, 471 &pty_select, 472 &pty_deselect, 473 NULL, // read_pages() 474 NULL // write_pages() 475 }; 476 477 device_hooks gSlavePTYHooks = { 478 &slave_open, 479 &pty_close, 480 &pty_free_cookie, 481 &pty_ioctl, 482 &pty_read, 483 &pty_write, 484 &pty_select, 485 &pty_deselect, 486 NULL, // read_pages() 487 NULL // write_pages() 488 }; 489