Blob Blame History Raw
/*******************************************************************/
/*  slibtool: a skinny libtool implementation, written in C        */
/*  Copyright (C) 2016--2021  SysDeer Technologies, LLC            */
/*  Released under the Standard MIT License; see COPYING.SLIBTOOL. */
/*******************************************************************/

#include <fcntl.h>
#include <stdio.h>
#include <string.h>
#include <stdbool.h>
#include <fcntl.h>
#include <errno.h>
#include <sys/stat.h>

#include <slibtool/slibtool.h>
#include "slibtool_driver_impl.h"
#include "slibtool_install_impl.h"
#include "slibtool_mapfile_impl.h"
#include "slibtool_readlink_impl.h"
#include "slibtool_spawn_impl.h"
#include "slibtool_symlink_impl.h"
#include "slibtool_errinfo_impl.h"
#include "slibtool_snprintf_impl.h"
#include "argv/argv.h"

static int slbt_install_usage(
	int				fdout,
	const char *			program,
	const char *			arg,
	const struct argv_option **	optv,
	struct argv_meta *		meta,
	int				noclr)
{
	char header[512];

	snprintf(header,sizeof(header),
		"Usage: %s --mode=install <install> [options] [SOURCE]... DEST\n"
		"Options:\n",
		program);

	switch (noclr) {
		case 0:
			slbt_argv_usage(fdout,header,optv,arg);
			break;

		default:
			slbt_argv_usage_plain(fdout,header,optv,arg);
			break;
	}

	slbt_argv_free(meta);

	return SLBT_USAGE;
}

static int slbt_exec_install_fail(
	struct slbt_exec_ctx *	actx,
	struct argv_meta *	meta,
	int			ret)
{
	slbt_argv_free(meta);
	slbt_free_exec_ctx(actx);
	return ret;
}

static int slbt_exec_install_init_dstdir(
	const struct slbt_driver_ctx *	dctx,
	struct argv_entry *	dest,
	struct argv_entry *	last,
	char *			dstdir)
{
	int		fdcwd;
	struct stat	st;
	char *		slash;
	size_t		len;

	/* fdcwd */
	fdcwd = slbt_driver_fdcwd(dctx);

	/* last */
	if (dest)
		last = dest;

	/* dstdir: initial string */
	if (slbt_snprintf(dstdir,PATH_MAX,
			"%s",last->arg) < 0)
		return SLBT_BUFFER_ERROR(dctx);

	/* dstdir might end with a slash */
	len = strlen(dstdir);

	if (dstdir[--len] == '/')
		dstdir[len] = 0;

	/* -t DSTDIR? */
	if (dest)
		return 0;

	/* is DEST a directory? */
	if (!fstatat(fdcwd,dstdir,&st,0))
		if (S_ISDIR(st.st_mode))
			return 0;

	/* remove last path component */
	if ((slash = strrchr(dstdir,'/')))
		*slash = 0;

	return 0;
}

