/* * Program: demo1.c * Written by: Scott Kilau @ Digi Unix Tech Support * * Purpose: A customer has a 16 port digiboard, set up as ttya01-ttya16, * and wants to sniff all incoming data from all 16 ports, for a specific * character, and to count the frequency of these characters versus all * other characters received on the ports. What the Devices are out on the * ports are not important, except that the ports should NOT be enabled, * (although if they ARE enabled, this program will still work fine!) thus * implying that these Devices will send data in at any random time. * These Devices are REQUIRED to be set for 19200, 7-2-O, and CTS/RTS * hardware flow control, so we also must set the 16 digi ports the same way. * * Objectives: I wanted to show the use of standard and digi ioctl()'s, * how to I/O multiplex 16 digi ports by using select(), and * also have it completely portable to all Unix's. */ #include #include #include #include #include #include #include /* For standard ioctl's */ #include /* For digi specific ioctl's */ #ifdef __linux__ # include /* Linux puts select() here */ #else # include /* SCO puts select() here */ #endif #define BUFF_SIZE 1024 #define TTY_BASE "/dev/tty" /* tty base to build our strings from */ #define MAX_WATCH_TTYS 64 /* MAX size for the our TTYArray */ #define DIGI_BASE 'a' /* The base name for our example digi ttys */ #define DIGI_PORTS 16 /* 16 ports per tty series. ie. 01-16 */ #define WATCH_CHAR 'e' /* The char we are sniffing for */ typedef struct tty_struct { char ttyname[20]; /* this tty name without "/dev/" */ int tty_fd; /* fd of this tty after open */ int total_bytes_read; /* Tally of bytes read from port */ int watched_char_hits; /* Tally of watched chars from port */ struct termio tio_old; /* Store old settings for port */ struct digi_struct dio_old; /* Store old digi settings for port */ } my_tty_array; my_tty_array TTYArray[MAX_WATCH_TTYS]; /* array to hold our tty stats */ int now_raw; /* used to see if stdin is in raw mode */ struct termio tbufsave; /* used to save stdin's old modes */ fd_set read_fds; /* our read fd_set for use with select() */ int highest_fd = 0; /* this tells select() how many fd's to watch */ /* Forward declarations, lets be nice to the compiler. */ void setup_ttys(void); void scan_fds(void); void process_tty_data(my_tty_array *); void reset_everything(void); void setraw(void); void unsetraw(void); /* main */ void main(void) { int i = 0; /* zero out everything in my tty array */ bzero(&TTYArray, sizeof(my_tty_array) * MAX_WATCH_TTYS); /* call this function to set up the tty's and their ioctl()'s. */ setup_ttys(); fprintf(stderr, "System allows %d open fd's at once.\n", FD_SETSIZE); fprintf(stderr, "select() will now sleep. Press any key to quit.\n"); /* set up stdin into raw mode, for later use in select() */ setraw(); #ifdef DEBUG for (i = 0; i < DIGI_PORTS; i++) { fprintf(stderr, "%s -> %d\n", TTYArray[i].ttyname, TTYArray[i].tty_fd); } #endif /* Loop forever */ while(1) { /* * Set up our sniffer on these ttys, there might be * many ways to do this, but since I know select(), * this is how I will set up these fds for watching. */ /* zero out fd select array */ FD_ZERO(&read_fds); /* add stdin to select array */ FD_SET(0, &read_fds); highest_fd = 0; /* now add all of our tty fds to the select array */ for (i = 0; i < DIGI_PORTS; i++) { FD_SET(TTYArray[i].tty_fd, &read_fds); if (highest_fd < TTYArray[i].tty_fd) highest_fd = TTYArray[i].tty_fd; } /* * Go into our select() loop, in this case, I want * a NULL timeout, since I want this to block forever, * until I get some data on one of my ports. */ switch(select(highest_fd + 1, &read_fds, NULL, NULL, NULL)) { case -1: perror("select() error, it should never return -1!\n"); exit(-1); case 0: /* Should never happen, as we will never timeout. */ exit(-1); default: /* at least 1 fd is alive, lets dig further */ scan_fds(); } } } /* * setup_ttys: this sets up each tty to our specific settings, using * ioctl()'s, and then adding these results to our tty array, for later * use with select() */ void setup_ttys(void) { struct termio tio; struct digi_struct dio; int i = 1; char my_buff[200]; /* * Set up a loop, to add all 16 digi ports to my array, * and to set them all to the 19200 and 7-2-O settings. */ for (i = 1; i <= DIGI_PORTS; i++) { sprintf(my_buff, "tty%c%.2d", DIGI_BASE, i); strcpy(TTYArray[i - 1].ttyname, my_buff); sprintf(my_buff, "%s%c%.2d", TTY_BASE, DIGI_BASE, i); if ((TTYArray[i - 1].tty_fd = open(my_buff, O_RDWR)) < 0) { /* Error opening this tty, exit somewhat gracefully */ fprintf(stderr, "Error opening %s\n", TTYArray[i - 1].ttyname); exit(-1); } /* call ioctl with "get", so we can get current port sets */ ioctl(TTYArray[i - 1].tty_fd, TCGETA, &tio); /* Make a backup copy of them */ ioctl(TTYArray[i - 1].tty_fd, TCGETA, &(TTYArray[i - 1].tio_old)); /* * Modify the current settings at will, I want to duplicate * a customers request of "7-2-O", and a speed of 19.2, * and a flow control setting of cts/rts flow control. * change these to whatever your device wants. */ /* Set our baud rate to 19.2 in the control flags */ tio.c_cflag = (tio.c_cflag & ~CBAUD) | B19200; /* Set our character size to 7, instead of default of 8 */ tio.c_cflag = (tio.c_cflag & ~CS8) | CS7; /* Set our stop bits to 2, instead of the usual 1 */ tio.c_cflag = (tio.c_cflag) | CSTOPB; /* Set up parity generation and make it odd */ tio.c_cflag = (tio.c_cflag) | PARENB | PARODD; #if 0 /* Note: here is where you could set xon/xoff or cts/rts * flow control, but we want to use digi's specific signals * of "ctspace/rtspace", instead of "ctsflow/rtsflow", so we * will ignore the possible settings here, and call digi's * more specific ioctl() later. */ tio.c_iflag = (tio.c_iflag & ~IXANY) | IXON | IXOFF ; #endif /* Got the control modes as we want, now set them */ ioctl(TTYArray[i - 1].tty_fd, TCSETA, &tio); /* Now we want to get the digi specific settings for this tty */ ioctl(TTYArray[i - 1].tty_fd, DIGI_GETA, &dio); /* Make a backup copy of them */ ioctl(TTYArray[i - 1].tty_fd, DIGI_GETA, &(TTYArray[i - 1].dio_old)); /* Customer wants CTS/RTS flow control set for this tty */ dio.digi_flags = CTSPACE | RTSPACE; /* Now set them */ ioctl(TTYArray[i - 1].tty_fd, DIGI_SETA, &dio); } } /* * scan_fds: check to see if it was our tty's that caused * select() to wake up. */ void scan_fds(void) { int i; for (i = 0; i < DIGI_PORTS; i++) { if (FD_ISSET(TTYArray[i].tty_fd, &read_fds)) { /* Got some data in on a digi port, lets process it */ process_tty_data(&TTYArray[i]); } } /* check stdin */ if (FD_ISSET(0, &read_fds)) { reset_everything(); } } /* * One of our ports that we were watching, received data on it, perform * whatever computations we want on the data here. */ void process_tty_data(my_tty_array *spot) { char buff[BUFF_SIZE * 2 + 1]; /* 2049 */ char *ptr = (char *) 0; int total = 0, seen = 0, i = 0; buff[0] = '\0'; /* * We will never need to worry about the read blocking, as it MUST have * at least 1 char ready, since this fd was triggered in select(). */ total = read(spot->tty_fd, buff, BUFF_SIZE * 2); fprintf(stdout, "Read %d bytes from %s...", total, spot->ttyname); /* Walk the buffer, looking for the "watch" char */ for (i = 0, ptr = buff; i < total; i++, ptr++) { if (*ptr == WATCH_CHAR) { seen++; } } fprintf(stdout, " And saw %d occurances of %c\n", seen, WATCH_CHAR); spot->total_bytes_read += total; spot->watched_char_hits += seen; fprintf(stdout, "Tally for %s so far [%d bytes read, with %d occurances of %c]\n\n", spot->ttyname, spot->total_bytes_read, spot->watched_char_hits, WATCH_CHAR); } /* * reset_everything: Just closes all the ports, and resets the ioctl()'s * back to the way they were when we started the program. */ void reset_everything(void) { int i = 0; char buff[BUFF_SIZE + 1]; /* eat the input */ read(0, buff, BUFF_SIZE); for (i = 0; i < DIGI_PORTS; i++) { ioctl(TTYArray[i].tty_fd, TCSETA, &(TTYArray[i].tio_old)); ioctl(TTYArray[i].tty_fd, DIGI_SETA, &(TTYArray[i].dio_old)); close(TTYArray[i].tty_fd); } unsetraw(); fprintf(stderr, "Program terminated normally.\n"); exit(0); } /* * setraw: sets stdin into raw mode. This extra function I bring along * into all my unix programs, as it allows me to select() on the stdin fd, * without worrying about "Line mode", or any character translations. * The function originated from Advanced UNIX Programming, Sec 4.5. */ void setraw(void) { struct termio tbuf; static int first = 1; if (!first) return; first = 0; if (ioctl(0, TCGETA, &tbuf) == -1) /* may be pipe */ return; tbufsave = tbuf; tbuf.c_iflag &= ~(INLCR | ICRNL | IUCLC | ISTRIP | IXON | BRKINT); #if 0 tbuf.c_oflag &= ~OPOST; #endif tbuf.c_lflag &= ~(ICANON | ISIG | ECHO); tbuf.c_cc[4] = 5; /* MIN */ tbuf.c_cc[5] = 2; /* TIME */ if (ioctl(0, TCSETAF, &tbuf) == -1) { fprintf(stderr, "Problems setting stdin to raw mode.\n"); exit(-1); } now_raw = 1; } /* unsetraw: restores stdin back to original state. see above setraw(). */ void unsetraw(void) { static int first = 1; if (!first || !now_raw) return; first = 0; ioctl(0, TCSETAF, &tbufsave); /* may be pipe */ now_raw = 0; }