Section 16.3. termios Examples


16.3. termios Examples

16.3.1. Passwords

One common reason to modify termios settings is to read a password without echoing characters. To do this, you want to turn off local echo while reading the password. Your code should look like this:

 struct termios ts, ots; 

One structure keeps the original termios settings so that you can restore them, and the other one is a copy to modify.

 tcgetattr(STDIN_FILENO, &ts); 

Generally, you read passwords from standard input.

 ots = ts; 

Keep a copy of the original termios settings to restore later.

 ts.c_lflag &= ~ECHO; ts.c_lflag |= ECHONL; tcsetattr(STDIN_FILENO, TCSAFLUSH, &ts); 

Turn off echoing characters except newlines, after all currently pending output is completed. (The first l in c_lflag stands for local processing.)


Here, you read the password. This may be as simple as a single fgets() or read() call, or it may include more complex processing, depending on whether the tty is in raw mode or cooked mode, and depending on the requirements of your program.

 tcsetattr(STDIN_FILENO, TCSANOW, &ots); 

This restores the original termios settings and does so immediately. (We explain other options later, in the reference section on page 371.)

A full example program, readpass, looks like this:

  1: /* readpass.c */  2:  3: #include <stdio.h>  4: #include <stdlib.h>  5: #include <termios.h>  6: #include <unistd.h>  7:  8: int main(void) {  9:     struct termios ts, ots; 10:     char passbuf[1024]; 11: 12:     /* get and save current termios settings */ 13:     tcgetattr(STDIN_FILENO, &ts); 14:     ots = ts; 15: 16:     /* change and set new termios settings */ 17:     ts.c_lflag &= ~ECHO; 18:     ts.c_lflag |= ECHONL; 19:     tcsetattr(STDIN_FILENO, TCSAFLUSH, &ts); 20: 21:     /* paranoia: check that the settings took effect */ 22:     tcgetattr(STDIN_FILENO, &ts); 23:     if (ts.c_lflag & ECHO) { 24:         fprintf(stderr, "Failed to turn off echo\n"); 25:         tcsetattr(STDIN_FILENO, TCSANOW, &ots); 26:         exit(1); 27:     } 28: 29:     /* get and print the password */ 30:     printf("enter password: "); 31:     fflush(stdout); 32:     fgets(passbuf, 1024, stdin); 33:     printf("read password: %s", passbuf); 34:     /* there was a terminating \n in passbuf */ 35: 36:     /* restore old termios settings */ 37:     tcsetattr(STDIN_FILENO, TCSANOW, &ots); 38: 39:     exit(0); 40: } 

16.3.2. Serial Communications

As an example of programming both ends of a tty, here is a program that connects the current terminal to a serial port. On one tty, the program, called robin, is talking to you as you type. On another tty, it is communicating with the serial port. In order to multiplex input to and output from the local tty and the serial port, the program uses the poll() system call described on page 245.