static int slbt_exec_install_import_libraries(
	const struct slbt_driver_ctx *	dctx,
	struct slbt_exec_ctx *		ectx,
	char *				srcdso,
	char *				dstdir)
{
	char *	slash;
	char *	dot;
	char *	mark;
	char	srcbuf [PATH_MAX];
	char	implib [PATH_MAX];
	char	hostlnk[PATH_MAX];
	char	major  [128];
	char	minor  [128];
	char	rev    [128];

	/* .libs/libfoo.so.x.y.z */
	if (slbt_snprintf(srcbuf,sizeof(srcbuf),
			"%s",srcdso) <0)
		return SLBT_BUFFER_ERROR(dctx);

	/* (dso is under .libs) */
	if (!(slash = strrchr(srcbuf,'/')))
		return SLBT_CUSTOM_ERROR(dctx,SLBT_ERR_INSTALL_FLOW);

	/* libfoo.so.x.y.z */
	const char * impsuffix = dctx->cctx->settings.impsuffix;

	if (slbt_snprintf(implib,
			sizeof(implib) - strlen(impsuffix),
			"%s",++slash) < 0)
		return SLBT_BUFFER_ERROR(dctx);

	/* guard against an infinitely long version */
	mark = srcbuf + strlen(srcbuf);

	if (dctx->cctx->asettings.osdfussix[0]) {
		if (!(dot = strrchr(srcbuf,'.')))
			return SLBT_CUSTOM_ERROR(dctx,SLBT_ERR_INSTALL_FLOW);

		*dot = 0;
	}

	/* rev */
	if (!(dot = strrchr(srcbuf,'.')))
		return SLBT_CUSTOM_ERROR(dctx,SLBT_ERR_INSTALL_FLOW);
	else if ((size_t)(mark - dot) > sizeof(rev))
		return SLBT_CUSTOM_ERROR(dctx,SLBT_ERR_INSTALL_REV);
	else {
		strcpy(rev,dot);
		*dot = 0;
	}

	/* minor */
	if (!(dot = strrchr(srcbuf,'.')))
		return SLBT_CUSTOM_ERROR(dctx,SLBT_ERR_INSTALL_FLOW);
	else if ((size_t)(mark - dot) > sizeof(minor))
		return SLBT_CUSTOM_ERROR(dctx,SLBT_ERR_INSTALL_REV);
	else {
		strcpy(minor,dot);
		*dot = 0;
	}

	/* major */
	if (!(dot = strrchr(srcbuf,'.')))
		return SLBT_CUSTOM_ERROR(dctx,SLBT_ERR_INSTALL_FLOW);
	else if ((size_t)(mark - dot) > sizeof(major))
		return SLBT_CUSTOM_ERROR(dctx,SLBT_ERR_INSTALL_REV);
	else {
		strcpy(major,dot);
		*dot = 0;
	}

	if (!dctx->cctx->asettings.osdfussix[0])
		if (!(dot = strrchr(srcbuf,'.')))
			return SLBT_CUSTOM_ERROR(dctx,SLBT_ERR_INSTALL_FLOW);

	/* .libs/libfoo.x.y.z.lib.a */
	sprintf(dot,"%s%s%s%s",
		major,minor,rev,
		dctx->cctx->asettings.impsuffix);

	/* copy: .libs/libfoo.x.y.z.lib.a --> dstdir */
	if (slbt_copy_file(dctx,ectx,srcbuf,dstdir))
		return SLBT_NESTED_ERROR(dctx);

	/* .libs/libfoo.x.lib.a */
	sprintf(dot,"%s%s",
		major,
		dctx->cctx->asettings.impsuffix);

	/* copy: .libs/libfoo.x.lib.a --> dstdir */
	if (slbt_copy_file(dctx,ectx,srcbuf,dstdir))
		return SLBT_NESTED_ERROR(dctx);

	/* /dstdir/libfoo.lib.a */
	strcpy(implib,slash);
	strcpy(dot,dctx->cctx->asettings.impsuffix);

	if (slbt_snprintf(hostlnk,sizeof(hostlnk),
			"%s/%s",dstdir,slash) <0)
		return SLBT_BUFFER_ERROR(dctx);

	if (slbt_create_symlink(
			dctx,ectx,
			implib,
			hostlnk,
			SLBT_SYMLINK_DEFAULT))
		return SLBT_NESTED_ERROR(dctx);

	return 0;
}

