[BACK]Return to xlint.c CVS log [TXT][DIR] Up to [cvs.NetBSD.org] / src / usr.bin / xlint / xlint

File: [cvs.NetBSD.org] / src / usr.bin / xlint / xlint / xlint.c (download)

Revision 1.92, Fri May 20 21:18:55 2022 UTC (22 months, 4 weeks ago) by rillig
Branch: MAIN
Changes since 1.91: +3 -3 lines

lint: use __RCSID in lint mode as well

Since 1995-10-02, lint supports __asm statements and __asm modifiers.

No binary change.

/* $NetBSD: xlint.c,v 1.92 2022/05/20 21:18:55 rillig Exp $ */

/*
 * Copyright (c) 1996 Christopher G. Demetriou.  All Rights Reserved.
 * Copyright (c) 1994, 1995 Jochen Pohl
 * All Rights Reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. All advertising materials mentioning features or use of this software
 *    must display the following acknowledgement:
 *      This product includes software developed by Jochen Pohl for
 *	The NetBSD Project.
 * 4. The name of the author may not be used to endorse or promote products
 *    derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#if HAVE_NBTOOL_CONFIG_H
#include "nbtool_config.h"
#endif

#include <sys/cdefs.h>
#if defined(__RCSID)
__RCSID("$NetBSD: xlint.c,v 1.92 2022/05/20 21:18:55 rillig Exp $");
#endif

#include <sys/param.h>
#include <sys/wait.h>
#include <sys/stat.h>
#include <sys/utsname.h>
#include <errno.h>
#include <stdarg.h>
#include <fcntl.h>
#include <paths.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <util.h>

#include "lint.h"
#include "pathnames.h"
#include "findcc.h"

#define DEFAULT_PATH		_PATH_DEFPATH

/* Parameters for the C preprocessor. */
static struct {
	char	**flags;	/* flags always passed */
	char	**lcflags;	/* flags, controlled by sflag/tflag */
	char	*outfile;	/* path name for preprocessed C source */
	int	outfd;		/* file descriptor for outfile */
} cpp = { NULL, NULL, NULL, -1 };

/* Parameters for lint1, which checks an isolated translation unit. */
static struct {
	char	**flags;
	char	**outfiles;
} lint1;

/* Parameters for lint2, which performs cross-translation-unit checks. */
static struct {
	char	**flags;
	char	**infiles;	/* input files (without libraries) */
	char	**inlibs;	/* input libraries */
	char	*outlib;	/* output library that will be created */
} lint2;

/* directory for temporary files */
static	const char *tmpdir;

/* default libraries */
static	char	**deflibs;

/* additional libraries */
static	char	**libs;

/* search path for libraries */
static	char	**libsrchpath;

static const char *libexec_dir;

/* flags */
static	bool	iflag, oflag, Cflag, sflag, tflag, Fflag, dflag;

/* print the commands executed to run the stages of compilation */
static	bool	Vflag;

/* filename for oflag */
static	char	*outputfn;

/* reset after first .c source has been processed */
static	bool	first = true;

/*
 * name of a file which is currently written by a child and should
 * be removed after abnormal termination of the child
 */
static	const	char *currfn;

#if !defined(TARGET_PREFIX)
#define	TARGET_PREFIX	""
#endif
static const char target_prefix[] = TARGET_PREFIX;

static	void	fname(const char *);
static	void	runchild(const char *, char *const *, const char *, int);
static	void	findlibs(char *const *);
static	bool	rdok(const char *);
static	void	run_lint2(void);
static	void	cat(char *const *, const char *);

static char **
list_new(void)
{
	char **list;

	list = xcalloc(1, sizeof(*list));
	return list;
}

static void
list_add_ref(char ***lstp, char *s)
{
	char	**lst, **olst;
	int	i;

	olst = *lstp;
	for (i = 0; olst[i] != NULL; i++)
		continue;
	lst = xrealloc(olst, (i + 2) * sizeof(*lst));
	lst[i] = s;
	lst[i + 1] = NULL;
	*lstp = lst;
}

static void
list_add(char ***lstp, const char *s)
{

	list_add_ref(lstp, xstrdup(s));
}

