summaryrefslogtreecommitdiff
path: root/libc/termios/ttyname.c
blob: 9cd281be8b12a647d9e21a4efe050d0c5d2e3584 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
/*
 * Copyright (C) Jan 1, 2004    Manuel Novoa III
 * Copyright (C) 2000-2006 Erik Andersen <andersen@uclibc.org>
 *
 * Licensed under the LGPL v2.1, see the file COPYING.LIB in this tarball.
 */

/*
 * Kept the same approach, but rewrote the code for the most part.
 * Fixed some minor issues plus (as I recall) one SUSv3 errno case.
 */

/* This is a fairly slow approach.  We do a linear search through some
 * directories looking for a match.  Yes this is lame.  But it should
 * work, should be small, and will return names that match what is on
 * disk.  Another approach we could use would be to use the info in
 * /proc/self/fd, but that is even more lame since it requires /proc */

/* SUSv3 mandates TTY_NAME_MAX as 9.  This is obviously insufficient.
 * However, there is no need to waste space and support non-standard
 * tty names either.  So we compromise and use the following buffer
 * length.  (Erik and Manuel agreed that 32 was more than reasonable.)
 *
 * If you change this, also change _SC_TTY_NAME_MAX in libc/unistd/sysconf.c
 */

#include <string.h>
#include <errno.h>
#include <assert.h>
#include <unistd.h>
#include <dirent.h>
#include <sys/stat.h>

#define STAT stat64
#define FSTAT fstat64
#define LSTAT lstat64

#define TTYNAME_BUFLEN		32

static const char dirlist[] =
/*   12345670123 */
"\010/dev/vc/\0"	/* Try /dev/vc first (be devfs compatible) */
"\011/dev/tts/\0"	/* and /dev/tts next (be devfs compatible) */
"\011/dev/pty/\0"	/* and /dev/pty next (be devfs compatible) */
"\011/dev/pts/\0"	/* and try /dev/pts next */
"\005/dev/\0";		/* and try walking through /dev last */

int ttyname_r(int fd, char *ubuf, size_t ubuflen)
{
	struct dirent *d;
	struct STAT st;
	struct STAT dst;
	const char *p;
	char *s;
	DIR *fp;
	int rv;
	size_t len;
	char buf[TTYNAME_BUFLEN];

	if (FSTAT(fd, &st) < 0) {
		return errno;
	}

	rv = ENOTTY;				/* Set up the default return value. */

	if (!isatty(fd)) {
		goto DONE;
	}

	for (p = dirlist ; *p ; p += 1 + p[-1]) {
		len = *p++;

		assert(len + 2 <= TTYNAME_BUFLEN); /* dirname + 1 char + nul */

		strcpy(buf, p);
		s = buf + len;
		len =  (TTYNAME_BUFLEN-2) - len; /* Available non-nul space. */

		if (!(fp = opendir(p))) {
			continue;
		}

		while ((d = readdir(fp)) != NULL) {
			/* This should never trigger for standard names, but we
			 * check it to be safe.  */
			if (strlen(d->d_name) > len) { /* Too big? */
				continue;
			}

			strcpy(s, d->d_name);

			if ((LSTAT(buf, &dst) == 0)
#if 0
				/* Stupid filesystems like cramfs fail to guarantee that
				 * st_ino and st_dev uniquely identify a file, contrary to
				 * SuSv3, so we cannot be quite so precise as to require an
				 * exact match.  Settle for something less...  Grumble... */
				&& (st.st_dev == dst.st_dev) && (st.st_ino == dst.st_ino)
#else
				&& S_ISCHR(dst.st_mode) && (st.st_rdev == dst.st_rdev)
#endif
				) {				/* Found it! */
				closedir(fp);

				/* We treat NULL buf as ERANGE rather than EINVAL. */
				rv = ERANGE;
				if (ubuf && (strlen(buf) <= ubuflen)) {
					strcpy(ubuf, buf);
					rv = 0;
				}
				goto DONE;
			}
		}

		closedir(fp);
	}

 DONE:
	__set_errno(rv);

	return rv;
}
libc_hidden_def(ttyname_r)

char *ttyname(int fd)
{
	static char name[TTYNAME_BUFLEN];

	return ttyname_r(fd, name, TTYNAME_BUFLEN) ? NULL : name;
}