| /* |
| * io.c - routines for dealing with input and output and records |
| */ |
| |
| /* |
| * Copyright (C) 1986, 1988, 1989 the Free Software Foundation, Inc. |
| * |
| * This file is part of GAWK, the GNU implementation of the |
| * AWK Progamming Language. |
| * |
| * GAWK is free software; you can redistribute it and/or modify |
| * it under the terms of the GNU General Public License as published by |
| * the Free Software Foundation; either version 1, or (at your option) |
| * any later version. |
| * |
| * GAWK is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| * GNU General Public License for more details. |
| * |
| * You should have received a copy of the GNU General Public License |
| * along with GAWK; see the file COPYING. If not, write to |
| * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. |
| */ |
| |
| #include "gawk.h" |
| #ifndef O_RDONLY |
| #include <fcntl.h> |
| #endif |
| #include <signal.h> |
| |
| extern FILE *popen(); |
| |
| static void do_file(); |
| static IOBUF *nextfile(); |
| static int get_a_record(); |
| static int iop_close(); |
| static IOBUF *iop_alloc(); |
| static void close_one(); |
| static int close_redir(); |
| static IOBUF *gawk_popen(); |
| static int gawk_pclose(); |
| |
| static struct redirect *red_head = NULL; |
| static int getline_redirect = 0; /* "getline <file" being executed */ |
| |
| extern char *line_buf; |
| extern int output_is_tty; |
| extern NODE *ARGC_node; |
| extern NODE *ARGV_node; |
| extern NODE **fields_arr; |
| |
| int field_num; |
| |
| static IOBUF * |
| nextfile() |
| { |
| static int i = 1; |
| static int files = 0; |
| static IOBUF *curfile = NULL; |
| char *arg; |
| char *cp; |
| int fd = -1; |
| |
| if (curfile != NULL && curfile->cnt != EOF) |
| return curfile; |
| for (; i < (int) (ARGC_node->lnode->numbr); i++) { |
| arg = (*assoc_lookup(ARGV_node, tmp_number((AWKNUM) i)))->stptr; |
| if (*arg == '\0') |
| continue; |
| cp = strchr(arg, '='); |
| if (cp != NULL) { |
| *cp++ = '\0'; |
| variable(arg)->var_value = make_string(cp, strlen(cp)); |
| *--cp = '='; /* restore original text of ARGV */ |
| } else { |
| files++; |
| if (STREQ(arg, "-")) |
| fd = 0; |
| else |
| fd = devopen(arg, "r"); |
| if (fd == -1) |
| fatal("cannot open file `%s' for reading (%s)", |
| arg, strerror(errno)); |
| /* NOTREACHED */ |
| /* This is a kludge. */ |
| deref = FILENAME_node->var_value; |
| do_deref(); |
| FILENAME_node->var_value = |
| make_string(arg, strlen(arg)); |
| FNR_node->var_value->numbr = 0.0; |
| i++; |
| break; |
| } |
| } |
| if (files == 0) { |
| files++; |
| /* no args. -- use stdin */ |
| /* FILENAME is init'ed to "-" */ |
| /* FNR is init'ed to 0 */ |
| fd = 0; |
| } |
| if (fd == -1) |
| return NULL; |
| return curfile = iop_alloc(fd); |
| } |
| |
| static IOBUF * |
| iop_alloc(fd) |
| int fd; |
| { |
| IOBUF *iop; |
| struct stat stb; |
| |
| /* |
| * System V doesn't have the file system block size in the |
| * stat structure. So we have to make some sort of reasonable |
| * guess. We use stdio's BUFSIZ, since that is what it was |
| * meant for in the first place. |
| */ |
| #ifdef BLKSIZE_MISSING |
| #define DEFBLKSIZE BUFSIZ |
| #else |
| #define DEFBLKSIZE (stb.st_blksize ? stb.st_blksize : BUFSIZ) |
| #endif |
| |
| if (fd == -1) |
| return NULL; |
| emalloc(iop, IOBUF *, sizeof(IOBUF), "nextfile"); |
| iop->flag = 0; |
| if (isatty(fd)) { |
| iop->flag |= IOP_IS_TTY; |
| iop->size = BUFSIZ; |
| } else if (fstat(fd, &stb) == -1) |
| fatal("can't stat fd %d (%s)", fd, strerror(errno)); |
| else if (lseek(fd, 0L, 0) == -1) |
| iop->size = DEFBLKSIZE; |
| else |
| iop->size = (stb.st_size < DEFBLKSIZE ? |
| stb.st_size+1 : DEFBLKSIZE); |
| errno = 0; |
| iop->fd = fd; |
| emalloc(iop->buf, char *, iop->size, "nextfile"); |
| iop->off = iop->buf; |
| iop->cnt = 0; |
| iop->secsiz = iop->size < BUFSIZ ? iop->size : BUFSIZ; |
| emalloc(iop->secbuf, char *, iop->secsiz, "nextfile"); |
| return iop; |
| } |
| |
| void |
| do_input() |
| { |
| IOBUF *iop; |
| extern int exiting; |
| |
| while ((iop = nextfile()) != NULL) { |
| do_file(iop); |
| if (exiting) |
| break; |
| } |
| } |
| |
| static int |
| iop_close(iop) |
| IOBUF *iop; |
| { |
| int ret; |
| |
| ret = close(iop->fd); |
| if (ret == -1) |
| warning("close of fd %d failed (%s)", iop->fd, strerror(errno)); |
| free(iop->buf); |
| free(iop->secbuf); |
| free((char *)iop); |
| return ret == -1 ? 1 : 0; |
| } |
| |
| /* |
| * This reads in a record from the input file |
| */ |
| static int |
| inrec(iop) |
| IOBUF *iop; |
| { |
| int cnt; |
| int retval = 0; |
| |
| cnt = get_a_record(&line_buf, iop); |
| if (cnt == EOF) { |
| cnt = 0; |
| retval = 1; |
| } else { |
| if (!getline_redirect) { |
| assign_number(&NR_node->var_value, |
| NR_node->var_value->numbr + 1.0); |
| assign_number(&FNR_node->var_value, |
| FNR_node->var_value->numbr + 1.0); |
| } |
| } |
| set_record(line_buf, cnt); |
| |
| return retval; |
| } |
| |
| static void |
| do_file(iop) |
| IOBUF *iop; |
| { |
| /* This is where it spends all its time. The infamous MAIN LOOP */ |
| if (inrec(iop) == 0) |
| while (interpret(expression_value) && inrec(iop) == 0) |
| ; |
| (void) iop_close(iop); |
| } |
| |
| int |
| get_rs() |
| { |
| register NODE *tmp; |
| |
| tmp = force_string(RS_node->var_value); |
| if (tmp->stlen == 0) |
| return 0; |
| return *(tmp->stptr); |
| } |
| |
| /* Redirection for printf and print commands */ |
| struct redirect * |
| redirect(tree, errflg) |
| NODE *tree; |
| int *errflg; |
| { |
| register NODE *tmp; |
| register struct redirect *rp; |
| register char *str; |
| int tflag = 0; |
| int outflag = 0; |
| char *direction = "to"; |
| char *mode; |
| int fd; |
| |
| switch (tree->type) { |
| case Node_redirect_append: |
| tflag = RED_APPEND; |
| case Node_redirect_output: |
| outflag = (RED_FILE|RED_WRITE); |
| tflag |= outflag; |
| break; |
| case Node_redirect_pipe: |
| tflag = (RED_PIPE|RED_WRITE); |
| break; |
| case Node_redirect_pipein: |
| tflag = (RED_PIPE|RED_READ); |
| break; |
| case Node_redirect_input: |
| tflag = (RED_FILE|RED_READ); |
| break; |
| default: |
| fatal ("invalid tree type %d in redirect()", tree->type); |
| break; |
| } |
| tmp = force_string(tree_eval(tree->subnode)); |
| str = tmp->stptr; |
| for (rp = red_head; rp != NULL; rp = rp->next) |
| if (STREQ(rp->value, str) |
| && ((rp->flag & ~RED_NOBUF) == tflag |
| || (outflag |
| && (rp->flag & (RED_FILE|RED_WRITE)) == outflag))) |
| break; |
| if (rp == NULL) { |
| emalloc(rp, struct redirect *, sizeof(struct redirect), |
| "redirect"); |
| emalloc(str, char *, tmp->stlen+1, "redirect"); |
| memcpy(str, tmp->stptr, tmp->stlen+1); |
| rp->value = str; |
| rp->flag = tflag; |
| rp->offset = 0; |
| rp->fp = NULL; |
| rp->iop = NULL; |
| /* maintain list in most-recently-used first order */ |
| if (red_head) |
| red_head->prev = rp; |
| rp->prev = NULL; |
| rp->next = red_head; |
| red_head = rp; |
| } |
| while (rp->fp == NULL && rp->iop == NULL) { |
| mode = NULL; |
| errno = 0; |
| switch (tree->type) { |
| case Node_redirect_output: |
| mode = "w"; |
| break; |
| case Node_redirect_append: |
| mode = "a"; |
| break; |
| case Node_redirect_pipe: |
| if ((rp->fp = popen(str, "w")) == NULL) |
| fatal("can't open pipe (\"%s\") for output (%s)", |
| str, strerror(errno)); |
| rp->flag |= RED_NOBUF; |
| break; |
| case Node_redirect_pipein: |
| direction = "from"; |
| if (gawk_popen(str, rp) == NULL) |
| fatal("can't open pipe (\"%s\") for input (%s)", |
| str, strerror(errno)); |
| break; |
| case Node_redirect_input: |
| direction = "from"; |
| rp->iop = iop_alloc(devopen(str, "r")); |
| break; |
| default: |
| cant_happen(); |
| } |
| if (mode != NULL) { |
| fd = devopen(str, mode); |
| if (fd != -1) { |
| rp->fp = fdopen(fd, mode); |
| if (isatty(fd)) |
| rp->flag |= RED_NOBUF; |
| } |
| } |
| if (rp->fp == NULL && rp->iop == NULL) { |
| /* too many files open -- close one and try again */ |
| if (errno == ENFILE || errno == EMFILE) |
| close_one(); |
| else { |
| /* |
| * Some other reason for failure. |
| * |
| * On redirection of input from a file, |
| * just return an error, so e.g. getline |
| * can return -1. For output to file, |
| * complain. The shell will complain on |
| * a bad command to a pipe. |
| */ |
| *errflg = 1; |
| if (tree->type == Node_redirect_output |
| || tree->type == Node_redirect_append) |
| fatal("can't redirect %s `%s' (%s)", |
| direction, str, strerror(errno)); |
| else |
| return NULL; |
| } |
| } |
| } |
| if (rp->offset != 0) /* this file was previously open */ |
| if (fseek(rp->fp, rp->offset, 0) == -1) |
| fatal("can't seek to %ld on `%s' (%s)", |
| rp->offset, str, strerror(errno)); |
| free_temp(tmp); |
| return rp; |
| } |
| |
| static void |
| close_one() |
| { |
| register struct redirect *rp; |
| register struct redirect *rplast = NULL; |
| |
| /* go to end of list first, to pick up least recently used entry */ |
| for (rp = red_head; rp != NULL; rp = rp->next) |
| rplast = rp; |
| /* now work back up through the list */ |
| for (rp = rplast; rp != NULL; rp = rp->prev) |
| if (rp->fp && (rp->flag & RED_FILE)) { |
| rp->offset = ftell(rp->fp); |
| if (fclose(rp->fp)) |
| warning("close of \"%s\" failed (%s).", |
| rp->value, strerror(errno)); |
| rp->fp = NULL; |
| break; |
| } |
| if (rp == NULL) |
| /* surely this is the only reason ??? */ |
| fatal("too many pipes or input files open"); |
| } |
| |
| NODE * |
| do_close(tree) |
| NODE *tree; |
| { |
| NODE *tmp; |
| register struct redirect *rp; |
| |
| tmp = force_string(tree_eval(tree->subnode)); |
| for (rp = red_head; rp != NULL; rp = rp->next) { |
| if (STREQ(rp->value, tmp->stptr)) |
| break; |
| } |
| free_temp(tmp); |
| if (rp == NULL) /* no match */ |
| return tmp_number((AWKNUM) 0.0); |
| fflush(stdout); /* synchronize regular output */ |
| return tmp_number((AWKNUM)close_redir(rp)); |
| } |
| |
| static int |
| close_redir(rp) |
| register struct redirect *rp; |
| { |
| int status = 0; |
| |
| if ((rp->flag & (RED_PIPE|RED_WRITE)) == (RED_PIPE|RED_WRITE)) |
| status = pclose(rp->fp); |
| else if (rp->fp) |
| status = fclose(rp->fp); |
| else if (rp->iop) { |
| if (rp->flag & RED_PIPE) |
| status = gawk_pclose(rp); |
| else |
| status = iop_close(rp->iop); |
| |
| } |
| /* SVR4 awk checks and warns about status of close */ |
| if (status) |
| warning("failure status (%d) on %s close of \"%s\" (%s).", |
| status, |
| (rp->flag & RED_PIPE) ? "pipe" : |
| "file", rp->value, strerror(errno)); |
| if (rp->next) |
| rp->next->prev = rp->prev; |
| if (rp->prev) |
| rp->prev->next = rp->next; |
| else |
| red_head = rp->next; |
| free(rp->value); |
| free((char *)rp); |
| return status; |
| } |
| |
| int |
| flush_io () |
| { |
| register struct redirect *rp; |
| int status = 0; |
| |
| errno = 0; |
| if (fflush(stdout)) { |
| warning("error writing standard output (%s).", strerror(errno)); |
| status++; |
| } |
| errno = 0; |
| if (fflush(stderr)) { |
| warning("error writing standard error (%s).", strerror(errno)); |
| status++; |
| } |
| for (rp = red_head; rp != NULL; rp = rp->next) |
| /* flush both files and pipes, what the heck */ |
| if ((rp->flag & RED_WRITE) && rp->fp != NULL) |
| if (fflush(rp->fp)) { |
| warning("%s flush of \"%s\" failed (%s).", |
| (rp->flag & RED_PIPE) ? "pipe" : |
| "file", rp->value, strerror(errno)); |
| status++; |
| } |
| return status; |
| } |
| |
| int |
| close_io () |
| { |
| register struct redirect *rp; |
| int status = 0; |
| |
| for (rp = red_head; rp != NULL; rp = rp->next) |
| if (close_redir(rp)) |
| status++; |
| return status; |
| } |
| |
| /* devopen --- handle /dev/std{in,out,err}, /dev/fd/N, regular files */ |
| int |
| devopen (name, mode) |
| char *name, *mode; |
| { |
| int openfd = -1; |
| FILE *fdopen (); |
| char *cp; |
| int flag = 0; |
| |
| switch(mode[0]) { |
| case 'r': |
| flag = O_RDONLY; |
| break; |
| |
| case 'w': |
| flag = O_WRONLY|O_CREAT|O_TRUNC; |
| break; |
| |
| case 'a': |
| flag = O_WRONLY|O_APPEND|O_CREAT; |
| break; |
| default: |
| cant_happen(); |
| } |
| |
| #if defined(STRICT) || defined(NO_DEV_FD) |
| return (open (name, flag, 0666)); |
| #else |
| if (strict) |
| return (open (name, flag, 0666)); |
| |
| if (!STREQN (name, "/dev/", 5)) |
| return (open (name, flag, 0666)); |
| else |
| cp = name + 5; |
| |
| /* XXX - first three tests ignore mode */ |
| if (STREQ(cp, "stdin")) |
| return (0); |
| else if (STREQ(cp, "stdout")) |
| return (1); |
| else if (STREQ(cp, "stderr")) |
| return (2); |
| else if (STREQN(cp, "fd/", 3)) { |
| cp += 3; |
| if (sscanf (cp, "%d", & openfd) == 1 && openfd >= 0) |
| /* got something */ |
| return openfd; |
| else |
| return -1; |
| } else |
| return (open (name, flag, 0666)); |
| #endif |
| } |
| |
| #ifndef MSDOS |
| static IOBUF * |
| gawk_popen(cmd, rp) |
| char *cmd; |
| struct redirect *rp; |
| { |
| int p[2]; |
| register int pid; |
| |
| rp->pid = -1; |
| rp->iop = NULL; |
| if (pipe(p) < 0) |
| return NULL; |
| if ((pid = fork()) == 0) { |
| close(p[0]); |
| dup2(p[1], 1); |
| close(p[1]); |
| execl("/bin/sh", "sh", "-c", cmd, 0); |
| _exit(127); |
| } |
| if (pid == -1) |
| return NULL; |
| rp->pid = pid; |
| close(p[1]); |
| return (rp->iop = iop_alloc(p[0])); |
| } |
| |
| static int |
| gawk_pclose(rp) |
| struct redirect *rp; |
| { |
| SIGTYPE (*hstat)(), (*istat)(), (*qstat)(); |
| int pid; |
| int status; |
| struct redirect *redp; |
| |
| iop_close(rp->iop); |
| if (rp->pid == -1) |
| return rp->status; |
| hstat = signal(SIGHUP, SIG_IGN); |
| istat = signal(SIGINT, SIG_IGN); |
| qstat = signal(SIGQUIT, SIG_IGN); |
| for (;;) { |
| pid = wait(&status); |
| if (pid == -1 && errno == ECHILD) |
| break; |
| else if (pid == rp->pid) { |
| rp->pid = -1; |
| rp->status = status; |
| break; |
| } else { |
| for (redp = red_head; redp != NULL; redp = redp->next) |
| if (pid == redp->pid) { |
| redp->pid = -1; |
| redp->status = status; |
| break; |
| } |
| } |
| } |
| signal(SIGHUP, hstat); |
| signal(SIGINT, istat); |
| signal(SIGQUIT, qstat); |
| return(rp->status); |
| } |
| #else |
| static |
| struct { |
| char *command; |
| char *name; |
| } pipes[_NFILE]; |
| |
| static IOBUF * |
| gawk_popen(cmd, rp) |
| char *cmd; |
| struct redirect *rp; |
| { |
| extern char *strdup(const char *); |
| int current; |
| char *name; |
| static char cmdbuf[256]; |
| |
| /* get a name to use. */ |
| if ((name = tempnam(".", "pip")) == NULL) |
| return NULL; |
| sprintf(cmdbuf,"%s > %s", cmd, name); |
| system(cmdbuf); |
| if ((current = open(name,O_RDONLY)) == -1) |
| return NULL; |
| pipes[current].name = name; |
| pipes[current].command = strdup(cmd); |
| return (rp->iop = iop_alloc(current)); |
| } |
| |
| static int |
| gawk_pclose(rp) |
| struct redirect *rp; |
| { |
| int cur = rp->iop->fd; |
| int rval; |
| |
| rval = iop_close(rp->iop); |
| |
| /* check for an open file */ |
| if (pipes[cur].name == NULL) |
| return -1; |
| unlink(pipes[cur].name); |
| free(pipes[cur].name); |
| pipes[cur].name = NULL; |
| free(pipes[cur].command); |
| return rval; |
| } |
| #endif |
| |
| #define DO_END_OF_BUF len = bp - iop->off;\ |
| used = last - start;\ |
| while (len + used > iop->secsiz) {\ |
| iop->secsiz *= 2;\ |
| erealloc(iop->secbuf,char *,iop->secsiz,"get");\ |
| }\ |
| last = iop->secbuf + used;\ |
| start = iop->secbuf;\ |
| memcpy(last, iop->off, len);\ |
| last += len;\ |
| iop->cnt = read(iop->fd, iop->buf, iop->size);\ |
| if (iop->cnt < 0)\ |
| return iop->cnt;\ |
| end_data = iop->buf + iop->cnt;\ |
| iop->off = bp = iop->buf; |
| |
| #define DO_END_OF_DATA iop->cnt = read(iop->fd, end_data, end_buf - end_data);\ |
| if (iop->cnt < 0)\ |
| return iop->cnt;\ |
| end_data += iop->cnt;\ |
| if (iop->cnt == 0)\ |
| break;\ |
| iop->cnt = end_data - iop->buf; |
| |
| static int |
| get_a_record(res, iop) |
| char **res; |
| IOBUF *iop; |
| { |
| register char *end_data; |
| register char *end_buf; |
| char *start; |
| register char *bp; |
| register char *last; |
| int len, used; |
| register char rs = get_rs(); |
| |
| if (iop->cnt < 0) |
| return iop->cnt; |
| if ((iop->flag & IOP_IS_TTY) && output_is_tty) |
| fflush(stdout); |
| end_data = iop->buf + iop->cnt; |
| if (iop->off >= end_data) { |
| iop->cnt = read(iop->fd, iop->buf, iop->size); |
| if (iop->cnt <= 0) |
| return iop->cnt = EOF; |
| end_data = iop->buf + iop->cnt; |
| iop->off = iop->buf; |
| } |
| last = start = bp = iop->off; |
| end_buf = iop->buf + iop->size; |
| if (rs == 0) { |
| while (!(*bp == '\n' && bp != iop->buf && bp[-1] == '\n')) { |
| if (++bp == end_buf) { |
| DO_END_OF_BUF |
| } |
| if (bp == end_data) { |
| DO_END_OF_DATA |
| } |
| } |
| if (*bp == '\n' && bp != iop->off && bp[-1] == '\n') { |
| int tmp = 0; |
| |
| /* allow for more than two newlines */ |
| while (*bp == '\n') { |
| tmp++; |
| if (++bp == end_buf) { |
| DO_END_OF_BUF |
| } |
| if (bp == end_data) { |
| DO_END_OF_DATA |
| } |
| } |
| iop->off = bp; |
| bp -= 1 + tmp; |
| } else if (bp != iop->buf && bp[-1] != '\n') { |
| warning("record not terminated"); |
| iop->off = bp + 2; |
| } else { |
| bp--; |
| iop->off = bp + 2; |
| } |
| } else { |
| while (*bp++ != rs) { |
| if (bp == end_buf) { |
| DO_END_OF_BUF |
| } |
| if (bp == end_data) { |
| DO_END_OF_DATA |
| } |
| } |
| if (*--bp != rs) { |
| warning("record not terminated"); |
| bp++; |
| } |
| iop->off = bp + 1; |
| } |
| if (start == iop->secbuf) { |
| len = bp - iop->buf; |
| if (len > 0) { |
| used = last - start; |
| while (len + used > iop->secsiz) { |
| iop->secsiz *= 2; |
| erealloc(iop->secbuf,char *,iop->secsiz,"get2"); |
| } |
| last = iop->secbuf + used; |
| start = iop->secbuf; |
| memcpy(last, iop->buf, len); |
| last += len; |
| } |
| } else |
| last = bp; |
| *last = '\0'; |
| *res = start; |
| return last - start; |
| } |
| |
| NODE * |
| do_getline(tree) |
| NODE *tree; |
| { |
| struct redirect *rp; |
| IOBUF *iop; |
| int cnt; |
| NODE **lhs; |
| int redir_error = 0; |
| |
| if (tree->rnode == NULL) { /* no redirection */ |
| iop = nextfile(); |
| if (iop == NULL) /* end of input */ |
| return tmp_number((AWKNUM) 0.0); |
| } else { |
| rp = redirect(tree->rnode, &redir_error); |
| if (rp == NULL && redir_error) /* failed redirect */ |
| return tmp_number((AWKNUM) -1.0); |
| iop = rp->iop; |
| getline_redirect++; |
| } |
| if (tree->lnode == NULL) { /* no optional var. -- read in $0 */ |
| if (inrec(iop) != 0) { |
| getline_redirect = 0; |
| return tmp_number((AWKNUM) 0.0); |
| } |
| } else { /* read in a named variable */ |
| char *s = NULL; |
| |
| lhs = get_lhs(tree->lnode, 1); |
| cnt = get_a_record(&s, iop); |
| if (!getline_redirect) { |
| assign_number(&NR_node->var_value, |
| NR_node->var_value->numbr + 1.0); |
| assign_number(&FNR_node->var_value, |
| FNR_node->var_value->numbr + 1.0); |
| } |
| if (cnt == EOF) { |
| getline_redirect = 0; |
| free(s); |
| return tmp_number((AWKNUM) 0.0); |
| } |
| *lhs = make_string(s, strlen(s)); |
| do_deref(); |
| /* we may have to regenerate $0 here! */ |
| if (field_num == 0) |
| set_record(fields_arr[0]->stptr, fields_arr[0]->stlen); |
| field_num = -1; |
| } |
| getline_redirect = 0; |
| return tmp_number((AWKNUM) 1.0); |
| } |