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