static void
list_add_unique(char ***lstp, const char *s)
{

	for (char **p = *lstp; *p != NULL; p++)
		if (strcmp(*p, s) == 0)
			return;
	list_add(lstp, s);
}

static void
list_add_all(char ***destp, char *const *src)
{
	int	i, k;
	char	**dest, **odest;

	odest = *destp;
	for (i = 0; odest[i] != NULL; i++)
		continue;
	for (k = 0; src[k] != NULL; k++)
		continue;
	dest = xrealloc(odest, (i + k + 1) * sizeof(*dest));
	for (k = 0; src[k] != NULL; k++)
		dest[i + k] = xstrdup(src[k]);
	dest[i + k] = NULL;
	*destp = dest;
}

static void
list_clear(char ***lstp)
{
	char	*s;
	int	i;

	for (i = 0; (*lstp)[i] != NULL; i++)
		continue;
	while (i-- > 0) {
		s = (*lstp)[i];
		(*lstp)[i] = NULL;
		free(s);
	}
}

static void
pass_to_lint1(const char *opt)
{

	list_add(&lint1.flags, opt);
}

static void
pass_flag_to_lint1(int flag)
{
	char buf[3];

	buf[0] = '-';
	buf[1] = (char)flag;
	buf[2] = '\0';
	pass_to_lint1(buf);
}

static void
pass_to_lint2(const char *opt)
{

	list_add(&lint2.flags, opt);
}

static void
pass_flag_to_lint2(int flag)
{
	char buf[3];

	buf[0] = '-';
	buf[1] = (char)flag;
	buf[2] = '\0';
	pass_to_lint2(buf);
}

static void
pass_to_cpp(const char *opt)
{

	list_add(&cpp.flags, opt);
}

static char *
concat2(const char *s1, const char *s2)
{
	char	*s;

	s = xmalloc(strlen(s1) + strlen(s2) + 1);
	(void)strcpy(s, s1);
	(void)strcat(s, s2);

	return s;
}

/*
 * Clean up after a signal.
 */
static void __attribute__((__noreturn__))
terminate(int signo)
{
	int	i;

	if (cpp.outfd != -1)
		(void)close(cpp.outfd);
	if (cpp.outfile != NULL) {
		if (signo != 0 && getenv("LINT_KEEP_CPPOUT_ON_ERROR") != NULL)
			printf("lint: preprocessor output kept in %s\n",
			    cpp.outfile);
		else
			(void)remove(cpp.outfile);
	}

	if (lint1.outfiles != NULL) {
		for (i = 0; lint1.outfiles[i] != NULL; i++)
			(void)remove(lint1.outfiles[i]);
	}

	if (lint2.outlib != NULL)
		(void)remove(lint2.outlib);

	if (currfn != NULL)
		(void)remove(currfn);

	if (signo != 0)
		(void)raise_default_signal(signo);
	exit(signo != 0 ? 1 : 0);
}

/*
 * Returns a pointer to the last component of strg after delim.
 * Returns strg if the string does not contain delim.
 */
static const char *
lbasename(const char *strg, int delim)
{
	const	char *cp, *cp1, *cp2;

	cp = cp1 = cp2 = strg;
	while (*cp != '\0') {
		if (*cp++ == delim) {
			cp2 = cp1;
			cp1 = cp;
		}
	}
	return *cp1 == '\0' ? cp2 : cp1;
}

static void __attribute__((__noreturn__, __format__(__printf__, 1, 2)))
usage(const char *fmt, ...)
{
	const char *name;
	int indent;
	va_list ap;

	name = getprogname();
	fprintf(stderr, "%s: ", name);
	va_start(ap, fmt);
	vfprintf(stderr, fmt, ap);
	va_end(ap);
	fprintf(stderr, "\n");

	indent = (int)(strlen("usage: ") + strlen(name));
	(void)fprintf(stderr,
	    "usage: %s [-abceghprvwxzHFST] [-s|-t] [-i|-nu]\n"
	    "%*s [-Dname[=def]] [-Uname] [-Idirectory] [-Z <cpparg>]\n"
	    "%*s [-Ldirectory] [-llibrary] [-ooutputfile]\n"
	    "%*s [-X <id>[,<id>]...] [-Ac11] file...\n",
	    name, indent, "", indent, "", indent, "");
	(void)fprintf(stderr,
	    "       %s [-abceghprvwzHFST] [-s|-t] -Clibrary\n"
	    "%*s [-Dname[=def]] [-Uname] [-Idirectory] [-Z <cpparg>]\n"
	    "%*s [-Bpath] [-X <id>[,<id>]...] [-R old=new] file ...\n",
	    name, indent, "", indent, "");
	terminate(-1);
}


