/* 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); >])