Here is robin.c in its entirety, followed by an explanation:

   1: /* robin.c */   2:   3: #include <sys/poll.h>   4: #include <errno.h>   5: #include <fcntl.h>   6: #include <popt.h>   7: #include <stdio.h>   8: #include <stdlib.h>   9: #include <signal.h>  10: #include <string.h>             /* for strerror() */  11: #include <termios.h>  12: #include <unistd.h>  13:  14: void die(int exitcode, const char *error, const char *addl) {  15:     if (error) fprintf(stderr, "%s: %s\n", error, addl);  16:     exit(exitcode);  17: }  18:  19: speed_t symbolic_speed(int speednum) {  20:     if (speednum >= 460800) return B460800;  21:     if (speednum >= 230400) return B230400;  22:     if (speednum >= 115200) return B115200;  23:     if (speednum >= 57600) return B57600;  24:     if (speednum >= 38400) return B38400;  25:     if (speednum >= 19200) return B19200;  26:     if (speednum >= 9600) return B9600;  27:     if (speednum >= 4800) return B4800;  28:     if (speednum >= 2400) return B2400;  29:     if (speednum >= 1800) return B1800;  30:     if (speednum >= 1200) return B1200;  31:     if (speednum >= 600) return B600;  32:     if (speednum >= 300) return B300;  33:     if (speednum >= 200) return B200;  34:     if (speednum >= 150) return B150;  35:     if (speednum >= 134) return B134;  36:     if (speednum >= 110) return B110;  37:     if (speednum >= 75) return B75;  38:     return B50;  39: }  40:  41: /* These need to have file scope so that we can use them in  42:  * signal handlers */  43: /* old port termios settings to restore */  44: static struct termios pots;  45: /* old stdout/in termios settings to restore */  46: static struct termios sots;  47: /* port file descriptor */  48: int    pf;  49:  50: /* restore original terminal settings on exit */  51: void cleanup_termios(int signal) {  52:     tcsetattr(pf, TCSANOW, &pots);  53:     tcsetattr(STDIN_FILENO, TCSANOW, &sots);  54:     exit(0);  55: }  56:  57: /* handle a single escape character */  58: void send_escape(int fd, char c) {  59:     switch (c) {  60:     case 'q':  61:         /* restore termios settings and exit */  62:         cleanup_termios(0);  63:         break;  64:     case 'b':  65:         /* send a break */  66:         tcsendbreak(fd, 0);  67:         break;  68:     default:  69:         /* pass the character through */  70:         /* "C-\ C-\" sends "C-\" */  71:         write(fd, &c, 1);  72:         break;  73:     }  74:     return;  75: }  76:  77: /* handle escape characters, writing to output */  78: void cook_buf(int fd, char *buf, int num) {  79:     int current = 0;  80:     static int in_escape = 0;  81:  82:     if (in_escape) {  83:         /* cook_buf last called with an incomplete escape  84:            sequence */  85:         send_escape(fd, buf[0]);  86:         num--;  87:         buf++;  88:         in_escape = 0;  89:     }  90:     while (current < num) {  91: #       define CTRLCHAR(c) ((c)-0x40)  92:         while ((current < num) && (buf[current] != CTRLCHAR('\\')))  93:             current++;  94:         if (current) write (fd, buf, current);  95:         if (current < num) {  96:             /* found an escape character */  97:             current++;  98:             if (current >= num) {  99:                 /* interpret first character of next sequence */ 100:                 in_escape = 1; 101:                 return; 102:             } 103:             send_escape(fd, buf[current]); 104:         } 105:         num -= current; 106:         buf += current; 107:         current = 0; 108:     } 109:     return; 110: } 111: 112: int main(int argc, const char *argv[]) { 113:     char    c;            /* used for argument parsing */ 114:     struct  termios pts;  /* termios settings on port */ 115:     struct  termios sts;  /* termios settings on stdout/in */ 116:     const char   *portname; 117:     int     speed = 0;     /* used in argument parsing for speed */ 118:     struct  sigaction sact;/* used to initialize signal handler */ 119:     struct  pollfd ufds[2]; /* communicate with poll() */ 120:     int     raw = 0;      /* raw mode? */ 121:     int     flow = 0;     /* type of flow control, if any */ 122:     int     crnl = 0;     /* send carriage return with newline? */ 123:     int     i = 0;        /* used in the multiplex loop */ 124:     int     done = 0; 125: #   define BUFSIZE 1024 126:     char    buf[BUFSIZE]; 127:     poptContext optCon;   /* context for command-line options */ 128:     struct poptOption optionsTable[] = { 129:              { "bps", 'b', POPT_ARG_INT, &speed, 0, 130:                "signaling rate for current maching in bps", 131:                "<BPS>" }, 132:              { "crnl", 'c', POPT_ARG_VAL, &crnl, 'c', 133:                "send carriage return with each newline", NULL }, 134:              { "hwflow", 'h', POPT_ARG_VAL, &flow, 'h', 135:                "use hardware flow control", NULL }, 136:              { "swflow", 's', POPT_ARG_VAL, &flow, 's', 137:                "use software flow control", NULL }, 138:              { "noflow", 'n', POPT_ARG_VAL, &flow, 'n', 139:                "disable flow control", NULL }, 140:              { "raw", 'r', POPT_ARG_VAL, &raw, 1, 141:                "enable raw mode", NULL }, 142:              POPT_AUTOHELP 143:              { NULL, '\0', 0, NULL, '\0', NULL, NULL } 144:     }; 145: 146: #ifdef DSLEEP 147:     /* wait 10 minutes so we can attach a debugger */ 148:     sleep(600); 149: #endif 150: 151:     optCon = poptGetContext("robin", argc, argv, optionsTable, 0); 152:     poptSetOtherOptionHelp(optCon, "<port>"); 153: 154:     if (argc < 2) { 155:         poptPrintUsage(optCon, stderr, 0); 156:         die(1, "Not enough arguments", ""); 157:     } 158: 159:     if ((c = poptGetNextOpt(optCon)) < -1) { 160:         /* an error occurred during option processing */ 161:         fprintf(stderr, "%s: %s\n", 162:                 poptBadOption(optCon, POPT_BADOPTION_NOALIAS), 163:                 poptStrerror(c)); 164:         return 1; 165:     } 166:     portname = poptGetArg(optCon); 167:     if (!portname) { 168:         poptPrintUsage(optCon, stderr, 0); 169:         die(1, "No port name specified", ""); 170:     } 171: 172:     pf = open(portname, O_RDWR); 173:     if (pf < 0) { 174:         poptPrintUsage(optCon, stderr, 0); 175:         die(1, strerror(errno), portname); 176:     } 177:     poptFreeContext(optCon); 178: 179:     /* modify the port configuration */ 180:     tcgetattr(pf, &pts); 181:     pots = pts; 182:     /* some things we want to set arbitrarily */ 183:     pts.c_lflag &= ~ICANON; 184:     pts.c_lflag &= ~(ECHO | ECHOCTL | ECHONL); 185:     pts.c_cflag |= HUPCL; 186:     pts.c_cc[VMIN] = 1; 187:     pts.c_cc[VTIME] = 0; 188: 189:     /* Standard CR/LF handling: this is a dumb terminal. 190:      * Do no translation: 191:      * no NL ->  CR/NL mapping on output, and 192:      * no CR ->  NL mapping on input. 193:      */ 194:     pts.c_oflag &= ~ONLCR; 195:     pts.c_iflag &= ~ICRNL; 196: 197:     /* Now deal with the local terminal side */ 198:     tcgetattr(STDIN_FILENO, &sts); 199:     sots = sts; 200:     /* again, some arbitrary things */ 201:     sts.c_iflag &= ~(BRKINT | ICRNL); 202:     sts.c_iflag |= IGNBRK; 203:     sts.c_lflag &= ~ISIG; 204:     sts.c_cc[VMIN] = 1; 205:     sts.c_cc[VTIME] = 0; 206:     sts.c_lflag &= ~ICANON; 207:     /* no local echo: allow the other end to do the echoing */ 208:     sts.c_lflag &= ~(ECHO | ECHOCTL | ECHONL); 209: 210:     /* option handling will now modify pts and sts */ 211:     switch (flow) { 212:     case 'h': 213:         /* hardware flow control */ 214:         pts.c_cflag |= CRTSCTS; 215:         pts.c_iflag &= ~(IXON | IXOFF | IXANY); 216:         break; 217:     case 's': 218:         /* software flow control */ 219:         pts.c_cflag &= ~CRTSCTS; 220:         pts.c_iflag |= IXON | IXOFF | IXANY; 221:         break; 222:     case 'n': 223:         /* no flow control */ 224:         pts.c_cflag &= ~CRTSCTS; 225:         pts.c_iflag &= ~(IXON | IXOFF | IXANY); 226:         break; 227:     } 228:     if (crnl) { 229:         /* send CR with NL */ 230:         pts.c_oflag |= ONLCR; 231:     } 232: 233:     /* speed is not modified unless -b is specified */ 234:     if (speed) { 235:         cfsetospeed(&pts, symbolic_speed(speed)); 236:         cfsetispeed(&pts, symbolic_speed(speed)); 237:     } 238: 239:     /* set the signal handler to restore the old 240:      * termios handler */ 241:     sact.sa_handler = cleanup_termios; 242:     sigaction(SIGHUP, &sact, NULL); 243:     sigaction(SIGINT, &sact, NULL); 244:     sigaction(SIGPIPE, &sact, NULL); 245:     sigaction(SIGTERM, &sact, NULL); 246: 247:     /* Now set the modified termios settings */ 248:     tcsetattr(pf, TCSANOW, &pts); 249:     tcsetattr(STDIN_FILENO, TCSANOW, &sts); 250: 251:     ufds[0].fd = STDIN_FILENO; 252:     ufds[0].events = POLLIN; 253:     ufds[1].fd = pf; 254:     ufds[1].events = POLLIN; 255: 256:     do { 257:         int r; 258: 259:         r = poll(ufds, 2, -1); 260:         if ((r < 0) && (errno != EINTR)) 261:             die(1, "poll failed unexpectedly", ""); 262: 263:         /* First check for an opportunity to exit */ 264:         if ((ufds[0].revents | ufds[1].revents) & 265:             (POLLERR | POLLHUP | POLLNVAL)) { 266:             done = 1; 267:             break; 268:         } 269: 270:         if (ufds[1].revents & POLLIN) { 271:             /* pf has characters for us */ 272:             i = read(pf, buf, BUFSIZE); 273:             if (i >= 1) { 274:                 write(STDOUT_FILENO, buf, i); 275:             } else { 276:                 done = 1; 277:             } 278:         } 279:         if (ufds[0].revents & POLLIN) { 280:             /* standard input has characters for us */ 281:             i = read(STDIN_FILENO, buf, BUFSIZE); 282:             if (i >= 1) { 283:                 if (raw) { 284:                     write(pf, buf, i); 285:                 } else { 286:                     cook_buf(pf, buf, i); 287:                 } 288:             } else { 289:                 done = 1; 290:         } 291:     } 292:     } while (!done); 293: 294:     /* restore original terminal settings and exit */ 295:     tcsetattr(pf, TCSANOW, &pots); 296:     tcsetattr(STDIN_FILENO, TCSANOW, &sots); 297:     exit(0); 298: } 

