/* This file is part of Mailfromd. -*- c -*-
Copyright (C) 2006-2019 Sergey Poznyakoff
This program 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 3, or (at your option)
any later version.
This program 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 this program. If not, see . */
MF_BUILTIN_MODULE
#include
#include
#include
#include
#include "global.h"
#include "msg.h"
static size_t nstreams = MAX_IOSTREAMS;
static struct mu_cfg_param io_cfg_param[] = {
{ "max-streams", mu_c_size, &nstreams, 0, NULL,
N_("Maximum number of stream descriptors.") },
{ NULL }
};
struct io_stream {
char *name;
mu_locker_t lock;
int fd[2];
pid_t pid;
char *buf;
size_t bufsize;
int (*shutdown)(struct io_stream *, int what);
void (*cleanup)(void*);
void *cleanup_data;
char *delim;
};
#define IFD(s) ((s).fd[0])
#define OFD(s) ((s).fd[1] == -1 ? (s).fd[0] : (s).fd[1])
static void
flush_stream(struct io_stream *str)
{
/*FIXME*/
}
static void
close_stream(struct io_stream *str)
{
if (OFD(*str) == -1)
return;
flush_stream(str);
close(OFD(*str));
if (IFD(*str) != -1)
close(IFD(*str));
if (str->pid) {
int status;
waitpid(str->pid, &status, 0);
}
str->fd[0] = -1;
str->fd[1] = -1;
str->pid = 0;
if (str->cleanup)
str->cleanup(str->cleanup_data);
str->cleanup = NULL;
str->cleanup_data = NULL;
if (str->name) {
free(str->name);
str->name = NULL;
}
if (str->delim) {
free(str->delim);
str->delim = NULL;
}
}
/* Read bytes from the stream STR into its buffer, until
DELIM is encountered. Return number of bytes read. */
static int
read_stream_delim(struct io_stream *str, char *delim)
{
int fd = IFD(*str);
size_t i = 0;
int rc;
size_t delim_len = strlen(delim);
for (;;) {
if (str->bufsize == i) {
if (str->bufsize == 0)
str->bufsize = 16;
str->buf = mu_2nrealloc(str->buf, &str->bufsize,
sizeof str->buf[1]);
}
rc = read(fd, str->buf + i, 1);
if (rc == -1)
return -1;
else if (rc == 0)
return 0;
i++;
if (i >= delim_len &&
memcmp(str->buf + i - delim_len, delim, delim_len) == 0) {
str->buf[i - delim_len] = 0;
break;
}
}
return i;
}
#define REDIRECT_STDIN_P(f) ((f) & (O_WRONLY|O_RDWR))
#define REDIRECT_STDOUT_P(f) (!((f) & O_WRONLY))
#define STDERR_SHUT 0
#define STDERR_NULL 1
#define STDERR_LOG 2
#define STDERR_FILE 3
#define STDERR_FILE_APPEND 4
#define LOG_TAG_PFX "mailfromd:"
#define LOG_TAG_PFX_LEN (sizeof(LOG_TAG_PFX)-1)
static void
stderr_to_log(char *arg, const char *cmd)
{
int p[2];
pid_t pid;
if (pipe(p)) {
mu_error(_("pipe failed: %s"), mu_strerror(errno));
close(2);
return;
}
pid = fork();
if (pid == (pid_t) -1) {
mu_error(_("fork failed: %s"), mu_strerror(errno));
close(p[0]);
close(p[1]);
close(2);
return;
}
/* Child */
if (pid == 0) {
FILE *fp;
fd_set fdset;
size_t len;
char buf[1024];
char *tag;
int fac = mu_log_facility, pri = LOG_ERR;
if (arg) {
char *p = strchr(arg, '.');
if (p)
*p++ = 0;
if (mu_string_to_syslog_facility(arg, &fac)) {
mu_error(_("unknown syslog facility (%s), "
"redirecting stderr to %s"),
arg,
mu_syslog_facility_to_string(fac));
}
if (p && mu_string_to_syslog_priority(p, &pri)) {
mu_error(_("unknown syslog priority (%s), "
"redirecting stderr to %s"),
arg,
mu_syslog_priority_to_string(pri));
}
}
MF_DEBUG(MU_DEBUG_TRACE2,
("redirecting stderr to syslog %s.%s",
mu_syslog_facility_to_string(fac),
mu_syslog_priority_to_string(pri)));
len = strcspn(cmd, " \t");
tag = malloc(LOG_TAG_PFX_LEN + len + 1);
if (!tag)
tag = (char*) cmd;
else {
strcpy(tag, LOG_TAG_PFX);
memcpy(tag + LOG_TAG_PFX_LEN, cmd, len);
tag[LOG_TAG_PFX_LEN + len] = 0;
}
mf_proctitle_format("%s redirector", cmd);
FD_ZERO(&fdset);
FD_SET(p[0], &fdset);
logger_fdset(&fdset);
close_fds_except(&fdset);
fp = fdopen(p[0], "r");
logger_open();
while (fgets(buf, sizeof(buf), fp))
syslog(pri, "%s", buf);
exit(0);
}
/* Parent */
close(p[0]);
dup2(p[1], 2);
close(p[1]);
}
static void
stderr_handler(int mode, char *arg, const char *cmd)
{
int fd;
int append = O_TRUNC;
switch (mode) {
case STDERR_SHUT:
close(2);
break;
case STDERR_NULL:
arg = "/dev/null";
case STDERR_FILE_APPEND:
append = O_APPEND;
case STDERR_FILE:
if (!arg || !*arg) {
close(2);
break;
}
MF_DEBUG(MU_DEBUG_TRACE2, ("redirecting stderr to %s", arg));
fd = open(arg, O_CREAT|O_WRONLY|append, 0644);
if (fd < 0) {
mu_error(_("cannot open file %s for appending: %s"),
arg, mu_strerror(errno));
close(2);
return;
}
if (fd != 2) {
dup2(fd, 2);
close(fd);
}
break;
case STDERR_LOG:
stderr_to_log(arg, cmd);
}
}
static void
parse_stderr_redirect(const char **pcmd, int *perr, char **parg)
{
int err;
size_t len;
char *arg;
const char *cmdline = *pcmd;
while (*cmdline && mu_isspace(*cmdline))
cmdline++;
if (strncmp(cmdline, "2>file:", 7) == 0) {
cmdline += 7;
err = STDERR_FILE;
} else if (strncmp(cmdline, "2>>file:", 8) == 0) {
cmdline += 8;
err = STDERR_FILE_APPEND;
} else if (strncmp(cmdline, "2>null:", 7) == 0) {
cmdline += 7;
err = STDERR_NULL;
} else if (strncmp(cmdline, "2>syslog:", 9) == 0) {
cmdline += 9;
err = STDERR_LOG;
} else
return;
len = strcspn(cmdline, " \t");
if (len > 0 && cmdline[len-1] == 0)
return;
if (len == 0)
arg = NULL;
else {
arg = malloc(len + 1);
if (!arg)
return;
memcpy(arg, cmdline, len);
arg[len] = 0;
}
*pcmd = cmdline + len;
*perr = err;
*parg = arg;
}
static int
open_program_stream_ioe(eval_environ_t env,
struct io_stream *str, const char *cmdline,
int flags,
int ioe[2])
{
int rightp[2], leftp[2];
int rc = 0;
pid_t pid;
int err = STDERR_SHUT;
char *arg = NULL;
struct mu_wordsplit ws;
parse_stderr_redirect(&cmdline, &err, &arg);
while (*cmdline && (*cmdline == ' ' || *cmdline == '\t'))
cmdline++;
if (REDIRECT_STDIN_P(flags)) {
if (pipe(leftp)) {
mu_diag_funcall(MU_DIAG_ERROR, "pipe", "leftp",
errno);
free(arg);
MF_THROW(mfe_failure, "pipe failed");
}
}
if (REDIRECT_STDOUT_P(flags)) {
if (pipe(rightp)) {
mu_diag_funcall(MU_DIAG_ERROR, "pipe", "rightp",
errno);
free(arg);
if (REDIRECT_STDIN_P(flags)) {
close(leftp[0]);
close(leftp[1]);
}
}
}
switch (pid = fork()) {
/* The child branch. */
case 0:
/* attach the pipes */
/* Right-end */
if (REDIRECT_STDOUT_P(flags)) {
if (rightp[1] != 1)
dup2(rightp[1], 1);
} else if (ioe && ioe[1] != -1 && ioe[1] != 1) {
dup2(ioe[1], 1);
}
/* Left-end */
if (REDIRECT_STDIN_P(flags)) {
if (leftp[0] != 0)
dup2(leftp[0], 0);
} else if (ioe && ioe[0] != -1 && ioe[0] != 0) {
dup2(ioe[0], 0);
}
if (ioe && ioe[2] != -1 && ioe[2] != 2)
dup2(ioe[2], 2);
else
stderr_handler(err, arg, cmdline);
/* Close unneded descripitors */
close_fds_above(2);
MF_DEBUG(MU_DEBUG_TRACE3, ("running %s", cmdline));
if (mu_wordsplit(cmdline, &ws,
MU_WRDSF_DEFFLAGS & ~MU_WRDSF_CESCAPES)) {
mu_error(_("cannot parse command line %s: %s"),
cmdline, mu_wordsplit_strerror(&ws));
exit(127);
}
execvp(ws.ws_wordv[0], ws.ws_wordv);
mu_error(_("cannot run %s: %s"),
cmdline, mu_strerror(errno));
exit(127);
/********************/
/* Parent branches: */
case -1:
/* Fork has failed */
/* Restore things */
rc = errno;
if (REDIRECT_STDOUT_P(flags)) {
close(rightp[0]);
close(rightp[1]);
}
if (REDIRECT_STDIN_P(flags)) {
close(leftp[0]);
close(leftp[1]);
}
break;
default:
str->pid = pid;
if (REDIRECT_STDOUT_P(flags)) {
str->fd[0] = rightp[0];
close(rightp[1]);
} else
str->fd[0] = -1;
if (REDIRECT_STDIN_P(flags)) {
str->fd[1] = leftp[1];
close(leftp[0]);
} else
str->fd[1] = -1;
}
free(arg);
return rc;
}
static int
open_program_stream(eval_environ_t env,
struct io_stream *str, const char *cmdline,
int flags)
{
return open_program_stream_ioe(env, str, cmdline, flags, NULL);
}
static int
open_file_stream(eval_environ_t env,
struct io_stream *str, const char *file, int flags)
{
str->fd[0] = open(file, flags, 0644); /* FIXME: mode? */
if (str->fd[0] == -1)
return errno;
return 0;
}
static int
open_parsed_inet_stream(eval_environ_t env,
struct io_stream *str,
const char *cstr,
char *proto, char *port, char *path,
int flags)
{
union {
struct sockaddr sa;
struct sockaddr_in s_in;
struct sockaddr_un s_un;
#ifdef GACOPYZ_IPV6
struct sockaddr_in6 s_in6;
#endif
} addr;
socklen_t socklen;
int fd;
int rc;
if (!proto
|| strcmp(proto, "unix") == 0 || strcmp(proto, "local") == 0) {
struct stat st;
MF_ASSERT(port == NULL,
mfe_failure,
_("invalid connection type: %s; "
"port is meaningless for UNIX sockets"),
cstr);
MF_ASSERT(strlen(path) <= sizeof addr.s_un.sun_path,
mfe_range,
_("%s: UNIX socket name too long"),
path);
addr.sa.sa_family = PF_UNIX;
socklen = sizeof(addr.s_un);
strcpy(addr.s_un.sun_path, path);
if (stat(path, &st)) {
MF_THROW(mfe_failure,
_("%s: cannot stat socket: %s"),
path, strerror(errno));
} else {
/* FIXME: Check permissions? */
MF_ASSERT(S_ISSOCK(st.st_mode),
mfe_failure,
_("%s: not a socket"),
path);
}
} else if (strcmp(proto, "inet") == 0) {
short pnum;
long num;
char *p;
addr.sa.sa_family = PF_INET;
socklen = sizeof(addr.s_in);
MF_ASSERT(port != NULL,
mfe_failure,
_("invalid connection type: %s; "
"missing port number"),
cstr);
num = pnum = strtol(port, &p, 0);
if (*p == 0) {
MF_ASSERT(num == pnum,
mfe_range,
_("invalid connection type: "
"%s; bad port number"),
cstr);
pnum = htons(pnum);
} else {
struct servent *sp = getservbyname(port, "tcp");
MF_ASSERT(sp != NULL,
mfe_failure,
_("invalid connection type: "
"%s; unknown port name"),
cstr);
pnum = sp->s_port;
}
if (!path)
addr.s_in.sin_addr.s_addr = INADDR_ANY;
else {
struct hostent *hp = gethostbyname(path);
MF_ASSERT(hp != NULL,
mfe_failure,
_("unknown host name %s"),
path);
addr.sa.sa_family = hp->h_addrtype;
switch (hp->h_addrtype) {
case AF_INET:
memmove(&addr.s_in.sin_addr, hp->h_addr, 4);
addr.s_in.sin_port = pnum;
break;
default:
MF_THROW(mfe_range,
_("invalid connection type: "
"%s; unsupported address family"),
cstr);
}
}
#ifdef GACOPYZ_IPV6
} else if (strcmp(proto, "inet6") == 0) {
struct addrinfo hints;
struct addrinfo *res;
MF_ASSERT(port != NULL,
mfe_failure,
_("invalid connection type: %s; "
"missing port number"),
cstr);
memset(&hints, 0, sizeof(hints));
hints.ai_family = AF_INET6;
hints.ai_socktype = SOCK_STREAM;
if (!path)
hints.ai_flags |= AI_PASSIVE;
rc = getaddrinfo(path, port, &hints, &res);
switch (rc) {
case 0:
break;
case EAI_SYSTEM:
MF_THROW(mfe_failure,
_("%s:%s: cannot parse address: %s"),
path, port, strerror(errno));
case EAI_BADFLAGS:
case EAI_SOCKTYPE:
MF_THROW(mfe_failure,
_("%s:%d: internal error converting %s:%s"),
__FILE__, __LINE__, path, port);
case EAI_MEMORY:
mu_alloc_die();
default:
MF_THROW(mfe_failure,
"%s:%s: %s",
path, port, gai_strerror(rc));
}
socklen = res->ai_addrlen;
if (socklen > sizeof(addr)) {
freeaddrinfo(res);
MF_THROW(mfe_failure,
_("%s:%s: address length too big (%lu)"),
path, port,
(unsigned long) socklen);
}
memcpy(&addr, res->ai_addr, res->ai_addrlen);
freeaddrinfo(res);
#endif
} else {
MF_THROW(mfe_range,
_("unsupported protocol: %s"),
proto);
}
fd = socket(addr.sa.sa_family, SOCK_STREAM, 0);
MF_ASSERT(fd != -1,
mfe_failure,
_("unable to create new socket: %s"),
strerror(errno));
/* FIXME: Bind to the source ? */
rc = connect(fd, &addr.sa, socklen);
if (rc) {
close(fd);
MF_THROW(mfe_failure,
_("cannot connect to %s: %s"),
cstr, strerror(errno));
}
str->fd[0] = fd;
return 0;
}
static int
shutdown_inet_stream(struct io_stream *str, int how)
{
switch (how) {
case 0:
how = SHUT_RD;
break;
case 1:
how = SHUT_WR;
break;
case 2:
how = SHUT_RDWR;
break;
default:
return EINVAL;
}
if (shutdown(str->fd[0], how))
return errno;
return 0;
}
static int
open_inet_stream(eval_environ_t env,
struct io_stream *str, const char *addr, int flags)
{
int rc;
char *proto, *port, *path;
if (gacopyz_parse_connection(addr, &proto, &port, &path)
!= MI_SUCCESS)
rc = ENOMEM; /* FIXME: or EINVAL? */
else {
rc = open_parsed_inet_stream(env,
str, addr,
proto, port, path, flags);
str->shutdown = shutdown_inet_stream;
free(proto);
free(port);
free(path);
}
return rc;
}
static void *
alloc_streams()
{
struct io_stream *p, *stab = mu_calloc(nstreams, sizeof *stab);
for (p = stab; p < stab + nstreams; p++)
p->fd[0] = p->fd[1] = -1;
return stab;
}
static void
destroy_streams(void *data)
{
struct io_stream *stab = data;
struct io_stream *p;
for (p = stab; p < stab + nstreams; p++) {
close_stream(p);
free(p->buf);
}
free(stab);
}
MF_DECLARE_DATA(IO, alloc_streams, destroy_streams)
int
_bi_io_fd(eval_environ_t env, int fd, int what)
{
struct io_stream *iotab = MF_GET_DATA;
int descr;
MF_ASSERT(fd >= 0 && fd < nstreams && what>=0 && what<=1,
mfe_range,
_("invalid file descriptor"));
descr = what == 0 ? IFD(iotab[fd]) : OFD(iotab[fd]);
MF_ASSERT(descr >= 0,
mfe_range,
_("invalid file descriptor"));
return descr;
}
MF_DEFUN(open, NUMBER, STRING name)
{
int i, rc;
int flags = 0;
int (*opf)(eval_environ_t env,
struct io_stream *, const char *, int) = open_file_stream;
struct io_stream *iotab = MF_GET_DATA;
for (i = 0; i < nstreams; i++) {
if (iotab[i].fd[0] == -1)
break;
}
MF_ASSERT(i < nstreams,
mfe_failure,
_("no more files available"));
MF_DEBUG(MU_DEBUG_TRACE1, ("opening stream %s", name));
iotab[i].name = mu_strdup(name);
iotab[i].delim = NULL;
if (*name == '>') {
flags |= O_RDWR|O_CREAT;
name++;
if (*name == '>') {
flags |= O_APPEND;
name++;
} else
flags |= O_TRUNC;
} else if (*name == '|') {
opf = open_program_stream;
flags = O_WRONLY;
name++;
if (*name == '&') {
flags = O_RDWR;
name++;
} else if (*name == '<') {
flags = O_RDONLY;
name++;
}
} else if (*name == '@') {
name++;
opf = open_inet_stream;
flags = O_RDWR;
} else
flags = O_RDONLY;
for (;*name && mu_isspace(*name); name++)
;
rc = opf(env, &iotab[i], name, flags);
MF_ASSERT(rc == 0,
mfe_failure,
_("cannot open stream %s: %s"), name,
mu_strerror(rc));
MF_DEBUG(MU_DEBUG_TRACE1, ("open(%s) = %d", name, i));
MF_RETURN(i);
}
END
MF_DEFUN(spawn, NUMBER, STRING name, OPTIONAL,
NUMBER fin, NUMBER fout, NUMBER ferr)
{
int i, rc;
struct io_stream *iotab = MF_GET_DATA;
int ioe[3];
int flags;
for (i = 0; i < nstreams; i++) {
if (iotab[i].fd[0] == -1)
break;
}
MF_ASSERT(i < nstreams,
mfe_failure,
_("no more files available"));
MF_DEBUG(MU_DEBUG_TRACE1, ("spawning %s", name));
iotab[i].name = mu_strdup(name);
iotab[i].delim = NULL;
flags = O_WRONLY;
if (*name == '|')
name++;
if (*name == '&') {
flags = O_RDWR;
name++;
} else if (*name == '<') {
flags = O_RDONLY;
name++;
}
for (;*name && mu_isspace(*name); name++)
;
if (MF_DEFINED(fin))
ioe[0] = _bi_io_fd(env, MF_OPTVAL(fin), 0);
else
ioe[0] = -1;
if (MF_DEFINED(fout))
ioe[1] = _bi_io_fd(env, MF_OPTVAL(fout), 1);
else
ioe[1] = -1;
if (MF_DEFINED(ferr))
ioe[2] = _bi_io_fd(env, MF_OPTVAL(fout), 1);
else
ioe[2] = -1;
rc = open_program_stream_ioe(env, &iotab[i], name, flags, ioe);
MF_ASSERT(rc == 0,
mfe_failure,
_("cannot open stream %s: %s"), name,
mu_strerror(rc));
MF_DEBUG(MU_DEBUG_TRACE1, ("spawn(%s) = %d", name, i));
MF_RETURN(i);
}
END
MF_DSEXP
MF_DEFUN(tempfile, NUMBER, OPTIONAL, STRING tempdir)
{
struct io_stream *iotab = MF_GET_DATA;
int i;
char *dir = MF_OPTVAL(tempdir, "/tmp");
size_t dirlen = strlen(dir);
mode_t u;
int fd;
char *template;
#define PATTERN "mfdXXXXXX"
for (i = 0; i < nstreams; i++) {
if (iotab[i].fd[0] == -1)
break;
}
MF_ASSERT(i < nstreams,
mfe_failure,
_("no more files available"));
while (dirlen > 0 && dir[dirlen-1] == '/')
dirlen--;
template = MF_ALLOC_HEAP_TEMP((dirlen ? dirlen + 1 : 0) +
sizeof(PATTERN));
if (dirlen) {
memcpy(template, dir, dirlen);
template[dirlen++] = '/';
}
strcpy(template + dirlen, PATTERN);
u = umask(077);
fd = mkstemp(template);
umask(u);
MF_ASSERT(fd >= 0,
mfe_failure,
"mkstemp failed: %s",
mu_strerror(errno));
unlink(template);
iotab[i].fd[0] = fd;
MF_RETURN(i);
#undef PATTERN
}
END
MF_DEFUN(close, VOID, NUMBER fd)
{
struct io_stream *iotab = MF_GET_DATA;
MF_ASSERT(fd >= 0 && fd < nstreams,
mfe_range,
_("invalid file descriptor"));
close_stream(&iotab[fd]);
}
END
static struct builtin_const_trans shutdown_modes[] = {
MF_TRANS(SHUT_RD),
MF_TRANS(SHUT_WR),
MF_TRANS(SHUT_RDWR)
};
MF_DEFUN(shutdown, VOID, NUMBER fd, NUMBER how)
{
struct io_stream *iotab = MF_GET_DATA;
struct io_stream *ioptr;
int mode;
MF_ASSERT(fd >= 0 && fd < nstreams,
mfe_range,
_("invalid file descriptor"));
MF_ASSERT(how >= 0 && how <= 2,
mfe_range,
_("invalid file descriptor"));
MF_ASSERT(_builtin_const_to_c(shutdown_modes,
MU_ARRAY_SIZE(shutdown_modes),
how,
&mode) == 0,
mfe_failure,
"bad shutdown mode");
ioptr = &iotab[fd];
if (ioptr->shutdown) {
int rc = ioptr->shutdown(ioptr, mode);
MF_ASSERT(rc == 0,
mfe_io,
"shutdown failed: %s",
mu_strerror(rc));
} else if (how == 2)
close_stream(ioptr);
else if (ioptr->fd[how]) {
close(ioptr->fd[how]);
ioptr->fd[how] = -1;
}
}
END
MF_DEFUN(write, VOID, NUMBER fd, STRING str, OPTIONAL, NUMBER n)
{
struct io_stream *iotab = MF_GET_DATA;
int rc;
MF_DEBUG(MU_DEBUG_TRACE1, ("writing %s to %lu", str, fd));
MF_ASSERT(fd >= 0 && fd < nstreams && OFD(iotab[fd]) != -1,
mfe_range,
_("invalid file descriptor"));
if (!MF_DEFINED(n))
n = strlen (str);
rc = write(OFD(iotab[fd]), str, n);
MF_ASSERT(n == rc,
mfe_io,
_("write error on %s: %s"),
iotab[fd].name, mu_strerror(errno));
}
END
MF_STATE(body)
MF_DEFUN(write_body, VOID, NUMBER fd, POINTER str, NUMBER n)
{
struct io_stream *iotab = MF_GET_DATA;
int rc;
MF_ASSERT(fd >= 0 && fd < nstreams && OFD(iotab[fd]) != -1,
mfe_range,
_("invalid file descriptor"));
rc = write(OFD(iotab[fd]), str, n);
MF_ASSERT(n == rc,
mfe_io,
_("write error on %s: %s"),
iotab[fd].name, mu_strerror(errno));
}
END
MF_DEFUN(read, STRING, NUMBER fd, NUMBER size)
{
struct io_stream *iotab = MF_GET_DATA;
int rc;
size_t off;
char *s = MF_ALLOC_HEAP(off, size + 1);
MF_ASSERT(fd >= 0 && fd < nstreams && IFD(iotab[fd]) != -1,
mfe_range,
_("invalid file descriptor"));
rc = read(IFD(iotab[fd]), s, size);
if (rc == 0)
MF_THROW(mfe_eof,
_("EOF on %s"), iotab[fd].name);
MF_ASSERT(rc == size,
mfe_io,
_("read error on %s: %s"),
iotab[fd].name, mu_strerror(errno));
s[size] = 0;
MF_RETURN(off, size);
}
END
MF_DEFUN(rewind, VOID, NUMBER fd)
{
struct io_stream *iotab = MF_GET_DATA;
MF_ASSERT(fd >= 0 && fd < nstreams && IFD(iotab[fd]) != -1,
mfe_range,
_("invalid file descriptor"));
if (lseek(IFD(iotab[fd]), 0, SEEK_SET) == -1)
MF_THROW(mfe_io,
"seek failed: %s",
mu_strerror(errno));
}
END
#define MINBUFSIZE 128
#define MAXBUFSIZE 65535
MF_DEFUN(copy, NUMBER, NUMBER dst, NUMBER src)
{
struct io_stream *iotab = MF_GET_DATA;
int ifd, ofd;
char *buffer;
size_t bufsize = MAXBUFSIZE;
char bs[MINBUFSIZE];
off_t cur, end;
size_t total = 0;
ssize_t rdbytes;
MF_ASSERT(src >= 0 && src < nstreams && (ifd = IFD(iotab[src])) != -1,
mfe_range,
_("invalid source file descriptor"));
MF_ASSERT(dst >= 0 && dst < nstreams && (ofd = OFD(iotab[dst])) != -1,
mfe_range,
_("invalid destination file descriptor"));
cur = lseek (ifd, 0, SEEK_CUR);
if (cur != -1) {
end = lseek (ifd, 0, SEEK_END);
if (end != -1) {
if (end < MAXBUFSIZE)
bufsize = end;
lseek (ifd, cur, SEEK_SET);
}
}
for (; (buffer = malloc (bufsize)) == NULL; bufsize >>= 1)
if (bufsize < MINBUFSIZE) {
buffer = bs;
bufsize = MINBUFSIZE;
break;
}
while ((rdbytes = read(ifd, buffer, bufsize)) > 0) {
char *p = buffer;
while (rdbytes) {
ssize_t wrbytes = write(ofd, p, rdbytes);
if (wrbytes == -1) {
if (buffer != bs)
free(buffer);
MF_THROW(mfe_io,
"write error: %s",
mu_strerror(errno));
} else if (wrbytes == 0) {
if (buffer != bs)
free(buffer);
MF_THROW(mfe_io,
"short write");
}
p += wrbytes;
rdbytes -= wrbytes;
total += wrbytes;
}
}
if (buffer != bs)
free(buffer);
MF_RETURN(total);
}
END
MF_DEFUN(getdelim, STRING, NUMBER fd, STRING delim)
{
struct io_stream *iotab = MF_GET_DATA;
struct io_stream *ioptr;
int rc;
MF_ASSERT(fd >= 0 && fd < nstreams && IFD(iotab[fd]) != -1,
mfe_range,
_("invalid file descriptor"));
ioptr = &iotab[fd];
rc = read_stream_delim(ioptr, delim);
if (rc == 0)
MF_THROW(mfe_eof, _("EOF on %s"), ioptr->name);
MF_ASSERT(rc > 0,
mfe_io,
_("read error on %s: %s"),
ioptr->name, mu_strerror(errno));
MF_RETURN(ioptr->buf);
}
END
MF_DEFUN(getline, STRING, NUMBER fd)
{
struct io_stream *iotab = MF_GET_DATA;
struct io_stream *ioptr;
int rc;
MF_ASSERT(fd >= 0 && fd < nstreams && IFD(iotab[fd]) != -1,
mfe_range,
_("invalid file descriptor"));
ioptr = &iotab[fd];
rc = read_stream_delim(ioptr, ioptr->delim ? ioptr->delim : "\n");
if (rc == 0)
MF_THROW(mfe_eof,
_("EOF on %s"), ioptr->name);
MF_ASSERT(rc > 0,
mfe_io,
_("read error on %s: %s"),
ioptr->name, mu_strerror(errno));
MF_RETURN(ioptr->buf);
}
END
MF_DEFUN(fd_set_delimiter, VOID, NUMBER fd, STRING delim)
{
struct io_stream *iotab = MF_GET_DATA;
struct io_stream *ioptr;
MF_ASSERT(fd >= 0 && fd < nstreams && IFD(iotab[fd]) != -1,
mfe_range,
_("invalid file descriptor"));
ioptr = &iotab[fd];
free(ioptr->delim);
ioptr->delim = mu_strdup(delim);
}
END
MF_DEFUN(fd_delimiter, STRING, NUMBER fd, STRING delim)
{
struct io_stream *iotab = MF_GET_DATA;
struct io_stream *ioptr;
MF_ASSERT(fd >= 0 && fd < nstreams && IFD(iotab[fd]) != -1,
mfe_range,
_("invalid file descriptor"));
ioptr = &iotab[fd];
MF_RETURN(ioptr->delim ? ioptr->delim : "\n");
}
END
MF_INIT([<
mf_add_runtime_params(io_cfg_param);
>])