tty/ttyname.cThis is tty/ttyname.c, an example to accompany the book, The Linux Programming Interface. This file is not printed in the book; it is the solution to Exercise 62-2 (page 1323). The source code file is copyright 2024, Michael Kerrisk, and is licensed under the GNU General Public License, version 3. In the listing below, the names of Linux system calls and C library functions are hyperlinked to manual pages from the Linux man-pages project, and the names of functions implemented in the book are hyperlinked to the implementations of those functions. |
/* ttyname.c An implementation of ttyname(3). */ #include <dirent.h> #include <sys/stat.h> #include <unistd.h> #include <stdlib.h> #include <string.h> #include <stdio.h> #include <stdbool.h>
/* Helper function for ttyname(). We do most of the real work here. Look in 'devDir' for the terminal device name corresponding to the stat structure given in 'fdStat' (which the caller must ensure derives from a terminal device). Return the device name (as a statically-allocated) string if found, or NULL if the device is not found or an error occurs */ static char * ttynameCheckDir(const struct stat *fdStat, const char *devDir) { static char *ttyPath; /* Currently checked entry; also used to return tty name, if found */ static int ttyLen = 0; /* Length of ttyPath */ if (ttyLen == 0) { /* First call - allocate ttyPath */ ttyPath = malloc(50); if (ttyPath == NULL) return NULL; ttyLen = 50; } DIR *dirh = opendir(devDir); if (dirh == NULL) return NULL; /* We walk through each file in /dev looking for an entry whose device ID (st_rdev) matches the device ID corresponding to 'fd'. To do this, we construct a pathname for each entry in /dev, and then call stat() on that pathname. This is somewhat expensive. The glibc implementation of ttyname() performs an optimization: it performs a first pass of the entries in /dev, performing a stat() call only if fdStat->st_ino == dent->d_ino. This speeds the search (since many calls to stat() are avoided), but is not guaranteed to work in every case (e.g., if there are symbolic links in /dev). Therefore, if the first pass fails to find a matching device, glibc's ttyname() performs a second pass without the st_ino check (i.e., like we do below). */ bool found = false; /* True if we find device entry */ struct dirent *dent; while ((dent = readdir(dirh)) != NULL) { int requiredLen = strlen(devDir) + 1 + strlen(dent->d_name) + 1; if (requiredLen > ttyLen) { /* Resize ttyPath if required */ char *nttyPath; nttyPath = realloc(ttyPath, requiredLen); if (nttyPath == NULL) break; ttyPath = nttyPath; ttyLen = requiredLen; } snprintf(ttyPath, ttyLen, "%s/%s", devDir, dent->d_name); struct stat devStat; if (stat(ttyPath, &devStat) == -1) continue; /* Ignore unstat-able entries */ if (S_ISCHR(devStat.st_mode) && fdStat->st_rdev == devStat.st_rdev) { found = true; break; } } closedir(dirh); return found ? ttyPath : NULL; }
/* Return the name of the terminal associated with 'fd' (in a statically-allocated string that is overwritten on subsequent calls), or NULL if it is not a terminal or an error occurs */ char * ttyname(int fd) { if (!isatty(fd)) /* Is fd even a terminal? */ return NULL; struct stat fdStat; /* stat entry for fd */ if (fstat(fd, &fdStat) == -1) return NULL; if (!S_ISCHR(fdStat.st_mode)) /* Is fd even a character device? */ return NULL; /* First check for a pseudoterminal entry in the /dev/pts directory. If that fails, try looking in /dev. (We check the directories in this order for efficiency: /dev/pts is small, and will contain the required device if fd refers to an X-terminal or similar.) */ char *d = ttynameCheckDir(&fdStat, "/dev/pts"); return (d != NULL) ? d : ttynameCheckDir(&fdStat, "/dev"); }
Note that, in most cases, the programs rendered in these web pages are not free standing: you'll typically also need a few other source files (mostly in the lib/ subdirectory) as well. Generally, it's easier to just download the entire source tarball and build the programs with make(1). By hovering your mouse over the various hyperlinked include files and function calls above, you can see which other source files this file depends on.