robin.c starts out by including a few header files (read the man page for each system call and library function to see which include files you need to include, as usual), then defines a few useful functions.

The symbolic_speed() function at line 19 converts an integer speed into a symbolic speed that termios can handle. Unfortunately, termios is not designed to handle arbitrary speeds, so each speed you wish to use must be part of the user-kernel interface.[4]

[4] See man setserial for a Linux-specific way to get around this limitation on a limited basis.

Note that it includes some rather high speeds. Not all serial ports support speeds as high as 230,400 or 460,800 bps; the POSIX standard defines speeds only up to 38,400 bps. To make this program portable, each line above the one that sets the speed to 38,400 bps would have to be expanded to three lines, like this:

 #  ifdef B460800     if (speednum >= 460800) return B460800; #  endif 

That still allows users to specify speeds beyond what the serial ports may be able to handle, but the source code will now compile on any system with POSIX termios. (As discussed on page 352 and page 371, any serial port has the option of refusing to honor any termios setting it is incapable of handling, and that includes speed settings. So just because B460800 is defined does not mean you can set the port speed to 460,800 bits per second.)

Next, on lines 44 through 55, we see a few global variables for communicating some variables to a signal handler, and the signal handler itself. The signal handler is designed to restore termios settings on both tty interfaces when a signal is delivered, so it needs to be able to access the structures containing the old termios settings. It also needs to know the file descriptor of the serial port (the file descriptor for standard input does not change, so it is compiled into the binary). The code is identical to that in the normal exit path, which is described later. The signal handler is later attached to signals that would terminate the process if they were ignored.

