* io.c --- routines for dealing with input and output and records
* Copyright (C) 1986, 1988, 1989, 1991, 1992 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(S_ISDIR) && defined(S_IFDIR)
#define S_ISDIR(m) (((m) & S_IFMT) == S_IFDIR)
#define INVALID_HANDLE (-1)
#define INVALID_HANDLE (__SMALLEST_VALID_HANDLE - 1)
#if defined(MSDOS) || defined(atarist)
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
));
static int wait_any
P((int interesting
));
static IOBUF
*gawk_popen
P((char *cmd
, struct redirect
*rp
));
static IOBUF
*iop_open
P((char *file
, char *how
));
static int gawk_pclose
P((struct redirect
*rp
));
static int do_pathopen
P((char *file
));
static struct redirect
*red_head
= NULL
;
extern int output_is_tty
;
extern NODE
*ARGIND_node
;
extern NODE
**fields_arr
;
static jmp_buf filebuf
; /* for do_nextfile() */
/* do_nextfile --- implement gawk "next file" extension */
static IOBUF
*curfile
= NULL
;
if (curfile
->cnt
== EOF
) {
(void) iop_close(curfile
);
for (; i
< (int) (ARGC_node
->lnode
->numbr
); i
++) {
arg
= *assoc_lookup(ARGV_node
, tmp_number((AWKNUM
) i
));
if (arg
->stptr
[0] == '\0')
arg
->stptr
[arg
->stlen
] = '\0';
ARGIND_node
->var_value
->numbr
= i
;
ARGIND_node
->var_value
->flags
= NUM
|NUMBER
;
if (!arg_assign(arg
->stptr
)) {
curfile
= iop_open(arg
->stptr
, "r");
fatal("cannot open file `%s' for reading (%s)",
arg
->stptr
, strerror(errno
));
unref(FILENAME_node
->var_value
);
FILENAME_node
->var_value
=
/* no args. -- use stdin */
/* FILENAME is init'ed to "-" */
/* FNR is init'ed to 0 */
curfile
= iop_alloc(fileno(stdin
));
FNR
= (int) FNR_node
->var_value
->numbr
;
NR
= (int) NR_node
->var_value
->numbr
;
* This reads in a record from the input file
cnt
= get_a_record(&begin
, iop
, *RS
, NULL
);
set_record(begin
, cnt
, 1);
/* Work around bug in UNICOS popen */
/* save these for re-use; don't free the storage */
if ((iop
->flag
& IOP_IS_INTERNAL
) != 0) {
iop
->end
= iop
->buf
+ strlen(iop
->buf
);
/* 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
))
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 ((fields_arr
[0]->stptr
>= iop
->buf
)
&& (fields_arr
[0]->stptr
< iop
->end
)) {
t
= make_string(fields_arr
[0]->stptr
,
return ret
== -1 ? 1 : 0;
if (setjmp(filebuf
) != 0) {
while ((iop
= nextfile(0)) != NULL
) {
while (interpret(expression_value
) && inrec(iop
) == 0)
/* Redirection for printf and print commands */
register struct redirect
*rp
;
case Node_redirect_append
:
case Node_redirect_output
:
outflag
= (RED_FILE
|RED_WRITE
);
if (tree
->type
== Node_redirect_output
)
tflag
= (RED_PIPE
|RED_WRITE
);
case Node_redirect_pipein
:
tflag
= (RED_PIPE
|RED_READ
);
case Node_redirect_input
:
tflag
= (RED_FILE
|RED_READ
);
fatal ("invalid tree type %d in redirect()", tree
->type
);
tmp
= tree_eval(tree
->subnode
);
if (do_lint
&& ! (tmp
->flags
& STR
))
warning("expression in `%s' redirection only has numeric value",
if (str
== NULL
|| *str
== '\0')
fatal("expression for `%s' redirection has null string value",
&& (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
&& (rp
->flag
& (RED_FILE
|RED_WRITE
)) == outflag
)))
emalloc(rp
, struct redirect
*, sizeof(struct redirect
),
emalloc(str
, char *, tmp
->stlen
+1, "redirect");
memcpy(str
, tmp
->stptr
, tmp
->stlen
);
rp
->pid
= 0; /* unlikely that we're worried about init */
/* maintain list in most-recently-used first order */
while (rp
->fp
== NULL
&& rp
->iop
== NULL
) {
/* encountered EOF on file or pipe -- must be cleared
* by explicit close() before reading more
case Node_redirect_output
:
case Node_redirect_append
:
if ((rp
->fp
= popen(str
, "w")) == NULL
)
fatal("can't open pipe (\"%s\") for output (%s)",
case Node_redirect_pipein
:
if (gawk_popen(str
, rp
) == NULL
)
fatal("can't open pipe (\"%s\") for input (%s)",
case Node_redirect_input
:
rp
->iop
= iop_open(str
, "r");
if (fd
> INVALID_HANDLE
) {
else if (fd
== fileno(stdout
))
else if (fd
== fileno(stderr
))
rp
->fp
= fdopen(fd
, mode
);
if (rp
->fp
== NULL
&& rp
->iop
== NULL
) {
/* too many files open -- close one and try again */
* 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.
if (tree
->type
== Node_redirect_output
|| tree
->type
== Node_redirect_append
)
fatal("can't redirect %s `%s' (%s)",
direction
, str
, strerror(errno
));
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
)
/* now work back up through the list */
for (rp
= rplast
; rp
!= NULL
; rp
= rp
->prev
)
if (rp
->fp
&& (rp
->flag
& RED_FILE
)) {
warning("close of \"%s\" failed (%s).",
rp
->value
, strerror(errno
));
/* surely this is the only reason ??? */
fatal("too many pipes or input files open");
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
))
if (rp
== NULL
) /* no match */
return tmp_number((AWKNUM
) 0.0);
fflush(stdout
); /* synchronize regular output */
tmp
= tmp_number((AWKNUM
)close_redir(rp
));
register struct redirect
*rp
;
if (rp
->fp
== stdout
|| rp
->fp
== stderr
)
if ((rp
->flag
& (RED_PIPE
|RED_WRITE
)) == (RED_PIPE
|RED_WRITE
))
status
= gawk_pclose(rp
);
status
= iop_close(rp
->iop
);
/* SVR4 awk checks and warns about status of close */
char *s
= strerror(errno
);
warning("failure status (%d) on %s close of \"%s\" (%s).",
(rp
->flag
& RED_PIPE
) ? "pipe" :
/* set ERRNO too so that program can get at it */
unref(ERRNO_node
->var_value
);
ERRNO_node
->var_value
= make_string(s
, strlen(s
));
rp
->next
->prev
= rp
->prev
;
rp
->prev
->next
= rp
->next
;
register struct redirect
*rp
;
warning("error writing standard output (%s).", strerror(errno
));
warning("error writing standard error (%s).", strerror(errno
));
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
) {
warning("%s flush of \"%s\" failed (%s).",
(rp
->flag
& RED_PIPE
) ? "pipe" :
"file", rp
->value
, strerror(errno
));
register struct redirect
*rp
;
register struct redirect
*next
;
warning("error writing standard output (%s).", strerror(errno
));
warning("error writing standard error (%s).", strerror(errno
));
for (rp
= red_head
; rp
!= NULL
; rp
= next
) {
/* str2mode --- convert a string mode to an integer mode */
ret
= O_WRONLY
|O_CREAT
|O_TRUNC
;
ret
= O_WRONLY
|O_APPEND
|O_CREAT
;
/* 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 openfd
= INVALID_HANDLE
;
if ((openfd
= vms_devopen(name
, flag
)) >= 0)
else if (STREQN(name
, "/dev/", 5) && stat(name
, &buf
) == -1) {
if (STREQ(cp
, "stdin") && (flag
& O_RDONLY
) == O_RDONLY
)
else if (STREQ(cp
, "stdout") && (flag
& O_WRONLY
) == O_WRONLY
)
else if (STREQ(cp
, "stderr") && (flag
& O_WRONLY
) == O_WRONLY
)
else if (STREQN(cp
, "fd/", 3)) {
openfd
= (int)strtod(cp
, &ptr
);
if (openfd
<= INVALID_HANDLE
|| ptr
== cp
)
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
);
/* spec_setup --- setup an IOBUF for a special internal file */
spec_setup(iop
, len
, allocate
)
emalloc(cp
, char *, len
+2, "spec_setup");
iop
->buf
[len
++] = '\n'; /* get_a_record clobbered it */
iop
->buf
[len
] = '\0'; /* just in case */
iop
->end
= iop
->buf
+ len
;
iop
->flag
= IOP_IS_INTERNAL
;
/* specfdopen --- open a fd special file */
specfdopen(iop
, name
, mode
)
fd
= devopen(name
, mode
);
if (fd
== INVALID_HANDLE
)
iop
->flag
|= IOP_NO_FREE
;
/* pidopen --- "open" /dev/pid, /dev/ppid, and /dev/pgrpid */
/* following #if will improve in 2.16 */
#if defined(__svr4__) || defined(i860) || defined(_AIX) || defined(BSD4_4) || defined(__386BSD__)
sprintf(tbuf
, "%d\n", getpgrp());
sprintf(tbuf
, "%d\n", getpgrp(getpid()));
sprintf(tbuf
, "%d\n", getpid());
sprintf(tbuf
, "%d\n", getppid());
/* useropen --- "open" /dev/user */
* /dev/user creates a record as follows:
* If multiple groups are supported, the $5 through $NF are the
* supplementary group set.
useropen(iop
, name
, mode
)
#if defined(NGROUPS_MAX) && NGROUPS_MAX > 0
int groupset
[NGROUPS_MAX
];
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
);
fatal("could not find groups: %s", strerror(errno
));
for (i
= 0; i
< ngroups
; i
++) {
sprintf(cp
, "%d", groupset
[i
]);
/* iop_open --- handle special and regular files for input */
int openfd
= INVALID_HANDLE
;
{ "/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]);
else if (STREQN(name
, "/dev/", 5) && stat(name
, &buf
) == -1) {
for (i
= 0; i
< devcount
; i
++) {
if (STREQN(name
, table
[i
].name
, table
[i
].compare
)) {
IOBUF
*iop
= & table
[i
].iob
;
} else if ((*table
[i
].fp
)(iop
, name
, mode
) == 0)
warning("could not open %s, mode `%s'",
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
);
int interesting
; /* pid of interest, if any */
SIGTYPE (*hstat
)(), (*istat
)(), (*qstat
)();
hstat
= signal(SIGHUP
, SIG_IGN
);
istat
= signal(SIGINT
, SIG_IGN
);
qstat
= signal(SIGQUIT
, SIG_IGN
);
pid
= wait((union wait
*)&status
);
if (interesting
&& pid
== interesting
) {
for (redp
= red_head
; redp
!= NULL
; redp
= redp
->next
)
(void) iop_close(redp
->iop
);
if (pid
== -1 && errno
== ECHILD
)
/* 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 */
fatal("cannot open pipe \"%s\" (%s)", cmd
, strerror(errno
));
if ((pid
= fork()) == 0) {
fatal("close of stdout in child failed (%s)",
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
));
fatal("close of stdin in child failed (%s)",
execl("/bin/sh", "sh", "-c", cmd
, 0);
fatal("cannot fork for \"%s\" (%s)", cmd
, strerror(errno
));
fatal("close of pipe failed (%s)", strerror(errno
));
return (rp
->iop
= iop_alloc(p
[0]));
(void) iop_close(rp
->iop
);
/* process previously found, return stored status */
return (rp
->status
>> 8) & 0xFF;
rp
->status
= wait_any(rp
->pid
);
return (rp
->status
>> 8) & 0xFF;
#else /* PIPES_SIMULATED */
/* use temporary file rather than pipe */
if ((current
= popen(cmd
, "r")) == NULL
)
return (rp
->iop
= iop_alloc(fileno(current
)));
int rval
, aval
, fd
= rp
->iop
->fd
;
FILE *kludge
= fdopen(fd
, "r"); /* pclose needs FILE* w/ right fileno */
rp
->iop
->fd
= dup(fd
); /* kludge to allow close() + pclose() */
rval
= iop_close(rp
->iop
);
return (rval
< 0 ? rval
: aval
);
extern char *strdup(const char *);
if ((name
= tempnam(".", "pip")) == NULL
)
sprintf(cmdbuf
,"%s > %s", cmd
, name
);
if ((current
= open(name
,O_RDONLY
)) == INVALID_HANDLE
)
pipes
[current
].name
= name
;
pipes
[current
].command
= strdup(cmd
);
rp
->iop
= iop_alloc(current
);
return (rp
->iop
= iop_alloc(current
));
rval
= iop_close(rp
->iop
);
/* check for an open file */
if (pipes
[cur
].name
== NULL
)
free(pipes
[cur
].command
);
#endif /* PIPES_SIMULATED */
struct redirect
*rp
= NULL
;
if (tree
->rnode
== NULL
) { /* no redirection */
if (iop
== NULL
) /* end of input */
return tmp_number((AWKNUM
) 0.0);
rp
= redirect(tree
->rnode
, &redir_error
);
if (rp
== NULL
&& redir_error
) { /* failed redirect */
char *s
= strerror(redir_error
);
unref(ERRNO_node
->var_value
);
make_string(s
, strlen(s
));
return tmp_number((AWKNUM
) -1.0);
if (iop
== NULL
) /* end of input */
return tmp_number((AWKNUM
) 0.0);
cnt
= get_a_record(&s
, iop
, *RS
, & errcode
);
if (! do_unix
&& errcode
!= 0) {
char *s
= strerror(errcode
);
unref(ERRNO_node
->var_value
);
ERRNO_node
->var_value
= make_string(s
, strlen(s
));
return tmp_number((AWKNUM
) -1.0);
* 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
)) {
rp
->flag
|= RED_EOF
; /* sticky EOF */
return tmp_number((AWKNUM
) 0.0);
continue; /* try another file */
if (tree
->lnode
== NULL
) /* no optional var. */
else { /* assignment to variable */
Func_ptr after_assign
= NULL
;
lhs
= get_lhs(tree
->lnode
, &after_assign
);
*lhs
= make_string(s
, strlen(s
));
(*lhs
)->flags
|= MAYBE_NUM
;
/* we may have to regenerate $0 here! */
return tmp_number((AWKNUM
) 1.0);
int fd
= do_pathopen(file
);
if (! do_unix
&& fd
<= INVALID_HANDLE
) {
int vms_save
= vaxc$errno
;
/* 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
);
if (fd
<= INVALID_HANDLE
) {
#endif /*DEFAULT_FILETYPE*/
static char *savepath
= DEFPATH
; /* defined in config.h */
return (devopen(file
, "r"));
if ((awkpath
= getenv ("AWKPATH")) != NULL
&& *awkpath
)
savepath
= awkpath
; /* used for restarting */
/* 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
, '/'))
if (strchr(file
, '/') != strchr(file
, '\\')
|| strchr(file
, ':') != NULL
)
if (strchr(file
, '/') != NULL
)
return (devopen(file
, "r"));
/* this should take into account limits on size of trypath */
for (cp
= trypath
; *awkpath
&& *awkpath
!= ENVSEP
; )
if (cp
!= trypath
) { /* nun-null element in path */
/* add directory punctuation only if needed */
if (strchr(":]>/", *(cp
-1)) == NULL
)
if (strchr(":\\/", *(cp
-1)) == NULL
)
if ((fd
= devopen(trypath
, "r")) >= 0)
/* no luck, keep going */
if(*awkpath
== ENVSEP
&& awkpath
[1] != '\0')
awkpath
++; /* skip colon */
* 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"));