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

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

#include <slibtool/slibtool.h>
#include "slibtool_driver_impl.h"
#include "slibtool_errinfo_impl.h"
#include "slibtool_spawn_impl.h"
#include "slibtool_linkcmd_impl.h"
#include "slibtool_mapfile_impl.h"
#include "slibtool_metafile_impl.h"
#include "slibtool_snprintf_impl.h"
#include "slibtool_symlink_impl.h"
#include "slibtool_readlink_impl.h"
#include "slibtool_visibility_impl.h"


static int slbt_linkcmd_exit(
	struct slbt_deps_meta *	depsmeta,
	int			ret)
{
	if (depsmeta->altv)
		free(depsmeta->altv);

	if (depsmeta->args)
		free(depsmeta->args);

	return ret;
}


static int slbt_emit_fdwrap_amend_dl_path(
	const struct slbt_driver_ctx *	dctx,
	struct slbt_exec_ctx *		ectx,
	struct slbt_deps_meta * 	depsmeta,
	const char *			fmt,
					...)
{
	va_list		ap;
	char *		buf;
	int		cnt;
	char		dlpathbuf[2048];
	int		fdwrap;
	const char *	fdwrap_fmt;
	int		size;

	va_start(ap,fmt);

	size = sizeof(dlpathbuf);

	buf  = ((cnt = vsnprintf(dlpathbuf,size,fmt,ap)) < size)
		? dlpathbuf : malloc((size = cnt + 1));

	va_end(ap);

	if (buf == dlpathbuf) {
		(void)0;

	} else if (buf) {
		va_start(ap,fmt);
		vsprintf(buf,fmt,ap);
		va_end(ap);

	} else {
		return slbt_linkcmd_exit(
			depsmeta,
			SLBT_SYSTEM_ERROR(dctx,0));
	}

	if ((fdwrap = slbt_exec_get_fdwrapper(ectx)) >= 0) {
		if (buf[0] == '/') {
			fdwrap_fmt =
				"DL_PATH=\"${DL_PATH}${COLON}%s\"\n"
				"COLON=':'\n\n";
		} else {
			fdwrap_fmt =
				"DL_PATH=\"${DL_PATH}${COLON}${DL_PATH_FIXUP}%s\"\n"
				"COLON=':'\n\n";
		}

		if (slbt_dprintf(fdwrap,fdwrap_fmt,buf) < 0) {
			return slbt_linkcmd_exit(
				depsmeta,
				SLBT_SYSTEM_ERROR(dctx,0));
		}
	}

	return 0;
}


slbt_hidden bool slbt_adjust_object_argument(
	char *		arg,
	bool		fpic,
	bool		fany,
	int		fdcwd)
{
	char *	slash;
	char *	dot;
	char	base[PATH_MAX];

	if (*arg == '-')
		return false;

	/* object argument: foo.lo or foo.o */
	if (!(dot = strrchr(arg,'.')))
		return false;

	if ((dot[1]=='l') && (dot[2]=='o') && !dot[3]) {
		dot[1] = 'o';
		dot[2] = 0;

	} else if ((dot[1]=='o') && !dot[2]) {
		(void)0;

	} else {
		return false;
	}

	/* foo.o requested and is present? */
	if (!fpic && !faccessat(fdcwd,arg,0,0))
		return true;

	/* .libs/foo.o */
	if ((slash = strrchr(arg,'/')))
		slash++;
	else
		slash = arg;

	if (slbt_snprintf(base,sizeof(base),
			"%s",slash) < 0)
		return false;

	sprintf(slash,".libs/%s",base);

	if (!faccessat(fdcwd,arg,0,0))
		return true;

	/* foo.o requested and neither is present? */
	if (!fpic) {
		strcpy(slash,base);
		return true;
	}

	/* .libs/foo.o explicitly requested and is not present? */
	if (!fany)
		return true;

	/* use foo.o in place of .libs/foo.o */
	strcpy(slash,base);

	if (faccessat(fdcwd,arg,0,0))
		sprintf(slash,".libs/%s",base);

	return true;
}


