Section 16.1. tty Operations

   


16.1. tty Operations

tty devices provide a large number of processing options; they are among the most complicated devices in the kernel. You can set input processing, output processing, and data flow processing options. You can also control a limited amount of data manipulation that occurs at the device driver level.

ttys operate in two basic modes: raw and cooked. Raw mode passes data to the application as it is received, with no changes made. Cooked mode, also known as canonical mode, provides a limited line editor inside the device driver and sends edited input to the application one line at a time. This mode is primarily derived from mainframe systems, in which dedicated input processing units provided cooked mode without interrupting the CPU at all.

Cooked mode processes certain control characters; for example, by default, ^U kills (erases) the current line, ^W erases the current word, backspace (^H) or delete erases the previous character, and ^R erases and then retypes the current line. Each of these control actions can be reassigned to a different character. For instance, on many terminals, DEL (character 127) is assigned the backspace action.

16.1.1. Terminal Utility Functions

Sometimes you do not know whether a file descriptor corresponds to a tty. This is most commonly the case for the standard output file descriptor. Programs that output text to standard output often format differently when they are writing to a pipe than when they are displaying information for human consumption. For example, when you use ls to list files, it will print multiple columns when you simply run it (most convenient for a human to read), but when you pipe it to another program, it will print one file per line (most convenient for a program to read). Run ls and ls | cat and see the difference.

You can determine whether a file descriptor corresponds to a tty by using the isatty() function, which takes a file descriptor as its argument and returns 1 if the descriptor corresponds to a tty, and 0 otherwise.

 #include <unistd.h> int isatty(int fd); 


The ttyname() function provides a canonical name for the terminal (if any) associated with a file descriptor. It takes as its argument any file descriptor, and returns a pointer to a character string.

 #include <unistd.h> char *ttyname(int fd); 


Because that character string is (the standard says "may be", but we all know better) in static space, you need to make a copy of the returned string before calling ttyname() again; it is not re-entrant. ttyname() returns NULL on any error, including if it is passed a file descriptor that is not associated with a tty.

16.1.2. Controlling Terminals

Every session (see Chapter 10) is tied to a terminal from which processes in the session get their input and to which they send their output. That terminal may be the machine's local console, a terminal connected over a serial line, or a pseudo terminal that maps to an X window or across a network (see page 389 later in this chapter for more on pseudo terminals). The terminal to which a session is related is called the controlling terminal (or controlling tty) of the session. A terminal can be the controlling terminal for only one session at a time.

Normal processes cannot change their controlling terminal; only a session leader can do that. Under Linux, a change in the session leader's controlling terminal is not propagated to other processes in that session. Session leaders almost always set a controlling terminal when they initially start running, before they create any child processes, in order to ensure that all the processes in the session share a common controlling terminal.

There are two interfaces for changing a session group leader's controlling tty. The first is through the normal open() and close() system calls:

1.

Close all file descriptors that reference the current controlling terminal.

2.

Open a new terminal without specifying the O_NOCTTY flag.

The other method involves ioctl()s on separate file descriptors that reference the old and the new terminal devices:

1.

TIOCNOTTY on a file descriptor tied to the original controlling tty (usually, ioctl(0, TIOCNOTTY, NULL) works fine). This breaks the bond between the session and the tty.

2.

TIOCSCTTY on the file descriptor tied to the new controlling tty. This sets a new controlling tty.

A terminal that is being used by a session keeps track of which process group is considered the foreground process group. Processes in that process group are allowed to read from and write to the terminal, whereas processes in other process groups are not (see Chapter 15 for details on what happens when background processes try to read and write from the controlling terminal). The tcsetpgrp() function allows a process running on a terminal to change the foreground process group for that terminal.[2]

[2] Older implementations of Unix provided this functionality through the TIOCSPGRP ioctl(), which is still supported in Linux. For comparison, tcsetpgrp() could be implemented as ioctl(ttyfd, TIOCSPGRP, &pgrp).

 int tcsetpgrp(int ttyfd, pid_t pgrp); 