static int slbt_exec_install_library_wrapper(
	const struct slbt_driver_ctx *	dctx,
	struct slbt_exec_ctx *		ectx,
	struct argv_entry *		entry,
	char *				dstdir)
{
	int			fdcwd;
	int			fddst;
	size_t			buflen;
	const char *		base;
	char *			srcline;
	char *			dstline;
	char			clainame[PATH_MAX];
	char			instname[PATH_MAX];
	char			cfgbuf  [PATH_MAX];
	struct slbt_map_info *	mapinfo;

	/* base libfoo.la */
	if ((base = strrchr(entry->arg,'/')))
		base++;
	else
		base = entry->arg;

	/* /dstdir/libfoo.la */
	if (slbt_snprintf(instname,sizeof(instname),
			"%s/%s",dstdir,base) < 0)
		return SLBT_BUFFER_ERROR(dctx);

	/* libfoo.la.slibtool.install */
	if (slbt_snprintf(clainame,sizeof(clainame),
			"%s.slibtool.install",
			entry->arg) < 0)
		return SLBT_BUFFER_ERROR(dctx);

	/* fdcwd */
	fdcwd = slbt_driver_fdcwd(dctx);

	/* fddst (libfoo.la.slibtool.install, build directory) */
	if ((fddst = openat(fdcwd,clainame,O_RDWR|O_CREAT|O_TRUNC,0644)) < 0)
		return SLBT_SYSTEM_ERROR(dctx,clainame);

	/* mapinfo (libfoo.la, build directory) */
	if (!(mapinfo = slbt_map_file(fdcwd,entry->arg,SLBT_MAP_INPUT))) {
		close(fddst);
		return SLBT_SYSTEM_ERROR(dctx,entry->arg);
	}

	/* srcline */
	if (mapinfo->size < sizeof(cfgbuf)) {
		buflen  = sizeof(cfgbuf);
		srcline = cfgbuf;
	} else {
		buflen  = mapinfo->size;
		srcline = malloc(++buflen);
	}

	if (!srcline) {
		close(fddst);
		slbt_unmap_file(mapinfo);
		return SLBT_SYSTEM_ERROR(dctx,0);
	}

	/* copy config, installed=no --> installed=yes */
	while (mapinfo->mark < mapinfo->cap) {
		if (slbt_mapped_readline(dctx,mapinfo,srcline,buflen) < 0) {
			close(fddst);
			slbt_unmap_file(mapinfo);
			return SLBT_NESTED_ERROR(dctx);
		}

		dstline = strcmp(srcline,"installed=no\n")
			? srcline
			: "installed=yes\n";

		if (slbt_dprintf(fddst,"%s",dstline) < 0) {
			close(fddst);
			slbt_unmap_file(mapinfo);
			return SLBT_SYSTEM_ERROR(dctx,0);
		}
	}

	if (srcline != cfgbuf)
		free(srcline);

	/* close, unmap */
	close(fddst);
	slbt_unmap_file(mapinfo);

	/* cp libfoo.la.slibtool.instal /dstdir/libfoo.la */
	if (slbt_copy_file(dctx,ectx,clainame,instname))
		return SLBT_NESTED_ERROR(dctx);

	return 0;
}