slbt_hidden bool slbt_adjust_wrapper_argument(
	char *		arg,
	bool		fpic)
{
	char *	slash;
	char *	dot;
	char	base[PATH_MAX];

	if (*arg == '-')
		return false;

	if (!(dot = strrchr(arg,'.')))
		return false;

	if (strcmp(dot,".la"))
		return false;

	if (fpic) {
		if ((slash = strrchr(arg,'/')))
			slash++;
		else
			slash = arg;

		if (slbt_snprintf(base,sizeof(base),
				"%s",slash) < 0)
			return false;

		sprintf(slash,".libs/%s",base);
		dot = strrchr(arg,'.');
	}

	strcpy(dot,".a");
	return true;
}


slbt_hidden int slbt_adjust_linker_argument(
	const struct slbt_driver_ctx *	dctx,
	char *				arg,
	char **				xarg,
	bool				fpic,
	const char *			dsosuffix,
	const char *			arsuffix,
	struct slbt_deps_meta * 	depsmeta)
{
	int	fdcwd;
	int	fdlib;
	char *	slash;
	char *	dot;
	char	base[PATH_MAX];

	if (*arg == '-')
		return 0;

	if (!(dot = strrchr(arg,'.')))
		return 0;

	if (!(strcmp(dot,arsuffix))) {
		*xarg = arg;
		return slbt_get_deps_meta(dctx,arg,1,depsmeta);
	}

	if (!(strcmp(dot,dsosuffix)))
		return slbt_get_deps_meta(dctx,arg,1,depsmeta);

	if (strcmp(dot,".la"))
		return 0;

	if (fpic) {
		if ((slash = strrchr(arg,'/')))
			slash++;
		else
			slash = arg;

		if (slbt_snprintf(base,sizeof(base),
				"%s",slash) < 0)
			return 0;

		sprintf(slash,".libs/%s",base);
		dot = strrchr(arg,'.');
	}

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

	/* shared library dependency? */
	if (fpic) {
		sprintf(dot,"%s",dsosuffix);

		if (slbt_symlink_is_a_placeholder(fdcwd,arg))
			sprintf(dot,"%s",arsuffix);
		else if (dctx->cctx->drvflags & SLBT_DRIVER_STATIC)
			sprintf(dot,"%s",arsuffix);
		else if ((fdlib = openat(fdcwd,arg,O_RDONLY)) >= 0)
			close(fdlib);
		else
			sprintf(dot,"%s",arsuffix);

		return slbt_get_deps_meta(dctx,arg,0,depsmeta);
	}

	/* input archive */
	sprintf(dot,"%s",arsuffix);
	return slbt_get_deps_meta(dctx,arg,0,depsmeta);
}