The first parameter specifies the tty whose controlling process group is being changed, and pgrp is the process group that should be moved to the foreground. Processes may change the foreground process group only for their controlling terminal. If the process making the change is not in the foreground process group on that terminal, a SIGTTOU is generated, unless that signal is being ignored or is blocked.[3]

[3] For more information on signals and their interaction with job control, see Chapter 12.

16.1.3. Terminal Ownership

There are two system databases used to keep track of logged-in users; utmp is used specifically for currently logged-in users, and wtmp is a record of all previous logins since the file was created. The who command uses the utmp database to print out its list of logged-in users, and the last command uses the wtmp database to print out its list of users who have logged into the system since the wtmp database was regenerated. On Linux systems, the utmp database is stored in the file /var/run/utmp, and the wtmp database is stored in the file /var/log/wtmp.

Programs that use ttys for user login sessions (whether or not they are associated with a graphical login) should update the two system databases, unless the user explicitly requests otherwise; for example, some users do not want every shell session they are running in a terminal emulator under the X Window System to be listed as a login process. Only add interactive sessions, because utmp and wtmp are not meant for logging automated programs. Any tty that is not a controlling terminal should normally not be added to the utmp and wtmp databases.

16.1.4. Recording with utempter

Applications that use ptys and that are written with security in mind rarely have sufficient permissions to modify the database files. These applications should provide the option to use a simple helper program that is available on most Linux systems and some other systems, but is not standardized: the utempter utility. The utempter utility is setgid (or setuid if necessary) with sufficient permissions to modify the utmp and wtmp databases, and it is accessed through a simple library. The utempter utility checks to make sure that the process owns the tty that it is trying to log into the utmp database before allowing the operation. utempter is meant to be used only for ptys; other ttys are generally opened by daemons with sufficient permissions to modify the system database files.

 #include <utempter.h> void addToUtmp(const char *pty, const char *hostname, int ptyfd); void removeLineFromUtmp(const char *pty, int ptyfd); void removeFromUtmp(void); 


The addToUtmp() function takes three arguments. The first, pty, is the full path to the pty being added. The second, hostname, may be NULL; if not, it is the network name of the system from which a network connection using this pty originated (this sets ut_host, as documented on page 343). The third, ptyfd, must be an open file descriptor referencing the pty device named in the pty argument.

The removeLineFromUtmp() function takes two arguments; they are defined exactly like the arguments of the same name passed to the addToUtmp() function.

Some existing applications are written with a structure that makes it difficult to keep the name and file descriptor around to clean up the utmp entry. Because of this, the utempter library keeps a cache of the more recent device name and file descriptor passed to addToUtmp() and a convenience function removeFromUtmp() that takes no arguments and acts like removeLineFromUtmp() on the information it has cached. This works only for applications that add only one utmp entry; more complex applications that use more than one pty must use removeLineFromUtmp() instead.

16.1.5. Recording by Hand

The area of utmp and wtmp handling is one of those inconsistent areas where mechanisms have differed between systems and changed over the years; even the definition of what information is available in utmp and wtmp still differs between systems. Originally, utmp and wtmp were essentially just arrays of structures written to disk; over time, APIs were created to handle the records reliably.

At least two such interfaces have been officially standardized; the original utmp interface (specified in XSI, XPG2, and SVID2) and the extended utmpx interface (specified in XPG4.2 and in recent editions of POSIX). Conveniently, both interfaces (utmp and utmpx) are available on Linux. The utmp interface, which varies widely between machines, has a set of defines to make it possible to write portable code that take advantage of the extensions that glibc provides. The utmpx interface, which is more strictly standardized, does not (currently) provide those defines but still provides the extensions.

The Linux utmp interface was originally constructed as a superset of other existing utmp interfaces, and utmpx was standardized as a superset of other existing utmp interfaces; happily, the two supersets are essentially the same, so on Linux, the difference between the utmp and utmpx data structures is the letter x.

If you do not wish to use any extensions, we recommend that you use the utmpx interface, because as long as you are using no extensions, it is the most portable; it is strictly standardized.

