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