slbt_hidden int slbt_exec_link_adjust_argument_vector(
	const struct slbt_driver_ctx *	dctx,
	struct slbt_exec_ctx *		ectx,
	struct slbt_deps_meta *		depsmeta,
	const char *			cwd,
	bool				flibrary)
{
	int			fd;
	int			fdcwd;
	char ** 		carg;
	char ** 		aarg;
	char *			ldir;
	char *			slash;
	char *			mark;
	char *			darg;
	char *			dot;
	char *			base;
	char *			dpath;
	int			argc;
	char			arg[PATH_MAX];
	char			lib[PATH_MAX];
	char			depdir  [PATH_MAX];
	char			rpathdir[PATH_MAX];
	char			rpathlnk[PATH_MAX];
	struct stat		st;
	size_t			size;
	size_t			dlen;
	struct slbt_map_info *	mapinfo = 0;
	bool			fwholearchive = false;
	int			ret;

	for (argc=0,carg=ectx->cargv; *carg; carg++)
		argc++;

	if (!(depsmeta->args = calloc(1,depsmeta->infolen)))
		return SLBT_SYSTEM_ERROR(dctx,0);

	argc *= 3;
	argc += depsmeta->depscnt;

	if (!(depsmeta->altv = calloc(argc,sizeof(char *))))
		return slbt_linkcmd_exit(
			depsmeta,
			SLBT_SYSTEM_ERROR(dctx,0));

	fdcwd = slbt_driver_fdcwd(dctx);

	carg = ectx->cargv;
	aarg = depsmeta->altv;
	darg = depsmeta->args;
	size = depsmeta->infolen;

	for (; *carg; ) {
		dpath = 0;

		if (!strcmp(*carg,"-Wl,--whole-archive"))
			fwholearchive = true;
		else if (!strcmp(*carg,"-Wl,--no-whole-archive"))
			fwholearchive = false;



		/* output annotation */
		if (carg == ectx->lout[0]) {
			ectx->mout[0] = &aarg[0];
			ectx->mout[1] = &aarg[1];
		}

		/* argument translation */
		mark = *carg;

		if ((mark[0] == '-') && (mark[1] == 'L')) {
			if (mark[2]) {
				ldir = &mark[2];
			} else {
				*aarg++ = *carg++;
				ldir    = *carg;
			}

			mark = ldir + strlen(ldir);

			if (mark[-1] == '/')
				strcpy(mark,".libs");
			else
				strcpy(mark,"/.libs");

			if ((fd = openat(fdcwd,ldir,O_DIRECTORY,0)) < 0)
				*mark = 0;
			else {
				close(fd);

				if ((ret = slbt_emit_fdwrap_amend_dl_path(
						dctx,ectx,depsmeta,
						"%s",ldir)) < 0)
					return ret;
			}

			*aarg++ = *carg++;

		} else if (**carg == '-') {
			*aarg++ = *carg++;

		} else if (!(dot = strrchr(*carg,'.'))) {
			*aarg++ = *carg++;

		} else if (ectx->xargv[carg - ectx->cargv]) {
			*aarg++ = *carg++;

		} else if (!(strcmp(dot,".a"))) {
			if (flibrary && !fwholearchive) {
				strcpy(lib,*carg);
				dot = strrchr(lib,'.');
				strcpy(dot,".lai");

				if ((fd = openat(fdcwd,lib,O_RDONLY,0)) < 0)
					*aarg++ = "-Wl,--whole-archive";
			}

			dpath = lib;
			sprintf(lib,"%s.slibtool.deps",*carg);
			*aarg++ = *carg++;

			if (flibrary && !fwholearchive) {
				if (fd < 0) {
					*aarg++ = "-Wl,--no-whole-archive";
				} else {
					close(fd);
				}
			}

		} else if (strcmp(dot,dctx->cctx->settings.dsosuffix)) {
			*aarg++ = *carg++;

		} else if (carg == ectx->lout[1]) {
			/* ^^^hoppla^^^ */
			*aarg++ = *carg++;
		} else {
			/* -rpath */
			sprintf(rpathlnk,"%s.slibtool.rpath",*carg);

			if (!fstatat(fdcwd,rpathlnk,&st,AT_SYMLINK_NOFOLLOW)) {
				if (slbt_readlinkat(
						fdcwd,
						rpathlnk,
						rpathdir,
						sizeof(rpathdir)))
					return slbt_linkcmd_exit(
						depsmeta,
						SLBT_SYSTEM_ERROR(dctx,rpathlnk));

				sprintf(darg,"-Wl,%s",rpathdir);
				*aarg++ = "-Wl,-rpath";
				*aarg++ = darg;
				darg   += strlen(darg);
				darg++;
			}

			dpath = lib;
			sprintf(lib,"%s.slibtool.deps",*carg);

			/* account for {'-','L','-','l'} */
			if (slbt_snprintf(arg,
					sizeof(arg) - 4,
					"%s",*carg) < 0)
				return slbt_linkcmd_exit(
					depsmeta,
					SLBT_BUFFER_ERROR(dctx));

			if ((slash = strrchr(arg,'/'))) {
				sprintf(*carg,"-L%s",arg);

				mark   = strrchr(*carg,'/');
				*mark  = 0;
				*slash = 0;

				if ((ret = slbt_emit_fdwrap_amend_dl_path(
						dctx,ectx,depsmeta,
						"%s%s%s",
						((arg[0] == '/') ? "" : cwd),
						((arg[0] == '/') ? "" : "/"),
						arg)) < 0) {
					return ret;
				}

				dlen = strlen(dctx->cctx->settings.dsoprefix);

				/* -module? (todo: non-portable usage, display warning) */
				if (strncmp(++slash,dctx->cctx->settings.dsoprefix,dlen)) {
					*--slash = '/';
					strcpy(*carg,arg);
					*aarg++ = *carg++;
				} else {
					*aarg++ = *carg++;
					*aarg++ = ++mark;

					slash += dlen;

					sprintf(mark,"-l%s",slash);
					dot  = strrchr(mark,'.');
					*dot = 0;
				}
			} else {
				*aarg++ = *carg++;
			}
		}

		if (dpath && !fstatat(fdcwd,dpath,&st,0)) {
			if (!(mapinfo = slbt_map_file(
					fdcwd,dpath,
					SLBT_MAP_INPUT)))
				return slbt_linkcmd_exit(
					depsmeta,
					SLBT_SYSTEM_ERROR(dctx,dpath));

			if (!(strncmp(lib,".libs/",6))) {
				*aarg++ = "-L.libs";
				lib[1] = 0;
			} else if ((base = strrchr(lib,'/'))) {
				if (base - lib == 5) {
					if (!(strncmp(&base[-5],".libs/",6)))
						base -= 4;

				} else if (base - lib >= 6) {
					if (!(strncmp(&base[-6],"/.libs/",7)))
						base -= 6;
				}

				*base = 0;
			} else {
				lib[0] = '.';
				lib[1] = 0;
			}

			while (mapinfo->mark < mapinfo->cap) {
				if (slbt_mapped_readline(dctx,mapinfo,darg,size))
					return slbt_linkcmd_exit(
						depsmeta,
						SLBT_NESTED_ERROR(dctx));

				*aarg++   = darg;
				mark      = darg;

				dlen      = strlen(darg);
				size     -= dlen;
				darg     += dlen;
				darg[-1]  = 0;

				/* handle -L... as needed */
				if ((mark[0] == '-')
						&& (mark[1] == 'L')
						&& (mark[2] != '/')) {
					if (strlen(mark) >= sizeof(depdir) - 1)
						return slbt_linkcmd_exit(
							depsmeta,
							SLBT_BUFFER_ERROR(dctx));

					darg = mark;
					strcpy(depdir,&mark[2]);
					sprintf(darg,"-L%s/%s",lib,depdir);

					darg += strlen(darg);
					darg++;

					if ((ret = slbt_emit_fdwrap_amend_dl_path(
							dctx,ectx,depsmeta,
							"%s/%s",lib,depdir)) < 0)
						return ret;

				} else if ((mark[0] == '-') && (mark[1] == 'L')) {
					if ((ret = slbt_emit_fdwrap_amend_dl_path(
							dctx,ectx,depsmeta,
							"%s",&mark[2])) < 0)
						return ret;
				}
			}
		}

		if (mapinfo) {
			slbt_unmap_file(mapinfo);
			mapinfo = 0;
		}
	}

	if (dctx->cctx->drvflags & SLBT_DRIVER_EXPORT_DYNAMIC)
		*aarg++ = "-Wl,--export-dynamic";

	return 0;
}