If you do wish to use extensions, however, we recommend that you use the utmp interface, because glibc provides defines that let you write portable code that takes advantage of the extensions.

There is also a hybrid approach include both header files and use the defines that glibc provides for the utmp interface to decide whether to use extensions in the utmpx interface. We recommend against this because there is no guarantee that the utmp.h and utmpx.h header files will not conflict on non-Linux systems. If you want both maximum portability and maximum functionality, this is one of those areas where you may have to write some code twice one version using utmpx for easy portability to new systems, and then one version with #ifdefs for maximum functionality on each new system you port to.

We document here only the most commonly used extensions; the glibc documentation covers all the extensions that it supports. The utmp functions operate in terms of struct utmp; we ignore some of the extensions. The utmpx structure and functions operate exactly the same as the utmp structure and functions, and so we do not document them separately. Note that the same structure is used for both utmp and wtmp, because the two databases are so similar.

 struct utmp {     short int ut_type;          /* type of login */     pid_t ut_pid;               /* process id of login process */     char ut_line[UT_LINESIZE];  /* 32 characters */     char ut_id[4];              /* inittab id */     char ut_user[UT_NAMESIZE];  /* 32 characters */     char ut_host[UT_HOSTSIZE];  /* 256 characters */     struct timeval ut_tv;     struct exit_status ut_exit; /* status of dead processes */     long ut_session;     int32_t ut_addr_v6[4]; }; 


Many of the same members are part of struct utmpx by the same name. The members that are not required to be members of struct utmpx are annotated as "not standardized by POSIX" (none of them are standardized as part of struct utmp since struct utmp is itself not standardized).

The character array members are not necessarily NULL-terminated strings. Use sizeof() or other size limitations judiciously.

ut_type

One of the following values: EMPTY, INIT_PROCESS, LOGIN_PROCESS, USER_PROCESS, DEAD_PROCESS, BOOT_TIME, NEW_TIME, OLD_TIME, RUN_LVL, or ACCOUNTING, each of which is described below.

ut_tv

The time associated with the event. This is the only member beside ut_type that POSIX specifies as always valid for nonempty entries. Some systems have instead a ut_time member instead that is measured only in seconds.

ut_pid

The process id of the associated process, for all types ending in_PROCESS.

ut_id

The inittab id of the associated process, for all types ending in_PROCESS. This is the first field in noncomment lines in the file /etc/inittab, where fields are separated by : characters. Network logins, which are not associated with inittab, may use this in other ways; for example, they may include parts of the device information here.

ut_line

The line (basename of the device or local display number for X) associated with the process. The POSIX specification is unclear about the status of ut_line; it does not list ut_line as meaningful for LOGIN_PROCESS, but in another place it implies that it is meaningful for LOGIN_PROCESS, and this makes sense in practice. POSIX says that ut_line is meaningful for USER_PROCESS. In practice, it is often also meaningful for DEAD_PROCESS, depending on the origin of the dead process.

ut_user

Normally, the name of the logged-in user; it can also be the name of the login process (normally, LOGIN) depending on the value of ut_type.

ut_host

The name of the remote host that has logged in (or is otherwise associated) with this process. The ut_host member applies only to USER_PROCESS. This member is not standardized by POSIX.

ut_exit

ut_exit.e_exit gives the exit code as provided by the WEXITSTATUS() macro, and ut_exit.e_termination gives the signal that caused the process to terminate (if it was terminated by a signal), as provided by the WTERMSIG() macro. This member is not standardized by POSIX.

ut_session

The session ID in the X Window System. This member is not standardized by POSIX.

ut_addr_v6

The IP address of the remote host, in the case of a USER_PROCESS initiated by a connection from a remote host. Use the inet_ntop() function to generate printable content. If only the first quad is nonzero, then it is an IPV4 address (inet_ntop() takes the AF_INET argument); otherwise it is an IPV6 address (inet_ntop() takes the AF_INET6 argument). This member is not standardized by POSIX.


The ut_type member defines how all the rest of the members are defined. Some ut_type values are reserved for recording system information; these are useful only for specialized system programs. We do not document these fully.

