1/* This file is part of Mailfromd.             -*- c -*-
2   Copyright (C) 2006-2021 Sergey Poznyakoff
3
4   This program is free software; you can redistribute it and/or modify
5   it under the terms of the GNU General Public License as published by
6   the Free Software Foundation; either version 3, or (at your option)
7   any later version.
8
9   This program is distributed in the hope that it will be useful,
10   but WITHOUT ANY WARRANTY; without even the implied warranty of
11   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12   GNU General Public License for more details.
13
14   You should have received a copy of the GNU General Public License
15   along with this program.  If not, see <http://www.gnu.org/licenses/>. */
16
17MF_BUILTIN_MODULE
18
19#include <mflib/status.h>
20#include <sys/types.h>
21#include <sys/stat.h>
22#include <sys/wait.h>
23#include "global.h"
24#include "msg.h"
25
26static size_t nstreams = MAX_IOSTREAMS;
27
28static struct mu_cfg_param io_cfg_param[] = {
29	{ "max-streams", mu_c_size, &nstreams, 0, NULL,
30	  N_("Maximum number of stream descriptors.") },
31	{ NULL }
32};
33
34struct io_stream {
35	char *name;
36	mu_locker_t lock;
37	int fd[2];
38	pid_t pid;
39	char *buf;
40	size_t bufsize;
41	int (*shutdown)(struct io_stream *, int what);
42	void (*cleanup)(void*);
43	void *cleanup_data;
44	char *delim;
45};
46
47#define IFD(s) ((s).fd[0])
48#define OFD(s) ((s).fd[1] == -1 ? (s).fd[0] : (s).fd[1])
49
50static void
51flush_stream(struct io_stream *str)
52{
53	/*FIXME*/
54}
55
56static void
57close_stream(struct io_stream *str)
58{
59	if (OFD(*str) == -1)
60		return;
61	flush_stream(str);
62	close(OFD(*str));
63	if (IFD(*str) != -1)
64		close(IFD(*str));
65	if (str->pid) {
66		int status;
67		waitpid(str->pid, &status, 0);
68	}
69	str->fd[0] = -1;
70	str->fd[1] = -1;
71	str->pid = 0;
72	if (str->cleanup)
73		str->cleanup(str->cleanup_data);
74	str->cleanup = NULL;
75	str->cleanup_data = NULL;
76	if (str->name) {
77		free(str->name);
78		str->name = NULL;
79	}
80	if (str->delim) {
81		free(str->delim);
82		str->delim = NULL;
83	}
84}
85
86/* Read bytes from the stream STR into its buffer, until
87   DELIM is encountered. Return number of bytes read. */
88static int
89read_stream_delim(struct io_stream *str, char *delim)
90{
91	int fd = IFD(*str);
92	size_t i = 0;
93	int rc;
94	size_t delim_len = strlen(delim);
95
96	for (;;) {
97		if (str->bufsize == i) {
98			if (str->bufsize == 0)
99				str->bufsize = 16;
100			str->buf = mu_2nrealloc(str->buf, &str->bufsize,
101					      sizeof str->buf[1]);
102		}
103		rc = read(fd, str->buf + i, 1);
104		if (rc == -1)
105			return -1;
106		else if (rc == 0)
107			return 0;
108		i++;
109		if (i >= delim_len &&
110		    memcmp(str->buf + i - delim_len, delim, delim_len) == 0) {
111			str->buf[i - delim_len] = 0;
112			break;
113		}
114	}
115	return i;
116}
117
118#define REDIRECT_STDIN_P(f) ((f) & (O_WRONLY|O_RDWR))
119#define REDIRECT_STDOUT_P(f) (!((f) & O_WRONLY))
120
121#define STDERR_SHUT        0
122#define STDERR_NULL        1
123#define STDERR_LOG         2
124#define STDERR_FILE        3
125#define STDERR_FILE_APPEND 4
126
127#define LOG_TAG_PFX "mailfromd:"
128#define LOG_TAG_PFX_LEN (sizeof(LOG_TAG_PFX)-1)
129
130static void
131stderr_to_log(char *arg, const char *cmd)
132{
133	int p[2];
134	pid_t pid;
135
136	if (pipe(p)) {
137		mu_error(_("pipe failed: %s"), mu_strerror(errno));
138		close(2);
139		return;
140	}
141
142	pid = fork();
143
144	if (pid == (pid_t) -1) {
145		mu_error(_("fork failed: %s"), mu_strerror(errno));
146		close(p[0]);
147		close(p[1]);
148		close(2);
149		return;
150	}
151
152	/* Child */
153	if (pid == 0) {
154		FILE *fp;
155		fd_set fdset;
156		size_t len;
157		char buf[1024];
158		char *tag;
159		int fac = mu_log_facility, pri = LOG_ERR;
160
161		if (arg) {
162			char *p = strchr(arg, '.');
163
164			if (p)
165				*p++ = 0;
166			if (mu_string_to_syslog_facility(arg, &fac)) {
167				mu_error(_("unknown syslog facility (%s), "
168					   "redirecting stderr to %s"),
169					 arg,
170					 mu_syslog_facility_to_string(fac));
171			}
172
173			if (p && mu_string_to_syslog_priority(p, &pri)) {
174				mu_error(_("unknown syslog priority (%s), "
175					   "redirecting stderr to %s"),
176					 arg,
177					 mu_syslog_priority_to_string(pri));
178			}
179		}
180		MF_DEBUG(MU_DEBUG_TRACE2,
181                         ("redirecting stderr to syslog %s.%s",
182		           mu_syslog_facility_to_string(fac),
183		           mu_syslog_priority_to_string(pri)));
184
185		len = strcspn(cmd, " \t");
186		tag = malloc(LOG_TAG_PFX_LEN + len + 1);
187		if (!tag)
188			tag = (char*) cmd;
189		else {
190			strcpy(tag, LOG_TAG_PFX);
191			memcpy(tag + LOG_TAG_PFX_LEN, cmd, len);
192			tag[LOG_TAG_PFX_LEN + len] = 0;
193		}
194		mf_proctitle_format("%s redirector", cmd);
195
196		FD_ZERO(&fdset);
197		FD_SET(p[0], &fdset);
198		logger_fdset(&fdset);
199		close_fds_except(&fdset);
200
201		fp = fdopen(p[0], "r");
202		logger_open();
203		while (fgets(buf, sizeof(buf), fp))
204			syslog(pri, "%s", buf);
205		exit(0);
206	}
207
208	/* Parent */
209	close(p[0]);
210	dup2(p[1], 2);
211	close(p[1]);
212}
213
214static void
215stderr_handler(int mode, char *arg, const char *cmd)
216{
217	int fd;
218	int append = O_TRUNC;
219
220	switch (mode) {
221	case STDERR_SHUT:
222		close(2);
223		break;
224
225	case STDERR_NULL:
226		arg = "/dev/null";
227	case STDERR_FILE_APPEND:
228		append = O_APPEND;
229	case STDERR_FILE:
230		if (!arg || !*arg) {
231			close(2);
232			break;
233		}
234		MF_DEBUG(MU_DEBUG_TRACE2, ("redirecting stderr to %s", arg));
235		fd = open(arg, O_CREAT|O_WRONLY|append, 0644);
236		if (fd < 0) {
237			mu_error(_("cannot open file %s for appending: %s"),
238				 arg, mu_strerror(errno));
239			close(2);
240			return;
241		}
242		if (fd != 2) {
243			dup2(fd, 2);
244			close(fd);
245		}
246		break;
247
248	case STDERR_LOG:
249		stderr_to_log(arg, cmd);
250	}
251}
252
253static void
254parse_stderr_redirect(const char **pcmd, int *perr, char **parg)
255{
256	int err;
257	size_t len;
258	char *arg;
259	const char *cmdline = *pcmd;
260
261	while (*cmdline && mu_isspace(*cmdline))
262		cmdline++;
263	if (strncmp(cmdline, "2>file:", 7) == 0) {
264		cmdline += 7;
265		err = STDERR_FILE;
266	} else if (strncmp(cmdline, "2>>file:", 8) == 0) {
267		cmdline += 8;
268		err = STDERR_FILE_APPEND;
269	} else if (strncmp(cmdline, "2>null:", 7) == 0) {
270		cmdline += 7;
271		err = STDERR_NULL;
272	} else if (strncmp(cmdline, "2>syslog:", 9) == 0) {
273		cmdline += 9;
274		err = STDERR_LOG;
275	} else
276		return;
277
278	len = strcspn(cmdline, " \t");
279	if (len > 0 && cmdline[len-1] == 0)
280		return;
281	if (len == 0)
282		arg = NULL;
283	else {
284		arg = malloc(len + 1);
285		if (!arg)
286			return;
287		memcpy(arg, cmdline, len);
288		arg[len] = 0;
289	}
290
291	*pcmd = cmdline + len;
292	*perr = err;
293	*parg = arg;
294}
295
296
297static int
298open_program_stream_ioe(eval_environ_t env,
299			struct io_stream *str, const char *cmdline,
300			int flags,
301			int ioe[2])
302{
303	int rightp[2], leftp[2];
304	int rc = 0;
305	pid_t pid;
306	int err = STDERR_SHUT;
307	char *arg = NULL;
308	struct mu_wordsplit ws;
309
310	parse_stderr_redirect(&cmdline, &err, &arg);
311	while (*cmdline && (*cmdline == ' ' || *cmdline == '\t'))
312		cmdline++;
313
314	if (REDIRECT_STDIN_P(flags)) {
315		if (pipe(leftp)) {
316			mu_diag_funcall(MU_DIAG_ERROR, "pipe", "leftp",
317					errno);
318			free(arg);
319			MF_THROW(mfe_failure, "pipe failed");
320		}
321	}
322
323	if (REDIRECT_STDOUT_P(flags)) {
324		if (pipe(rightp)) {
325			mu_diag_funcall(MU_DIAG_ERROR, "pipe", "rightp",
326					errno);
327			free(arg);
328			if (REDIRECT_STDIN_P(flags)) {
329				close(leftp[0]);
330				close(leftp[1]);
331			}
332		}
333	}
334
335	switch (pid = fork()) {
336		/* The child branch.  */
337	case 0:
338		/* attach the pipes */
339
340		/* Right-end */
341		if (REDIRECT_STDOUT_P(flags)) {
342			if (rightp[1] != 1)
343				dup2(rightp[1], 1);
344		} else if (ioe && ioe[1] != -1 && ioe[1] != 1) {
345			dup2(ioe[1], 1);
346		}
347
348		/* Left-end */
349		if (REDIRECT_STDIN_P(flags)) {
350			if (leftp[0] != 0)
351				dup2(leftp[0], 0);
352		} else if (ioe && ioe[0] != -1 && ioe[0] != 0) {
353			dup2(ioe[0], 0);
354		}
355
356		if (ioe && ioe[2] != -1 && ioe[2] != 2)
357			dup2(ioe[2], 2);
358		else
359			stderr_handler(err, arg, cmdline);
360
361		/* Close unneeded descriptors */
362		close_fds_above(2);
363
364		MF_DEBUG(MU_DEBUG_TRACE3, ("running %s", cmdline));
365		if (mu_wordsplit(cmdline, &ws,
366				 MU_WRDSF_DEFFLAGS & ~MU_WRDSF_CESCAPES)) {
367			mu_error(_("cannot parse command line %s: %s"),
368				 cmdline, mu_wordsplit_strerror(&ws));
369			exit(127);
370		}
371		execvp(ws.ws_wordv[0], ws.ws_wordv);
372		mu_error(_("cannot run %s: %s"),
373			 cmdline, mu_strerror(errno));
374		exit(127);
375		/********************/
376
377		/* Parent branches: */
378	case -1:
379		/* Fork has failed */
380		/* Restore things */
381		rc = errno;
382		if (REDIRECT_STDOUT_P(flags)) {
383			close(rightp[0]);
384			close(rightp[1]);
385		}
386		if (REDIRECT_STDIN_P(flags)) {
387			close(leftp[0]);
388			close(leftp[1]);
389		}
390		break;
391
392	default:
393		str->pid = pid;
394		if (REDIRECT_STDOUT_P(flags)) {
395			str->fd[0] = rightp[0];
396			close(rightp[1]);
397		} else
398			str->fd[0] = -1;
399
400		if (REDIRECT_STDIN_P(flags)) {
401			str->fd[1] = leftp[1];
402			close(leftp[0]);
403		} else
404			str->fd[1] = -1;
405	}
406	free(arg);
407	return rc;
408}
409
410static int
411open_program_stream(eval_environ_t env,
412		    struct io_stream *str, const char *cmdline,
413		    int flags)
414{
415	return open_program_stream_ioe(env, str, cmdline, flags, NULL);
416}
417
418static int
419open_file_stream(eval_environ_t env,
420		 struct io_stream *str, const char *file, int flags)
421{
422	str->fd[0] = open(file, flags, 0644); /* FIXME: mode? */
423	if (str->fd[0] == -1)
424		return errno;
425	return 0;
426}
427
428
429
430static int
431open_parsed_inet_stream(eval_environ_t env,
432			struct io_stream *str,
433			const char *cstr,
434			char *proto, char *port, char *path,
435			int flags)
436{
437	union {
438		struct sockaddr sa;
439		struct sockaddr_in s_in;
440		struct sockaddr_un s_un;
441#ifdef GACOPYZ_IPV6
442		struct sockaddr_in6 s_in6;
443#endif
444	} addr;
445
446	socklen_t socklen;
447	int fd;
448	int rc;
449
450	if (!proto
451	    || strcmp(proto, "unix") == 0 || strcmp(proto, "local") == 0) {
452		struct stat st;
453
454		MF_ASSERT(port == NULL,
455			  mfe_failure,
456			  _("invalid connection type: %s; "
457			    "port is meaningless for UNIX sockets"),
458			  cstr);
459
460		MF_ASSERT(strlen(path) <= sizeof addr.s_un.sun_path,
461			  mfe_range,
462			  _("%s: UNIX socket name too long"),
463			  path);
464
465		addr.sa.sa_family = PF_UNIX;
466		socklen = sizeof(addr.s_un);
467		strcpy(addr.s_un.sun_path, path);
468
469		if (stat(path, &st)) {
470			MF_THROW(mfe_failure,
471				 _("%s: cannot stat socket: %s"),
472				 path, strerror(errno));
473		} else {
474			/* FIXME: Check permissions? */
475			MF_ASSERT(S_ISSOCK(st.st_mode),
476				  mfe_failure,
477				  _("%s: not a socket"),
478				  path);
479		}
480
481	} else if (strcmp(proto, "inet") == 0) {
482		short pnum;
483		long num;
484		char *p;
485
486		addr.sa.sa_family = PF_INET;
487		socklen = sizeof(addr.s_in);
488
489		MF_ASSERT(port != NULL,
490			  mfe_failure,
491			  _("invalid connection type: %s; "
492			    "missing port number"),
493			  cstr);
494
495		num = pnum = strtol(port, &p, 0);
496		if (*p == 0) {
497			MF_ASSERT(num == pnum,
498				  mfe_range,
499				  _("invalid connection type: "
500				    "%s; bad port number"),
501				  cstr);
502			pnum = htons(pnum);
503		} else {
504			struct servent *sp = getservbyname(port, "tcp");
505
506			MF_ASSERT(sp != NULL,
507				  mfe_failure,
508				  _("invalid connection type: "
509				    "%s; unknown port name"),
510				  cstr);
511			pnum = sp->s_port;
512		}
513
514		if (!path)
515			addr.s_in.sin_addr.s_addr = INADDR_ANY;
516		else {
517			struct hostent *hp = gethostbyname(path);
518			MF_ASSERT(hp != NULL,
519				  mfe_failure,
520				  _("unknown host name %s"),
521				  path);
522			addr.sa.sa_family = hp->h_addrtype;
523			switch (hp->h_addrtype) {
524			case AF_INET:
525				memmove(&addr.s_in.sin_addr, hp->h_addr, 4);
526				addr.s_in.sin_port = pnum;
527				break;
528
529			default:
530				MF_THROW(mfe_range,
531					 _("invalid connection type: "
532					   "%s; unsupported address family"),
533					 cstr);
534			}
535		}
536#ifdef GACOPYZ_IPV6
537	} else if (strcmp(proto, "inet6") == 0) {
538		struct addrinfo hints;
539		struct addrinfo *res;
540
541		MF_ASSERT(port != NULL,
542			  mfe_failure,
543			  _("invalid connection type: %s; "
544			    "missing port number"),
545			  cstr);
546
547		memset(&hints, 0, sizeof(hints));
548		hints.ai_family = AF_INET6;
549		hints.ai_socktype = SOCK_STREAM;
550		if (!path)
551			hints.ai_flags |= AI_PASSIVE;
552
553		rc = getaddrinfo(path, port, &hints, &res);
554
555		switch (rc) {
556		case 0:
557			break;
558
559		case EAI_SYSTEM:
560			MF_THROW(mfe_failure,
561				 _("%s:%s: cannot parse address: %s"),
562				 path, port, strerror(errno));
563
564		case EAI_BADFLAGS:
565		case EAI_SOCKTYPE:
566			MF_THROW(mfe_failure,
567				 _("%s:%d: internal error converting %s:%s"),
568				 __FILE__, __LINE__, path, port);
569
570		case EAI_MEMORY:
571			mu_alloc_die();
572
573		default:
574			MF_THROW(mfe_failure,
575				 "%s:%s: %s",
576				 path, port, gai_strerror(rc));
577		}
578
579		socklen = res->ai_addrlen;
580		if (socklen > sizeof(addr)) {
581			freeaddrinfo(res);
582			MF_THROW(mfe_failure,
583				 _("%s:%s: address length too big (%lu)"),
584				 path, port,
585				 (unsigned long) socklen);
586		}
587		memcpy(&addr, res->ai_addr, res->ai_addrlen);
588		freeaddrinfo(res);
589#endif
590	} else {
591		MF_THROW(mfe_range,
592			 _("unsupported protocol: %s"),
593			 proto);
594	}
595
596	fd = socket(addr.sa.sa_family, SOCK_STREAM, 0);
597	MF_ASSERT(fd != -1,
598		  mfe_failure,
599		  _("unable to create new socket: %s"),
600		  strerror(errno));
601
602	/* FIXME: Bind to the source ? */
603
604	rc = connect(fd, &addr.sa, socklen);
605	if (rc) {
606		close(fd);
607		MF_THROW(mfe_failure,
608			 _("cannot connect to %s: %s"),
609			 cstr, strerror(errno));
610	}
611
612	str->fd[0] = fd;
613	return 0;
614}
615
616static int
617shutdown_inet_stream(struct io_stream *str, int how)
618{
619	switch (how) {
620	case 0:
621		how = SHUT_RD;
622		break;
623
624	case 1:
625		how = SHUT_WR;
626		break;
627
628	case 2:
629		how = SHUT_RDWR;
630		break;
631
632	default:
633		return EINVAL;
634	}
635	if (shutdown(str->fd[0], how))
636		return errno;
637	return 0;
638}
639
640static int
641open_inet_stream(eval_environ_t env,
642		 struct io_stream *str, const char *addr, int flags)
643{
644	int rc;
645	char *proto, *port, *path;
646
647	if (gacopyz_parse_connection(addr, &proto, &port, &path)
648	    != MI_SUCCESS)
649		rc = ENOMEM; /* FIXME: or EINVAL? */
650	else {
651		rc = open_parsed_inet_stream(env,
652					     str, addr,
653					     proto, port, path, flags);
654		str->shutdown = shutdown_inet_stream;
655		free(proto);
656		free(port);
657		free(path);
658	}
659	return rc;
660}
661
662
663static void *
664alloc_streams()
665{
666	struct io_stream *p, *stab = mu_calloc(nstreams, sizeof *stab);
667	for (p = stab; p < stab + nstreams; p++)
668		p->fd[0] = p->fd[1] = -1;
669	return stab;
670}
671
672static void
673destroy_streams(void *data)
674{
675	struct io_stream *stab = data;
676	struct io_stream *p;
677	for (p = stab; p < stab + nstreams; p++) {
678		close_stream(p);
679		free(p->buf);
680	}
681	free(stab);
682}
683
684MF_DECLARE_DATA(IO, alloc_streams, destroy_streams)
685
686int
687_bi_io_fd(eval_environ_t env, int fd, int what)
688{
689	struct io_stream *iotab = MF_GET_DATA;
690	int descr;
691
692	MF_ASSERT(fd >= 0 && fd < nstreams && what>=0 && what<=1,
693		  mfe_range,
694		  _("invalid file descriptor"));
695	descr = what == 0 ? IFD(iotab[fd]) : OFD(iotab[fd]);
696	MF_ASSERT(descr >= 0,
697		  mfe_range,
698		  _("invalid file descriptor"));
699	return descr;
700}
701
702
703MF_DEFUN(open, NUMBER, STRING name)
704{
705	int i, rc;
706	int flags = 0;
707	int (*opf)(eval_environ_t env,
708		   struct io_stream *, const char *, int) = open_file_stream;
709	struct io_stream *iotab = MF_GET_DATA;
710
711	for (i = 0; i < nstreams; i++) {
712		if (iotab[i].fd[0] == -1)
713			break;
714	}
715	MF_ASSERT(i < nstreams,
716		  mfe_failure,
717		  _("no more files available"));
718
719	MF_DEBUG(MU_DEBUG_TRACE1, ("opening stream %s", name));
720	iotab[i].name = mu_strdup(name);
721	iotab[i].delim = NULL;
722	if (*name == '>') {
723		flags |= O_RDWR|O_CREAT;
724		name++;
725		if (*name == '>') {
726			flags |= O_APPEND;
727			name++;
728		} else
729			flags |= O_TRUNC;
730	} else if (*name == '|') {
731		opf = open_program_stream;
732		flags = O_WRONLY;
733		name++;
734		if (*name == '&') {
735			flags = O_RDWR;
736			name++;
737		} else if (*name == '<') {
738			flags = O_RDONLY;
739			name++;
740		}
741	} else if (*name == '@') {
742		name++;
743		opf = open_inet_stream;
744		flags = O_RDWR;
745	} else
746		flags = O_RDONLY;
747
748	for (;*name && mu_isspace(*name); name++)
749		;
750
751	rc = opf(env, &iotab[i], name, flags);
752
753	MF_ASSERT(rc == 0,
754		  mfe_failure,
755		  _("cannot open stream %s: %s"), name,
756		  mu_strerror(rc));
757	MF_DEBUG(MU_DEBUG_TRACE1, ("open(%s) = %d", name, i));
758	MF_RETURN(i);
759}
760END
761
762MF_DEFUN(spawn, NUMBER, STRING name, OPTIONAL,
763	 NUMBER fin, NUMBER fout, NUMBER ferr)
764{
765	int i, rc;
766	struct io_stream *iotab = MF_GET_DATA;
767	int ioe[3];
768	int flags;
769
770	for (i = 0; i < nstreams; i++) {
771		if (iotab[i].fd[0] == -1)
772			break;
773	}
774	MF_ASSERT(i < nstreams,
775		  mfe_failure,
776		  _("no more files available"));
777
778	MF_DEBUG(MU_DEBUG_TRACE1, ("spawning %s", name));
779	iotab[i].name = mu_strdup(name);
780	iotab[i].delim = NULL;
781
782	flags = O_WRONLY;
783	if (*name == '|')
784		name++;
785	if (*name == '&') {
786		flags = O_RDWR;
787		name++;
788	} else if (*name == '<') {
789		flags = O_RDONLY;
790		name++;
791	}
792
793	for (;*name && mu_isspace(*name); name++)
794		;
795
796	if (MF_DEFINED(fin))
797		ioe[0] = _bi_io_fd(env, MF_OPTVAL(fin), 0);
798	else
799		ioe[0] = -1;
800	if (MF_DEFINED(fout))
801		ioe[1] = _bi_io_fd(env, MF_OPTVAL(fout), 1);
802	else
803		ioe[1] = -1;
804	if (MF_DEFINED(ferr))
805		ioe[2] = _bi_io_fd(env, MF_OPTVAL(fout), 1);
806	else
807		ioe[2] = -1;
808
809	rc = open_program_stream_ioe(env, &iotab[i], name, flags, ioe);
810
811	MF_ASSERT(rc == 0,
812		  mfe_failure,
813		  _("cannot open stream %s: %s"), name,
814		  mu_strerror(rc));
815	MF_DEBUG(MU_DEBUG_TRACE1, ("spawn(%s) = %d", name, i));
816	MF_RETURN(i);
817
818}
819END
820
821MF_DSEXP
822MF_DEFUN(tempfile, NUMBER, OPTIONAL, STRING tempdir)
823{
824	struct io_stream *iotab = MF_GET_DATA;
825	int i;
826	char *dir = MF_OPTVAL(tempdir, "/tmp");
827	size_t dirlen = strlen(dir);
828	mode_t u;
829	int fd;
830	char *template;
831#define PATTERN "mfdXXXXXX"
832
833	for (i = 0; i < nstreams; i++) {
834		if (iotab[i].fd[0] == -1)
835			break;
836	}
837	MF_ASSERT(i < nstreams,
838		  mfe_failure,
839		  _("no more files available"));
840
841
842	while (dirlen > 0 && dir[dirlen-1] == '/')
843		dirlen--;
844
845	template = MF_ALLOC_HEAP_TEMP((dirlen ? dirlen + 1 : 0) +
846				      sizeof(PATTERN));
847	if (dirlen) {
848		memcpy(template, dir, dirlen);
849		template[dirlen++] = '/';
850	}
851	strcpy(template + dirlen, PATTERN);
852	u = umask(077);
853	fd = mkstemp(template);
854	umask(u);
855	MF_ASSERT(fd >= 0,
856		  mfe_failure,
857		  "mkstemp failed: %s",
858		  mu_strerror(errno));
859	unlink(template);
860
861	iotab[i].fd[0] = fd;
862
863	MF_RETURN(i);
864#undef PATTERN
865}
866END
867
868MF_DEFUN(close, VOID, NUMBER fd)
869{
870	struct io_stream *iotab = MF_GET_DATA;
871
872	MF_ASSERT(fd >= 0 && fd < nstreams,
873		  mfe_range,
874		  _("invalid file descriptor"));
875	close_stream(&iotab[fd]);
876}
877END
878
879static struct builtin_const_trans shutdown_modes[] = {
880	MF_TRANS(SHUT_RD),
881	MF_TRANS(SHUT_WR),
882	MF_TRANS(SHUT_RDWR)
883};
884
885MF_DEFUN(shutdown, VOID, NUMBER fd, NUMBER how)
886{
887	struct io_stream *iotab = MF_GET_DATA;
888	struct io_stream *ioptr;
889	int mode;
890
891	MF_ASSERT(fd >= 0 && fd < nstreams,
892		  mfe_range,
893		  _("invalid file descriptor"));
894	MF_ASSERT(how >= 0 && how <= 2,
895		  mfe_range,
896		  _("invalid file descriptor"));
897	MF_ASSERT(_builtin_const_to_c(shutdown_modes,
898				      MU_ARRAY_SIZE(shutdown_modes),
899				      how,
900				      &mode) == 0,
901		  mfe_failure,
902		  "bad shutdown mode");
903
904	ioptr = &iotab[fd];
905	if (ioptr->shutdown) {
906		int rc = ioptr->shutdown(ioptr, mode);
907		MF_ASSERT(rc == 0,
908			  mfe_io,
909			  "shutdown failed: %s",
910			  mu_strerror(rc));
911	} else if (how == 2)
912		close_stream(ioptr);
913	else if (ioptr->fd[how]) {
914		close(ioptr->fd[how]);
915		ioptr->fd[how] = -1;
916	}
917}
918END
919
920MF_DEFUN(write, VOID, NUMBER fd, STRING str, OPTIONAL, NUMBER n)
921{
922	struct io_stream *iotab = MF_GET_DATA;
923	int rc;
924
925	MF_DEBUG(MU_DEBUG_TRACE1, ("writing %s to %lu", str, fd));
926	MF_ASSERT(fd >= 0 && fd < nstreams && OFD(iotab[fd]) != -1,
927		  mfe_range,
928		  _("invalid file descriptor"));
929	if (!MF_DEFINED(n))
930		n = strlen (str);
931	rc = write(OFD(iotab[fd]), str, n);
932	MF_ASSERT(n == rc,
933		  mfe_io,
934		  _("write error on %s: %s"),
935		  iotab[fd].name, mu_strerror(errno));
936}
937END
938
939MF_STATE(body)
940MF_DEFUN(write_body, VOID, NUMBER fd, POINTER str, NUMBER n)
941{
942	struct io_stream *iotab = MF_GET_DATA;
943	int rc;
944
945	MF_ASSERT(fd >= 0 && fd < nstreams && OFD(iotab[fd]) != -1,
946		  mfe_range,
947		  _("invalid file descriptor"));
948	rc = write(OFD(iotab[fd]), str, n);
949	MF_ASSERT(n == rc,
950		  mfe_io,
951		  _("write error on %s: %s"),
952		  iotab[fd].name, mu_strerror(errno));
953}
954END
955
956MF_DEFUN(read, STRING, NUMBER fd, NUMBER size)
957{
958	struct io_stream *iotab = MF_GET_DATA;
959	int rc;
960	size_t off;
961	char *s = MF_ALLOC_HEAP(off, size + 1);
962
963	MF_ASSERT(fd >= 0 && fd < nstreams && IFD(iotab[fd]) != -1,
964		  mfe_range,
965		  _("invalid file descriptor"));
966
967	rc = read(IFD(iotab[fd]), s, size);
968	if (rc == 0)
969		MF_THROW(mfe_eof,
970			 _("EOF on %s"), iotab[fd].name);
971	MF_ASSERT(rc == size,
972		  mfe_io,
973		  _("read error on %s: %s"),
974		  iotab[fd].name, mu_strerror(errno));
975	s[size] = 0;
976	MF_RETURN(off, size);
977}
978END
979
980MF_DEFUN(rewind, VOID, NUMBER fd)
981{
982	struct io_stream *iotab = MF_GET_DATA;
983
984	MF_ASSERT(fd >= 0 && fd < nstreams && IFD(iotab[fd]) != -1,
985		  mfe_range,
986		  _("invalid file descriptor"));
987	if (lseek(IFD(iotab[fd]), 0, SEEK_SET) == -1)
988		MF_THROW(mfe_io,
989			 "seek failed: %s",
990			 mu_strerror(errno));
991}
992END
993
994
995#define MINBUFSIZE 128
996#define MAXBUFSIZE 65535
997
998MF_DEFUN(copy, NUMBER, NUMBER dst, NUMBER src)
999{
1000	struct io_stream *iotab = MF_GET_DATA;
1001	int ifd, ofd;
1002	char *buffer;
1003	size_t bufsize = MAXBUFSIZE;
1004	char bs[MINBUFSIZE];
1005	off_t cur, end;
1006	size_t total = 0;
1007	ssize_t rdbytes;
1008
1009	MF_ASSERT(src >= 0 && src < nstreams && (ifd = IFD(iotab[src])) != -1,
1010		  mfe_range,
1011		  _("invalid source file descriptor"));
1012	MF_ASSERT(dst >= 0 && dst < nstreams && (ofd = OFD(iotab[dst])) != -1,
1013		  mfe_range,
1014		  _("invalid destination file descriptor"));
1015
1016	cur = lseek (ifd, 0, SEEK_CUR);
1017	if (cur != -1) {
1018		end = lseek (ifd, 0, SEEK_END);
1019		if (end != -1) {
1020			if (end < MAXBUFSIZE)
1021				bufsize = end;
1022			lseek (ifd, cur, SEEK_SET);
1023		}
1024	}
1025
1026	for (; (buffer = malloc (bufsize)) == NULL; bufsize >>= 1)
1027		if (bufsize < MINBUFSIZE) {
1028			buffer = bs;
1029			bufsize = MINBUFSIZE;
1030			break;
1031		}
1032
1033	while ((rdbytes = read(ifd, buffer, bufsize)) > 0) {
1034		char *p = buffer;
1035		while (rdbytes) {
1036			ssize_t wrbytes = write(ofd, p, rdbytes);
1037			if (wrbytes == -1) {
1038				if (buffer != bs)
1039					free(buffer);
1040				MF_THROW(mfe_io,
1041					 "write error: %s",
1042					 mu_strerror(errno));
1043			} else if (wrbytes == 0) {
1044				if (buffer != bs)
1045					free(buffer);
1046				MF_THROW(mfe_io,
1047					 "short write");
1048			}
1049			p += wrbytes;
1050			rdbytes -= wrbytes;
1051			total += wrbytes;
1052		}
1053	}
1054	if (buffer != bs)
1055		free(buffer);
1056	MF_RETURN(total);
1057}
1058END
1059
1060MF_DEFUN(getdelim, STRING, NUMBER fd, STRING delim)
1061{
1062	struct io_stream *iotab = MF_GET_DATA;
1063	struct io_stream *ioptr;
1064	int rc;
1065
1066	MF_ASSERT(fd >= 0 && fd < nstreams && IFD(iotab[fd]) != -1,
1067		  mfe_range,
1068		  _("invalid file descriptor"));
1069	ioptr = &iotab[fd];
1070	rc = read_stream_delim(ioptr, delim);
1071	if (rc == 0)
1072		MF_THROW(mfe_eof, _("EOF on %s"), ioptr->name);
1073	MF_ASSERT(rc > 0,
1074		  mfe_io,
1075		  _("read error on %s: %s"),
1076		  ioptr->name, mu_strerror(errno));
1077	MF_RETURN(ioptr->buf);
1078}
1079END
1080
1081MF_DEFUN(getline, STRING, NUMBER fd)
1082{
1083	struct io_stream *iotab = MF_GET_DATA;
1084	struct io_stream *ioptr;
1085	int rc;
1086
1087	MF_ASSERT(fd >= 0 && fd < nstreams && IFD(iotab[fd]) != -1,
1088		  mfe_range,
1089		  _("invalid file descriptor"));
1090	ioptr = &iotab[fd];
1091	rc = read_stream_delim(ioptr, ioptr->delim ? ioptr->delim : "\n");
1092	if (rc == 0)
1093		MF_THROW(mfe_eof,
1094			 _("EOF on %s"), ioptr->name);
1095	MF_ASSERT(rc > 0,
1096		  mfe_io,
1097		  _("read error on %s: %s"),
1098		  ioptr->name, mu_strerror(errno));
1099	MF_RETURN(ioptr->buf);
1100}
1101END
1102
1103MF_DEFUN(fd_set_delimiter, VOID, NUMBER fd, STRING delim)
1104{
1105	struct io_stream *iotab = MF_GET_DATA;
1106	struct io_stream *ioptr;
1107
1108	MF_ASSERT(fd >= 0 && fd < nstreams && IFD(iotab[fd]) != -1,
1109		  mfe_range,
1110		  _("invalid file descriptor"));
1111	ioptr = &iotab[fd];
1112	free(ioptr->delim);
1113	ioptr->delim = mu_strdup(delim);
1114}
1115END
1116
1117MF_DEFUN(fd_delimiter, STRING, NUMBER fd, STRING delim)
1118{
1119	struct io_stream *iotab = MF_GET_DATA;
1120	struct io_stream *ioptr;
1121
1122	MF_ASSERT(fd >= 0 && fd < nstreams && IFD(iotab[fd]) != -1,
1123		  mfe_range,
1124		  _("invalid file descriptor"));
1125	ioptr = &iotab[fd];
1126	MF_RETURN(ioptr->delim ? ioptr->delim : "\n");
1127}
1128END
1129
1130MF_INIT([<
1131	 mf_add_runtime_params(io_cfg_param);
1132	 >])
1133