The send_escape() and cook_buf() functions are discussed later. They are used as part of the input processing in the I/O loop at the end of the main() function.

The conditionally compiled sleep(600) at the beginning of the main() function is there for debugging. In order to debug programs that modify termios settings for standard input or standard output, it is best to attach to the process from a different window or terminal session. However, that means that you cannot just set a breakpoint on the main function and step into the process one instruction at a time. You have to start the program running, find its pid, and attach to it from within the debugger. This process is described in more detail on page 370.

Therefore, if we are debugging and need to debug code that runs before the program waits for input, we need the program to sleep for a while to give us time to attach. Once we attach, we interrupt the sleep, so there is no harm in using a long sleep time. Compile robin.c with -DDSLEEP in order to activate this feature.

Ignoring debugging, we first parse the options using the popt library described in Chapter 26, and then open the serial port to which we will talk.

Next, we use the tcgetattr() function to get the existing termios configuration of the serial port, then we save a copy in pots so that we can restore it when we are through.

Starting with line 183, we modify settings for the serial port:

 183:     pts.c_lflag &= ~ICANON; 

This line turns off canonicalization in the serial port driver that is, puts it in raw mode. In this mode, no characters are special not newlines, not control characters:

 184:     pts.c_lflag &= ~(ECHO | ECHOCTL | ECHONL); 