EMPTY

There is no valid information in this utmp record (such records may be reused later), so ignore the contents of this record. None of the other structure members have meaning.

INIT_PROCESS

The listed process was spawned directly from init. This value may be set by system programs (normally only the init process itself); applications should read and recognize this value but never set it. The ut_pid,ut_id, and ut_tv members are meaningful.

LOGIN_PROCESS

Instances of the login program waiting for a user to log in. The ut_id, ut_pid, and ut_tv members are useful; the ut_user member is nominally useful (on Linux, it will say LOGIN but this name of the login process is implementation-defined according to POSIX.

USER_PROCESS

This entry denotes a session leader for a logged-in user. This may be the login program after a user has logged in, the display manager or session manager for an X Window System login, a terminal emulator program configured to mark login sessions, or any other interactive user login. The ut_id, ut_user, ut_line, ut_pid, and ut_tv members are meaningful.

DEAD_PROCESS

The listed process was a session leader for a logged-in user, but has exited. The ut_id, ut_pid, and ut_tv members are meaningful according to POSIX. The ut_exit member (not specified by POSIX) is meaningful only in this context.

BOOT_TIME

The time the system booted. In utmp this will be the most recent boot; in wtmp there will be an entry for every system boot since wtmp was cleared. Only ut_tv is meaningful.

OLD_TIME and NEW_TIME

These are used only for recording times that the clock time jumped, and are recorded in pairs. Do not depend on these entries being recorded on the system even if the clock time is changed for any reason.

RUN_LVL and ACCOUNTING

These are internal system values; do not use in applications.


The interfaces defined by XPG2, SVID 2, and FSSTND 1.2 are:

 #include <utmp.h> int utmpname(char *file); struct utmp *getutent(void); struct utmp *getutid(const struct utmp *id); struct utmp *getutline(const struct utmp *line); struct utmp *pututline(const struct utmp *ut); void setutent(void); void endutent(void); void updwtmp(const char *file, const struct utmp *ut); void logwtmp(const char *line, const char *name, const char *host); 


Each record in a utmp or wtmp database is called a line. All the functions that return a pointer to a struct utmp return a pointer to static data on success, and a NULL pointer on failure. Note that the static data is overwritten by every new call to any function that returns a struct utmp. Also, the POSIX standard (for utmpx) requires that the static data be cleared by the application before some searches.

The utmpx versions of these functions take struct utmpx instead of struct utmp, require including utmpx.h, and are named getutxent, getutxid, getutxline, pututxline, setutxent, and endutxent, but are otherwise identical to the utmp versions of these functions on Linux. There is no utmpxname() function defined by POSIX, although some platforms may choose to define it anyway (as does glibc).

The utmpname() function is used to determine which database you are looking at. The default database is the utmp database, but you can use this function to point to wtmp instead. Two predefined names are _PATH_UTMP for the utmp file and _PATH_WTMP for the wtmp file; for testing you might choose instead to point to a local copy. The utmpname() function returns zero on success, nonzero on failure, but success may simply mean that it was able to copy a filename into the library; it does not mean that a database actually exists at the path you have provided to it!

The getutent() function simply returns the next line from the database. If the database has not been opened yet, it returns the contents of the first line. If no more lines are available, it returns NULL.

The getutid() function takes a struct utmp and looks only at one or two members. If the ut_type is BOOT_TIME, OLD_TIME, or NEW_TIME, then it returns the next line of that type. If the ut_type is any of INIT_PROCESS, LOGIN_PROCESS, USER_PROCESS, or DEAD_PROCESS, then getutid() returns the next line matching any of those types that also has a ut_id value matching the ut_id value in the struct utmp passed to getutid(). You must clear the data in the struct utmp returned by getutid() before calling it again; otherwise, it is allowed by POSIX to return the same line as the previous invocation. If no matching lines are available, it returns NULL.

The getutline() function returns the next line with ut_id set to LOGIN_PROCESS or USER_PROCESS that also has a ut_line value matching the ut_line value in the struct utmp passed to getutline(). Like getutid(), you must clear the data in the struct utmp returned by getutline() before calling it again; otherwise, it is allowed by POSIX to return the same line as the previous invocation. If no matching lines are available, it returns NULL.

The pututline() function modifies (or adds, if necessary) the database record matching the ut_line member of its struct utmp argument. It does this only if the process has sufficient permissions to modify the database. If it succeeds in modifying the database, it returns a struct utmp that matches the data it wrote to the database. Otherwise, it returns NULL. The pututline() function is not portably applicable to the wtmp database. To modify the wtmp database, use updwtmp() or logwtmp() instead.

The setutent() function rewinds the database to the beginning.

The endutent() function closes the database. This closes the file descriptor and frees any associated data. Call endutent() before using utmpname() to access a different utmp file, as well as after you are done accessing utmp data.

BSD defined two functions that are also available as part of glibc, which are the most robust way to modify the wtmp database.

The updwtmp() function takes the filename of the wtmp database (normally _PATH_WTMP) and a filled-in struct utmp, and attempts to append the entry to the wtmp file. Failure is not reported.

The logwtmp() function is a convenience function that fills in a struct utmp and calls updwtmp() with it. The line argument is copied to ut_line, name is copied to ut_user, host is copied to ut_host, ut_tv is filled in from the current time, and ut_pid is filled in from the current process id. Like updwtmp(), it does not report failure.

The utmp program demonstrates several methods of reading utmp and wtmp databases:

   1: /* utmp.c */   2:   3: #include <stdio.h>   4: #include <unistd.h>   5: #include <string.h>   6: #include <time.h>   7: #include <sys/time.h>   8: #include <sys/types.h>   9: #include <sys/socket.h>  10: #include <netinet/in.h>  11: #include <arpa/inet.h>  12: #include <utmp.h>  13: #include <popt.h>  14:  15: void print_utmp_entry(struct utmp *u) {  16:     struct tm *tp;  17:     char *type;  18:     char addrtext[INET6_ADDRSTRLEN];  19:  20:     switch (u->ut_type) {  21:         case EMPTY: type = "EMPTY"; break;  22:         case RUN_LVL: type = "RUN_LVL"; break;  23:         case BOOT_TIME: type = "BOOT_TIME"; break;  24:         case NEW_TIME: type = "NEW_TIME"; break;  25:         case OLD_TIME: type = "OLD_TIME"; break;  26:         case INIT_PROCESS: type = "INIT_PROCESS"; break;  27:         case LOGIN_PROCESS: type = "LOGIN_PROCESS"; break;  28:         case USER_PROCESS: type = "USER_PROCESS"; break;  29:         case DEAD_PROCESS: type = "DEAD_PROCESS"; break;  30:         case ACCOUNTING: type = "ACCOUNTING"; break;  31:     }  32:     printf("%-13s:", type);  33:     switch (u->ut_type) {  34:         case LOGIN_PROCESS:  35:         case USER_PROCESS:  36:         case DEAD_PROCESS:  37:             printf(" line: %s", u->ut_line);  38:             /* fall through */  39:         case INIT_PROCESS:  40:             printf("\n pid: %6d id: %4.4s", u->ut_pid, u->ut_id);  41:     }  42:     printf("\n");  43:     tp = gmtime(&u->ut_tv.tv_sec);  44:     printf("  time: %24.24s.%lu\n", asctime(tp), u->ut_tv.tv_usec);  45:     switch (u->ut_type) {  46:         case USER_PROCESS:  47:         case LOGIN_PROCESS:  48:         case RUN_LVL:  49:         case BOOT_TIME:  50:             printf("  user: %s\n", u->ut_user);  51:     }  52:     if (u->ut_type == USER_PROCESS) {  53:         if (u->ut_session)  54:             printf("  sess: %lu\n", u->ut_session);  55:         if (u->ut_host)  56:             printf("  host: %s\n", u->ut_host);  57:         if (u->ut_addr_v6[0]) {  58:             if (!(u->ut_addr_v6[1] |  59:                   u->ut_addr_v6[2] |  60:                   u->ut_addr_v6[3])) {  61:                /* only first quad filled in implies IPV4 address */  62:                 inet_ntop(AF_INET, u->ut_addr_v6,  63:                           addrtext, sizeof(addrtext));  64:                 printf("  IPV4: %s\n", addrtext);  65:             } else {  66:                 inet_ntop(AF_INET6, u->ut_addr_v6,  67:                           addrtext, sizeof(addrtext));  68:                 printf("  IPV6: %s\n", addrtext);  69:             }  70:         }  71:     }  72:     if (u->ut_type == DEAD_PROCESS) {  73:            printf("  exit: %u:%u\n",  74:                   u->ut_exit.e_termination,  75:                   u->ut_exit.e_exit);  76:     }  77:     printf("\n");  78: }  79:  80: struct utmp * get_next_line(char *id, char *line) {  81:     struct utmp request;  82:  83:     if (!id && !line)  84:         return getutent();  85:  86:     memset(&request, 0, sizeof(request));  87:  88:     if (line) {  89:         strncpy(&request.ut_line[0], line, UT_LINESIZE);  90:         return getutline(&request);  91:     }  92:  93:     request.ut_type = INIT_PROCESS;  94:     strncpy(&request.ut_id[0], id, 4);  95:     return getutid(&request);  96: }  97:  98: void print_file(char * name, char *id, char *line) {  99:     struct utmp *u; 100: 101:     if (utmpname(name)) { 102:         fprintf(stderr, "utmp database %s open failed\n", name); 103:         return; 104:     } 105:     setutent(); 106:     printf("%s:\n====================\n", name); 107:     while ((u=get_next_line(id, line))) { 108:         print_utmp_entry(u); 109:         /* POSIX requires us to clear the static data before 110:          * calling getutline or getutid again 111:          */ 112:         memset(u, 0, sizeof(struct utmp)); 113:     } 114:     endutent(); 115: } 116: 117: int main(int argc, const char **argv) { 118:     char *id = NULL, *line = NULL; 119:     int show_utmp = 1, show_wtmp = 0; 120:     int c; 121:     poptContext optCon; 122:     struct poptOption optionsTable[] = { 123:         { "utmp", 'u', POPT_ARG_NONE|POPT_ARGFLAG_XOR, 124:            &show_utmp, 0, 125:           "toggle showing contents of utmp file", NULL }, 126:         { "wtmp", 'w', POPT_ARG_NONE|POPT_ARGFLAG_XOR, 127:           &show_wtmp, 0, 128:           "toggle showing contents of wtmp file", NULL }, 129:         { "id", 'i', POPT_ARG_STRING, &id, 0, 130:           "show process entries for specified inittab id", 131:           "<inittab id>" }, 132:         { "line", 'l', POPT_ARG_STRING, &line, 0, 133:           "show process entries for specified device line", 134:           "<line>" }, 135:         POPT_AUTOHELP 136:         POPT_TABLEEND 137:     }; 138: 139:     optCon = poptGetContext("utmp", argc, argv, optionsTable, 0); 140:     if ((c = poptGetNextOpt(optCon)) < -1) { 141:         fprintf(stderr, "%s: %s\n", 142:             poptBadOption(optCon, POPT_BADOPTION_NOALIAS), 143:             poptStrerror(c)); 144:         return 1; 145:     } 146:     poptFreeContext(optCon); 147: 148:     if (id && line) 149:         fprintf(stderr, "Cannot choose both by id and line, " 150:                         "choosing by line\n"); 151: 152:     if (show_utmp) 153:         print_file(_PATH_UTMP, id, line); 154:     if (show_utmp && show_wtmp) 155:         printf("\n\n\n"); 156:     if (show_wtmp) 157:         print_file(_PATH_WTMP, id, line); 158: 159:     return 0; 160: } 



       
    top
     


    Linux Application Development
    Linux Application Development (paperback) (2nd Edition)
    ISBN: 0321563220
    EAN: 2147483647
    Year: 2003
    Pages: 168

    flylib.com © 2008-2017.
    If you may any questions please contact us: flylib@qtcs.net