int
main(int argc, char *argv[])
{
	int	c;
	char	*tmp;
	size_t	len;
	const char *ks;

	setprogname(argv[0]);

	if ((tmp = getenv("TMPDIR")) == NULL || (len = strlen(tmp)) == 0) {
		tmpdir = xstrdup(_PATH_TMP);
	} else {
		tmpdir = concat2(tmp, tmp[len - 1] == '/' ? "" : "/");
	}

	cpp.outfile = concat2(tmpdir, "lint0.XXXXXX");
	cpp.outfd = mkstemp(cpp.outfile);
	if (cpp.outfd == -1) {
		warn("can't make temp");
		terminate(-1);
	}

	lint1.outfiles = list_new();
	lint2.infiles = list_new();
	cpp.flags = list_new();
	cpp.lcflags = list_new();
	lint1.flags = list_new();
	lint2.flags = list_new();
	lint2.inlibs = list_new();
	deflibs = list_new();
	libs = list_new();
	libsrchpath = list_new();

	pass_to_cpp("-E");
	pass_to_cpp("-x");
	pass_to_cpp("c");
	pass_to_cpp("-U__GNUC__");
	pass_to_cpp("-U__PCC__");
	pass_to_cpp("-U__SSE__");
	pass_to_cpp("-U__SSE4_1__");
	pass_to_cpp("-Wp,-CC");
	pass_to_cpp("-Wcomment");
	pass_to_cpp("-D__LINT__");
	pass_to_cpp("-Dlint");		/* XXX don't def. with -s */
	pass_to_cpp("-D__lint");
	pass_to_cpp("-D__lint__");

	list_add(&deflibs, "c");

	if (signal(SIGHUP, terminate) == SIG_IGN)
		(void)signal(SIGHUP, SIG_IGN);
	(void)signal(SIGINT, terminate);
	(void)signal(SIGQUIT, terminate);
	(void)signal(SIGTERM, terminate);
	while ((c = getopt(argc, argv,
	    "abcd:eghil:no:prstuvwxzA:B:C:D:FHI:L:M:PR:STU:VW:X:Z:")) != -1) {
		switch (c) {

		case 'a':
		case 'b':
		case 'c':
		case 'e':
		case 'g':
		case 'r':
		case 'v':
		case 'w':
		case 'z':
		case 'P':
			pass_flag_to_lint1(c);
			break;

		case 'A':
		case 'R':
		case 'X':
			pass_flag_to_lint1(c);
			pass_to_lint1(optarg);
			break;

		case 'F':
			Fflag = true;
			/* FALLTHROUGH */
		case 'u':
		case 'h':
			pass_flag_to_lint1(c);
			pass_flag_to_lint2(c);
			break;

		case 'i':
			if (Cflag)
				usage("%c and %s flags cannot be specified "
				    "together", 'C', "i");
			iflag = true;
			break;

		case 'n':
			list_clear(&deflibs);
			break;

		case 'p':
			if (*deflibs != NULL) {
				list_clear(&deflibs);
				list_add(&deflibs, "c");
			}
			pass_flag_to_lint1(c);
			break;

		case 's':
			if (tflag)
				usage("%c and %s flags cannot be specified "
				    "together", 's', "t");
			list_clear(&cpp.lcflags);
			list_add(&cpp.lcflags, "-trigraphs");
			list_add(&cpp.lcflags, "-Wtrigraphs");
			list_add(&cpp.lcflags, "-pedantic");
			list_add(&cpp.lcflags, "-D__STRICT_ANSI__");
			sflag = true;
			pass_flag_to_lint1(c);
			pass_flag_to_lint2(c);
			break;

		case 'S':
			if (tflag)
				usage("%c and %s flags cannot be specified "
				    "together", 'S', "t");
			pass_flag_to_lint1(c);
			break;

		case 'T':
			pass_to_cpp("-I" PATH_STRICT_BOOL_INCLUDE);
			pass_flag_to_lint1(c);
			break;

#if !HAVE_NBTOOL_CONFIG_H
		case 't':
			if (sflag)
				usage("%c and %s flags cannot be specified "
				    "together", 's', "t");
			tflag = true;
			list_clear(&cpp.lcflags);
			list_add(&cpp.lcflags, "-traditional");
			list_add(&cpp.lcflags, "-Wtraditional");
			list_add(&cpp.lcflags, "-D" MACHINE);
			list_add(&cpp.lcflags, "-D" MACHINE_ARCH);
			pass_flag_to_lint1(c);
			pass_flag_to_lint2(c);
			break;
#endif

		case 'x':
		case 'H':
			pass_flag_to_lint2(c);
			break;

		case 'C':
			if (Cflag)
				usage("%c flag already specified", 'C');
			if (oflag || iflag)
				usage("%c and %s flags cannot be specified "
				    "together", 'C', "o or i");
			Cflag = true;
			pass_flag_to_lint2(c);
			pass_to_lint2(optarg);
			lint2.outlib = xasprintf("llib-l%s.ln", optarg);
			list_clear(&deflibs);
			break;

		case 'd':
			if (dflag)
				usage("%c flag already specified", 'd');
			dflag = true;
			pass_to_cpp("-nostdinc");
			pass_to_cpp("-isystem");
			pass_to_cpp(optarg);
			break;

		case 'D':
		case 'I':
		case 'M':
		case 'U':
		case 'W':
			list_add_ref(&cpp.flags,
			    xasprintf("-%c%s", c, optarg));
			break;

		case 'l':
			list_add_unique(&libs, optarg);
			break;

		case 'o':
			if (oflag)
				usage("%c flag already specified", 'o');
			if (Cflag)
				usage("%c and %s flags cannot be specified "
				    "together", 'C', "o");
			oflag = true;
			outputfn = xstrdup(optarg);
			break;

		case 'L':
			list_add_unique(&libsrchpath, optarg);
			break;

		case 'B':
			libexec_dir = xstrdup(optarg);
			break;

		case 'V':
			Vflag = true;
			break;

		case 'Z':
			pass_to_cpp(optarg);
			break;

		default:
			usage("Unknown flag %c", c);
			/* NOTREACHED */
		}
	}
	argc -= optind;
	argv += optind;

	/*
	 * To avoid modifying getopt(3)'s state engine midstream, we
	 * explicitly accept just a few options after the first source file.
	 *
	 * In particular, only -l<lib> and -L<libdir> (and these with a space
	 * after -l or -L) are allowed.
	 */
	while (argc > 0) {
		const char *arg = argv[0];

		if (arg[0] == '-') {
			char ***list;

			/* option */
			if (arg[1] == 'l')
				list = &libs;
			else if (arg[1] == 'L')
				list = &libsrchpath;
			else {
				usage("Unknown argument %s", arg);
				/* NOTREACHED */
			}

			if (arg[2] != '\0')
				list_add_unique(list, arg + 2);
			else if (argc > 1) {
				argc--;
				list_add_unique(list, *++argv);
			} else
				usage("Missing argument for l or L");
		} else {
			/* filename */
			fname(arg);
			first = false;
		}
		argc--;
		argv++;
	}

	if (first)
		usage("Missing filename");

	if (iflag)
		terminate(0);

	if (!oflag) {
		if ((ks = getenv("LIBDIR")) == NULL || strlen(ks) == 0)
			ks = PATH_LINTLIB;
		list_add(&libsrchpath, ks);
		findlibs(libs);
		findlibs(deflibs);
	}

	run_lint2();

	if (oflag)
		cat(lint2.infiles, outputfn);

	if (Cflag)
		lint2.outlib = NULL;

	terminate(0);
	/* NOTREACHED */
}

