1 /* Copyright (C) 1991-2022 Free Software Foundation, Inc.
2    This file is part of the GNU C Library.
3 
4    The GNU C Library is free software; you can redistribute it and/or
5    modify it under the terms of the GNU Lesser General Public
6    License as published by the Free Software Foundation; either
7    version 2.1 of the License, or (at your option) any later version.
8 
9    The GNU C Library is distributed in the hope that it will be useful,
10    but WITHOUT ANY WARRANTY; without even the implied warranty of
11    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12    Lesser General Public License for more details.
13 
14    You should have received a copy of the GNU Lesser General Public
15    License along with the GNU C Library; if not, see
16    <https://www.gnu.org/licenses/>.  */
17 
18 #include <errno.h>
19 #include <limits.h>
20 #include <stddef.h>
21 #include <dirent.h>
22 #include <sys/types.h>
23 #include <sys/stat.h>
24 #include <termios.h>
25 #include <unistd.h>
26 #include <string.h>
27 #include <stdlib.h>
28 
29 #include <fd_to_filename.h>
30 
31 #include "ttyname.h"
32 
33 static int getttyname_r (char *buf, size_t buflen,
34 			 const struct __stat64_t64 *mytty, int save,
35 			 int *dostat);
36 
37 static int
38 attribute_compat_text_section
getttyname_r(char * buf,size_t buflen,const struct __stat64_t64 * mytty,int save,int * dostat)39 getttyname_r (char *buf, size_t buflen, const struct __stat64_t64 *mytty,
40 	      int save, int *dostat)
41 {
42   struct __stat64_t64 st;
43   DIR *dirstream;
44   struct dirent64 *d;
45   size_t devlen = strlen (buf);
46 
47   dirstream = __opendir (buf);
48   if (dirstream == NULL)
49     {
50       *dostat = -1;
51       return errno;
52     }
53 
54   while ((d = __readdir64 (dirstream)) != NULL)
55     if ((d->d_fileno == mytty->st_ino || *dostat)
56 	&& strcmp (d->d_name, "stdin")
57 	&& strcmp (d->d_name, "stdout")
58 	&& strcmp (d->d_name, "stderr"))
59       {
60 	char *cp;
61 	size_t needed = _D_EXACT_NAMLEN (d) + 1;
62 
63 	if (needed > buflen)
64 	  {
65 	    *dostat = -1;
66 	    (void) __closedir (dirstream);
67 	    __set_errno (ERANGE);
68 	    return ERANGE;
69 	  }
70 
71 	cp = __stpncpy (buf + devlen, d->d_name, needed);
72 	cp[0] = '\0';
73 
74 	if (__stat64_time64 (buf, &st) == 0
75 	    && is_mytty (mytty, &st))
76 	  {
77 	    (void) __closedir (dirstream);
78 	    __set_errno (save);
79 	    return 0;
80 	  }
81       }
82 
83   (void) __closedir (dirstream);
84   __set_errno (save);
85   /* It is not clear what to return in this case.  `isatty' says FD
86      refers to a TTY but no entry in /dev has this inode.  */
87   return ENOTTY;
88 }
89 
90 /* Store at most BUFLEN character of the pathname of the terminal FD is
91    open on in BUF.  Return 0 on success,  otherwise an error number.  */
92 int
__ttyname_r(int fd,char * buf,size_t buflen)93 __ttyname_r (int fd, char *buf, size_t buflen)
94 {
95   struct fd_to_filename filename;
96   struct __stat64_t64 st, st1;
97   int dostat = 0;
98   int doispty = 0;
99   int save = errno;
100 
101   /* Test for the absolute minimal size.  This makes life easier inside
102      the loop.  */
103   if (!buf)
104     {
105       __set_errno (EINVAL);
106       return EINVAL;
107     }
108 
109   if (buflen < sizeof ("/dev/pts/"))
110     {
111       __set_errno (ERANGE);
112       return ERANGE;
113     }
114 
115   /* isatty check, tcgetattr is used because it sets the correct
116      errno (EBADF resp. ENOTTY) on error.  */
117   struct termios term;
118   if (__glibc_unlikely (__tcgetattr (fd, &term) < 0))
119     return errno;
120 
121   if (__fstat64_time64 (fd, &st) < 0)
122     return errno;
123 
124   /* We try using the /proc filesystem.  */
125   ssize_t ret = __readlink (__fd_to_filename (fd, &filename), buf, buflen - 1);
126   if (__glibc_unlikely (ret == -1 && errno == ENAMETOOLONG))
127     {
128       __set_errno (ERANGE);
129       return ERANGE;
130     }
131 
132   if (__glibc_likely (ret != -1))
133     {
134 #define UNREACHABLE_LEN strlen ("(unreachable)")
135       if (ret > UNREACHABLE_LEN
136 	  && memcmp (buf, "(unreachable)", UNREACHABLE_LEN) == 0)
137 	{
138 	  memmove (buf, buf + UNREACHABLE_LEN, ret - UNREACHABLE_LEN);
139 	  ret -= UNREACHABLE_LEN;
140 	}
141 
142       /* readlink need not terminate the string.  */
143       buf[ret] = '\0';
144 
145       /* Verify readlink result, fall back on iterating through devices.  */
146       if (buf[0] == '/'
147 	  && __stat64_time64 (buf, &st1) == 0
148 	  && is_mytty (&st, &st1))
149 	return 0;
150 
151       doispty = 1;
152     }
153 
154   /* Prepare the result buffer.  */
155   memcpy (buf, "/dev/pts/", sizeof ("/dev/pts/"));
156   buflen -= sizeof ("/dev/pts/") - 1;
157 
158   if (__stat64_time64 (buf, &st1) == 0 && S_ISDIR (st1.st_mode))
159     {
160       ret = getttyname_r (buf, buflen, &st, save,
161 			  &dostat);
162     }
163   else
164     {
165       __set_errno (save);
166       ret = ENOENT;
167     }
168 
169   if (ret && dostat != -1)
170     {
171       buf[sizeof ("/dev/") - 1] = '\0';
172       buflen += sizeof ("pts/") - 1;
173       ret = getttyname_r (buf, buflen, &st, save,
174 			  &dostat);
175     }
176 
177   if (ret && dostat != -1)
178     {
179       buf[sizeof ("/dev/") - 1] = '\0';
180       dostat = 1;
181       ret = getttyname_r (buf, buflen, &st,
182 			  save, &dostat);
183     }
184 
185   if (ret && doispty && is_pty (&st))
186     {
187       /* We failed to figure out the TTY's name, but we can at least
188          signal that we did verify that it really is a PTY slave.
189          This happens when we have inherited the file descriptor from
190          a different mount namespace.  */
191       __set_errno (ENODEV);
192       return ENODEV;
193     }
194 
195   return ret;
196 }
197 libc_hidden_def (__ttyname_r)
198 weak_alias (__ttyname_r, ttyname_r)
199