1 /* 2 * CDDL HEADER START 3 * 4 * The contents of this file are subject to the terms of the 5 * Common Development and Distribution License (the "License"). 6 * You may not use this file except in compliance with the License. 7 * 8 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE 9 * or http://www.opensolaris.org/os/licensing. 10 * See the License for the specific language governing permissions 11 * and limitations under the License. 12 * 13 * When distributing Covered Code, include this CDDL HEADER in each 14 * file and include the License file at usr/src/OPENSOLARIS.LICENSE. 15 * If applicable, add the following below this CDDL HEADER, with the 16 * fields enclosed by brackets "[]" replaced with your own identifying 17 * information: Portions Copyright [yyyy] [name of copyright owner] 18 * 19 * CDDL HEADER END 20 */ 21 22 /* 23 * Copyright 2008 Sun Microsystems, Inc. All rights reserved. 24 * Use is subject to license terms. 25 */ 26 27 /* Copyright (c) 1988 AT&T */ 28 /* All Rights Reserved */ 29 30 /* 31 * ttyname(f): return "/dev/X" (where X is a relative pathname 32 * under /dev/), which is the name of the tty character special 33 * file associated with the file descriptor f, or NULL if the 34 * pathname cannot be found. 35 * 36 * Ttyname tries to find the tty file by matching major/minor 37 * device, file system ID, and inode numbers of the file 38 * descriptor parameter to those of files in the /dev/ directory. 39 * 40 * It attempts to find a match on major/minor device numbers, 41 * file system ID, and inode numbers, but failing to match on 42 * all three, settles for just a match on device numbers and 43 * file system ID. 44 * 45 * To achieve higher performance and more flexible functionality, 46 * ttyname first looks for the tty file in the directories specified 47 * in the configuration file /etc/ttysrch. Entries in /etc/ttysrch 48 * may be qualified to specify that a partial match may be acceptable. 49 * This further improves performance by allowing an entry which 50 * matches major/minor and file system ID, but not inode number 51 * without searching the entire /dev tree. If /etc/ttysrch does not 52 * exist, ttyname looks in a default list of directories. If after 53 * looking in the most likely places, ttyname still cannot find the 54 * tty file, it recursively searches thru the rest of the /dev/ 55 * directory. 56 * 57 * In addition to the public interfaces, ttyname() & ttyname_r(), which 58 * do the lookup based on an open file descriptor, 59 * the private interface _ttyname_dev() does the lookup based on a 60 * major/minor device. It follows the same order of lookup rules and 61 * returns similar information, but matches on just the major/minor 62 * device numbers. 63 */ 64 65 #pragma weak _ttyname = ttyname 66 67 #include "lint.h" 68 #include "mtlib.h" 69 #include "libc.h" 70 #include "_libc_gettext.h" 71 #include <sys/sysmacros.h> 72 #include <sys/types.h> 73 #include <dirent.h> 74 #include <fcntl.h> 75 #include <sys/stat.h> 76 #include <stdlib.h> 77 #include <ctype.h> 78 #include <string.h> 79 #include <stdio.h> 80 #include <unistd.h> 81 #include <thread.h> 82 #include <synch.h> 83 #include <errno.h> 84 #include <limits.h> 85 #include <sys/mkdev.h> 86 #include "tsd.h" 87 88 typedef struct entry { 89 char *name; 90 int flags; 91 } entry_t; 92 93 typedef struct special { 94 const char *spcl_name; /* device name */ 95 dev_t spcl_rdev; /* matching major/minor devnum */ 96 dev_t spcl_fsdev; /* devnum of containing FS */ 97 ino_t spcl_inum; /* inum of entry in FS */ 98 } spcl_t; 99 100 static int srch_dir(const entry_t path, int match_mask, int depth, 101 const entry_t skip_dirs[], struct stat64 *fsb); 102 static const entry_t *get_pri_dirs(void); 103 static char *ispts(struct stat64 *fsb, int match_mask); 104 static char *ispty(struct stat64 *fsb, int match_mask); 105 static void itoa(int i, char *ptr); 106 static char *_ttyname_common(struct stat64 *fsp, char *buffer, 107 uint_t match_mask); 108 109 110 #define MAX_DEV_PATH TTYNAME_MAX 111 #define MAX_SRCH_DEPTH 4 112 113 #define MATCH_MM 1 114 #define MATCH_FS 2 115 #define MATCH_INO 4 116 #define MATCH_ALL 7 117 118 #define DEV "/dev" 119 #define TTYSRCH "/etc/ttysrch" 120 #define PTS "/dev/pts" 121 122 static const entry_t dev_dir = 123 { "/dev", MATCH_ALL }; 124 125 static const entry_t def_srch_dirs[] = { /* default search list */ 126 { "/dev/pts", MATCH_ALL }, 127 { "/dev/term", MATCH_ALL }, 128 { "/dev/zcons", MATCH_ALL }, 129 { NULL, 0 } 130 }; 131 132 static char *dir_buf; /* directory buffer for ttysrch body */ 133 static entry_t *dir_vec; /* directory vector for ttysrch ptrs */ 134 static size_t dir_size; /* ttysrch file size */ 135 static timestruc_t dir_mtim; /* ttysrch file modification time */ 136 static char rbuf[MAX_DEV_PATH]; /* perfect match file name */ 137 static char dev_rbuf[MAX_DEV_PATH]; /* partial match file name */ 138 static int dev_flag; /* if set, dev + rdev match was found */ 139 static spcl_t special_case[] = { 140 "/dev/tty", 0, 0, 0, 141 "/dev/console", 0, 0, 0, 142 "/dev/conslog", 0, 0, 0, 143 "/dev/systty", 0, 0, 0, 144 "/dev/wscons", 0, 0, 0, 145 "/dev/msglog", 0, 0, 0, 146 }; 147 #define NUMSPECIAL (sizeof (special_case) / sizeof (spcl_t)) 148 static spcl_t ptmspecial = { 149 "/dev/ptmx", 0, 0, 0 150 }; 151 static dev_t ptcdev = NODEV; 152 static dev_t ptsldev = NODEV; 153 154 char * 155 _ttyname_dev(dev_t rdev, char *buffer, size_t buflen) 156 { 157 struct stat64 fsb; 158 159 fsb.st_rdev = rdev; 160 161 if (buflen < MAX_DEV_PATH) { 162 errno = ERANGE; 163 return (NULL); 164 } 165 166 return (_ttyname_common(&fsb, buffer, MATCH_MM)); 167 } 168 169 /* 170 * POSIX.1c Draft-6 version of the function ttyname_r. 171 * It was implemented by Solaris 2.3. 172 */ 173 char * 174 ttyname_r(int f, char *buffer, int buflen) 175 { 176 struct stat64 fsb; /* what we are searching for */ 177 /* 178 * do we need to search anything at all? (is fildes a char special tty 179 * file?) 180 */ 181 if (fstat64(f, &fsb) < 0) { 182 errno = EBADF; 183 return (0); 184 } 185 if ((isatty(f) == 0) || 186 ((fsb.st_mode & S_IFMT) != S_IFCHR)) { 187 errno = ENOTTY; 188 return (0); 189 } 190 191 if (buflen < MAX_DEV_PATH) { 192 errno = ERANGE; 193 return (0); 194 } 195 196 return (_ttyname_common(&fsb, buffer, MATCH_ALL)); 197 } 198 199 static char * 200 _ttyname_common(struct stat64 *fsp, char *buffer, uint_t match_mask) 201 { 202 struct stat64 tfsb; 203 const entry_t *srch_dirs; /* priority directories */ 204 spcl_t *spclp; 205 int i; 206 int found = 0; 207 int dirno = 0; 208 int is_pts = 0; 209 char *retval = NULL; 210 char *pt = NULL; 211 212 /* 213 * We can't use lmutex_lock() here because we call malloc()/free() 214 * and _libc_gettext(). Use the brute-force callout_lock_enter(). 215 */ 216 callout_lock_enter(); 217 218 /* 219 * match special cases 220 */ 221 222 for (spclp = special_case, i = 0; i < NUMSPECIAL; spclp++, i++) { 223 if ((spclp->spcl_inum | spclp->spcl_fsdev | 224 spclp->spcl_rdev) == 0) { 225 if (stat64(spclp->spcl_name, &tfsb) != 0) 226 continue; 227 spclp->spcl_rdev = tfsb.st_rdev; 228 spclp->spcl_fsdev = tfsb.st_dev; 229 spclp->spcl_inum = tfsb.st_ino; 230 } 231 if (match_mask == MATCH_MM) { 232 if (spclp->spcl_rdev == fsp->st_rdev) { 233 retval = strcpy(rbuf, spclp->spcl_name); 234 goto out; 235 } 236 } else if (spclp->spcl_fsdev == fsp->st_dev && 237 spclp->spcl_rdev == fsp->st_rdev && 238 spclp->spcl_inum == fsp->st_ino) { 239 retval = strcpy(rbuf, spclp->spcl_name); 240 goto out; 241 } 242 } 243 /* 244 * additional special case: ptm clone device 245 * ptm devs have no entries in /dev 246 * if major number matches, just short circuit any further lookup 247 * NOTE: the minor number of /dev/ptmx is the ptm major number 248 */ 249 spclp = &ptmspecial; 250 if ((spclp->spcl_inum | spclp->spcl_fsdev | spclp->spcl_rdev) == 0) { 251 if (stat64(spclp->spcl_name, &tfsb) == 0) { 252 spclp->spcl_rdev = tfsb.st_rdev; 253 spclp->spcl_fsdev = tfsb.st_dev; 254 spclp->spcl_inum = tfsb.st_ino; 255 } 256 } 257 if ((spclp->spcl_rdev != 0) && 258 (minor(spclp->spcl_rdev) == major(fsp->st_rdev))) 259 goto out; 260 261 /* 262 * additional special case: pty dev 263 * one of the known default pairs of /dev/ptyXX or /dev/ttyXX 264 */ 265 if ((retval = ispty(fsp, match_mask)) != NULL) 266 goto out; 267 268 /* 269 * search the priority directories 270 */ 271 272 273 srch_dirs = get_pri_dirs(); 274 dev_flag = 0; 275 276 while ((!found) && (srch_dirs[dirno].name != NULL)) { 277 278 /* 279 * if /dev is one of the priority directories, only 280 * search its top level(set depth = MAX_SEARCH_DEPTH) 281 */ 282 283 /* 284 * Is /dev/pts then just do a quick check. We don't have 285 * to stat the entire /dev/pts dir. 286 */ 287 if (strcmp(PTS, srch_dirs[dirno].name) == NULL) { 288 if ((pt = ispts(fsp, match_mask)) != NULL) { 289 is_pts = 1; 290 found = 1; 291 } 292 } else { 293 found = srch_dir(srch_dirs[dirno], match_mask, 294 ((strcmp(srch_dirs[dirno].name, dev_dir.name) 295 == 0) ? MAX_SRCH_DEPTH : 1), 0, fsp); 296 } 297 dirno++; 298 } 299 300 /* 301 * search the /dev/ directory, skipping priority directories 302 */ 303 if (!found) 304 found = srch_dir(dev_dir, match_mask, 0, srch_dirs, fsp); 305 306 307 /* 308 * return 309 */ 310 311 if (found) { 312 if (is_pts) 313 retval = pt; 314 else 315 retval = rbuf; 316 } else if (dev_flag) 317 retval = dev_rbuf; 318 else 319 retval = NULL; 320 out: retval = (retval ? strcpy(buffer, retval) : NULL); 321 callout_lock_exit(); 322 return (retval); 323 } 324 325 /* 326 * POSIX.1c standard version of the function ttyname_r. 327 * User gets it via static ttyname_r from the header file. 328 */ 329 int 330 __posix_ttyname_r(int fildes, char *name, size_t namesize) 331 { 332 int nerrno = 0; 333 int oerrno = errno; 334 int namelen; 335 336 errno = 0; 337 338 if (namesize > INT_MAX) 339 namelen = INT_MAX; 340 else 341 namelen = (int)namesize; 342 343 if (ttyname_r(fildes, name, namelen) == NULL) { 344 if (errno == 0) 345 nerrno = EINVAL; 346 else 347 nerrno = errno; 348 } 349 errno = oerrno; 350 return (nerrno); 351 } 352 353 /* 354 * Checks if the name is under /dev/pts directory 355 */ 356 static char * 357 ispts(struct stat64 *fsb, int match_mask) 358 { 359 static char buf[MAX_DEV_PATH]; 360 struct stat64 stb; 361 362 (void) strcpy(buf, "/dev/pts/"); 363 itoa(minor(fsb->st_rdev), buf+strlen(buf)); 364 365 if (stat64(buf, &stb) != 0) 366 return (NULL); 367 368 if (match_mask == MATCH_MM) { 369 if (stb.st_rdev == fsb->st_rdev) 370 return (buf); 371 } else if (stb.st_rdev == fsb->st_rdev && stb.st_dev == fsb->st_dev && 372 stb.st_ino == fsb->st_ino) 373 return (buf); 374 375 return (NULL); 376 } 377 378 /* 379 * Checks if the dev is a known pty master or slave device 380 */ 381 #define MAXDEFAULTPTY 48 382 383 static char * 384 ispty(struct stat64 *fsb, int match_mask) 385 { 386 static char buf[16]; /* big enough for "/dev/XtyXX" */ 387 struct stat64 stb; 388 minor_t dmin; 389 char prefix; 390 391 if (ptsldev == NODEV && stat64("/dev/ttyp0", &stb) == 0) 392 ptsldev = stb.st_rdev; 393 394 /* 395 * check for a ptsl dev (/dev/ttyXX) 396 */ 397 prefix = 't'; 398 if (major(fsb->st_rdev) != major(ptsldev)) { 399 /* 400 * not a ptsl, check for a ptc 401 */ 402 if (ptcdev == NODEV && stat64("/dev/ptyp0", &stb) == 0) 403 ptcdev = stb.st_rdev; 404 405 /* 406 * check for a ptc dev (/dev/ptyXX) 407 */ 408 prefix = 'p'; 409 if (major(fsb->st_rdev) != major(ptcdev)) 410 return (NULL); 411 } 412 413 /* 414 * check if minor number is in the known range 415 */ 416 dmin = minor(fsb->st_rdev); 417 if (dmin > MAXDEFAULTPTY) 418 return (NULL); 419 420 /* 421 * modify name based on minor number 422 */ 423 (void) snprintf(buf, sizeof (buf), "/dev/%cty%c%c", 424 prefix, 'p' + dmin / 16, "0123456789abcdef"[dmin % 16]); 425 426 if (stat64(buf, &stb) != 0) 427 return (NULL); 428 429 if (match_mask == MATCH_MM) { 430 if (stb.st_rdev == fsb->st_rdev) 431 return (buf); 432 } else if (stb.st_rdev == fsb->st_rdev && 433 stb.st_dev == fsb->st_dev && 434 stb.st_ino == fsb->st_ino) 435 return (buf); 436 437 return (NULL); 438 } 439 440 441 /* 442 * Converts a number to a string (null terminated). 443 */ 444 static void 445 itoa(int i, char *ptr) 446 { 447 int dig = 0; 448 int tempi; 449 450 tempi = i; 451 do { 452 dig++; 453 tempi /= 10; 454 } while (tempi); 455 456 ptr += dig; 457 *ptr = '\0'; 458 while (--dig >= 0) { 459 *(--ptr) = i % 10 + '0'; 460 i /= 10; 461 } 462 } 463 464 /* 465 * srch_dir() searches directory path and all directories under it up 466 * to depth directories deep for a file described by a stat structure 467 * fsb. It puts the answer into rbuf. If a match is found on device 468 * number only, the file name is put into dev_rbuf and dev_flag is set. 469 * 470 * srch_dir() returns 1 if a match (on device and inode) is found, 471 * or 0 otherwise. 472 * 473 */ 474 475 static int 476 srch_dir(const entry_t path, /* current path */ 477 int match_mask, /* flags mask */ 478 int depth, /* current depth (/dev = 0) */ 479 const entry_t skip_dirs[], /* directories not needing searching */ 480 struct stat64 *fsb) /* the file being searched for */ 481 { 482 DIR *dirp; 483 struct dirent64 *direntp; 484 struct stat64 tsb; 485 char file_name[MAX_DEV_PATH]; 486 entry_t file; 487 char *last_comp; 488 int found = 0; 489 int dirno = 0; 490 size_t path_len; 491 492 file.name = file_name; 493 file.flags = path.flags & match_mask; 494 if (file.flags == 0) 495 file.flags = match_mask; 496 497 /* 498 * do we need to search this directory? (always search /dev at depth 0) 499 */ 500 if ((skip_dirs != NULL) && (depth != 0)) 501 while (skip_dirs[dirno].name != NULL) 502 if (strcmp(skip_dirs[dirno++].name, path.name) == 0) 503 return (0); 504 505 /* 506 * open directory 507 */ 508 if ((dirp = opendir(path.name)) == NULL) { 509 return (0); 510 } 511 512 path_len = strlen(path.name); 513 (void) strcpy(file_name, path.name); 514 last_comp = file_name + path_len; 515 *last_comp++ = '/'; 516 517 /* 518 * read thru the directory 519 */ 520 while ((!found) && ((direntp = readdir64(dirp)) != NULL)) { 521 /* 522 * skip "." and ".." entries, if present 523 */ 524 if (direntp->d_name[0] == '.' && 525 (strcmp(direntp->d_name, ".") == 0 || 526 strcmp(direntp->d_name, "..") == 0)) 527 continue; 528 529 /* 530 * if the file name (path + "/" + d_name + NULL) would be too 531 * long, skip it 532 */ 533 if ((path_len + strlen(direntp->d_name) + 2) > MAX_DEV_PATH) 534 continue; 535 536 (void) strcpy(last_comp, direntp->d_name); 537 if (stat64(file_name, &tsb) < 0) 538 continue; 539 540 /* 541 * skip "/dev/syscon" because it may be an invalid link after 542 * single user mode. 543 */ 544 if (strcmp(file_name, "/dev/syscon") == 0) 545 continue; 546 547 /* 548 * if a file is a directory and we are not too deep, recurse 549 */ 550 if ((tsb.st_mode & S_IFMT) == S_IFDIR) 551 if (depth < MAX_SRCH_DEPTH) 552 found = srch_dir(file, match_mask, depth+1, 553 skip_dirs, fsb); 554 else 555 continue; 556 557 /* 558 * else if it is not a directory, is it a character special 559 * file? 560 */ 561 else if ((tsb.st_mode & S_IFMT) == S_IFCHR) { 562 int flag = 0; 563 if (tsb.st_dev == fsb->st_dev) 564 flag |= MATCH_FS; 565 if (tsb.st_rdev == fsb->st_rdev) 566 flag |= MATCH_MM; 567 if (tsb.st_ino == fsb->st_ino) 568 flag |= MATCH_INO; 569 570 if ((flag & file.flags) == file.flags) { 571 (void) strcpy(rbuf, file.name); 572 found = 1; 573 } else if ((flag & (MATCH_MM | MATCH_FS)) == 574 (MATCH_MM | MATCH_FS)) { 575 576 /* 577 * no (inodes do not match), but save the name 578 * for later 579 */ 580 (void) strcpy(dev_rbuf, file.name); 581 dev_flag = 1; 582 } 583 } 584 } 585 (void) closedir(dirp); 586 return (found); 587 } 588 589 590 /* 591 * get_pri_dirs() - returns a pointer to an array of strings, where each string 592 * is a priority directory name. The end of the array is marked by a NULL 593 * pointer. The priority directories' names are obtained from the file 594 * /etc/ttysrch if it exists and is readable, or if not, a default hard-coded 595 * list of directories. 596 * 597 * /etc/ttysrch, if used, is read in as a string of characters into memory and 598 * then parsed into strings of priority directory names, omitting comments and 599 * blank lines. 600 * 601 */ 602 603 #define START_STATE 1 604 #define COMMENT_STATE 2 605 #define DIRNAME_STATE 3 606 #define FLAG_STATE 4 607 #define CHECK_STATE 5 608 609 #define COMMENT_CHAR '#' 610 #define EOLN_CHAR '\n' 611 612 static const entry_t * 613 get_pri_dirs(void) 614 { 615 int fd, state; 616 size_t sz; 617 ssize_t size; 618 struct stat64 sb; 619 char *buf, *ebuf; 620 entry_t *vec; 621 622 /* 623 * if no /etc/ttysrch, use defaults 624 */ 625 if ((fd = open(TTYSRCH, 0)) < 0) 626 return (def_srch_dirs); 627 628 if (fstat64(fd, &sb) < 0) { 629 (void) close(fd); 630 return (def_srch_dirs); 631 } 632 633 sz = (size_t)sb.st_size; 634 if (dir_vec != NULL && sz == dir_size && 635 sb.st_mtim.tv_sec == dir_mtim.tv_sec && 636 sb.st_mtim.tv_nsec == dir_mtim.tv_nsec) { 637 /* 638 * size & modification time match 639 * no need to reread TTYSRCH 640 * just return old pointer 641 */ 642 (void) close(fd); 643 return (dir_vec); 644 } 645 buf = realloc(dir_buf, sz + 1); 646 if (buf != NULL) { 647 dir_buf = buf; 648 size = read(fd, dir_buf, sz); 649 } 650 (void) close(fd); 651 652 if (buf == NULL || size < 0) { 653 if (dir_vec != NULL) { 654 free(dir_vec); 655 dir_vec = NULL; 656 } 657 return ((entry_t *)def_srch_dirs); 658 } 659 dir_size = sz; 660 dir_mtim = sb.st_mtim; 661 662 /* 663 * ensure newline termination for buffer. Add an extra 664 * entry to dir_vec for null terminator 665 */ 666 ebuf = &dir_buf[size]; 667 *ebuf++ = '\n'; 668 for (sz = 1, buf = dir_buf; buf < ebuf; ++buf) 669 if (*buf == '\n') 670 ++sz; 671 672 sz *= sizeof (*dir_vec); 673 vec = realloc(dir_vec, sz); 674 if (vec == NULL) { 675 if (dir_vec != NULL) { 676 free(dir_vec); 677 dir_vec = NULL; 678 } 679 return (def_srch_dirs); 680 } 681 dir_vec = vec; 682 state = START_STATE; 683 for (buf = dir_buf; buf < ebuf; ++buf) { 684 switch (state) { 685 686 case START_STATE: 687 if (*buf == COMMENT_CHAR) { 688 state = COMMENT_STATE; 689 break; 690 } 691 if (!isspace(*buf)) /* skip leading white space */ 692 state = DIRNAME_STATE; 693 vec->name = buf; 694 vec->flags = 0; 695 break; 696 697 case COMMENT_STATE: 698 if (*buf == EOLN_CHAR) 699 state = START_STATE; 700 break; 701 702 case DIRNAME_STATE: 703 if (*buf == EOLN_CHAR) { 704 state = CHECK_STATE; 705 *buf = '\0'; 706 } else if (isspace(*buf)) { 707 /* skip trailing white space */ 708 state = FLAG_STATE; 709 *buf = '\0'; 710 } 711 break; 712 713 case FLAG_STATE: 714 switch (*buf) { 715 case 'M': 716 vec->flags |= MATCH_MM; 717 break; 718 case 'F': 719 vec->flags |= MATCH_FS; 720 break; 721 case 'I': 722 vec->flags |= MATCH_INO; 723 break; 724 case EOLN_CHAR: 725 state = CHECK_STATE; 726 break; 727 } 728 break; 729 730 case CHECK_STATE: 731 if (strncmp(vec->name, DEV, strlen(DEV)) != 0) { 732 int tfd = open("/dev/console", O_WRONLY); 733 if (tfd >= 0) { 734 char buf[256]; 735 (void) snprintf(buf, sizeof (buf), 736 _libc_gettext( 737 "ERROR: Entry '%s' in /etc/ttysrch ignored.\n"), vec->name); 738 (void) write(tfd, buf, strlen(buf)); 739 (void) close(tfd); 740 } 741 } else { 742 char *slash; 743 slash = vec->name + strlen(vec->name) - 1; 744 while (*slash == '/') 745 *slash-- = '\0'; 746 if (vec->flags == 0) 747 vec->flags = MATCH_ALL; 748 vec++; 749 } 750 state = START_STATE; 751 /* 752 * This state does not consume a character, so 753 * reposition the pointer. 754 */ 755 buf--; 756 break; 757 758 } 759 } 760 vec->name = NULL; 761 return (dir_vec); 762 } 763 764 765 char * 766 ttyname(int f) 767 { 768 char *ans = tsdalloc(_T_TTYNAME, MAX_DEV_PATH, NULL); 769 770 if (ans == NULL) 771 return (NULL); 772 return (ttyname_r(f, ans, MAX_DEV_PATH)); 773 } 774