Team BBL
Previous Page Next Page

18.9. Terminal Identification

Historically, 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.

#include <stdio.h>

char *ctermid(char *ptr);

Returns: pointer to name of controlling terminal
on success, pointer to empty string on error


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.

All four platforms described in this text return the string /dev/tty when we call ctermid.

Examplectermid Function

Figure 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.

#include <unistd.h>

int isatty(int filedes);

Returns: 1 (true) if terminal device, 0 (false) otherwise

char *ttyname(int filedes);

Returns: pointer to pathname of terminal, NULL on error


Exampleisatty Function

The 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 Function

The 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);
}

    Team BBL
    Previous Page Next Page