static int slbt_exec_install_entry(
	const struct slbt_driver_ctx *	dctx,
	struct slbt_exec_ctx *		ectx,
	struct argv_entry *		entry,
	struct argv_entry *		last,
	struct argv_entry *		dest,
	char *				dstdir,
	char **				src,
	char **				dst)
{
	int		fdcwd;
	const char *	base;
	char *		dot;
	char *		host;
	char *		mark;
	char *		slash;
	char *		suffix;
	char *		dsosuffix;
	char		sobuf   [64];
	char		target  [PATH_MAX];
	char		srcfile [PATH_MAX];
	char		dstfile [PATH_MAX];
	char		slnkname[PATH_MAX];
	char		dlnkname[PATH_MAX];
	char		hosttag [PATH_MAX];
	char		lasource[PATH_MAX - 8];
	bool		fexe = false;
	bool		fpe;
	bool		frelease;
	bool		fdualver;
	bool		fstatic;
	bool		farchive;
	size_t		slen;
	size_t		dlen;
	struct stat	st;

	/* executable wrapper? */
	base = (slash = strrchr(entry->arg,'/'))
		? ++slash : entry->arg;

	strcpy(slnkname,entry->arg);
	mark = &slnkname[base - entry->arg];
	slen = sizeof(slnkname) - (mark - slnkname);

	if (slbt_snprintf(mark,slen,
			".libs/%s.exe.wrapper",
			base) < 0)
		return SLBT_BUFFER_ERROR(dctx);

	/* fdcwd */
	fdcwd = slbt_driver_fdcwd(dctx);

	/* fexe */
	fexe = !fstatat(fdcwd,slnkname,&st,0);

	/* argument suffix */
	dot  = strrchr(entry->arg,'.');

	/* .lai --> .la */
	if (!fexe && dot && !strcmp(dot,".lai"))
		dot[3] = 0;

	/* srcfile */
	if (strlen(entry->arg) + strlen(".libs/") >= (PATH_MAX-1))
		return SLBT_BUFFER_ERROR(dctx);

	strcpy(lasource,entry->arg);

	if ((slash = strrchr(lasource,'/'))) {
		*slash++ = 0;
		sprintf(srcfile,"%s/.libs/%s",lasource,slash);
	} else {
		sprintf(srcfile,".libs/%s",lasource);
	}

	/* executable? ordinary file? */
	if (fexe || !dot || strcmp(dot,".la")) {
		*src = fexe ? srcfile : (char *)entry->arg;
		*dst = dest ? 0 : (char *)last->arg;

		if (!(dctx->cctx->drvflags & SLBT_DRIVER_SILENT))
			if (slbt_output_install(dctx,ectx))
				return SLBT_NESTED_ERROR(dctx);

		if ((slbt_spawn(ectx,true) < 0) && (ectx->pid < 0)) {
			return SLBT_SPAWN_ERROR(dctx);

		} else if (ectx->exitcode) {
			return SLBT_CUSTOM_ERROR(
				dctx,
				SLBT_ERR_INSTALL_ERROR);
		}

		return 0;
	}

	/* -shrext, dsosuffix */
	strcpy(sobuf,dctx->cctx->settings.dsosuffix);
	dsosuffix = sobuf;

	if (slbt_snprintf(slnkname,sizeof(slnkname),
			"%s.shrext",srcfile) < 0)
		return SLBT_BUFFER_ERROR(dctx);

	if (!fstatat(fdcwd,slnkname,&st,0)) {
		if (slbt_readlinkat(fdcwd,slnkname,target,sizeof(target)) < 0)
			return SLBT_SYSTEM_ERROR(dctx,slnkname);

		if (strncmp(lasource,target,(slen = strlen(lasource))))
			return SLBT_CUSTOM_ERROR(dctx,SLBT_ERR_INSTALL_FLOW);

		if (strncmp(&target[slen],".shrext",7))
			return SLBT_CUSTOM_ERROR(dctx,SLBT_ERR_INSTALL_FLOW);

		strcpy(sobuf,&target[slen+7]);
	}

	/* legabits? */
	if (dctx->cctx->drvflags & SLBT_DRIVER_LEGABITS)
		if (slbt_exec_install_library_wrapper(dctx,ectx,entry,dstdir))
			return SLBT_NESTED_ERROR(dctx);

	/* *dst: consider: cp libfoo.la /dest/dir/libfoo.la */
	if ((*dst = dest ? 0 : (char *)last->arg))
		if ((dot = strrchr(last->arg,'.')))
			if (!(strcmp(dot,".la")))
				*dst = dstdir;

	/* libfoo.a */
	dot = strrchr(srcfile,'.');
	strcpy(dot,dctx->cctx->settings.arsuffix);

	/* dot/suffix */
	strcpy(slnkname,srcfile);
	dot = strrchr(slnkname,'.');

	/* .libs/libfoo.so.def.host */
	slen  = sizeof(slnkname);
	slen -= (dot - slnkname);

	/* detect -release, exclusively or alongside -version-info */
	frelease = false;
	fdualver = false;
	fstatic  = false;
	fpe      = false;

	/* static library only? */
	sprintf(dot,"%s",dsosuffix);
	fstatic = slbt_symlink_is_a_placeholder(fdcwd,slnkname);

	/* libfoo.a --> libfoo.so.release */
	if (!fstatic) {
		sprintf(dot,"%s.release",dsosuffix);
		frelease = !fstatat(fdcwd,slnkname,&st,0);
	}

	/* libfoo.a --> libfoo.so.dualver */
	if (!fstatic && !frelease) {
		sprintf(dot,"%s.dualver",dsosuffix);
		fdualver = !fstatat(fdcwd,slnkname,&st,0);
	}

	/* libfoo.so.def.{flavor} */
	if (fstatic) {
		(void)0;

	} else if (frelease || fdualver) {
		strcpy(dlnkname,slnkname);
		slash = strrchr(dlnkname,'/');

		dlen  = sizeof(dlnkname);
		dlen -= (++slash - dlnkname);
		dlen -= 9;

		if (slbt_readlinkat(fdcwd,slnkname,slash,dlen))
			return SLBT_SYSTEM_ERROR(dctx,slnkname);

		if (fdualver) {
			/* remove .patch */
			if (!(mark = strrchr(dlnkname,'.')))
				return SLBT_CUSTOM_ERROR(dctx,SLBT_ERR_INSTALL_FLOW);

			*mark = 0;

			/* remove .minor */
			if (!(mark = strrchr(dlnkname,'.')))
				return SLBT_CUSTOM_ERROR(dctx,SLBT_ERR_INSTALL_FLOW);

			*mark = 0;

			/* remove .major */
			if (!(mark = strrchr(dlnkname,'.')))
				return SLBT_CUSTOM_ERROR(dctx,SLBT_ERR_INSTALL_FLOW);
		} else {
			mark = slash += strlen(slash);
		}

		strcpy(mark,".def.host");

		if (slbt_readlinkat(fdcwd,dlnkname,hosttag,sizeof(hosttag)))
			return SLBT_SYSTEM_ERROR(dctx,slnkname);
	} else {
		if (slbt_snprintf(dot,slen,"%s.def.host",dsosuffix) < 0)
			return SLBT_BUFFER_ERROR(dctx);

		if (slbt_readlinkat(fdcwd,frelease ? dlnkname : slnkname,hosttag,sizeof(hosttag)))
			return SLBT_SYSTEM_ERROR(dctx,slnkname);
	}

	/* host/flabor */
	if (fstatic) {
		(void)0;

	} else if (!(host = strrchr(hosttag,'.'))) {
		return SLBT_CUSTOM_ERROR(dctx,SLBT_ERR_INSTALL_FLOW);
	} else {
		host++;
	}

	/* symlink-based alternate host */
	if (!fstatic) {
		if (slbt_set_alternate_host(dctx,host,host))
			return SLBT_NESTED_ERROR(dctx);

		fpe = !strcmp(dctx->cctx->asettings.imagefmt,"pe");
	}

	/* libfoo.a --> libfoo.so */
	strcpy(dot,dsosuffix);

	/* libfoo.a installation */
	if (!(dctx->cctx->drvflags & SLBT_DRIVER_DISABLE_STATIC))
		farchive = true;
	else if (fstatic)
		farchive = true;
	else
		farchive = false;

	if (farchive)
		if (slbt_copy_file(dctx,ectx,
				srcfile,
				dest ? (char *)dest->arg : *dst))
			return SLBT_NESTED_ERROR(dctx);

	/* basename */
	if ((base = strrchr(slnkname,'/')))
		base++;
	else
		base = slnkname;

	/* source (build) symlink target */
	if (slbt_readlinkat(fdcwd,slnkname,target,sizeof(target)) < 0) {
		/* -all-static? */
		if (fstatic)
			return 0;

		/* -avoid-version? */
		if (fstatat(fdcwd,slnkname,&st,0))
			return SLBT_SYSTEM_ERROR(dctx,slnkname);

		/* dstfile */
		if (slbt_snprintf(dstfile,sizeof(dstfile),
				"%s/%s",dstdir,base) < 0)
			return SLBT_BUFFER_ERROR(dctx);

		/* single spawn, no symlinks */
		*src = slnkname;
		*dst = dest ? 0 : dstfile;

		if (!(dctx->cctx->drvflags & SLBT_DRIVER_SILENT))
			if (slbt_output_install(dctx,ectx))
				return SLBT_NESTED_ERROR(dctx);

		if ((slbt_spawn(ectx,true) < 0) && (ectx->pid < 0)) {
			return SLBT_SPAWN_ERROR(dctx);

		} else if (ectx->exitcode) {
			return SLBT_CUSTOM_ERROR(
				dctx,
				SLBT_ERR_INSTALL_ERROR);
		}

		return 0;
	}

	/* srcfile: .libs/libfoo.so.x.y.z */
	slash = strrchr(srcfile,'/');
	strcpy(++slash,target);

	/* dstfile */
	if (!dest)
		if (slbt_snprintf(dstfile,sizeof(dstfile),
				"%s/%s",dstdir,target) < 0)
			return SLBT_BUFFER_ERROR(dctx);

	/* spawn */
	*src = srcfile;
	*dst = dest ? 0 : dstfile;

	if (!(dctx->cctx->drvflags & SLBT_DRIVER_SILENT))
		if (slbt_output_install(dctx,ectx))
			return SLBT_NESTED_ERROR(dctx);

	if ((slbt_spawn(ectx,true) < 0) && (ectx->pid < 0)) {
		return SLBT_SPAWN_ERROR(dctx);

	} else if (ectx->exitcode) {
		return SLBT_CUSTOM_ERROR(
			dctx,
			SLBT_ERR_INSTALL_ERROR);
	}

	/* destination symlink: dstdir/libfoo.so */
	if (slbt_snprintf(dlnkname,sizeof(dlnkname),
			"%s/%s",dstdir,base) < 0)
		return SLBT_BUFFER_ERROR(dctx);

	/* create symlink: libfoo.so --> libfoo.so.x.y.z */
	if (slbt_create_symlink(
			dctx,ectx,
			target,dlnkname,
			SLBT_SYMLINK_DEFAULT))
		return SLBT_NESTED_ERROR(dctx);

	if (frelease)
		return 0;

	/* libfoo.so.x --> libfoo.so.x.y.z */
	strcpy(slnkname,target);

	if ((suffix = strrchr(slnkname,'.')))
		*suffix++ = 0;
	else
		return SLBT_CUSTOM_ERROR(dctx,SLBT_ERR_INSTALL_FLOW);

	if ((dot = strrchr(slnkname,'.')))
		*dot++ = 0;
	else
		return SLBT_CUSTOM_ERROR(dctx,SLBT_ERR_INSTALL_FLOW);

	if ((*dot < '0') || (*dot > '9'))
		return SLBT_CUSTOM_ERROR(dctx,SLBT_ERR_INSTALL_FLOW);

	/* libfoo.x.y.z.so? */
	if ((suffix[0] < '0') || (suffix[0] > '9')) {
		if ((dot = strrchr(slnkname,'.')))
			dot++;
		else
			return SLBT_CUSTOM_ERROR(dctx,SLBT_ERR_INSTALL_FLOW);

		if ((*dot < '0') || (*dot > '9'))
			return SLBT_CUSTOM_ERROR(dctx,SLBT_ERR_INSTALL_FLOW);

		for (; *suffix; )
			*dot++ = *suffix++;

		*dot++ = 0;
	}

	/* destination symlink: dstdir/libfoo.so.x */
	if (slbt_snprintf(dlnkname,sizeof(dlnkname),
			"%s/%s",dstdir,slnkname) < 0)
		return SLBT_BUFFER_ERROR(dctx);

	if (fpe) {
		/* copy: .libs/libfoo.so.x.y.z --> libfoo.so.x */
		if (slbt_copy_file(
				dctx,ectx,
				srcfile,
				dlnkname))
			return SLBT_NESTED_ERROR(dctx);

		/* import libraries */
		if (slbt_exec_install_import_libraries(
				dctx,ectx,
				srcfile,
				dstdir))
			return SLBT_NESTED_ERROR(dctx);
	} else {
		/* create symlink: libfoo.so.x --> libfoo.so.x.y.z */
		if (slbt_create_symlink(
				dctx,ectx,
				target,dlnkname,
				SLBT_SYMLINK_DEFAULT))
			return SLBT_NESTED_ERROR(dctx);
	}

	return 0;
}