This turns off all local echoing on the serial port:

 185:     pts.c_cflag |= HUPCL; 

If there is a modem connected, HUPCL arranges for it to be told to hang up when the final program closes the device:

 186:     pts.c_cc[VMIN] = 1; 187:     pts.c_cc[VTIME] = 0; 

When a tty is in raw mode, these two settings determine the read() system call's behavior. This particular setting says that when we call read(), we want read() to wait to return until one or more bytes have been read. We never call read() unless we know that there is at least one byte to read, so this is functionally equivalent to a nonblocking read(). The definition of VMIN and VTIME is complex, as we demonstrate on page 387.

The default termios settings include some end-of-line character translation. That is okay for dial-in lines and for terminal sessions, but when we are connecting two ttys, we do not want the translation to happen twice. We do not want to map newline characters to a carriage return/newline pair on output, and we do not want to map a received carriage return to a newline on input, because we are already receiving carriage return/newline pairs from the remote system:

 194:     pts.c_oflag &= ~ONLCR; 195:     pts.c_iflag &= ~ICRNL; 

Without these two lines, using robin to connect to another Linux or Unix computer would result in the remote system seeing you press Return twice each time you press it once, and each time it tries to display a new line on your screen, you will see two lines. So each time you press Return (assuming you manage to log in with these terminal settings), you will see two prompts echoed back to you, and if you run vi, you will see ~ characters on every other line rather than on every line.

At this point, we have made all the changes to the serial port's termios settings that we know we need to make before we process the command-line arguments. We now turn to modifying the settings for the tty that gives us standard input and output. Since it is one tty, we need to deal with only one file descriptor of the pair. We have chosen standard input, following the convention set by the stty program. We start out, again, by getting and saving attributes.

Then we modify some flags:

 201:     sts.c_iflag &= ~(BRKINT | ICRNL); 202:     sts.c_iflag |= IGNBRK; 203:     sts.c_lflag &= ~ISIG; 

Turning off BRKINT makes a difference only if robin is being called from a login session attached to another serial port on which a break can be received. Turning it off means that the tty driver does not send a SIGINT to robin when a break condition occurs on robin's standard input, since robin does not have anything useful to do when it receives a break. Turning off ICRNL prevents any received carriage-return ('\r') characters from being reported to robin as newline ('\n')characters. Like turning off BRKINT, this applies only when the login session is attached to another serial port. In addition, it applies only if carriage-return characters are not being ignored (that is, if the IGNCR flag is not set).

The IGNBRK function tells the tty driver to ignore breaks. Turning on IGNBRK is actually redundant here. If IGNBRK is set, then BRKINT is ignored. But it does not hurt to set both.

Those are input processing flags. We also modify a local processing flag: We turn off ISIG. This keeps the tty driver from sending SIGINT, SIGQUIT, and SIGTSTP when the respective character (INTR, QUIT, or SUSP) is received. We do this because we want those characters to be sent to the remote system (or whatever is connected to the serial port) for processing there.

Next comes option handling. In some cases, the default modifications we made to the termios settings might not be sufficient, or they might be too much. In these cases, we provide some command-line options to modify the termios options.

By default, we leave the serial port in whatever flow-control state we find it in. However, at line 212, there are options to do hardware flow control (which uses the CTS and RTS flow-control wires), software flow control (reserving ^S and ^Q for STOP and START, respectively), and explicitly disable flow control entirely:

 212:     case 'h': 213:         /* hardware flow control */ 214:         pts.c_cflag |= CRTSCTS; 215:         pts.c_iflag &= ~(IXON | IXOFF | IXANY); 216:         break; 217:     case 's': 218:         /* software flow control */ 219:         pts.c_cflag &= ~CRTSCTS; 220:         pts.c_iflag |= IXON | IXOFF | IXANY; 221:         break; 222:     case 'n': 223:         /* no flow control */ 224:         pts.c_cflag &= ~CRTSCTS; 225:         pts.c_iflag &= ~(IXON | IXOFF | IXANY); 226:         break; 

Note that software flow control involves three flags:


Stop sending output if a STOP character (usually, ^S) is received, and start again when a START character (usually, ^Q) is received.


Send a STOP character when there is too much data in the incoming buffer, and send a START character when enough of the data has been read.