/*
 * Read a file name from the command line
 * and pass it through lint1 if it is a C source.
 */
static void
fname(const char *name)
{
	const	char *bn, *suff;
	char	**args, *ofn, *pathname;
	const char *CC;
	size_t	len;
	int	fd;

	bn = lbasename(name, '/');
	suff = lbasename(bn, '.');

	if (strcmp(suff, "ln") == 0) {
		/* only for lint2 */
		if (!iflag)
			list_add(&lint2.infiles, name);
		return;
	}

	if (strcmp(suff, "c") != 0 &&
	    (strncmp(bn, "llib-l", 6) != 0 || bn != suff)) {
		warnx("unknown file type: %s", name);
		return;
	}

	if (!iflag || !first)
		(void)printf("%s:\n", Fflag ? name : bn);

	/* build the name of the output file of lint1 */
	if (oflag) {
		ofn = outputfn;
		outputfn = NULL;
		oflag = false;
	} else if (iflag) {
		len = bn == suff ? strlen(bn) : (size_t)((suff - 1) - bn);
		ofn = xasprintf("%.*s.ln", (int)len, bn);
	} else {
		ofn = xasprintf("%slint1.XXXXXX", tmpdir);
		fd = mkstemp(ofn);
		if (fd == -1) {
			warn("can't make temp");
			terminate(-1);
		}
		(void)close(fd);
	}
	if (!iflag)
		list_add(&lint1.outfiles, ofn);

	args = list_new();

	/* run cc */
	if ((CC = getenv("CC")) == NULL)
		CC = DEFAULT_CC;
	if ((pathname = findcc(CC)) == NULL)
		if (setenv("PATH", DEFAULT_PATH, 1) == 0)
			pathname = findcc(CC);
	if (pathname == NULL) {
		(void)fprintf(stderr, "%s: %s: not found\n", getprogname(), CC);
		exit(EXIT_FAILURE);
	}

	list_add(&args, pathname);
	list_add_all(&args, cpp.flags);
	list_add_all(&args, cpp.lcflags);
	list_add(&args, name);

	/* we reuse the same tmp file for cpp output, so rewind and truncate */
	if (lseek(cpp.outfd, (off_t)0, SEEK_SET) != 0) {
		warn("lseek");
		terminate(-1);
	}
	if (ftruncate(cpp.outfd, (off_t)0) != 0) {
		warn("ftruncate");
		terminate(-1);
	}

	runchild(pathname, args, cpp.outfile, cpp.outfd);
	free(pathname);
	list_clear(&args);

	/* run lint1 */

	if (libexec_dir == NULL) {
		pathname = xasprintf("%s/%slint1",
		    PATH_LIBEXEC, target_prefix);
	} else {
		/*
		 * XXX Unclear whether we should be using target_prefix
		 * XXX here.  --thorpej@wasabisystems.com
		 */
		pathname = concat2(libexec_dir, "/lint1");
	}

	list_add(&args, pathname);
	list_add_all(&args, lint1.flags);
	list_add(&args, cpp.outfile);
	list_add(&args, ofn);

	runchild(pathname, args, ofn, -1);
	free(pathname);
	list_clear(&args);

	list_add(&lint2.infiles, ofn);
	free(ofn);

	free(args);
}