int slbt_exec_install(
	const struct slbt_driver_ctx *	dctx,
	struct slbt_exec_ctx *		ectx)
{
	int				ret;
	int				fdout;
	char **				argv;
	char **				iargv;
	char **				src;
	char **				dst;
	char *				slash;
	char *				optsh;
	char *				script;
	char *				shtool;
	struct slbt_exec_ctx *		actx;
	struct argv_meta *		meta;
	struct argv_entry *		entry;
	struct argv_entry *		copy;
	struct argv_entry *		dest;
	struct argv_entry *		last;
	const struct argv_option *	optv[SLBT_OPTV_ELEMENTS];
	char				dstdir[PATH_MAX];

	/* dry run */
	if (dctx->cctx->drvflags & SLBT_DRIVER_DRY_RUN)
		return 0;

	/* context */
	if (ectx)
		actx = 0;
	else if ((ret = slbt_get_exec_ctx(dctx,&ectx)))
		return ret;
	else
		actx = ectx;

	/* initial state, install mode skin */
	slbt_reset_arguments(ectx);
	slbt_disable_placeholders(ectx);
	iargv = ectx->cargv;
	fdout = slbt_driver_fdout(dctx);
	optsh = 0;
	script = 0;

	/* work around non-conforming uses of --mode=install */
	if (iargv[1] && (slash = strrchr(iargv[1],'/'))) {
		if (!strcmp(++slash,"install-sh")) {
			optsh  = *iargv++;
			script = *iargv;
		}
	} else {
		slash  = strrchr(iargv[0],'/');
		shtool = slash ? ++slash : iargv[0];
		shtool = strcmp(shtool,"shtool") ? 0 : shtool;

		if (shtool && iargv[1] && !strcmp(iargv[1],"install")) {
			iargv++;
		} else if (shtool) {
			return slbt_install_usage(
				fdout,
				dctx->program,
				0,optv,0,
				dctx->cctx->drvflags & SLBT_DRIVER_ANNOTATE_NEVER);
		}
	}