static int slbt_exec_link_remove_file(
	const struct slbt_driver_ctx *	dctx,
	struct slbt_exec_ctx *		ectx,
	const char *			target)
{
	int fdcwd;

	(void)ectx;

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

	/* remove target (if any) */
	if (!unlinkat(fdcwd,target,0) || (errno == ENOENT))
		return 0;

	return SLBT_SYSTEM_ERROR(dctx,0);
}


static int slbt_exec_link_create_expsyms_archive(
	const struct slbt_driver_ctx *  dctx,
	struct slbt_exec_ctx *          ectx,
	char **                         lobjv,
	char **                         cnvlv)
{
	int                             ret;
	char *                          dot;
	char **                         argv;
	char **                         aarg;
	char **                         parg;
	struct slbt_archive_ctx *       arctx;
	char **                         ectx_argv;
	char *                          ectx_program;
	char                            program[PATH_MAX];
	char                            output [PATH_MAX];

	/* output */
	if (slbt_snprintf(output,sizeof(output),
			"%s",ectx->mapfilename) < 0)
		return SLBT_BUFFER_ERROR(dctx);

	if (!(dot = strrchr(output,'.')))
		return SLBT_CUSTOM_ERROR(
			dctx,
			SLBT_ERR_FLOW_ERROR);

	/* .expsyms.xxx --> .expsyms.a */
	dot[1] = 'a';
	dot[2] = '\0';

	/* tool-specific argument vector */
	argv = (slbt_get_driver_ictx(dctx))->host.ar_argv;

	/* ar alternate argument vector */
	if (!argv)
		if (slbt_snprintf(program,sizeof(program),
				"%s",dctx->cctx->host.ar) < 0)
			return SLBT_BUFFER_ERROR(dctx);

	/* ar command argument vector */
	aarg = lobjv;

	if ((parg = argv)) {
		for (; *parg; )
			*aarg++ = *parg++;
	} else {
		*aarg++ = program;
	}

	*aarg++ = "-crs";
	*aarg++ = output;

	ectx_argv     = ectx->argv;
	ectx_program  = ectx->program;

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

	/* step output */
	if (!(dctx->cctx->drvflags & SLBT_DRIVER_SILENT))
		if (slbt_output_link(ectx))
			return SLBT_NESTED_ERROR(dctx);

	/* remove old archive as needed */
	if (slbt_exec_link_remove_file(dctx,ectx,output))
		return SLBT_NESTED_ERROR(dctx);

	/* ar spawn */
	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_AR_ERROR);
	}

	/* restore link command ectx */
	ectx->argv    = ectx_argv;
	ectx->program = ectx_program;

	/* input objects associated with .la archives */
	for (parg=cnvlv; *parg; parg++)
		if (slbt_util_import_archive(ectx,output,*parg))
			return SLBT_NESTED_ERROR(dctx);

	/* do the thing */
	if (slbt_ar_get_archive_ctx(dctx,output,&arctx) < 0)
		return SLBT_NESTED_ERROR(dctx);

	/* .expsyms.a --> .exp */
	if ((*dot = '\0'), !(dot = strrchr(output,'.'))) {
		slbt_ar_free_archive_ctx(arctx);
		return SLBT_CUSTOM_ERROR(
			dctx,
			SLBT_ERR_FLOW_ERROR);
	}

	dot[1] = 'e';
	dot[2] = 'x';
	dot[3] = 'p';
	dot[4] = '\0';

	/* symfile */
	if (dctx->cctx->expsyms) {
		struct slbt_symlist_ctx * sctx;
		sctx = (slbt_get_exec_ictx(ectx))->sctx;

		ret = slbt_util_create_symfile(
			sctx,output,0644);
	} else {
		ret = slbt_ar_create_symfile(
			arctx->meta,
			output,
			0644);
	}

	/* mapfile */
	if ((ret == 0) && (dctx->cctx->regex)) {
		ret = slbt_ar_create_mapfile(
			arctx->meta,
			ectx->mapfilename,
			0644);
	}

	slbt_ar_free_archive_ctx(arctx);

	return (ret < 0) ? SLBT_NESTED_ERROR(dctx) : 0;
}