static bool
is_safe_shell(char ch)
{

	return ch_isalnum(ch) || ch == '%' || ch == '+' || ch == ',' ||
	       ch == '-' || ch == '.' || ch == '/' || ch == ':' ||
	       ch == '=' || ch == '@' || ch == '_';
}

static void
print_sh_quoted(const char *s)
{

	if (s[0] == '\0')
		goto needs_quoting;
	for (const char *p = s; *p != '\0'; p++)
		if (!is_safe_shell(*p))
			goto needs_quoting;

	(void)printf("%s", s);
	return;

needs_quoting:
	(void)putchar('\'');
	for (const char *p = s; *p != '\0'; p++) {
		if (*p == '\'')
			(void)printf("'\\''");
		else
			(void)putchar(*p);
	}
	(void)putchar('\'');
}

static void
runchild(const char *path, char *const *args, const char *crfn, int fdout)
{
	int	status, rv, signo, i;

	if (Vflag) {
		print_sh_quoted(args[0]);
		for (i = 1; args[i] != NULL; i++) {
			(void)printf(" ");
			print_sh_quoted(args[i]);
		}
		(void)printf("\n");
	}

	currfn = crfn;

	(void)fflush(stdout);

	switch (vfork()) {
	case -1:
		warn("cannot fork");
		terminate(-1);
		/* NOTREACHED */
	default:
		/* parent */
		break;
	case 0:
		/* child */

		/* set up the standard output if necessary */
		if (fdout != -1) {
			(void)dup2(fdout, STDOUT_FILENO);
			(void)close(fdout);
		}
		(void)execvp(path, args);
		warn("cannot exec %s", path);
		_exit(1);
		/* NOTREACHED */
	}

	while ((rv = wait(&status)) == -1 && errno == EINTR) ;
	if (rv == -1) {
		warn("wait");
		terminate(-1);
	}
	if (WIFSIGNALED(status)) {
		signo = WTERMSIG(status);
#if HAVE_DECL_SYS_SIGNAME
		warnx("%s got SIG%s", path, sys_signame[signo]);
#else
		warnx("%s got signal %d", path, signo);
#endif
		terminate(-1);
	}
	if (WEXITSTATUS(status) != 0)
		terminate(-1);
	currfn = NULL;
}