	/* missing arguments? */
	slbt_optv_init(slbt_install_options,optv);

	if (!iargv[1] && (dctx->cctx->drvflags & SLBT_DRIVER_VERBOSITY_USAGE))
		return slbt_install_usage(
			fdout,
			dctx->program,
			0,optv,0,
			dctx->cctx->drvflags & SLBT_DRIVER_ANNOTATE_NEVER);

	/* <install> argv meta */
	if (!(meta = slbt_argv_get(
			iargv,optv,
			dctx->cctx->drvflags & SLBT_DRIVER_VERBOSITY_ERRORS
				? ARGV_VERBOSITY_ERRORS
				: ARGV_VERBOSITY_NONE,
			fdout)))
		return slbt_exec_install_fail(
			actx,meta,
			SLBT_CUSTOM_ERROR(dctx,SLBT_ERR_INSTALL_FAIL));

	/* dest, alternate argument vector options */
	argv = ectx->altv;
	copy = meta->entries;
	dest = 0;
	last = 0;

	if (optsh)
		*argv++ = script;

	*argv++ = iargv[0];

	for (entry=meta->entries; entry->fopt || entry->arg; entry++) {
		if (entry->fopt) {
			switch (entry->tag) {
				case TAG_INSTALL_SYSROOT:
					break;

				case TAG_INSTALL_COPY:
					*argv++ = "-c";
					copy = entry;
					break;

				case TAG_INSTALL_FORCE:
					*argv++ = "-f";
					break;

				case TAG_INSTALL_MKDIR:
					*argv++ = "-d";
					copy = 0;
					break;

				case TAG_INSTALL_TARGET_MKDIR:
					*argv++ = "-D";
					copy = 0;
					break;

				case TAG_INSTALL_STRIP:
					*argv++ = "-s";
					break;

				case TAG_INSTALL_PRESERVE:
					*argv++ = "-p";
					break;

				case TAG_INSTALL_USER:
					*argv++ = "-o";
					break;

				case TAG_INSTALL_GROUP:
					*argv++ = "-g";
					break;

				case TAG_INSTALL_MODE:
					*argv++ = "-m";
					break;

				case TAG_INSTALL_DSTDIR:
					*argv++ = "-t";
					dest = entry;
					break;
			}

			if (entry->tag == TAG_INSTALL_SYSROOT) {
				(void)0;

			} else if (entry->fval) {
				*argv++ = (char *)entry->arg;
			}
		} else
			last = entry;
	}

