/*******************************************************************/
/*  slibtool: a strong libtool implementation, written in C        */
/*  Copyright (C) 2016--2025  SysDeer Technologies, LLC            */
/*  Released under the Standard MIT License; see COPYING.SLIBTOOL. */
/*******************************************************************/

#include <stdio.h>
#include <string.h>
#include <stdbool.h>
#include <unistd.h>

#include "slibtool_driver_impl.h"
#include "slibtool_errinfo_impl.h"
#include "slibtool_symlink_impl.h"
#include "slibtool_readlink_impl.h"
#include "slibtool_realpath_impl.h"
#include "slibtool_snprintf_impl.h"
#include "slibtool_visibility_impl.h"

#define SLBT_DEV_NULL_FLAGS	(SLBT_DRIVER_ALL_STATIC      \
				| SLBT_DRIVER_DISABLE_SHARED \
				| SLBT_DRIVER_DISABLE_STATIC)

slbt_hidden int slbt_create_symlink_ex(
	const struct slbt_driver_ctx *	dctx,
	struct slbt_exec_ctx *		ectx,
	int                             fddst,
	const char *			target,
	const char *			lnkname,
	uint32_t			options)
{
	int		fliteral;
	int		fwrapper;
	int		fdevnull;
	size_t          slen;
	char **		oargv;
	const char *	slash;
	char *		ln[5];
	char *          dot;
	char *		dotdot;
	char *          mark;
	char		tmplnk [PATH_MAX];
	char		lnkarg [PATH_MAX];
	char		alnkarg[PATH_MAX];
	char		atarget[PATH_MAX];
	char *		suffix = 0;

	/* options */
	fliteral = (options & SLBT_SYMLINK_LITERAL);
	fwrapper = (options & SLBT_SYMLINK_WRAPPER);
	fdevnull = (options & SLBT_SYMLINK_DEVNULL);

	/* symlink is a placeholder? */
	if (fliteral) {
		slash = target;

	/* .disabled .so or .a file */
	} else if (fdevnull) {
		slash  = target;
		suffix = ".disabled";

	/* symlink target contains a dirname? */
	} else if ((slash = strrchr(target,'/'))) {
		slash++;

	/* symlink target is a basename */
	} else {
		slash = target;
	}

	/* .la wrapper? */
	dotdot = fwrapper ? "../" : "";

	/* atarget */
	if (slbt_snprintf(atarget,sizeof(atarget),
			"%s%s",dotdot,slash) < 0)
		return SLBT_BUFFER_ERROR(dctx);

	/* tmplnk */
	if (slbt_snprintf(tmplnk,sizeof(tmplnk),
			"%s.symlink.tmp",
			lnkname) <0)
		return SLBT_BUFFER_ERROR(dctx);

	/* placeholder? */
	if (fdevnull) {
		if (unlinkat(fddst,lnkname,0) && (errno != ENOENT))
			return SLBT_SYSTEM_ERROR(dctx,0);

		if ((dot = strrchr(lnkname,'.'))) {
			if (!strcmp(dot,dctx->cctx->settings.dsosuffix)) {
				strcpy(dot,".expsyms.a");

				if (unlinkat(fddst,lnkname,0) && (errno != ENOENT))
					return SLBT_SYSTEM_ERROR(dctx,0);

				strcpy(dot,dctx->cctx->settings.dsosuffix);
			}
		}
	}

	if (suffix) {
		sprintf(alnkarg,"%s%s",lnkname,suffix);
		lnkname = alnkarg;
	}

	/* lnkarg */
	if (fddst == slbt_driver_fdcwd(dctx)) {
		strcpy(lnkarg,lnkname);
	} else {
		if (slbt_realpath(fddst,".",0,lnkarg,sizeof(lnkarg)) < 0)
			return SLBT_BUFFER_ERROR(dctx);

		if ((slen = strlen(lnkarg)) + strlen(lnkname) + 1 >= PATH_MAX)
			return SLBT_BUFFER_ERROR(dctx);

		mark    = &lnkarg[slen];
		mark[0] = '/';
		strcpy(++mark,lnkname);
	}

	/* ln argv (fake) */
	ln[0] = "ln";
	ln[1] = "-s";
	ln[2] = atarget;
	ln[3] = lnkarg;
	ln[4] = 0;

	oargv      = ectx->argv;
	ectx->argv = ln;

	/* step output */
	if (!(dctx->cctx->drvflags & SLBT_DRIVER_SILENT)) {
		if (dctx->cctx->mode == SLBT_MODE_LINK) {
			if (slbt_output_link(ectx)) {
				ectx->argv = oargv;
				return SLBT_NESTED_ERROR(dctx);
			}
		} else {
			if (slbt_output_install(ectx)) {
				ectx->argv = oargv;
				return SLBT_NESTED_ERROR(dctx);
			}
		}
	}

	/* restore execution context */
	ectx->argv = oargv;

	/* create symlink */
	if (symlinkat(atarget,fddst,tmplnk))
		return SLBT_SYSTEM_ERROR(dctx,tmplnk);

	return renameat(fddst,tmplnk,fddst,lnkname)
		? SLBT_SYSTEM_ERROR(dctx,lnkname)
		: 0;
}

slbt_hidden int slbt_create_symlink(
	const struct slbt_driver_ctx *	dctx,
	struct slbt_exec_ctx *		ectx,
	const char *			target,
	const char *			lnkname,
	uint32_t			options)
{
	return slbt_create_symlink_ex(
		dctx,ectx,
		slbt_driver_fdcwd(dctx),
		target,lnkname,options);
}

slbt_hidden int slbt_symlink_is_a_placeholder(int fdcwd, const char * lnkpath)
{
	size_t		len;
	char		slink [PATH_MAX];
	char		target[PATH_MAX];
	const char	suffix[] = ".disabled";

	if ((sizeof(slink)-sizeof(suffix)) < (len=strlen(lnkpath)))
		return 0;

	memcpy(slink,lnkpath,len);
	memcpy(&slink[len],suffix,sizeof(suffix));

	return (!slbt_readlinkat(fdcwd,slink,target,sizeof(target)))
		&& (!strcmp(target,"/dev/null"));
}