slbt_hidden int slbt_exec_link_finalize_argument_vector(
	const struct slbt_driver_ctx *	dctx,
	struct slbt_exec_ctx *		ectx)
{
	size_t          nargs;
	char *		sargv[1024];
	char **		sargvbuf;
	char **		base;
	char **		parg;
	char **		pcap;
	char **         argv;
	char **         mark;
	char **		aarg;
	char **		oarg;
	char **		lobj;
	char **		cnvl;
	char **		larg;
	char **		darg;
	char **		earg;
	char **		rarg;
	char **		aargv;
	char **		oargv;
	char **		lobjv;
	char **		cnvlv;
	char **		dlargv;
	char **		cap;
	char **		src;
	char **		dst;
	char *		arg;
	char *		dot;
	char *		ccwrap;
	const char *	arsuffix;

	/* vector size */
	base     = ectx->argv;
	arsuffix = dctx->cctx->settings.arsuffix;

	for (parg=base; *parg; parg++)
		(void)0;

	if (dctx->cctx->regex) {
		argv = (slbt_get_driver_ictx(dctx))->host.ar_argv;

		for (mark=argv; mark && *mark; mark++)
			(void)0;
	} else {
		argv = 0;
		mark  = 0;
	}

	/* buffer */
	if ((nargs = ((parg - base) + (mark - argv))) < 256) {
		aargv    = &sargv[0];
		oargv    = &sargv[1*256];
		lobjv    = &sargv[2*256];
		cnvlv    = &sargv[3*256];
		sargvbuf = 0;

		parg = &sargv[0];
		pcap = &sargv[1024];

		for (; parg<pcap; )
			*parg++ = 0;

	} else if (!(sargvbuf = calloc(4*(nargs+1),sizeof(char *)))) {
		return SLBT_SYSTEM_ERROR(dctx,0);

	} else {
		aargv = &sargvbuf[0];
		oargv = &sargvbuf[1*(nargs+1)];
		lobjv = &sargvbuf[2*(nargs+1)];
		cnvlv = &sargvbuf[3*(nargs+1)];
	}