	/* install */
	if (copy) {
		/* using alternate argument vector */
		if (optsh)
			ectx->altv[0] = optsh;

		ectx->argv    = ectx->altv;
		ectx->program = ectx->altv[0];

		/* marks */
		src = argv++;
		dst = argv++;

		/* dstdir */
		if (slbt_exec_install_init_dstdir(dctx,dest,last,dstdir))
			return slbt_exec_install_fail(
				actx,meta,
				SLBT_NESTED_ERROR(dctx));

		/* install entries one at a time */
		for (entry=meta->entries; entry->fopt || entry->arg; entry++)
			if (!entry->fopt && (dest || (entry != last)))
				if (slbt_exec_install_entry(
						dctx,ectx,
						entry,last,
						dest,dstdir,
						src,dst))
					return slbt_exec_install_fail(
						actx,meta,
						SLBT_NESTED_ERROR(dctx));
	} else {
		/* using original argument vector */
		ectx->argv    = ectx->cargv;
		ectx->program = ectx->cargv[0];

		/* spawn */
		if (!(dctx->cctx->drvflags & SLBT_DRIVER_SILENT))
			if (slbt_output_install(dctx,ectx))
				return SLBT_NESTED_ERROR(dctx);

		if ((slbt_spawn(ectx,true) < 0) && (ectx->pid < 0)) {
			return slbt_exec_install_fail(
				actx,meta,
				SLBT_SPAWN_ERROR(dctx));

		} else if (ectx->exitcode) {
			return slbt_exec_install_fail(
				actx,meta,
				SLBT_CUSTOM_ERROR(
					dctx,
					SLBT_ERR_INSTALL_ERROR));
		}
	}

	slbt_argv_free(meta);
	slbt_free_exec_ctx(actx);

	return 0;
}