18.9. Terminal IdentificationHistorically, the name of the controlling terminal in most versions of the UNIX System has been /dev/tty. POSIX.1 provides a runtime function that we can call to determine the name of the controlling terminal.
If ptr is non-null, it is assumed to point to an array of at least L_ctermid bytes, and the name of the controlling terminal of the process is stored in the array. The constant L_ctermid is defined in <stdio.h>. If ptr is a null pointer, the function allocates room for the array (usually as a static variable). Again, the name of the controlling terminal of the process is stored in the array. In both cases, the starting address of the array is returned as the value of the function. Since most UNIX systems use /dev/tty as the name of the controlling terminal, this function is intended to aid portability to other operating systems.
Examplectermid FunctionFigure 18.12 shows an implementation of the POSIX.1 ctermid function. Note that we can't protect against overrunning the caller's buffer, because we have no way to determine its size. Figure 18.12. Implementation of POSIX.1 ctermid function#include <stdio.h> #include <string.h> static char ctermid_name[L_ctermid]; char * ctermid(char *str) { if (str == NULL) str = ctermid_name; return(strcpy(str, "/dev/tty")); /* strcpy() returns str */ } Two functions that are more interesting for a UNIX system are isatty, which returns true if a file descriptor refers to a terminal device, and ttyname, which returns the pathname of the terminal device that is open on a file descriptor.
Exampleisatty FunctionThe isatty function is trivial to implement, as we show in Figure 18.13. We simply try one of the terminal-specific functions (that doesn't change anything if it succeeds) and look at the return value. We test our isatty function with the program in Figure 18.14. When we run the program from Figure 18.14, we get the following output: $ ./a.out fd 0: tty fd 1: tty fd 2: tty $ ./a.out </etc/passwd 2>/dev/null fd 0: not a tty fd 1: tty fd 2: not a tty Figure 18.13. Implementation of POSIX.1 isatty function#include <termios.h> int isatty(int fd) { struct termios ts; return(tcgetattr(fd, &ts) != -1); /* true if no error (is a tty) */ } Figure 18.14. Test the isatty function#include "apue.h" int main(void) { printf("fd 0: %s\n", isatty(0) ? "tty" : "not a tty"); printf("fd 1: %s\n", isatty(1) ? "tty" : "not a tty"); printf("fd 2: %s\n", isatty(2) ? "tty" : "not a tty"); exit(0); } Examplettyname FunctionThe ttyname function (Figure 18.15) is longer, as we have to search all the device entries, looking for a match. The technique is to read the /dev directory, looking for an entry with the same device number and i-node number. Recall from Section 4.23 that each file system has a unique device number (the st_dev field in the stat structure, from Section 4.2), and each directory entry in that file system has a unique i-node number (the st_ino field in the stat structure). We assume in this function that when we hit a matching device number and matching i-node number, we've located the desired directory entry. We could also verify that the two entries have matching st_rdev fields (the major and minor device numbers for the terminal device) and that the directory entry is also a character special file. But since we've already verified that the file descriptor argument is both a terminal device and a character special file, and since a matching device number and i-node number is unique on a UNIX system, there is no need for the additional comparisons. The name of our terminal might reside in a subdirectory in /dev. Thus, we might need to search the entire file system tree under /dev. We skip several directories that might produce incorrect or odd-looking results: /dev/., /dev/.., and /dev/fd. We also skip the aliases /dev/stdin, /dev/stdout, and /dev/stderr, since they are symbolic links to files in /dev/fd. We can test this implementation with the program shown in Figure 18.16. Running the program from Figure 18.16 gives us
$ ./a.out < /dev/console 2> /dev/null
fd 0: /dev/console
fd 1: /dev/ttyp3
fd 2: not a tty
Figure 18.15. Implementation of POSIX.1 ttyname function#include <sys/stat.h> #include <dirent.h> #include <limits.h> #include <string.h> #include <termios.h> #include <unistd.h> #include <stdlib.h> struct devdir { struct devdir *d_next; char *d_name; }; static struct devdir *head; static struct devdir *tail; static char pathname[_POSIX_PATH_MAX + 1]; static void add(char *dirname) { struct devdir *ddp; int len; len = strlen(dirname); /* * Skip ., .., and /dev/fd. */ if ((dirname[len-1] == '.') && (dirname[len-2] == '/' || (dirname[len-2] == '.' && dirname[len-3] == '/'))) return; if (strcmp(dirname, "/dev/fd") == 0) return; ddp = malloc(sizeof(struct devdir)); if (ddp == NULL) return; ddp->d_name = strdup(dirname); if (ddp->d_name == NULL) { free(ddp); return; } ddp->d_next = NULL; if (tail == NULL) { head = ddp; tail = ddp; } else { tail->d_next = ddp; tail = ddp; } } static void cleanup(void) { struct devdir *ddp, *nddp; ddp = head; while (ddp != NULL) { nddp = ddp->d_next; free(ddp->d_name); free(ddp); ddp = nddp; } head = NULL; tail = NULL; } static char * searchdir(char *dirname, struct stat *fdstatp) { struct stat devstat; DIR *dp; int devlen; struct dirent *dirp; strcpy(pathname, dirname); if ((dp = opendir(dirname)) == NULL) return(NULL); strcat(pathname, "/"); devlen = strlen(pathname); while ((dirp = readdir(dp)) != NULL) { strncpy(pathname + devlen, dirp->d_name, _POSIX_PATH_MAX - devlen); /* * Skip aliases. */ if (strcmp(pathname, "/dev/stdin") == 0 || strcmp(pathname, "/dev/stdout") == 0 || strcmp(pathname, "/dev/stderr") == 0) continue; if (stat(pathname, &devstat) < 0) continue; if (S_ISDIR(devstat.st_mode)) { add(pathname); continue; } if (devstat.st_ino == fdstatp->st_ino && devstat.st_dev == fdstatp->st_dev) { /* found a match */ closedir(dp); return(pathname); } } closedir(dp); return(NULL); } char * ttyname(int fd) { struct stat fdstat; struct devdir *ddp; char *rval; if (isatty(fd) == 0) return(NULL); if (fstat(fd, &fdstat) < 0) return(NULL); if (S_ISCHR(fdstat.st_mode) == 0) return(NULL); rval = searchdir("/dev", &fdstat); if (rval == NULL) { for (ddp = head; ddp != NULL; ddp = ddp->d_next) if ((rval = searchdir(ddp->d_name, &fdstat)) != NULL) break; } cleanup(); return(rval); } Figure 18.16. Test the ttyname function#include "apue.h" int main(void) { char *name; if (isatty(0)) { name = ttyname(0); if (name == NULL) name = "undefined"; } else { name = "not a tty"; } printf("fd 0: %s\n", name); if (isatty(1)) { name = ttyname(1); if (name == NULL) name = "undefined"; } else { name = "not a tty"; } printf("fd 1: %s\n", name); if (isatty(2)) { name = ttyname(2); if (name == NULL) name = "undefined"; } else { name = "not a tty"; } printf("fd 2: %s\n", name); exit(0); } |