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