	aarg = aargv;
	oarg = oargv;
	cnvl = cnvlv;
	lobj = lobjv;

	/* -export-symbols-regex: lobjv in place: ar [arg] [arg] -crs <output> */
	if (dctx->cctx->regex && argv)
		lobj += mark - argv + 2;
	else
		lobj += 3;

	/* (program name) */
	parg = &base[1];

	/* split object args from all other args, record output */
	/* annotation, and remove redundant -l arguments; and   */
	/* create additional vectors of all input objects as    */
	/* convenience libraries for -export-symbols-regex.     */
	for (; *parg; ) {
		if (ectx->lout[0] == parg) {
			ectx->lout[0] = &aarg[0];
			ectx->lout[1] = &aarg[1];
		}

		if (ectx->mout[0] == parg) {
			ectx->mout[0] = &aarg[0];
			ectx->mout[1] = &aarg[1];
		}

		arg = *parg;
		dot = strrchr(arg,'.');

		/* object input argument? */
		if (dot && (!strcmp(dot,".o") || !strcmp(dot,".lo"))) {
			*lobj++ = *parg;
			*oarg++ = *parg++;

		/* --whole-archive input argument? */
		} else if ((arg[0] == '-')
				&& (arg[1] == 'W')
				&& (arg[2] == 'l')
				&& (arg[3] == ',')
				&& !strcmp(&arg[4],"--whole-archive")
				&& parg[1] && parg[2]
				&& !strcmp(parg[2],"-Wl,--no-whole-archive")
				&& (dot = strrchr(parg[1],'.'))
				&& !strcmp(dot,arsuffix)) {
			*cnvl++ = parg[1];
			*oarg++ = *parg++;
			*oarg++ = *parg++;
			*oarg++ = *parg++;

		/* local archive input argument? */
		} else if (dot && !strcmp(dot,arsuffix)) {
			*aarg++ = *parg++;

		/* -l argument? */
		} else if ((parg[0][0] == '-') && (parg[0][1] == 'l')) {
			/* find the previous occurence of this -l argument */
			for (rarg=0, larg=&aarg[-1]; !rarg && (larg>=aargv); larg--)
				if (!strcmp(*larg,*parg))
					rarg = larg;

			/* first occurence of this specific -l argument? */
			if (!rarg) {
				*aarg++ = *parg++;

			} else {
				larg = rarg;

				/* if all -l arguments following the previous */
				/* occurence had already appeared before the */
				/* previous argument, then the current      */
				/* occurence is (possibly) redundant.      */

				for (darg=&larg[1]; rarg && darg<aarg; darg++) {
					/* only test -l arguments */
					if ((darg[0][0] == '-') && (darg[0][1] == 'l')) {
						for (rarg=0, earg=aargv; !rarg && earg<larg; earg++)
							if (!strcmp(*earg,*darg))
								rarg = darg;
					}
				}

				/* any archive (.a) input arguments between the */
				/* current occurrence and the previous one?    */
				for (darg=&larg[1]; rarg && darg<aarg; darg++)
					if ((dot = strrchr(*darg,'.')))
						if (!(strcmp(dot,arsuffix)))
							rarg = 0;

				/* final verdict: repeated -l argument? */
				if (rarg) {
					parg++;

				} else {
					*aarg++ = *parg++;
				}
			}

		/* -L argument? */
		} else if ((parg[0][0] == '-') && (parg[0][1] == 'L')) {
			/* find a previous occurence of this -L argument */
			for (rarg=0, larg=aargv; !rarg && (larg<aarg); larg++)
				if (!strcmp(*larg,*parg))
					rarg = larg;

			/* repeated -L argument? */
			if (rarg) {
				parg++;
			} else {
				*aarg++ = *parg++;
			}

		/* dlsyms vtable object must only be added once (see below) */
		} else if (!strcmp(*parg,"-dlpreopen")) {
			parg++;

		/* placeholder argument? */
		} else if (!strncmp(*parg,"-USLIBTOOL_PLACEHOLDER_",23)) {
			parg++;

		/* all other arguments */
		} else {
			*aarg++ = *parg++;
		}
	}

	/* dlsyms vtable object inclusion */
	if (ectx->dlopenobj)
		*oarg++ = ectx->dlopenobj;

	/* export-symbols-regex, proper dlpreopen support */
	if (dctx->cctx->libname)
		if (slbt_exec_link_create_expsyms_archive(
				dctx,ectx,lobjv,cnvlv) < 0)
			return SLBT_NESTED_ERROR(dctx);

	/* program name, ccwrap */
	if ((ccwrap = (char *)dctx->cctx->ccwrap)) {
		base[1] = base[0];
		base[0] = ccwrap;
		base++;
	}

	/* join object args */
	src = oargv;
	cap = oarg;
	dst = &base[1];

	for (; src<cap; )
		*dst++ = *src++;

	/* join all other args */
	src = aargv;
	cap = aarg;

	for (; src<cap; )
		*dst++ = *src++;

	/* properly null-terminate argv, accounting for redundant -l arguments */
	*dst = 0;

	/* output annotation */
	if (ectx->lout[0]) {
		ectx->lout[0] = &base[1] + (oarg - oargv) + (ectx->lout[0] - aargv);
		ectx->lout[1] = ectx->lout[0] + 1;
	}

	if (ectx->mout[0]) {
		ectx->mout[0] = &base[1] + (oarg - oargv) + (ectx->mout[0] - aargv);
		ectx->mout[1] = ectx->mout[0] + 1;
	}

	/* dlsyms vtable object compilation */
	if (ectx->dlopenobj) {
		dlargv  = (slbt_get_exec_ictx(ectx))->dlargv;
		*dlargv = base[0];

		src = aargv;
		cap = aarg;
		dst = &dlargv[1];

		/* compile argv, based on the linkcmd argv */
		for (; src<cap; ) {
			if ((src[0][0] == '-') && (src[0][1] == '-')) {
				*dst++ = *src;

			} else if ((src[0][0] == '-') && (src[0][1] == 'L')) {
				(void)0;

			} else if ((src[0][0] == '-') && (src[0][1] == 'l')) {
				(void)0;

			} else if ((src[0][0] == '-') && (src[0][1] == 'o')) {
				src++;

			} else if ((src[0][0] == '-') && (src[0][1] == 'D')) {
				if (!src[0][2])
					src++;

			} else if ((src[0][0] == '-') && (src[0][1] == 'U')) {
				if (!src[0][2])
					src++;

			} else if ((src[0][0] == '-') && (src[0][1] == 'W')) {
				if ((src[0][2] == 'a') && (src[0][3] == ','))
					*dst++ = *src;
			} else {
				*dst++ = *src;
			}

			src++;
		}

		*dst++ = dctx->cctx->settings.picswitch
			? dctx->cctx->settings.picswitch
			: *ectx->fpic;

		*dst++ = "-c";
		*dst++ = ectx->dlopensrc;

		*dst++ = "-o";
		*dst++ = ectx->dlopenobj;

		/* nested compile step */
		ectx->argv = dlargv;

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

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

		if (ectx->exitcode)
			return SLBT_CUSTOM_ERROR(
				dctx,
				SLBT_ERR_COMPILE_ERROR);

		ectx->argv = base;
	}

	/* all done */
	if (sargvbuf)
		free(sargvbuf);

	return 0;
}