1291 lines
28 KiB
C
1291 lines
28 KiB
C
/*
|
|
* io.c --- routines for dealing with input and output and records
|
|
*/
|
|
|
|
/*
|
|
* Copyright (C) 1986, 1988, 1989, 1991-1995 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 2 of the License, 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, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
|
*/
|
|
|
|
#if !defined(VMS) && !defined(VMS_POSIX) && !defined(_MSC_VER)
|
|
#include <sys/param.h>
|
|
#endif
|
|
#include "awk.h"
|
|
|
|
#ifndef O_RDONLY
|
|
#include <fcntl.h>
|
|
#endif
|
|
|
|
#if !defined(S_ISDIR) && defined(S_IFDIR)
|
|
#define S_ISDIR(m) (((m) & S_IFMT) == S_IFDIR)
|
|
#endif
|
|
|
|
#ifndef ENFILE
|
|
#define ENFILE EMFILE
|
|
#endif
|
|
|
|
#ifndef atarist
|
|
#define INVALID_HANDLE (-1)
|
|
#else
|
|
#define INVALID_HANDLE (__SMALLEST_VALID_HANDLE - 1)
|
|
#endif
|
|
|
|
#if defined(MSDOS) || defined(OS2) || defined(atarist)
|
|
#define PIPES_SIMULATED
|
|
#endif
|
|
|
|
static IOBUF *nextfile P((int skipping));
|
|
static int inrec P((IOBUF *iop));
|
|
static int iop_close P((IOBUF *iop));
|
|
struct redirect *redirect P((NODE *tree, int *errflg));
|
|
static void close_one P((void));
|
|
static int close_redir P((struct redirect *rp, int exitwarn));
|
|
#ifndef PIPES_SIMULATED
|
|
static int wait_any P((int interesting));
|
|
#endif
|
|
static IOBUF *gawk_popen P((char *cmd, struct redirect *rp));
|
|
static IOBUF *iop_open P((const char *file, const char *how));
|
|
static int gawk_pclose P((struct redirect *rp));
|
|
static int do_pathopen P((const char *file));
|
|
static int str2mode P((const char *mode));
|
|
static void spec_setup P((IOBUF *iop, int len, int allocate));
|
|
static int specfdopen P((IOBUF *iop, const char *name, const char *mode));
|
|
static int pidopen P((IOBUF *iop, const char *name, const char *mode));
|
|
static int useropen P((IOBUF *iop, const char *name, const char *mode));
|
|
|
|
extern FILE *fdopen();
|
|
|
|
#if defined (MSDOS)
|
|
#include "popen.h"
|
|
#define popen(c,m) os_popen(c,m)
|
|
#define pclose(f) os_pclose(f)
|
|
#else
|
|
#if defined (OS2) /* OS/2, but not family mode */
|
|
#if defined (_MSC_VER)
|
|
#define popen(c,m) _popen(c,m)
|
|
#define pclose(f) _pclose(f)
|
|
#endif
|
|
#else
|
|
extern FILE *popen();
|
|
#endif
|
|
#endif
|
|
|
|
static struct redirect *red_head = NULL;
|
|
|
|
extern int output_is_tty;
|
|
extern NODE *ARGC_node;
|
|
extern NODE *ARGV_node;
|
|
extern NODE *ARGIND_node;
|
|
extern NODE *ERRNO_node;
|
|
extern NODE **fields_arr;
|
|
|
|
static jmp_buf filebuf; /* for do_nextfile() */
|
|
|
|
/* do_nextfile --- implement gawk "next file" extension */
|
|
|
|
void
|
|
do_nextfile()
|
|
{
|
|
(void) nextfile(1);
|
|
longjmp(filebuf, 1);
|
|
}
|
|
|
|
static IOBUF *
|
|
nextfile(skipping)
|
|
int skipping;
|
|
{
|
|
static int i = 1;
|
|
static int files = 0;
|
|
NODE *arg;
|
|
static IOBUF *curfile = NULL;
|
|
|
|
if (skipping) {
|
|
if (curfile != NULL)
|
|
iop_close(curfile);
|
|
curfile = NULL;
|
|
return NULL;
|
|
}
|
|
if (curfile != NULL) {
|
|
if (curfile->cnt == EOF) {
|
|
(void) iop_close(curfile);
|
|
curfile = NULL;
|
|
} else
|
|
return curfile;
|
|
}
|
|
for (; i < (int) (ARGC_node->lnode->numbr); i++) {
|
|
arg = *assoc_lookup(ARGV_node, tmp_number((AWKNUM) i));
|
|
if (arg->stptr[0] == '\0')
|
|
continue;
|
|
arg->stptr[arg->stlen] = '\0';
|
|
if (! do_unix) {
|
|
ARGIND_node->var_value->numbr = i;
|
|
ARGIND_node->var_value->flags = NUM|NUMBER;
|
|
}
|
|
if (!arg_assign(arg->stptr)) {
|
|
files++;
|
|
curfile = iop_open(arg->stptr, "r");
|
|
if (curfile == NULL)
|
|
fatal("cannot open file `%s' for reading (%s)",
|
|
arg->stptr, strerror(errno));
|
|
/* NOTREACHED */
|
|
/* This is a kludge. */
|
|
unref(FILENAME_node->var_value);
|
|
FILENAME_node->var_value = dupnode(arg);
|
|
FNR = 0;
|
|
i++;
|
|
break;
|
|
}
|
|
}
|
|
if (files == 0) {
|
|
files++;
|
|
/* no args. -- use stdin */
|
|
/* FNR is init'ed to 0 */
|
|
FILENAME_node->var_value = make_string("-", 1);
|
|
curfile = iop_alloc(fileno(stdin));
|
|
}
|
|
return curfile;
|
|
}
|
|
|
|
void
|
|
set_FNR()
|
|
{
|
|
FNR = (long) FNR_node->var_value->numbr;
|
|
}
|
|
|
|
void
|
|
set_NR()
|
|
{
|
|
NR = (long) NR_node->var_value->numbr;
|
|
}
|
|
|
|
/*
|
|
* This reads in a record from the input file
|
|
*/
|
|
static int
|
|
inrec(iop)
|
|
IOBUF *iop;
|
|
{
|
|
char *begin;
|
|
register int cnt;
|
|
int retval = 0;
|
|
|
|
cnt = get_a_record(&begin, iop, *RS, NULL);
|
|
if (cnt == EOF) {
|
|
cnt = 0;
|
|
retval = 1;
|
|
} else {
|
|
NR += 1;
|
|
FNR += 1;
|
|
set_record(begin, cnt, 1);
|
|
}
|
|
|
|
return retval;
|
|
}
|
|
|
|
static int
|
|
iop_close(iop)
|
|
IOBUF *iop;
|
|
{
|
|
int ret;
|
|
|
|
if (iop == NULL)
|
|
return 0;
|
|
errno = 0;
|
|
|
|
#ifdef _CRAY
|
|
/* Work around bug in UNICOS popen */
|
|
if (iop->fd < 3)
|
|
ret = 0;
|
|
else
|
|
#endif
|
|
/* save these for re-use; don't free the storage */
|
|
if ((iop->flag & IOP_IS_INTERNAL) != 0) {
|
|
iop->off = iop->buf;
|
|
iop->end = iop->buf + strlen(iop->buf);
|
|
iop->cnt = 0;
|
|
iop->secsiz = 0;
|
|
return 0;
|
|
}
|
|
|
|
/* Don't close standard files or else crufty code elsewhere will lose */
|
|
if (iop->fd == fileno(stdin) ||
|
|
iop->fd == fileno(stdout) ||
|
|
iop->fd == fileno(stderr))
|
|
ret = 0;
|
|
else
|
|
ret = close(iop->fd);
|
|
if (ret == -1)
|
|
warning("close of fd %d failed (%s)", iop->fd, strerror(errno));
|
|
if ((iop->flag & IOP_NO_FREE) == 0) {
|
|
/*
|
|
* be careful -- $0 may still reference the buffer even though
|
|
* an explicit close is being done; in the future, maybe we
|
|
* can do this a bit better
|
|
*/
|
|
if (iop->buf) {
|
|
if ((fields_arr[0]->stptr >= iop->buf)
|
|
&& (fields_arr[0]->stptr < iop->end)) {
|
|
NODE *t;
|
|
|
|
t = make_string(fields_arr[0]->stptr,
|
|
fields_arr[0]->stlen);
|
|
unref(fields_arr[0]);
|
|
fields_arr [0] = t;
|
|
reset_record ();
|
|
}
|
|
free(iop->buf);
|
|
}
|
|
free((char *)iop);
|
|
}
|
|
return ret == -1 ? 1 : 0;
|
|
}
|
|
|
|
void
|
|
do_input()
|
|
{
|
|
IOBUF *iop;
|
|
extern int exiting;
|
|
|
|
(void) setjmp(filebuf);
|
|
|
|
while ((iop = nextfile(0)) != NULL) {
|
|
if (inrec(iop) == 0)
|
|
while (interpret(expression_value) && inrec(iop) == 0)
|
|
continue;
|
|
/* recover any space from C based alloca */
|
|
(void) alloca(0);
|
|
|
|
if (exiting)
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* 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;
|
|
const char *direction = "to";
|
|
const char *mode;
|
|
int fd;
|
|
const char *what = NULL;
|
|
|
|
switch (tree->type) {
|
|
case Node_redirect_append:
|
|
tflag = RED_APPEND;
|
|
/* FALL THROUGH */
|
|
case Node_redirect_output:
|
|
outflag = (RED_FILE|RED_WRITE);
|
|
tflag |= outflag;
|
|
if (tree->type == Node_redirect_output)
|
|
what = ">";
|
|
else
|
|
what = ">>";
|
|
break;
|
|
case Node_redirect_pipe:
|
|
tflag = (RED_PIPE|RED_WRITE);
|
|
what = "|";
|
|
break;
|
|
case Node_redirect_pipein:
|
|
tflag = (RED_PIPE|RED_READ);
|
|
what = "|";
|
|
break;
|
|
case Node_redirect_input:
|
|
tflag = (RED_FILE|RED_READ);
|
|
what = "<";
|
|
break;
|
|
default:
|
|
fatal ("invalid tree type %d in redirect()", tree->type);
|
|
break;
|
|
}
|
|
tmp = tree_eval(tree->subnode);
|
|
if (do_lint && ! (tmp->flags & STR))
|
|
warning("expression in `%s' redirection only has numeric value",
|
|
what);
|
|
tmp = force_string(tmp);
|
|
str = tmp->stptr;
|
|
if (str == NULL || *str == '\0')
|
|
fatal("expression for `%s' redirection has null string value",
|
|
what);
|
|
if (do_lint
|
|
&& (STREQN(str, "0", tmp->stlen) || STREQN(str, "1", tmp->stlen)))
|
|
warning("filename `%s' for `%s' redirection may be result of logical expression", str, what);
|
|
for (rp = red_head; rp != NULL; rp = rp->next)
|
|
if (strlen(rp->value) == tmp->stlen
|
|
&& STREQN(rp->value, str, tmp->stlen)
|
|
&& ((rp->flag & ~(RED_NOBUF|RED_EOF)) == 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);
|
|
str[tmp->stlen] = '\0';
|
|
rp->value = str;
|
|
rp->flag = tflag;
|
|
rp->fp = NULL;
|
|
rp->iop = NULL;
|
|
rp->pid = 0; /* unlikely that we're worried about init */
|
|
rp->status = 0;
|
|
/* 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) {
|
|
if (rp->flag & RED_EOF)
|
|
/* encountered EOF on file or pipe -- must be cleared
|
|
* by explicit close() before reading more
|
|
*/
|
|
return rp;
|
|
mode = NULL;
|
|
errno = 0;
|
|
switch (tree->type) {
|
|
case Node_redirect_output:
|
|
mode = "w";
|
|
if (rp->flag & RED_USED)
|
|
mode = "a";
|
|
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_open(str, "r");
|
|
break;
|
|
default:
|
|
cant_happen();
|
|
}
|
|
if (mode != NULL) {
|
|
fd = devopen(str, mode);
|
|
if (fd > INVALID_HANDLE) {
|
|
if (fd == fileno(stdin))
|
|
rp->fp = stdin;
|
|
else if (fd == fileno(stdout))
|
|
rp->fp = stdout;
|
|
else if (fd == fileno(stderr))
|
|
rp->fp = stderr;
|
|
else {
|
|
rp->fp = fdopen(fd, (char *) mode);
|
|
/* don't leak file descriptors */
|
|
if (rp->fp == NULL)
|
|
close(fd);
|
|
}
|
|
if (rp->fp != NULL && isatty(fd))
|
|
rp->flag |= RED_NOBUF;
|
|
}
|
|
}
|
|
if (rp->fp == NULL && rp->iop == NULL) {
|
|
/* too many files open -- close one and try again */
|
|
if (errno == EMFILE || errno == ENFILE)
|
|
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 = errno;
|
|
if (tree->type == Node_redirect_output
|
|
|| tree->type == Node_redirect_append)
|
|
fatal("can't redirect %s `%s' (%s)",
|
|
direction, str, strerror(errno));
|
|
else {
|
|
free_temp(tmp);
|
|
return NULL;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
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->flag |= RED_USED;
|
|
errno = 0;
|
|
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 (strlen(rp->value) == tmp->stlen
|
|
&& STREQN(rp->value, tmp->stptr, tmp->stlen))
|
|
break;
|
|
}
|
|
free_temp(tmp);
|
|
if (rp == NULL) /* no match */
|
|
return tmp_number((AWKNUM) 0.0);
|
|
fflush(stdout); /* synchronize regular output */
|
|
tmp = tmp_number((AWKNUM)close_redir(rp, 0));
|
|
rp = NULL;
|
|
return tmp;
|
|
}
|
|
|
|
static int
|
|
close_redir(rp, exitwarn)
|
|
register struct redirect *rp;
|
|
int exitwarn;
|
|
{
|
|
int status = 0;
|
|
char *what;
|
|
|
|
if (rp == NULL)
|
|
return 0;
|
|
if (rp->fp == stdout || rp->fp == stderr)
|
|
return 0;
|
|
errno = 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);
|
|
rp->iop = NULL;
|
|
}
|
|
}
|
|
|
|
what = (rp->flag & RED_PIPE) ? "pipe" : "file";
|
|
|
|
if (exitwarn)
|
|
warning("no explicit close of %s \"%s\" provided",
|
|
what, rp->value);
|
|
|
|
/* SVR4 awk checks and warns about status of close */
|
|
if (status) {
|
|
char *s = (char *)strerror(errno);
|
|
|
|
/*
|
|
* Too many people have complained about this.
|
|
* As of 2.15.6, it is now under lint control.
|
|
*/
|
|
if (do_lint)
|
|
warning("failure status (%d) on %s close of \"%s\" (%s)",
|
|
status, what, rp->value, s);
|
|
|
|
if (! do_unix) {
|
|
/* set ERRNO too so that program can get at it */
|
|
unref(ERRNO_node->var_value);
|
|
ERRNO_node->var_value = make_string(s, strlen(s));
|
|
}
|
|
}
|
|
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++;
|
|
}
|
|
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;
|
|
register struct redirect *next;
|
|
int status = 0;
|
|
|
|
errno = 0;
|
|
for (rp = red_head; rp != NULL; rp = next) {
|
|
next = rp->next;
|
|
/* close_redir() will print a message if needed */
|
|
/* if do_lint, warn about lack of explicit close */
|
|
if (close_redir(rp, do_lint))
|
|
status++;
|
|
rp = NULL;
|
|
}
|
|
/*
|
|
* Some of the non-Unix os's have problems doing an fclose
|
|
* on stdout and stderr. Since we don't really need to close
|
|
* them, we just flush them, and do that across the board.
|
|
*/
|
|
if (fflush(stdout)) {
|
|
warning("error writing standard output (%s).", strerror(errno));
|
|
status++;
|
|
}
|
|
if (fflush(stderr)) {
|
|
warning("error writing standard error (%s).", strerror(errno));
|
|
status++;
|
|
}
|
|
return status;
|
|
}
|
|
|
|
/* str2mode --- convert a string mode to an integer mode */
|
|
|
|
static int
|
|
str2mode(mode)
|
|
const char *mode;
|
|
{
|
|
int ret;
|
|
|
|
switch(mode[0]) {
|
|
case 'r':
|
|
ret = O_RDONLY;
|
|
break;
|
|
|
|
case 'w':
|
|
ret = O_WRONLY|O_CREAT|O_TRUNC;
|
|
break;
|
|
|
|
case 'a':
|
|
ret = O_WRONLY|O_APPEND|O_CREAT;
|
|
break;
|
|
|
|
default:
|
|
ret = 0; /* lint */
|
|
cant_happen();
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
/* devopen --- handle /dev/std{in,out,err}, /dev/fd/N, regular files */
|
|
|
|
/*
|
|
* This separate version is still needed for output, since file and pipe
|
|
* output is done with stdio. iop_open() handles input with IOBUFs of
|
|
* more "special" files. Those files are not handled here since it makes
|
|
* no sense to use them for output.
|
|
*/
|
|
|
|
int
|
|
devopen(name, mode)
|
|
const char *name, *mode;
|
|
{
|
|
int openfd = INVALID_HANDLE;
|
|
const char *cp;
|
|
char *ptr;
|
|
int flag = 0;
|
|
struct stat buf;
|
|
extern double strtod();
|
|
|
|
flag = str2mode(mode);
|
|
|
|
if (do_unix)
|
|
goto strictopen;
|
|
|
|
#ifdef VMS
|
|
if ((openfd = vms_devopen(name, flag)) >= 0)
|
|
return openfd;
|
|
#endif /* VMS */
|
|
|
|
if (STREQ(name, "-"))
|
|
openfd = fileno(stdin);
|
|
else if (STREQN(name, "/dev/", 5) && stat((char *) name, &buf) == -1) {
|
|
cp = name + 5;
|
|
|
|
if (STREQ(cp, "stdin") && (flag & O_RDONLY) == O_RDONLY)
|
|
openfd = fileno(stdin);
|
|
else if (STREQ(cp, "stdout") && (flag & O_WRONLY) == O_WRONLY)
|
|
openfd = fileno(stdout);
|
|
else if (STREQ(cp, "stderr") && (flag & O_WRONLY) == O_WRONLY)
|
|
openfd = fileno(stderr);
|
|
else if (STREQN(cp, "fd/", 3)) {
|
|
cp += 3;
|
|
openfd = (int)strtod(cp, &ptr);
|
|
if (openfd <= INVALID_HANDLE || ptr == cp)
|
|
openfd = INVALID_HANDLE;
|
|
}
|
|
}
|
|
|
|
strictopen:
|
|
if (openfd == INVALID_HANDLE)
|
|
openfd = open(name, flag, 0666);
|
|
if (openfd != INVALID_HANDLE && fstat(openfd, &buf) > 0)
|
|
if (S_ISDIR(buf.st_mode))
|
|
fatal("file `%s' is a directory", name);
|
|
return openfd;
|
|
}
|
|
|
|
|
|
/* spec_setup --- setup an IOBUF for a special internal file */
|
|
|
|
static void
|
|
spec_setup(iop, len, allocate)
|
|
IOBUF *iop;
|
|
int len;
|
|
int allocate;
|
|
{
|
|
char *cp;
|
|
|
|
if (allocate) {
|
|
emalloc(cp, char *, len+2, "spec_setup");
|
|
iop->buf = cp;
|
|
} else {
|
|
len = strlen(iop->buf);
|
|
iop->buf[len++] = '\n'; /* get_a_record clobbered it */
|
|
iop->buf[len] = '\0'; /* just in case */
|
|
}
|
|
iop->off = iop->buf;
|
|
iop->cnt = 0;
|
|
iop->secsiz = 0;
|
|
iop->size = len;
|
|
iop->end = iop->buf + len;
|
|
iop->fd = -1;
|
|
iop->flag = IOP_IS_INTERNAL;
|
|
}
|
|
|
|
/* specfdopen --- open a fd special file */
|
|
|
|
static int
|
|
specfdopen(iop, name, mode)
|
|
IOBUF *iop;
|
|
const char *name, *mode;
|
|
{
|
|
int fd;
|
|
IOBUF *tp;
|
|
|
|
fd = devopen(name, mode);
|
|
if (fd == INVALID_HANDLE)
|
|
return INVALID_HANDLE;
|
|
tp = iop_alloc(fd);
|
|
if (tp == NULL)
|
|
return INVALID_HANDLE;
|
|
*iop = *tp;
|
|
iop->flag |= IOP_NO_FREE;
|
|
free(tp);
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Following mess will improve in 2.16; this is written to avoid
|
|
* long lines, avoid splitting #if with backslash, and avoid #elif
|
|
* to maximize portability.
|
|
*/
|
|
#ifndef GETPGRP_NOARG
|
|
#if defined(__svr4__) || defined(BSD4_4) || defined(_POSIX_SOURCE)
|
|
#define GETPGRP_NOARG
|
|
#else
|
|
#if defined(i860) || defined(_AIX) || defined(hpux) || defined(VMS)
|
|
#define GETPGRP_NOARG
|
|
#else
|
|
#if defined(OS2) || defined(MSDOS) || defined(AMIGA) || defined(atarist)
|
|
#define GETPGRP_NOARG
|
|
#endif
|
|
#endif
|
|
#endif
|
|
#endif
|
|
|
|
#ifdef GETPGRP_NOARG
|
|
#define getpgrp_ARG /* nothing */
|
|
#else
|
|
#define getpgrp_ARG getpid()
|
|
#endif
|
|
|
|
/* pidopen --- "open" /dev/pid, /dev/ppid, and /dev/pgrpid */
|
|
|
|
static int
|
|
pidopen(iop, name, mode)
|
|
IOBUF *iop;
|
|
const char *name, *mode;
|
|
{
|
|
char tbuf[BUFSIZ];
|
|
int i;
|
|
|
|
if (name[6] == 'g')
|
|
sprintf(tbuf, "%d\n", getpgrp( getpgrp_ARG ));
|
|
else if (name[6] == 'i')
|
|
sprintf(tbuf, "%d\n", getpid());
|
|
else
|
|
sprintf(tbuf, "%d\n", getppid());
|
|
i = strlen(tbuf);
|
|
spec_setup(iop, i, 1);
|
|
strcpy(iop->buf, tbuf);
|
|
return 0;
|
|
}
|
|
|
|
/* useropen --- "open" /dev/user */
|
|
|
|
/*
|
|
* /dev/user creates a record as follows:
|
|
* $1 = getuid()
|
|
* $2 = geteuid()
|
|
* $3 = getgid()
|
|
* $4 = getegid()
|
|
* If multiple groups are supported, the $5 through $NF are the
|
|
* supplementary group set.
|
|
*/
|
|
|
|
static int
|
|
useropen(iop, name, mode)
|
|
IOBUF *iop;
|
|
const char *name, *mode;
|
|
{
|
|
char tbuf[BUFSIZ], *cp;
|
|
int i;
|
|
#if defined(NGROUPS_MAX) && NGROUPS_MAX > 0
|
|
#if defined(atarist) || defined(__svr4__) || defined(__osf__) || defined(__bsdi__) || defined(__NetBSD__)
|
|
gid_t groupset[NGROUPS_MAX];
|
|
#else
|
|
int groupset[NGROUPS_MAX];
|
|
#endif
|
|
int ngroups;
|
|
#endif
|
|
|
|
sprintf(tbuf, "%d %d %d %d", getuid(), geteuid(), getgid(), getegid());
|
|
|
|
cp = tbuf + strlen(tbuf);
|
|
#if defined(NGROUPS_MAX) && NGROUPS_MAX > 0
|
|
ngroups = getgroups(NGROUPS_MAX, groupset);
|
|
if (ngroups == -1)
|
|
fatal("could not find groups: %s", strerror(errno));
|
|
|
|
for (i = 0; i < ngroups; i++) {
|
|
*cp++ = ' ';
|
|
sprintf(cp, "%d", (int)groupset[i]);
|
|
cp += strlen(cp);
|
|
}
|
|
#endif
|
|
*cp++ = '\n';
|
|
*cp++ = '\0';
|
|
|
|
|
|
i = strlen(tbuf);
|
|
spec_setup(iop, i, 1);
|
|
strcpy(iop->buf, tbuf);
|
|
return 0;
|
|
}
|
|
|
|
/* iop_open --- handle special and regular files for input */
|
|
|
|
static IOBUF *
|
|
iop_open(name, mode)
|
|
const char *name, *mode;
|
|
{
|
|
int openfd = INVALID_HANDLE;
|
|
int flag = 0;
|
|
struct stat buf;
|
|
IOBUF *iop;
|
|
static struct internal {
|
|
const char *name;
|
|
int compare;
|
|
int (*fp) P((IOBUF*,const char *,const char *));
|
|
IOBUF iob;
|
|
} table[] = {
|
|
{ "/dev/fd/", 8, specfdopen },
|
|
{ "/dev/stdin", 10, specfdopen },
|
|
{ "/dev/stdout", 11, specfdopen },
|
|
{ "/dev/stderr", 11, specfdopen },
|
|
{ "/dev/pid", 8, pidopen },
|
|
{ "/dev/ppid", 9, pidopen },
|
|
{ "/dev/pgrpid", 11, pidopen },
|
|
{ "/dev/user", 9, useropen },
|
|
};
|
|
int devcount = sizeof(table) / sizeof(table[0]);
|
|
|
|
flag = str2mode(mode);
|
|
|
|
if (do_unix)
|
|
goto strictopen;
|
|
|
|
if (STREQ(name, "-"))
|
|
openfd = fileno(stdin);
|
|
else if (STREQN(name, "/dev/", 5) && stat((char *) name, &buf) == -1) {
|
|
int i;
|
|
|
|
for (i = 0; i < devcount; i++) {
|
|
if (STREQN(name, table[i].name, table[i].compare)) {
|
|
iop = & table[i].iob;
|
|
|
|
if (iop->buf != NULL) {
|
|
spec_setup(iop, 0, 0);
|
|
return iop;
|
|
} else if ((*table[i].fp)(iop, name, mode) == 0)
|
|
return iop;
|
|
else {
|
|
warning("could not open %s, mode `%s'",
|
|
name, mode);
|
|
return NULL;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
strictopen:
|
|
if (openfd == INVALID_HANDLE)
|
|
openfd = open(name, flag, 0666);
|
|
if (openfd != INVALID_HANDLE && fstat(openfd, &buf) > 0)
|
|
if ((buf.st_mode & S_IFMT) == S_IFDIR)
|
|
fatal("file `%s' is a directory", name);
|
|
iop = iop_alloc(openfd);
|
|
return iop;
|
|
}
|
|
|
|
#ifndef PIPES_SIMULATED
|
|
/* real pipes */
|
|
static int
|
|
wait_any(interesting)
|
|
int interesting; /* pid of interest, if any */
|
|
{
|
|
SIGTYPE (*hstat)(), (*istat)(), (*qstat)();
|
|
int pid;
|
|
int status = 0;
|
|
struct redirect *redp;
|
|
extern int errno;
|
|
|
|
hstat = signal(SIGHUP, SIG_IGN);
|
|
istat = signal(SIGINT, SIG_IGN);
|
|
qstat = signal(SIGQUIT, SIG_IGN);
|
|
for (;;) {
|
|
#ifdef NeXT
|
|
pid = wait((union wait *)&status);
|
|
#else
|
|
pid = wait(&status);
|
|
#endif /* NeXT */
|
|
if (interesting && pid == interesting) {
|
|
break;
|
|
} else if (pid != -1) {
|
|
for (redp = red_head; redp != NULL; redp = redp->next)
|
|
if (pid == redp->pid) {
|
|
redp->pid = -1;
|
|
redp->status = status;
|
|
if (redp->fp) {
|
|
pclose(redp->fp);
|
|
redp->fp = 0;
|
|
}
|
|
if (redp->iop) {
|
|
(void) iop_close(redp->iop);
|
|
redp->iop = 0;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
if (pid == -1 && errno == ECHILD)
|
|
break;
|
|
}
|
|
signal(SIGHUP, hstat);
|
|
signal(SIGINT, istat);
|
|
signal(SIGQUIT, qstat);
|
|
return(status);
|
|
}
|
|
|
|
static IOBUF *
|
|
gawk_popen(cmd, rp)
|
|
char *cmd;
|
|
struct redirect *rp;
|
|
{
|
|
int p[2];
|
|
register int pid;
|
|
|
|
/* used to wait for any children to synchronize input and output,
|
|
* but this could cause gawk to hang when it is started in a pipeline
|
|
* and thus has a child process feeding it input (shell dependant)
|
|
*/
|
|
/*(void) wait_any(0);*/ /* wait for outstanding processes */
|
|
|
|
if (pipe(p) < 0)
|
|
fatal("cannot open pipe \"%s\" (%s)", cmd, strerror(errno));
|
|
if ((pid = fork()) == 0) {
|
|
if (close(1) == -1)
|
|
fatal("close of stdout in child failed (%s)",
|
|
strerror(errno));
|
|
if (dup(p[1]) != 1)
|
|
fatal("dup of pipe failed (%s)", strerror(errno));
|
|
if (close(p[0]) == -1 || close(p[1]) == -1)
|
|
fatal("close of pipe failed (%s)", strerror(errno));
|
|
if (close(0) == -1)
|
|
fatal("close of stdin in child failed (%s)",
|
|
strerror(errno));
|
|
execl("/bin/sh", "sh", "-c", cmd, 0);
|
|
_exit(127);
|
|
}
|
|
if (pid == -1)
|
|
fatal("cannot fork for \"%s\" (%s)", cmd, strerror(errno));
|
|
rp->pid = pid;
|
|
if (close(p[1]) == -1)
|
|
fatal("close of pipe failed (%s)", strerror(errno));
|
|
return (rp->iop = iop_alloc(p[0]));
|
|
}
|
|
|
|
static int
|
|
gawk_pclose(rp)
|
|
struct redirect *rp;
|
|
{
|
|
(void) iop_close(rp->iop);
|
|
rp->iop = NULL;
|
|
|
|
/* process previously found, return stored status */
|
|
if (rp->pid == -1)
|
|
return (rp->status >> 8) & 0xFF;
|
|
rp->status = wait_any(rp->pid);
|
|
rp->pid = -1;
|
|
return (rp->status >> 8) & 0xFF;
|
|
}
|
|
|
|
#else /* PIPES_SIMULATED */
|
|
/* use temporary file rather than pipe */
|
|
/* except if popen() provides real pipes too */
|
|
|
|
#if defined(VMS) || defined(OS2) || defined (MSDOS)
|
|
static IOBUF *
|
|
gawk_popen(cmd, rp)
|
|
char *cmd;
|
|
struct redirect *rp;
|
|
{
|
|
FILE *current;
|
|
|
|
if ((current = popen(cmd, "r")) == NULL)
|
|
return NULL;
|
|
return (rp->iop = iop_alloc(fileno(current)));
|
|
}
|
|
|
|
static int
|
|
gawk_pclose(rp)
|
|
struct redirect *rp;
|
|
{
|
|
int rval, aval, fd = rp->iop->fd;
|
|
FILE *kludge = fdopen(fd, (char *) "r"); /* pclose needs FILE* w/ right fileno */
|
|
|
|
rp->iop->fd = dup(fd); /* kludge to allow close() + pclose() */
|
|
rval = iop_close(rp->iop);
|
|
rp->iop = NULL;
|
|
aval = pclose(kludge);
|
|
return (rval < 0 ? rval : aval);
|
|
}
|
|
#else /* VMS || OS2 || MSDOS */
|
|
|
|
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)) == INVALID_HANDLE)
|
|
return NULL;
|
|
pipes[current].name = name;
|
|
pipes[current].command = strdup(cmd);
|
|
rp->iop = iop_alloc(current);
|
|
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);
|
|
rp->iop = NULL;
|
|
|
|
/* 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 /* VMS || OS2 || MSDOS */
|
|
|
|
#endif /* PIPES_SIMULATED */
|
|
|
|
NODE *
|
|
do_getline(tree)
|
|
NODE *tree;
|
|
{
|
|
struct redirect *rp = NULL;
|
|
IOBUF *iop;
|
|
int cnt = EOF;
|
|
char *s = NULL;
|
|
int errcode;
|
|
|
|
while (cnt == EOF) {
|
|
if (tree->rnode == NULL) { /* no redirection */
|
|
iop = nextfile(0);
|
|
if (iop == NULL) /* end of input */
|
|
return tmp_number((AWKNUM) 0.0);
|
|
} else {
|
|
int redir_error = 0;
|
|
|
|
rp = redirect(tree->rnode, &redir_error);
|
|
if (rp == NULL && redir_error) { /* failed redirect */
|
|
if (! do_unix) {
|
|
s = (char *)strerror(redir_error);
|
|
|
|
unref(ERRNO_node->var_value);
|
|
ERRNO_node->var_value =
|
|
make_string(s, strlen(s));
|
|
}
|
|
return tmp_number((AWKNUM) -1.0);
|
|
}
|
|
iop = rp->iop;
|
|
if (iop == NULL) /* end of input */
|
|
return tmp_number((AWKNUM) 0.0);
|
|
}
|
|
errcode = 0;
|
|
cnt = get_a_record(&s, iop, *RS, & errcode);
|
|
if (! do_unix && errcode != 0) {
|
|
s = (char *)strerror(errcode);
|
|
|
|
unref(ERRNO_node->var_value);
|
|
ERRNO_node->var_value = make_string(s, strlen(s));
|
|
return tmp_number((AWKNUM) -1.0);
|
|
}
|
|
if (cnt == EOF) {
|
|
if (rp) {
|
|
/*
|
|
* Don't do iop_close() here if we are
|
|
* reading from a pipe; otherwise
|
|
* gawk_pclose will not be called.
|
|
*/
|
|
if (!(rp->flag & RED_PIPE)) {
|
|
(void) iop_close(iop);
|
|
rp->iop = NULL;
|
|
}
|
|
rp->flag |= RED_EOF; /* sticky EOF */
|
|
return tmp_number((AWKNUM) 0.0);
|
|
} else
|
|
continue; /* try another file */
|
|
}
|
|
if (!rp) {
|
|
NR += 1;
|
|
FNR += 1;
|
|
}
|
|
if (tree->lnode == NULL) /* no optional var. */
|
|
set_record(s, cnt, 1);
|
|
else { /* assignment to variable */
|
|
Func_ptr after_assign = NULL;
|
|
NODE **lhs;
|
|
|
|
lhs = get_lhs(tree->lnode, &after_assign);
|
|
unref(*lhs);
|
|
*lhs = make_string(s, strlen(s));
|
|
(*lhs)->flags |= MAYBE_NUM;
|
|
/* we may have to regenerate $0 here! */
|
|
if (after_assign)
|
|
(*after_assign)();
|
|
}
|
|
}
|
|
return tmp_number((AWKNUM) 1.0);
|
|
}
|
|
|
|
int
|
|
pathopen (file)
|
|
const char *file;
|
|
{
|
|
int fd = do_pathopen(file);
|
|
|
|
#ifdef DEFAULT_FILETYPE
|
|
if (! do_unix && fd <= INVALID_HANDLE) {
|
|
char *file_awk;
|
|
int save = errno;
|
|
#ifdef VMS
|
|
int vms_save = vaxc$errno;
|
|
#endif
|
|
|
|
/* append ".awk" and try again */
|
|
emalloc(file_awk, char *, strlen(file) +
|
|
sizeof(DEFAULT_FILETYPE) + 1, "pathopen");
|
|
sprintf(file_awk, "%s%s", file, DEFAULT_FILETYPE);
|
|
fd = do_pathopen(file_awk);
|
|
free(file_awk);
|
|
if (fd <= INVALID_HANDLE) {
|
|
errno = save;
|
|
#ifdef VMS
|
|
vaxc$errno = vms_save;
|
|
#endif
|
|
}
|
|
}
|
|
#endif /*DEFAULT_FILETYPE*/
|
|
|
|
return fd;
|
|
}
|
|
|
|
static int
|
|
do_pathopen (file)
|
|
const char *file;
|
|
{
|
|
static const char *savepath = DEFPATH; /* defined in config.h */
|
|
static int first = 1;
|
|
const char *awkpath;
|
|
char *cp, trypath[BUFSIZ];
|
|
int fd;
|
|
|
|
if (STREQ(file, "-"))
|
|
return (0);
|
|
|
|
if (do_unix)
|
|
return (devopen(file, "r"));
|
|
|
|
if (first) {
|
|
first = 0;
|
|
if ((awkpath = getenv ("AWKPATH")) != NULL && *awkpath)
|
|
savepath = awkpath; /* used for restarting */
|
|
}
|
|
awkpath = savepath;
|
|
|
|
/* some kind of path name, no search */
|
|
#ifdef VMS /* (strchr not equal implies either or both not NULL) */
|
|
if (strchr(file, ':') != strchr(file, ']')
|
|
|| strchr(file, '>') != strchr(file, '/'))
|
|
#else /*!VMS*/
|
|
#if defined(MSDOS) || defined(OS2)
|
|
if (strchr(file, '/') != strchr(file, '\\')
|
|
|| strchr(file, ':') != NULL)
|
|
#else
|
|
if (strchr(file, '/') != NULL)
|
|
#endif /*MSDOS*/
|
|
#endif /*VMS*/
|
|
return (devopen(file, "r"));
|
|
|
|
#if defined(MSDOS) || defined(OS2)
|
|
_searchenv(file, "AWKPATH", trypath);
|
|
if (trypath[0] == '\0')
|
|
_searchenv(file, "PATH", trypath);
|
|
return (trypath[0] == '\0') ? 0 : devopen(trypath, "r");
|
|
#else
|
|
do {
|
|
trypath[0] = '\0';
|
|
/* this should take into account limits on size of trypath */
|
|
for (cp = trypath; *awkpath && *awkpath != ENVSEP; )
|
|
*cp++ = *awkpath++;
|
|
|
|
if (cp != trypath) { /* nun-null element in path */
|
|
/* add directory punctuation only if needed */
|
|
#ifdef VMS
|
|
if (strchr(":]>/", *(cp-1)) == NULL)
|
|
#else
|
|
#if defined(MSDOS) || defined(OS2)
|
|
if (strchr(":\\/", *(cp-1)) == NULL)
|
|
#else
|
|
if (*(cp-1) != '/')
|
|
#endif
|
|
#endif
|
|
*cp++ = '/';
|
|
/* append filename */
|
|
strcpy (cp, file);
|
|
} else
|
|
strcpy (trypath, file);
|
|
if ((fd = devopen(trypath, "r")) >= 0)
|
|
return (fd);
|
|
|
|
/* no luck, keep going */
|
|
if(*awkpath == ENVSEP && awkpath[1] != '\0')
|
|
awkpath++; /* skip colon */
|
|
} while (*awkpath);
|
|
/*
|
|
* You might have one of the awk
|
|
* paths defined, WITHOUT the current working directory in it.
|
|
* Therefore try to open the file in the current directory.
|
|
*/
|
|
return (devopen(file, "r"));
|
|
#endif
|
|
}
|