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.
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.
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]
23 * Copyright 2008 Sun Microsystems, Inc. All rights reserved.
24 * Use is subject to license terms.
27 /* Copyright (c) 1988 AT&T */
28 /* All Rights Reserved */
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.
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.
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
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/
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
65 #pragma weak _ttyname = ttyname
70 #include "_libc_gettext.h"
71 #include <sys/sysmacros.h>
72 #include <sys/types.h>
85 #include <sys/mkdev.h>
88 typedef struct entry
{
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 */
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
,
110 #define MAX_DEV_PATH TTYNAME_MAX
111 #define MAX_SRCH_DEPTH 4
119 #define TTYSRCH "/etc/ttysrch"
120 #define PTS "/dev/pts"
122 static const entry_t dev_dir
=
123 { "/dev", MATCH_ALL
};
125 static const entry_t def_srch_dirs
[] = { /* default search list */
126 { "/dev/pts", MATCH_ALL
},
127 { "/dev/vt", MATCH_ALL
},
128 { "/dev/term", MATCH_ALL
},
129 { "/dev/zcons", MATCH_ALL
},
133 static char *dir_buf
; /* directory buffer for ttysrch body */
134 static entry_t
*dir_vec
; /* directory vector for ttysrch ptrs */
135 static size_t dir_size
; /* ttysrch file size */
136 static timestruc_t dir_mtim
; /* ttysrch file modification time */
137 static char rbuf
[MAX_DEV_PATH
]; /* perfect match file name */
138 static char dev_rbuf
[MAX_DEV_PATH
]; /* partial match file name */
139 static int dev_flag
; /* if set, dev + rdev match was found */
140 static spcl_t special_case
[] = {
142 "/dev/console", 0, 0, 0,
143 "/dev/conslog", 0, 0, 0,
144 "/dev/systty", 0, 0, 0,
145 "/dev/wscons", 0, 0, 0,
146 "/dev/msglog", 0, 0, 0,
148 #define NUMSPECIAL (sizeof (special_case) / sizeof (spcl_t))
149 static spcl_t ptmspecial
= {
152 static dev_t ptcdev
= NODEV
;
153 static dev_t ptsldev
= NODEV
;
156 _ttyname_dev(dev_t rdev
, char *buffer
, size_t buflen
)
162 if (buflen
< MAX_DEV_PATH
) {
167 return (_ttyname_common(&fsb
, buffer
, MATCH_MM
));
171 * POSIX.1c Draft-6 version of the function ttyname_r.
172 * It was implemented by Solaris 2.3.
175 ttyname_r(int f
, char *buffer
, int buflen
)
177 struct stat64 fsb
; /* what we are searching for */
179 * do we need to search anything at all? (is fildes a char special tty
182 if (fstat64(f
, &fsb
) < 0) {
186 if ((isatty(f
) == 0) ||
187 ((fsb
.st_mode
& S_IFMT
) != S_IFCHR
)) {
192 if (buflen
< MAX_DEV_PATH
) {
197 return (_ttyname_common(&fsb
, buffer
, MATCH_ALL
));
201 _ttyname_common(struct stat64
*fsp
, char *buffer
, uint_t match_mask
)
204 const entry_t
*srch_dirs
; /* priority directories */
214 * We can't use lmutex_lock() here because we call malloc()/free()
215 * and _libc_gettext(). Use the brute-force callout_lock_enter().
217 callout_lock_enter();
220 * match special cases
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)
228 spclp
->spcl_rdev
= tfsb
.st_rdev
;
229 spclp
->spcl_fsdev
= tfsb
.st_dev
;
230 spclp
->spcl_inum
= tfsb
.st_ino
;
232 if (match_mask
== MATCH_MM
) {
233 if (spclp
->spcl_rdev
== fsp
->st_rdev
) {
234 retval
= strcpy(rbuf
, spclp
->spcl_name
);
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
);
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
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
;
258 if ((spclp
->spcl_rdev
!= 0) &&
259 (minor(spclp
->spcl_rdev
) == major(fsp
->st_rdev
)))
263 * additional special case: pty dev
264 * one of the known default pairs of /dev/ptyXX or /dev/ttyXX
266 if ((retval
= ispty(fsp
, match_mask
)) != NULL
)
270 * search the priority directories
274 srch_dirs
= get_pri_dirs();
277 while ((!found
) && (srch_dirs
[dirno
].name
!= NULL
)) {
280 * if /dev is one of the priority directories, only
281 * search its top level(set depth = MAX_SEARCH_DEPTH)
285 * Is /dev/pts then just do a quick check. We don't have
286 * to stat the entire /dev/pts dir.
288 if (strcmp(PTS
, srch_dirs
[dirno
].name
) == 0) {
289 if ((pt
= ispts(fsp
, match_mask
)) != NULL
) {
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
);
302 * search the /dev/ directory, skipping priority directories
305 found
= srch_dir(dev_dir
, match_mask
, 0, srch_dirs
, fsp
);
321 out
: retval
= (retval
? strcpy(buffer
, retval
) : NULL
);
327 * POSIX.1c standard version of the function ttyname_r.
328 * User gets it via static ttyname_r from the header file.
331 __posix_ttyname_r(int fildes
, char *name
, size_t namesize
)
339 if (namesize
> INT_MAX
)
342 namelen
= (int)namesize
;
344 if (ttyname_r(fildes
, name
, namelen
) == NULL
) {
355 * Checks if the name is under /dev/pts directory
358 ispts(struct stat64
*fsb
, int match_mask
)
360 static char buf
[MAX_DEV_PATH
];
363 (void) strcpy(buf
, "/dev/pts/");
364 itoa(minor(fsb
->st_rdev
), buf
+strlen(buf
));
366 if (stat64(buf
, &stb
) != 0)
369 if (match_mask
== MATCH_MM
) {
370 if (stb
.st_rdev
== fsb
->st_rdev
)
372 } else if (stb
.st_rdev
== fsb
->st_rdev
&& stb
.st_dev
== fsb
->st_dev
&&
373 stb
.st_ino
== fsb
->st_ino
)
380 * Checks if the dev is a known pty master or slave device
382 #define MAXDEFAULTPTY 48
385 ispty(struct stat64
*fsb
, int match_mask
)
387 static char buf
[16]; /* big enough for "/dev/XtyXX" */
392 if (ptsldev
== NODEV
&& stat64("/dev/ttyp0", &stb
) == 0)
393 ptsldev
= stb
.st_rdev
;
396 * check for a ptsl dev (/dev/ttyXX)
399 if (major(fsb
->st_rdev
) != major(ptsldev
)) {
401 * not a ptsl, check for a ptc
403 if (ptcdev
== NODEV
&& stat64("/dev/ptyp0", &stb
) == 0)
404 ptcdev
= stb
.st_rdev
;
407 * check for a ptc dev (/dev/ptyXX)
410 if (major(fsb
->st_rdev
) != major(ptcdev
))
415 * check if minor number is in the known range
417 dmin
= minor(fsb
->st_rdev
);
418 if (dmin
> MAXDEFAULTPTY
)
422 * modify name based on minor number
424 (void) snprintf(buf
, sizeof (buf
), "/dev/%cty%c%c",
425 prefix
, 'p' + dmin
/ 16, "0123456789abcdef"[dmin
% 16]);
427 if (stat64(buf
, &stb
) != 0)
430 if (match_mask
== MATCH_MM
) {
431 if (stb
.st_rdev
== fsb
->st_rdev
)
433 } else if (stb
.st_rdev
== fsb
->st_rdev
&&
434 stb
.st_dev
== fsb
->st_dev
&&
435 stb
.st_ino
== fsb
->st_ino
)
443 * Converts a number to a string (null terminated).
446 itoa(int i
, char *ptr
)
460 *(--ptr
) = i
% 10 + '0';
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.
471 * srch_dir() returns 1 if a match (on device and inode) is found,
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 */
484 struct dirent64
*direntp
;
486 char file_name
[MAX_DEV_PATH
];
493 file
.name
= file_name
;
494 file
.flags
= path
.flags
& match_mask
;
496 file
.flags
= match_mask
;
499 * do we need to search this directory? (always search /dev at depth 0)
501 if ((skip_dirs
!= NULL
) && (depth
!= 0))
502 while (skip_dirs
[dirno
].name
!= NULL
)
503 if (strcmp(skip_dirs
[dirno
++].name
, path
.name
) == 0)
509 if ((dirp
= opendir(path
.name
)) == NULL
) {
513 path_len
= strlen(path
.name
);
514 (void) strcpy(file_name
, path
.name
);
515 last_comp
= file_name
+ path_len
;
519 * read thru the directory
521 while ((!found
) && ((direntp
= readdir64(dirp
)) != NULL
)) {
523 * skip "." and ".." entries, if present
525 if (direntp
->d_name
[0] == '.' &&
526 (strcmp(direntp
->d_name
, ".") == 0 ||
527 strcmp(direntp
->d_name
, "..") == 0))
531 * if the file name (path + "/" + d_name + NULL) would be too
534 if ((path_len
+ strlen(direntp
->d_name
) + 2) > MAX_DEV_PATH
)
537 (void) strcpy(last_comp
, direntp
->d_name
);
538 if (stat64(file_name
, &tsb
) < 0)
541 if (strcmp(file_name
, "/dev/vt/active") == 0)
545 * skip "/dev/syscon" because it may be an invalid link after
548 if (strcmp(file_name
, "/dev/syscon") == 0)
552 * if a file is a directory and we are not too deep, recurse
554 if ((tsb
.st_mode
& S_IFMT
) == S_IFDIR
)
555 if (depth
< MAX_SRCH_DEPTH
)
556 found
= srch_dir(file
, match_mask
, depth
+1,
562 * else if it is not a directory, is it a character special
565 else if ((tsb
.st_mode
& S_IFMT
) == S_IFCHR
) {
567 if (tsb
.st_dev
== fsb
->st_dev
)
569 if (tsb
.st_rdev
== fsb
->st_rdev
)
571 if (tsb
.st_ino
== fsb
->st_ino
)
574 if ((flag
& file
.flags
) == file
.flags
) {
575 (void) strcpy(rbuf
, file
.name
);
577 } else if ((flag
& (MATCH_MM
| MATCH_FS
)) ==
578 (MATCH_MM
| MATCH_FS
)) {
581 * no (inodes do not match), but save the name
584 (void) strcpy(dev_rbuf
, file
.name
);
589 (void) closedir(dirp
);
595 * get_pri_dirs() - returns a pointer to an array of strings, where each string
596 * is a priority directory name. The end of the array is marked by a NULL
597 * pointer. The priority directories' names are obtained from the file
598 * /etc/ttysrch if it exists and is readable, or if not, a default hard-coded
599 * list of directories.
601 * /etc/ttysrch, if used, is read in as a string of characters into memory and
602 * then parsed into strings of priority directory names, omitting comments and
607 #define START_STATE 1
608 #define COMMENT_STATE 2
609 #define DIRNAME_STATE 3
611 #define CHECK_STATE 5
613 #define COMMENT_CHAR '#'
614 #define EOLN_CHAR '\n'
616 static const entry_t
*
627 * if no /etc/ttysrch, use defaults
629 if ((fd
= open(TTYSRCH
, O_RDONLY
)) < 0)
630 return (def_srch_dirs
);
632 if (fstat64(fd
, &sb
) < 0) {
634 return (def_srch_dirs
);
637 sz
= (size_t)sb
.st_size
;
638 if (dir_vec
!= NULL
&& sz
== dir_size
&&
639 sb
.st_mtim
.tv_sec
== dir_mtim
.tv_sec
&&
640 sb
.st_mtim
.tv_nsec
== dir_mtim
.tv_nsec
) {
642 * size & modification time match
643 * no need to reread TTYSRCH
644 * just return old pointer
649 buf
= realloc(dir_buf
, sz
+ 1);
652 size
= read(fd
, dir_buf
, sz
);
656 if (buf
== NULL
|| size
< 0) {
657 if (dir_vec
!= NULL
) {
661 return ((entry_t
*)def_srch_dirs
);
664 dir_mtim
= sb
.st_mtim
;
667 * ensure newline termination for buffer. Add an extra
668 * entry to dir_vec for null terminator
670 ebuf
= &dir_buf
[size
];
672 for (sz
= 1, buf
= dir_buf
; buf
< ebuf
; ++buf
)
676 sz
*= sizeof (*dir_vec
);
677 vec
= realloc(dir_vec
, sz
);
679 if (dir_vec
!= NULL
) {
683 return (def_srch_dirs
);
687 for (buf
= dir_buf
; buf
< ebuf
; ++buf
) {
691 if (*buf
== COMMENT_CHAR
) {
692 state
= COMMENT_STATE
;
695 if (!isspace(*buf
)) { /* skip leading white space */
696 state
= DIRNAME_STATE
;
703 if (*buf
== EOLN_CHAR
)
708 if (*buf
== EOLN_CHAR
) {
711 } else if (isspace(*buf
)) {
712 /* skip trailing white space */
721 vec
->flags
|= MATCH_MM
;
724 vec
->flags
|= MATCH_FS
;
727 vec
->flags
|= MATCH_INO
;
736 if (strncmp(vec
->name
, DEV
, strlen(DEV
)) != 0) {
737 int tfd
= open("/dev/console", O_WRONLY
);
740 (void) snprintf(buf
, sizeof (buf
),
742 "ERROR: Entry '%s' in /etc/ttysrch ignored.\n"), vec
->name
);
743 (void) write(tfd
, buf
, strlen(buf
));
748 slash
= vec
->name
+ strlen(vec
->name
) - 1;
749 while (*slash
== '/')
752 vec
->flags
= MATCH_ALL
;
757 * This state does not consume a character, so
758 * reposition the pointer.
773 char *ans
= tsdalloc(_T_TTYNAME
, MAX_DEV_PATH
, NULL
);
777 return (ttyname_r(f
, ans
, MAX_DEV_PATH
));