xref: /illumos-gate/usr/src/lib/libc/port/gen/ttyname.c (revision 31c0bba7)
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