/* atmtcp.c - control ATM on TCP emulation */ /* Written 1995-2000 by Werner Almesberger, EPFL-LRC/ICA */ #if HAVE_CONFIG_H #include #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define DEFAULT_PORT 2812 /* IANA-assigned; old ATMTCP used 8401 & 8402 */ #define MAX_PACKET (ATM_MAX_AAL5_PDU+sizeof(struct atmtcp_control)+ \ sizeof(int)) typedef struct _in { int fd; void (*recv)(struct _in *in); int link; unsigned char buf[MAX_PACKET]; int bytes; void *user; struct _in *next; } IN; typedef struct _out { const struct _out_ops *ops; int link; void *user; struct _out *next; } OUT; typedef struct _out_ops { int (*open)(struct _out *out,int in_link,struct atmtcp_control *msg); void (*send)(struct _out *out,int in_link,const struct atmtcp_hdr *hdr, int size); int (*close)(struct _out *out,int in_link,struct atmtcp_control *msg); } OUT_OPS; static OUT *outputs = NULL; static IN *inputs = NULL; static fd_set in_set; static int fds = 0; int debug = 0; static int links = 0; /* misc. */ /* * Policy: * - first link sends to everybody except itself * - second link sends to everybody except itself * - all other links send to everybody except the first link the sender itself */ static int right_link(const OUT *out,int in_link) { if (out->link == in_link) return 0; return out != outputs || in_link == 1; } static void emit(int in_link,const struct atmtcp_hdr *hdr,int size) { OUT *out; if (debug) fprintf(stderr,"Emit: %d.%d, %d bytes\n",ntohs(hdr->vpi), ntohs(hdr->vci),(int) ntohl(hdr->length)); for (out = outputs; out; out = out->next) if (out->ops->send) out->ops->send(out,in_link,hdr,size); } static void control(int in_link,struct atmtcp_control *msg) { OUT *out; int changed = 0; if (debug) fprintf(stderr,"Control: (%d.%d) %s %d.%d, vcc %s\n", ntohs(msg->hdr.vpi),ntohs(msg->hdr.vci), msg->type == ATMTCP_CTRL_OPEN ? "OPEN" : msg->type == ATMTCP_CTRL_CLOSE ? "CLOSE" : "???", msg->addr.sap_addr.vpi,msg->addr.sap_addr.vci,kptr_print(&msg->vcc)); for (out = outputs; out; out = out->next) switch (msg->type) { case ATMTCP_CTRL_OPEN: if (out->ops->open) changed += out->ops->open(out,in_link,msg); break; case ATMTCP_CTRL_CLOSE: if (out->ops->close) changed += out->ops->close(out,in_link,msg); break; default: fprintf(stderr,"interval error\n"); exit(1); } if (changed > 1) fprintf(stderr,"WARNING: multiple changes on control()\n"); } /* ----- Registries -------------------------------------------------------- */ static IN *register_in(int fd,void (*receive)(IN *in),void *user) { IN *in = alloc_t(IN); IN **walk; in->fd = fd; in->recv = receive; in->link = links; in->bytes = 0; in->user = user; in->next = NULL; for (walk = &inputs; *walk; walk = &(*walk)->next); *walk = in; if (fd >= fds) fds = fd+1; FD_SET(fd,&in_set); return in; } static void unregister_in(IN *in) { IN **walk; for (walk = &inputs; *walk != in; walk = &(*walk)->next); FD_CLR(in->fd,&in_set); if (fds > in->fd+1) return; fds = 0; for (walk = &inputs; *walk; walk = &(*walk)->next) if ((*walk)->fd >= fds) fds = (*walk)->fd+1; } static void register_out(const OUT_OPS *ops,void *user) { OUT *out = alloc_t(OUT); OUT **walk; out->ops = ops; out->link = links; out->user = user; out->next = NULL; for (walk = &outputs; *walk; walk = &(*walk)->next); *walk = out; } /* ----- Virtual (ATMTCP) interface ---------------------------------------- */ static void virtual_do_send(int fd,const void *data,int size) { int wrote; wrote = write(fd,data,size); if (wrote < 0) perror("write to kernel"); else if (wrote != size) { fprintf(stderr,"bad write (%d != %d)\n",wrote,size); exit(1); } } static void virtual_in(IN *in) { const struct atmtcp_hdr *hdr = (struct atmtcp_hdr *) in->buf; int got; got = read(in->fd,in->buf,MAX_PACKET); if (got < 0) { perror("virtual interface"); exit(1); } if (!got) exit(0); /* we don't use that yet */ if (got < sizeof(struct atmtcp_hdr)) { fprintf(stderr,"kernel message too small (%d)\n",got); exit(1); } if (ntohl(hdr->length) == ATMTCP_HDR_MAGIC) { if (got < sizeof(struct atmtcp_control)) { fprintf(stderr,"invalid control message\n"); exit(1); } control(in->link,(struct atmtcp_control *) in->buf); virtual_do_send(in->fd,hdr,sizeof(struct atmtcp_control)); return; } if (got != sizeof(struct atmtcp_hdr)+ntohl(hdr->length)) { fprintf(stderr,"invalid kernel message\n"); exit(1); } emit(in->link,hdr,got); } static void virtual_send(OUT *out,int in_link,const struct atmtcp_hdr *hdr, int size) { if (!right_link(out,in_link)) return; virtual_do_send(*(int *) out->user,hdr,size); } static OUT_OPS virtual_ops = { NULL, /* open */ virtual_send, /* send */ NULL /* close */ }; /* ----- Real ATM interface ------------------------------------------------ */ typedef struct _vcc { struct sockaddr_atmpvc addr; int fd; /* for output */ IN *in; /* NULL is output only */ struct _vcc *next; } VCC; typedef struct { int itf; VCC *vccs; } REAL_DATA; static VCC **real_lookup(REAL_DATA *data,const struct sockaddr_atmpvc *addr) { VCC **walk; for (walk = &data->vccs; *walk; walk = &(*walk)->next) if ((*walk)->addr.sap_addr.vpi == addr->sap_addr.vpi && (*walk)->addr.sap_addr.vci == addr->sap_addr.vci) break; return walk; } static void real_in(IN *in) { VCC *vcc = (VCC *) in->user; struct atmtcp_hdr *hdr = (struct atmtcp_hdr *) in->buf; int got; got = read(in->fd,hdr+1,MAX_PACKET-sizeof(*hdr)); if (got < 0) { perror("real interface"); exit(1); } hdr->length = htonl(got); hdr->vpi = htons(vcc->addr.sap_addr.vpi); hdr->vci = htons(vcc->addr.sap_addr.vci); emit(in->link,hdr,got+sizeof(*hdr)); } static int real_open(OUT *out,int in_link,struct atmtcp_control *msg) { REAL_DATA *data = (REAL_DATA *) out->user; VCC **vcc; int s; if (!right_link(out,in_link)) return 0; vcc = real_lookup(data,&msg->addr); if (*vcc) { msg->result = -EADDRINUSE; return 1; } if ((s = socket(PF_ATMPVC,SOCK_DGRAM,0)) < 0) { msg->result = -errno; if (debug) perror("socket"); return 1; } if (setsockopt(s,SOL_ATM,SO_ATMQOS,&msg->qos,sizeof(msg->qos)) < 0) { msg->result = -errno; if (debug) perror("setsockopt SO_ATMQOS"); return 1; } msg->addr.sap_addr.itf = data->itf; if (connect(s,(struct sockaddr *) &msg->addr, sizeof(struct sockaddr_atmpvc)) < 0) { msg->result = -errno; if (debug) perror("connect"); return 1; } (*vcc) = alloc_t(VCC); (*vcc)->addr = msg->addr; (*vcc)->next = NULL; (*vcc)->fd = s; (*vcc)->in = msg->qos.rxtp.traffic_class == ATM_NONE ? NULL : register_in(s,real_in,*vcc); return 1; } static void real_send(OUT *out,int in_link,const struct atmtcp_hdr *hdr, int size) { REAL_DATA *data = (REAL_DATA *) out->user; struct sockaddr_atmpvc addr; VCC **vcc; int wrote; if (!right_link(out,in_link)) return; addr.sap_addr.vpi = ntohs(hdr->vpi); addr.sap_addr.vci = ntohs(hdr->vci); vcc = real_lookup(data,&addr); if (!*vcc) { if (debug) fprintf(stderr,"VCC %d.%d not found\n",addr.sap_addr.vpi, addr.sap_addr.vci); return; } wrote = write((*vcc)->fd,hdr+1,ntohl(hdr->length)); if (wrote < 0) { perror("real write"); exit(1); } if (!wrote) exit(0); /* EOF */ if (wrote != ntohl(hdr->length)) { fprintf(stderr,"bad write (%d != %d)\n",wrote,(int) ntohl(hdr->length)); exit(1); } } static int real_close(OUT *out,int in_link,struct atmtcp_control *msg) { REAL_DATA *data = (REAL_DATA *) out->user; VCC **vcc,*this; int fd; if (!right_link(out,in_link)) return 0; vcc = real_lookup(data,&msg->addr); if (!*vcc) { msg->result = -ENOENT; return 1; } this = *vcc; *vcc = this->next; if (this->in) unregister_in(this->in); fd = this->fd; free(this); if (close(fd) >= 0) return 0; msg->result = -errno; return 1; } static OUT_OPS real_ops = { real_open, /* open */ real_send, /* send */ real_close /* close */ }; /* ----- ATMTCP connection ------------------------------------------------- */ static void tcp_do_send(int fd,const void *data,int size) { int wrote; wrote = write(fd,data,size); if (wrote < 0) { perror("write to TCP"); exit(1); } if (!wrote) exit(0); /* EOF */ if (wrote != size) { fprintf(stderr,"bad write (%d != %d)\n",wrote,size); exit(1); } } static void tcp_in(IN *in) { struct atmtcp_hdr *hdr = (struct atmtcp_hdr *) in->buf; char *msg = (char *) (hdr+1); int vpi,vci; char orig_addr[MAX_ATM_ADDR_LEN]; char qos[MAX_ATM_QOS_LEN]; struct atmtcp_control ctl; int size,got; size = sizeof(*hdr)-in->bytes; if (size <= 0) size += ntohl(hdr->length); got = read(in->fd,in->buf+in->bytes,size); if (got < 0) { perror("read from file"); exit(1); } if (!got) { fprintf(stderr,"TCP disconnected\n"); exit(0); } in->bytes += got; if (in->bytes < sizeof(*hdr)) return; if (ntohl(hdr->length) > ATM_MAX_AAL5_PDU) { fprintf(stderr,"giant PDU (length = %d) received\n", (unsigned int) ntohl(hdr->length)); exit(1); } if (in->bytes < sizeof(*hdr)+ntohl(hdr->length)) return; if (debug) fprintf(stderr,"TCP %d.%d, %d bytes\n",ntohs(hdr->vpi), ntohs(hdr->vci),(unsigned int) ntohl(hdr->length)); in->bytes = 0; if (hdr->vpi || hdr->vci) { emit(in->link,hdr,sizeof(*hdr)+ntohl(hdr->length)); return; } msg[ntohl(hdr->length)] = 0; memset(&ctl,0,sizeof(ctl)); if (sscanf(msg,"O %d.%d %s %s",&vpi,&vci,orig_addr,qos) == 4) ctl.type = ATMTCP_CTRL_OPEN; else if (sscanf(msg,"C %d.%d",&vpi,&vci) == 2) ctl.type = ATMTCP_CTRL_CLOSE; else { fprintf(stderr,"unrecognized control message \"%s\"\n",msg); return; } if (debug) fprintf(stderr,"received control \"%s\"\n",msg); ctl.hdr.vpi = htons(vpi); ctl.hdr.vci = htons(vci); ctl.hdr.length = htonl(ATMTCP_HDR_MAGIC); ctl.addr.sap_family = AF_ATMPVC; ctl.addr.sap_addr.itf = 0; ctl.addr.sap_addr.vpi = vpi; ctl.addr.sap_addr.vci = vci; if (ctl.type == ATMTCP_CTRL_OPEN) { if (text2atm(orig_addr,(struct sockaddr *) &ctl.addr,sizeof(ctl.addr), T2A_PVC) < 0) { fprintf(stderr,"invalid address \"%s\"\n",orig_addr); return; } if (text2qos(qos,&ctl.qos,0) < 0) { fprintf(stderr,"invalid QOS \"%s\"\n",qos); return; } } control(in->link,&ctl); } static void tcp_send(OUT *out,int in_link,const struct atmtcp_hdr *hdr,int size) { if (!right_link(out,in_link)) return; tcp_do_send(*(int *) out->user,hdr,size); } static int tcp_control(OUT *out,int in_link,struct atmtcp_control *msg) { char buf[MAX_ATM_NAME_LEN*2+MAX_ATM_NAME_LEN+5]; struct atmtcp_hdr *hdr = (struct atmtcp_hdr *) buf; char *start = (char *) (hdr+1); char *pos = start; if (!right_link(out,in_link)) return 0; if (msg->type != ATMTCP_CTRL_OPEN && msg->type != ATMTCP_CTRL_CLOSE) { fprintf(stderr,"unrecognized control message %d\n",msg->type); return 0; } pos += sprintf(pos,"%c %d.%d",msg->type == ATMTCP_CTRL_OPEN ? 'O' : 'C', msg->addr.sap_addr.vpi,msg->addr.sap_addr.vci); if (msg->type == ATMTCP_CTRL_OPEN) { *pos++ = ' '; if (atm2text(pos,sizeof(buf)-(pos-buf),(struct sockaddr *) &msg->addr, 0) < 0) { fprintf(stderr,"invalid ATM address\n"); return 0; } pos = strchr(pos,0); *pos++ = ' '; if (qos2text(pos,sizeof(buf)-(pos-buf),&msg->qos,0) < 0) { fprintf(stderr,"invalid QOS\n"); return 0; } pos = strchr(pos,0); } hdr->vpi = hdr->vci = htons(0); hdr->length = htonl(pos-start); if (debug) fprintf(stderr,"sending control \"%s\"\n",start); tcp_do_send(*(int *) out->user,buf,pos-buf); return 0; } static OUT_OPS tcp_ops = { tcp_control, /* open */ tcp_send, /* send */ tcp_control /* close */ }; /* ----- File -------------------------------------------------------------- */ static void file_in(IN *in) { int *stream = (int *) in->buf; struct atmtcp_hdr *hdr = (struct atmtcp_hdr *) (stream+1); int size,got; size = sizeof(int)+sizeof(*hdr)-in->bytes; if (size <= 0) size += ntohl(hdr->length) == ATMTCP_HDR_MAGIC ? sizeof(struct atmtcp_control)-sizeof(*hdr) : ntohl(hdr->length); got = read(in->fd,in->buf+in->bytes,size); if (got < 0) { perror("read from file"); exit(1); } if (!got) { fprintf(stderr,"EOF\n"); exit(0); } in->bytes += got; if (in->bytes < sizeof(int)+sizeof(*hdr)) return; if (ntohl(hdr->length) == ATMTCP_HDR_MAGIC) { if (in->bytes < sizeof(int)+sizeof(struct atmtcp_control)) return; } else { if (ntohl(hdr->length) > ATM_MAX_AAL5_PDU) { fprintf(stderr,"giant PDU (length = %d) received\n", (unsigned int) ntohl(hdr->length)); exit(1); } if (in->bytes < sizeof(int)+sizeof(*hdr)+ntohl(hdr->length)) return; if (debug) fprintf(stderr,"File %d.%d, %d bytes\n",ntohs(hdr->vpi), ntohs(hdr->vci),(unsigned int) ntohl(hdr->length)); } in->bytes = 0; if (*(int *) in->user != -1 && ntohl(*stream) != *(int *) in->user) return; if (ntohl(hdr->length) == ATMTCP_HDR_MAGIC) control(in->link,(struct atmtcp_control *) hdr); else emit(in->link,hdr,sizeof(*hdr)+ntohl(hdr->length)); } static void file_write(int fd,int stream,int is_control,const void *data, int size) { int wrote; stream = htonl(stream); wrote = write(fd,&stream,sizeof(stream)); if (wrote < 0) { perror("file write"); exit(1); } if (wrote != sizeof(stream)) { fprintf(stderr,"short write (%d < %d)\n",wrote,sizeof(stream)); exit(1); } wrote = write(fd,data,size); if (wrote < 0) { perror("file write"); exit(1); } if (wrote != size) { fprintf(stderr,"short write (%d < %d)\n",wrote,size); exit(1); } } static void file_send(OUT *out,int in_link,const struct atmtcp_hdr *hdr, int size) { file_write(*(int *) out->user,in_link,0,hdr,size); } static int file_control(OUT *out,int in_link,struct atmtcp_control *msg) { file_write(*(int *) out->user,in_link,1,msg,sizeof(*msg)); return 0; } static OUT_OPS file_ops = { file_control, /* open */ file_send, /* send */ file_control /* close */ }; /* ----- Print ------------------------------------------------------------- */ static void print_send(OUT *out,int in_link,const struct atmtcp_hdr *hdr, int size) { int length = ntohl(hdr->length); int i; printf("Link %d (from link %d), VPI %d, VCI %d, %d byte%s:",out->link, in_link,ntohs(hdr->vpi),ntohs(hdr->vci),length,length == 1 ? "" : "s"); for (i = 0; i < length; i++) { if (!(i & 15)) printf("\n "); printf(" %02x",((unsigned char *) (hdr+1))[i]); } putchar('\n'); fflush(stdout); } static OUT_OPS print_ops = { NULL, /* open */ print_send, /* send */ NULL /* close */ }; /* ----- Initialization ---------------------------------------------------- */ static void background(void) { static int backgrounding = 0; pid_t pid; if (backgrounding++) { fprintf(stderr,"\"bg\" only allowed once\n"); exit(1); } pid = fork(); if (pid < 0) { perror("fork"); exit(1); } if (pid) exit(0); } static void usage(const char *name) { fprintf(stderr,"usage: %s [ -d ] [ -v ] ...\n",name); fprintf(stderr,"%6s %s -V\n\n","",name); fprintf(stderr," -d debug\n"); fprintf(stderr," -v verbose\n"); fprintf(stderr," -V version\n\n"); fprintf(stderr,": create [ ] (persistent; default itf: 0)\n"); fprintf(stderr," remove [ ] (persistent; default itf: 0)\n"); fprintf(stderr," virtual [ ] (default itf: assigned by " "kernel)\n"); fprintf(stderr," real [ ] (default itf: 0)\n"); fprintf(stderr," connect [ ]\n"); fprintf(stderr," switch [ ] (to ATMTCP " "virtual switch)\n"); fprintf(stderr," listen [ ]\n"); fprintf(stderr," read [ ]\n"); fprintf(stderr," write \n"); fprintf(stderr," print\n"); fprintf(stderr," bg (background)\n"); fprintf(stderr," wait [ ] (default: wait for [Enter])\n\n"); fprintf(stderr,"create, remove, bg, and wait don't create new links.\n"); exit(1); } #define NEXT (++optind < argc) #define HAS_MORE (optind < argc-1) #define NEED_NEXT { if (!NEXT) usage(*argv); } #define ARG argv[optind] #define NEXT_ARG argv[optind+1] int main(int argc,char **argv) { struct sockaddr_in addr; char *end; int verbose = 0; int do_create,do_background = 0,to_switch = 0; int c; FD_ZERO(&in_set); while ((c = getopt(argc,argv,"dvV")) != EOF) switch (c) { case 'd': debug = 1; break; case 'v': verbose = 1; break; case 'V': printf("%s\n",VERSION); return 0; default: usage(*argv); } optind--; if (!HAS_MORE) usage(*argv); while (NEXT) { if ((do_create = !strcmp(ARG,"create")) || !strcmp(ARG,"remove")) { int s; int itf = 0; if ((s = socket(PF_ATMSVC,SOCK_DGRAM,0)) < 0) { perror("socket"); return 1; } if (HAS_MORE) { itf = strtoul(NEXT_ARG,&end,10); if (*end) itf = 0; else (void) NEXT; } if (ioctl(s,do_create ? ATMTCP_CREATE : ATMTCP_REMOVE,itf) < 0) { perror(do_create ? "ioctl ATMTCP_CREATE" : "ioctl ATMTCP_REMOVE"); return 1; } (void) close(s); if (verbose) fprintf(stderr,"Persistent ATMTCP interface %d %s\n",itf, do_create ? "created" : "removed"); } else if (!strcmp(ARG,"virtual")) { int *fd = alloc_t(int); int itf = -1; if (HAS_MORE) { itf = strtoul(NEXT_ARG,&end,10); if (*end) itf = -1; else (void) NEXT; } if ((*fd = socket(PF_ATMSVC,SOCK_DGRAM,0)) < 0) { perror("socket"); return 1; } if ((itf = ioctl(*fd,SIOCSIFATMTCP,itf)) < 0) { perror("ioctl SIOCSIFATMTCP"); return 1; } (void) register_in(*fd,&virtual_in,NULL); register_out(&virtual_ops,fd); fprintf(stderr,"Link %d: virtual interface %d\n",links++,itf); } else if (!strcmp(ARG,"real")) { REAL_DATA *data = alloc_t(REAL_DATA); int itf = 0; if (HAS_MORE) { itf = strtoul(NEXT_ARG,&end,10); if (*end) itf = 0; else (void) NEXT; } data->itf = itf; data->vccs = NULL; register_out(&real_ops,data); fprintf(stderr,"Link %d: real interface %d\n",links++,itf); } else if (!strcmp(ARG,"connect") || (to_switch = !strcmp(ARG,"switch"))) { int *fd = alloc_t(int); const char *host,*line = NULL; int port; NEED_NEXT; host = ARG; if ((*fd = socket(PF_INET,SOCK_STREAM,0)) < 0) { perror("socket"); return 1; } addr.sin_family = AF_INET; addr.sin_addr.s_addr = text2ip(ARG,NULL,T2I_NAME | T2I_ERROR); if (addr.sin_addr.s_addr == INADDR_NONE) return 1; if (to_switch) { NEED_NEXT; line = ARG; } if (!HAS_MORE) port = DEFAULT_PORT; else { port = strtoul(NEXT_ARG,&end,10); if (*end) port = DEFAULT_PORT; else (void) NEXT; } addr.sin_port = htons(port); if (connect(*fd,(struct sockaddr *) &addr,sizeof(addr)) < 0) { perror("connect"); return 1; } (void) register_in(*fd,&tcp_in,NULL); register_out(&tcp_ops,fd); fprintf(stderr,"Link %d: ATMTCP connection to %s\n",links++,host); if (to_switch) tcp_do_send(*fd,line,strlen(line)+1); } else if (!strcmp(ARG,"listen") || (do_background = !strcmp(ARG,"listen-bg"))) { int fd,port; socklen_t addr_len; int *fd2 = alloc_t(int); if ((fd = socket(PF_INET,SOCK_STREAM,0)) < 0) { perror("socket"); return 1; } addr.sin_family = AF_INET; addr.sin_addr.s_addr = htonl(INADDR_ANY); if (!HAS_MORE) port = DEFAULT_PORT; else { port = strtoul(NEXT_ARG,&end,10); if (*end) port = DEFAULT_PORT; else (void) NEXT; } addr.sin_port = htons(port); if (bind(fd,(struct sockaddr *) &addr,sizeof(addr)) < 0) { perror("bind"); return 1; } if (listen(fd,5) < 0) { perror("listen"); return 1; } if (verbose) fprintf(stderr,"Listening on TCP port %d\n",port); if (do_background) background(); addr_len = sizeof(addr); if ((*fd2 = accept(fd,(struct sockaddr *) &addr,&addr_len)) < 0) { perror("accept"); return 1; } (void) close(fd); (void) register_in(*fd2,&tcp_in,NULL); register_out(&tcp_ops,fd2); fprintf(stderr,"Link %d: incoming ATMTCP connection from %s\n", links++,addr.sin_family == AF_INET ? inet_ntoa(addr.sin_addr) : "non-IPv4 host"); } else if (!strcmp(ARG,"read")) { int *stream = alloc_t(int); const char *name; int fd; NEED_NEXT; name = ARG; if ((fd = open(ARG,O_RDONLY)) < 0) { perror(ARG); return 1; } if (!HAS_MORE) *stream = -1; else { *stream = strtoul(NEXT_ARG,&end,10); if (*end) *stream = -1; else (void) NEXT; } (void) register_in(fd,&file_in,stream); fprintf(stderr,"Link %d: read-only file \"%s\"\n",links++,name); } else if (!strcmp(ARG,"write")) { int *fd = alloc_t(int); NEED_NEXT; if ((*fd = open(ARG,O_CREAT | O_WRONLY | O_TRUNC,0666)) < 0) { perror(ARG); return 1; } register_out(&file_ops,fd); fprintf(stderr,"Link %d: write-only file \"%s\"\n",links++,ARG); } else if (!strcmp(ARG,"print")) { register_out(&print_ops,0); fprintf(stderr,"Link %d: printing on standard output\n",links++); } else if (!strcmp(ARG,"bg")) background(); else if (!strcmp(ARG,"wait")) { int secs = -1; if (HAS_MORE) { secs = strtoul(NEXT_ARG,&end,10); if (*end) secs = -1; else (void) NEXT; } if (secs != -1) { sleep(secs); continue; } fprintf(stderr,"Press to continue\n"); do c = getchar(); while (c != EOF && c != '\n'); } else usage(*argv); } if (!fds) return 0; while (1) { IN *in; fd_set set; int ret; set = in_set; ret = select(fds,&set,NULL,NULL,NULL); if (ret < 0) { if (errno != EINTR) perror("select"); continue; } for (in = inputs; in; in = in->next) if (FD_ISSET(in->fd,&set)) in->recv(in); } }