Allow any received character, not just START, to restart output. (This flag is commonly implemented on Unix systems, but it is not specified by POSIX.)

When robin is being used as a helper program by another program, doing any special character processing (robin usually interprets a control-\ character specially) may get in the way, so we provide raw mode to sidestep all such processing. On line 120, we provide a variable that determines whether raw mode is enabled; the default is not to enable raw mode. On line 140, we tell popt how to inform us that the -r or --raw option was given on the command line, enabling raw mode.

Some systems require that you send them a carriage-return character to represent a newline. The word systems should be taken broadly here; as an example, this is true of many smart peripherals, such as UPSs, that have serial ports, because they were designed to function in the DOS world, where a carriage return/newline pair is always used to denote a new line. Line 228 allows us to specify this DOS-oriented behavior:

 228:     if (crnl) { 229:         /* send CR with NL */ 230:         pts.c_oflag |= ONLCR; 231:     } 

The final bit of option handling controls the bits per second[5] rate. Rather than include a large nested switch statement here, we call symbolic_speed(), already described, to get a speed_t that termios understands, as shown on line 233.

[5] Note: "bits per second" or "bps," not "baud." Bits per second indicates the rate at which information is sent. Baud is an engineering term that describes phase changes per second. Baud is irrelevant to termios, but the word baud unfortunately has made its way into some termios flags that are not covered in this book.

 233:     /* speed is not modified unless -b is specified */ 234:     if (speed) { 235:         cfsetospeed(&pts, symbolic_speed(speed)); 236:         cfsetispeed(&pts, symbolic_speed(speed)); 237:     } 

Having determined the actual speed and gotten the symbolic value that represents it, we need to put that speed into the termios structure for the serial port. Since the termios structure supports asynchronous devices that can have different input and output speeds, we need to set both speeds to the same value here.

Before committing the changes we have made in our copies of the termios structures to the devices, on line 241 we register signal handlers for important signals that might otherwise kill us, causing us to leave the ttys in their raw state. For more information on signal handlers, see Chapter 12.

 241:     sact.sa_handler = cleanup_termios; 242:     sigaction(SIGHUP, &sact, NULL); 243:     sigaction(SIGINT, &sact, NULL); 244:     sigaction(SIGPIPE, &sact, NULL); 245:     sigaction(SIGTERM, &sact, NULL); 

Once the signal handler is in place to restore the old termios settings if robin is killed, we can safely put the new termios settings in place:

 248:     tcsetattr(pf, TCSANOW, &pts); 249:     tcsetattr(STDIN_FILENO, TCSANOW, &sts); 

Note that we use the TCSANOW option when setting these options, indicating that we want them to take effect immediately. Sometimes, it is appropriate to use other options; they are covered later in the reference section.

At this point, robin is ready to read and write characters. robin has two file descriptors to read from: data coming from the serial port and data coming from the keyboard. We use poll() to multiplex I/O between the four file descriptors, as described on page 245.

The poll() loop makes the simplifying assumption that it can always write as much as it was able to read. This is almost always true and does not cause problems in practice, because blocking for short periods is not noticeable in normal circumstances. The loop never reads from a file descriptor unless poll() has already said that that file descriptor has data waiting to be read, so we know that we do not block while reading.

For data coming from the keyboard, we may need to process escape sequences before writing, if the user did not select raw mode when running robin. Rather than include that code in the middle of this loop, we call cook_buf() (line 78), which calls send_escape() (line 58) when necessary. Both of these functions are simple. The only tricks are that cook_buf() may be called once with the escape character, then a second time with the character to interpret, and that it is optimized to call the write() function as little as is reasonably possible.

cook_buf() calls the send_escape() function once for every character that is preceded by an unescaped control-\ character. A q character restores the original termios settings and quits by calling the signal handler (with a bogus signal number of 0), which restores the termios settings appropriately before exiting. A b character generates a break condition, which is a long string of continuous 0 bits. Any other character, including a second control-\ character, is passed through to the serial port verbatim.

If either input file descriptor returns an end-of-file condition, robin exits the poll() loop and falls through to the exit processing, which is the same as the signal handler: It restores the old termios settings on both input file descriptors and exits. In raw mode, the only ways to get robin to quit are to close one of the file descriptors or to send it a signal.


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

    Similar book on Amazon © 2008-2017.
    If you may any questions please contact us: