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 <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_linkcmd_impl.h"
#include "slibtool_mapfile_impl.h"
#include "slibtool_metafile_impl.h"
#include "slibtool_realpath_impl.h"
#include "slibtool_snprintf_impl.h"
#include "slibtool_visibility_impl.h"

slbt_hidden int slbt_get_deps_meta(
	const struct slbt_driver_ctx *	dctx,
	char *				libfilename,
	int				fexternal,
	struct slbt_deps_meta *		depsmeta)
{
	int			fdcwd;
	char *			ch;
	char *			cap;
	char *			base;
	size_t			libexlen;
	struct stat		st;
	struct slbt_map_info *	mapinfo;
	char			depfile[PATH_MAX];

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

	/* -rpath */
	if (slbt_snprintf(depfile,sizeof(depfile),
				"%s.slibtool.rpath",
				libfilename) < 0)
		return SLBT_BUFFER_ERROR(dctx);

	/* -Wl,%s */
	if (!fstatat(fdcwd,depfile,&st,AT_SYMLINK_NOFOLLOW)) {
		depsmeta->infolen += st.st_size + 4;
		depsmeta->infolen++;
	}

	/* .deps */
	if (slbt_snprintf(depfile,sizeof(depfile),
				"%s.slibtool.deps",
				libfilename) < 0)
		return SLBT_BUFFER_ERROR(dctx);

	/* mapinfo */
	if (!(mapinfo = slbt_map_file(fdcwd,depfile,SLBT_MAP_INPUT)))
		return (fexternal && (errno == ENOENT))
			? 0 : SLBT_SYSTEM_ERROR(dctx,depfile);

	/* copied length */
	depsmeta->infolen += mapinfo->size;
	depsmeta->infolen++;

	/* libexlen */
	libexlen = (base = strrchr(libfilename,'/'))
		? strlen(depfile) + 2 + (base - libfilename)
		: strlen(depfile) + 2;

	/* iterate */
	ch  = mapinfo->addr;
	cap = mapinfo->cap;

	for (; ch<cap; ) {
		if (*ch++ == '\n') {
			depsmeta->infolen += libexlen;
			depsmeta->depscnt++;
		}
	}

	slbt_unmap_file(mapinfo);

	return 0;
}


static int slbt_exec_link_normalize_dep_file(
	const struct slbt_driver_ctx *  dctx,
	int                             deps,
	char                            (*depfile)[PATH_MAX])
{
	int                             ret;
	int                             fdcwd;
	int                             fdtgt;
	char *                          dot;
	char *                          slash;
	const char *                    mark;
	struct slbt_txtfile_ctx *       tctx;
	const char **                   pline;
	char *                          tgtmark;
	char *                          depmark;
	char *                          relmark;
	char *                          tgtnext;
	char *                          depnext;
	char                            tgtdir  [PATH_MAX];
	char                            tgtpath [PATH_MAX];
	char                            deppath [PATH_MAX];
	char                            pathbuf [PATH_MAX];
	char                            depsbuf [PATH_MAX];
	char                            relapath[PATH_MAX];

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

	/* first-pass dependency file */
	if (slbt_impl_get_txtfile_ctx(dctx,*depfile,deps,&tctx) < 0)
		return SLBT_NESTED_ERROR(dctx);

	/* second-pass dependency file */
	dot = strrchr(*depfile,'.');
	strcpy(dot,".tmp2");

	if ((deps = openat(fdcwd,*depfile,O_RDWR|O_CREAT|O_TRUNC,0644)) < 0) {
		slbt_lib_free_txtfile_ctx(tctx);
		return SLBT_SYSTEM_ERROR(dctx,depfile);
	}

	/* fdtgt */
	strcpy(tgtdir,*depfile);

	if (!(slash = strrchr(tgtdir,'/')))
		slash = tgtdir;

	slash[0] = '\0';

	if ((slash = strrchr(tgtdir,'/'))) {
		slash[0] = '\0';
		slash++;
	} else {
		slash    = tgtdir;
		slash[0] = '.';
		slash[1] = '/';
		slash[2] = '\0';
	}

	if ((slash > tgtdir) && strcmp(slash,".libs")) {
		close(deps);
		slbt_lib_free_txtfile_ctx(tctx);

		return SLBT_CUSTOM_ERROR(
			dctx,
			SLBT_ERR_FLOW_ERROR);
	}

	if ((fdtgt = openat(fdcwd,tgtdir,O_DIRECTORY|O_CLOEXEC,0)) < 0) {
		close(deps);
		slbt_lib_free_txtfile_ctx(tctx);

		return SLBT_CUSTOM_ERROR(
			dctx,
			SLBT_ERR_FLOW_ERROR);
	}

	if (slbt_realpath(fdcwd,tgtdir,0,tgtpath,sizeof(tgtpath)) < 0) {
				close(fdtgt);
				close(deps);
				slbt_lib_free_txtfile_ctx(tctx);

				return SLBT_CUSTOM_ERROR(
					dctx,
					SLBT_ERR_FLOW_ERROR);
	}

	strcpy(pathbuf,tgtpath);

	/* normalize dependency lines as needed */
	for (pline=tctx->txtlinev; *pline; pline++) {
		if ((mark = *pline)) {
			if ((mark[0] == '-') && (mark[1] == 'L'))
					mark = &mark[2];

			if ((mark > *pline) && (mark[0] == '/'))
				mark = *pline;
		} else {
			mark = *pline;
		}

		if (mark > *pline) {
			if (slbt_realpath(
					fdtgt,mark,0,deppath,
					sizeof(deppath)) < 0)
				mark = *pline;

			else if ((tgtpath[0] != '/') || (deppath[0] != '/'))
				mark = 0;

			else
				strcpy(depsbuf,deppath);
		}

		if ((mark > *pline) && strcmp(tgtpath,deppath)) {
			tgtmark = tgtpath;
			depmark = deppath;

			tgtnext = strchr(tgtmark,'/');
			depnext = strchr(depmark,'/');

			while (tgtnext && depnext) {
				*tgtnext = '\0';
				*depnext = '\0';

				if (strcmp(tgtmark,depmark)) {
					tgtnext = 0;
					depnext = 0;
				} else {
					tgtmark = &tgtnext[1];
					depmark = &depnext[1];

					if (*tgtmark && *depmark) {
						tgtnext = strchr(tgtmark,'/');
						depnext = strchr(depmark,'/');
					} else {
						tgtnext = 0;
						depnext = 0;
					}
				}
			}

			strcpy(tgtmark,&pathbuf[tgtmark-tgtpath]);
			strcpy(depmark,&depsbuf[depmark-deppath]);

			if ((tgtmark - tgtpath) == (depmark - deppath)) {
				mark = &depmark[strlen(tgtmark)];

				if ((mark[0] == '/') && !strncmp(tgtmark,depmark,mark-depmark))
					sprintf(relapath,"-L.%s",mark);
				else
					mark = relapath;
			} else {
				mark = relapath;
			}

			if (mark == relapath) {
				relmark = relapath;
				relmark += sprintf(relapath,"-L../");

				while ((tgtnext = strchr(tgtmark,'/'))) {
					tgtmark = &tgtnext[1];
					relmark += sprintf(relmark,"../");
				}

				strcpy(relmark,depmark);
			}

			mark = relapath;

		} else if (mark > *pline) {
			strcpy(relapath,"-L.");
			mark = relapath;
		}

		ret = mark ? slbt_dprintf(deps,"%s\n",mark) : (-1);

		if (ret < 0) {
			close(deps);
			close(fdtgt);
			slbt_lib_free_txtfile_ctx(tctx);

			return mark
				? SLBT_SYSTEM_ERROR(dctx,0)
				: SLBT_NESTED_ERROR(dctx);
		}

		if (mark == relapath)
			strcpy(tgtpath,pathbuf);
	}

	close(fdtgt);
	slbt_lib_free_txtfile_ctx(tctx);

	return deps;
}


static int slbt_exec_link_compact_dep_file(
	const struct slbt_driver_ctx *  dctx,
	int                             deps,
	char                            (*depfile)[PATH_MAX])
{
	int                             fdcwd;
	struct slbt_txtfile_ctx *       tctx;
	const char **                   pline;
	const char **                   pcomp;
	const char *                    depline;
	char *                          dot;

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

	/* second-pass dependency file */
	if (slbt_impl_get_txtfile_ctx(dctx,*depfile,deps,&tctx) < 0)
		return SLBT_NESTED_ERROR(dctx);

	/* second-pass dependency file */
	dot = strrchr(*depfile,'.');
	dot[0] = '\0';

	if ((deps = openat(fdcwd,*depfile,O_RDWR|O_CREAT|O_TRUNC,0644)) < 0) {
		slbt_lib_free_txtfile_ctx(tctx);
		return SLBT_SYSTEM_ERROR(dctx,depfile);
	}

	/* iterate, only write unique entries */
	for (pline=tctx->txtlinev; *pline; pline++) {
		depline = *pline;

		if ((depline[0] == '-') && (depline[1] == 'L'))
			for (pcomp=tctx->txtlinev; depline && pcomp<pline; pcomp++)
				if (!strcmp(*pcomp,depline))
					depline = 0;

		if (depline && (slbt_dprintf(deps,"%s\n",depline) < 0)) {
			close(deps);
			slbt_lib_free_txtfile_ctx(tctx);
			return SLBT_SYSTEM_ERROR(dctx,0);
		}
	}

	slbt_lib_free_txtfile_ctx(tctx);

	return deps;
}


slbt_hidden int slbt_exec_link_create_dep_file(
	const struct slbt_driver_ctx *	dctx,
	struct slbt_exec_ctx *		ectx,
	char **				altv,
	const char *			libfilename,
	bool				farchive)
{
	int			ret;
	int			deps;
	int			fdtmp;
	int			slen;
	int			fdcwd;
	char **			parg;
	char *			popt;
	char *			plib;
	char *			path;
	char *			mark;
	char *			base;
	size_t			size;
	char			deplib [PATH_MAX];
	bool			is_reladir;
	char			reladir[PATH_MAX];
	char			depfile[PATH_MAX];
	char			pathbuf[PATH_MAX];
	struct stat		st;
	int			ldepth;
	int			fdyndep;
	struct slbt_map_info *  mapinfo;

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

	/* depfile */
	if (slbt_snprintf(depfile,sizeof(depfile),
			"%s.slibtool.deps.tmp1",
			libfilename) < 0)
		return SLBT_BUFFER_ERROR(dctx);

	strcpy(pathbuf,depfile);

	/* deps */
	if ((deps = openat(fdcwd,depfile,O_RDWR|O_CREAT|O_TRUNC,0644)) < 0)
		return SLBT_SYSTEM_ERROR(dctx,depfile);

	/* iterate */
	for (parg=altv; *parg; parg++) {
		popt    = 0;
		plib    = 0;
		path    = 0;
		mapinfo = 0;

		if (!strncmp(*parg,"-l",2)) {
			popt = *parg;
			plib = popt + 2;

		} else if (!strncmp(*parg,"-L",2)) {
			popt = *parg;
			path = popt + 2;

		} else if (!strncmp(*parg,"-f",2)) {
			(void)0;

		} else if ((popt = strrchr(*parg,'.')) && !strcmp(popt,".la")) {
			/* import dependency list */
			if ((base = strrchr(*parg,'/')))
				base++;
			else
				base = *parg;

			/* [relative .la directory] */
			if (base > *parg) {
				slen = slbt_snprintf(
					reladir,
					sizeof(reladir),
					"%s",*parg);

				if (slen < 0) {
					close(deps);
					return SLBT_BUFFER_ERROR(dctx);
				}

				is_reladir = true;
				reladir[base - *parg - 1] = 0;
			} else {
				is_reladir = false;
				reladir[0] = '.';
				reladir[1] = 0;
			}


			/* dynamic library dependency? */
			strcpy(depfile,*parg);
			mark = depfile + (base - *parg);
			size = sizeof(depfile) - (base - *parg);
			slen = slbt_snprintf(mark,size,".libs/%s",base);

			if (slen < 0) {
				close(deps);
				return SLBT_BUFFER_ERROR(dctx);
			}

			mark = strrchr(mark,'.');
			strcpy(mark,dctx->cctx->settings.dsosuffix);

			fdyndep = !fstatat(fdcwd,depfile,&st,0);

			/* [-L... as needed] */
			if (fdyndep && (ectx->ldirdepth >= 0)) {
				if (slbt_dprintf(deps,"-L") < 0) {
					close(deps);
					return SLBT_SYSTEM_ERROR(dctx,0);
				}

				for (ldepth=ectx->ldirdepth; ldepth; ldepth--) {
					if (slbt_dprintf(deps,"../") < 0) {
						close(deps);
						return SLBT_SYSTEM_ERROR(dctx,0);
					}
				}

				if (slbt_dprintf(deps,"%s%s.libs\n",
						 (is_reladir ? reladir : ""),
						 (is_reladir ? "/" : "")) < 0) {
					close(deps);
					return SLBT_SYSTEM_ERROR(dctx,0);
				}
			}

			/* -ldeplib */
			if (fdyndep) {
				*popt = 0;
				mark  = base;
				mark += strlen(dctx->cctx->settings.dsoprefix);

				if (slbt_dprintf(deps,"-l%s\n",mark) < 0) {
					close(deps);
					return SLBT_SYSTEM_ERROR(dctx,0);
				}

				*popt = '.';
			}

			/* [open dependency list] */
			strcpy(depfile,*parg);
			mark = depfile + (base - *parg);
			size = sizeof(depfile) - (base - *parg);
			slen = slbt_snprintf(mark,size,".libs/%s",base);

			if (slen < 0) {
				close(deps);
				return SLBT_BUFFER_ERROR(dctx);
			}

			mapinfo = 0;

			mark = strrchr(mark,'.');
			size = sizeof(depfile) - (mark - depfile);

			if (!farchive) {
				slen = slbt_snprintf(mark,size,
					"%s.slibtool.deps",
					dctx->cctx->settings.dsosuffix);

				if (slen < 0) {
					close(deps);
					return SLBT_BUFFER_ERROR(dctx);
				}

				mapinfo = slbt_map_file(
					fdcwd,depfile,
					SLBT_MAP_INPUT);

				if (!mapinfo && (errno != ENOENT)) {
					close(deps);
					return SLBT_SYSTEM_ERROR(dctx,0);
				}
			}

			if (!mapinfo) {
				slen = slbt_snprintf(mark,size,
					".a.slibtool.deps");

				if (slen < 0) {
					close(deps);
					return SLBT_BUFFER_ERROR(dctx);
				}

				mapinfo = slbt_map_file(
					fdcwd,depfile,
					SLBT_MAP_INPUT);

				if (!mapinfo) {
					strcpy(mark,".a.disabled");

					if (fstatat(fdcwd,depfile,&st,AT_SYMLINK_NOFOLLOW)) {
						close(deps);
						return SLBT_SYSTEM_ERROR(dctx,depfile);
					}
				}
			}

			/* [-l... as needed] */
			while (mapinfo && (mapinfo->mark < mapinfo->cap)) {
				ret = slbt_mapped_readline(
					dctx,mapinfo,
					deplib,sizeof(deplib));

				if (ret) {
					close(deps);
					return SLBT_NESTED_ERROR(dctx);
				}

				ret = ((deplib[0] == '-')
						&& (deplib[1] == 'L')
						&& (deplib[2] != '/'))
					? slbt_dprintf(
						deps,"-L%s/%s",
						reladir,&deplib[2])
					: slbt_dprintf(
						deps,"%s",
						deplib);

				if (ret < 0) {
					close(deps);
					return SLBT_SYSTEM_ERROR(dctx,0);
				}
			}

			if (mapinfo)
				slbt_unmap_file(mapinfo);
		}

		if (plib && (slbt_dprintf(deps,"-l%s\n",plib) < 0)) {
			close(deps);
			return SLBT_SYSTEM_ERROR(dctx,0);
		}

		if (path && (slbt_dprintf(deps,"-L%s\n",path) < 0)) {
			close(deps);
			return SLBT_SYSTEM_ERROR(dctx,0);
		}
	}

	if ((fdtmp = slbt_exec_link_normalize_dep_file(dctx,deps,&pathbuf)) < 0)
		return SLBT_NESTED_ERROR(dctx);

	close(deps);

	if ((deps = slbt_exec_link_compact_dep_file(dctx,fdtmp,&pathbuf)) < 0)
		return SLBT_NESTED_ERROR(dctx);

	close(deps);
	close(fdtmp);

	return 0;
}