static void
findlib(const char *lib)
{
	char *const *dir;
	char *lfn;

	for (dir = libsrchpath; *dir != NULL; dir++) {
		lfn = xasprintf("%s/llib-l%s.ln", *dir, lib);
		if (rdok(lfn))
			goto found;
		free(lfn);

		lfn = xasprintf("%s/lint/llib-l%s.ln", *dir, lib);
		if (rdok(lfn))
			goto found;
		free(lfn);
	}

	warnx("cannot find llib-l%s.ln", lib);
	return;

found:
	list_add_ref(&lint2.inlibs, concat2("-l", lfn));
	free(lfn);
}

static void
findlibs(char *const *liblst)
{
	char *const *p;

	for (p = liblst; *p != NULL; p++)
		findlib(*p);
}

static bool
rdok(const char *path)
{
	struct	stat sbuf;

	if (stat(path, &sbuf) == -1)
		return false;
	if (!S_ISREG(sbuf.st_mode))
		return false;
	if (access(path, R_OK) == -1)
		return false;
	return true;
}

static void
run_lint2(void)
{
	char	*path, **args;

	args = list_new();

	if (libexec_dir == NULL) {
		path = xasprintf("%s/%slint2", PATH_LIBEXEC, target_prefix);
	} else {
		/*
		 * XXX Unclear whether we should be using target_prefix
		 * XXX here.  --thorpej@wasabisystems.com
		 */
		path = concat2(libexec_dir, "/lint2");
	}

	list_add(&args, path);
	list_add_all(&args, lint2.flags);
	list_add_all(&args, lint2.inlibs);
	list_add_all(&args, lint2.infiles);

	runchild(path, args, lint2.outlib, -1);
	free(path);
	list_clear(&args);
	free(args);
}

static void
cat(char *const *srcs, const char *dest)
{
	int	ifd, ofd, i;
	char	*src, *buf;
	ssize_t	rlen;

	if ((ofd = open(dest, O_WRONLY | O_CREAT | O_TRUNC, 0666)) == -1) {
		warn("cannot open %s", dest);
		terminate(-1);
	}

	buf = xmalloc(MBLKSIZ);

	for (i = 0; (src = srcs[i]) != NULL; i++) {
		if ((ifd = open(src, O_RDONLY)) == -1) {
			free(buf);
			warn("cannot open %s", src);
			terminate(-1);
		}
		do {
			if ((rlen = read(ifd, buf, MBLKSIZ)) == -1) {
				free(buf);
				warn("read error on %s", src);
				terminate(-1);
			}
			if (write(ofd, buf, (size_t)rlen) == -1) {
				free(buf);
				warn("write error on %s", dest);
				terminate(-1);
			}
		} while (rlen == MBLKSIZ);
		(void)close(ifd);
	}
	(void)close(ofd);
	free(buf);
}