hdr	nc
mem	exception.name,_exception.name math.h
lib	setreuid,setregid,nice,fork,fchdir
lib	pathnative,pathposix
lib	memcntl sys/mman.h

# for main.c fixargs():
lib,sys	pstat
lib	setproctitle

reference	unistd.h

extern	nice		int	(int)
extern	setreuid	int	(uid_t,uid_t)
extern	setregid	int	(gid_t,gid_t)

tst	note{ does the system support #! interpreter paths }end cross{
	cat > "$EXECROOT/file$$" <<!
	#! $(command -v env) true
	exit 1
	!
	chmod 755 "$EXECROOT/file$$"
	if	"$EXECROOT/file$$" 2>/dev/null
	then	echo "#define SHELLMAGIC	1"
	fi
	rm -f "$EXECROOT/file$$"
}end

tst	note{ determining default number of extra bytes per argument for arguments list }end output{
	/*
	 * Figure out if this OS requires extra bytes per argument
	 * in the arguments list of a process.
	 * Outputs an appropriate #define _arg_extrabytes.
	 * Without this, 'command -x' failed with E2BIG on macOS, Linux and Solaris
	 * even if all the arguments should fit in ARG_MAX based on their length.
	 *
	 * POSIX: "The number of bytes available for the new process' combined argument
	 * and environment lists is {ARG_MAX}. It is implementation-defined whether null
	 * terminators, pointers, and/or any alignment bytes are included in this total."
	 * https://pubs.opengroup.org/onlinepubs/9699919799/functions/exec.html
	 * So, operating systems are free to consume ARG_MAX space in whatever bizarre way they want, and
	 * may even come up with more innovative ways to waste buffer space in future. This test assumes
	 * that null terminators are included in the total, because why wouldn't they be? It builds a
	 * dummy arguments list and tries fork(2)/execvp(2) to figure how how many extra bytes to add to
	 * each argument to account for pointers and whatnot, and outputs that (_arg_extrabytes) as a
	 * default value for command_xargs() in path.c. We still cannot entirely rely on this value on all
	 * systems, so path_spawn() in path.c is able to increase it at runtime if E2BIG still occurs.
	 */

	#include <ast.h>
	#include <error.h>
	#include <sfio.h>
	#include <stk.h>
	#include <wait.h>

	int main(int argc,char *argv[])
	{
		int extra_bytes = 0, envlen, argmax, i;
		pid_t childpid;
		Sfio_t *notebuf;
		char *note;

		error_info.id="_arg_extrabytes test (parent)";
		while(1)
		{
			envlen = 0;
			for(i=0; argv[i]; i++)
				envlen += strlen(argv[i]) + 1 + extra_bytes;
			envlen += 1 + extra_bytes;	/* final null element */
			for(i=0; environ[i]; i++)
				envlen += strlen(environ[i]) + 1 + extra_bytes;
			envlen += 1 + extra_bytes;	/* final null element */
			argmax = (int)astconf_long(CONF_ARG_MAX) - envlen;
			if (argmax < 2048)
			{
				error(ERROR_ERROR|2, "argmax too small");
				return 1;
			}
			argmax -= 512;
			if(!(childpid = fork()))
			{
				/* child */
				int bytec;

				error_info.id="_arg_extrabytes test (child)";
				argv = stkalloc(stkstd, (argmax / 2 + 1) * sizeof(char*));
				argc = bytec = 0;
				while(bytec < argmax)
				{
					if(argc==0)
						argv[argc] = "env";
					else if(argc==1)
						argv[argc] = "true";
					else
						argv[argc] = "xxxxxxxxxxxxxxxxxxxxxxx";
					/* also add 1 for terminating zero byte */
					bytec += strlen(argv[argc]) + 1 + extra_bytes;
					argc++;
				}
				argv[argc] = NULL;
				if(execvp(argv[0], argv) < 0)
				{
					if(errno == E2BIG)
						return 1;
					else
					{
						error(ERROR_SYSTEM|2, "execvp failed");
						return 2;
					}
				}
				error(ERROR_SYSTEM|2, "[BUG] we should never get here!");
				return 2;
			}
			else
			{
				/* parent */
				int exitstatus;

				if (waitpid(childpid,&i,0) < 0)
				{
					error(ERROR_SYSTEM|2, "waitpid failed");
					return 1;
				}
				if (!WIFEXITED(i) || (exitstatus = WEXITSTATUS(i)) > 1 && exitstatus != 126)
				{
					error(ERROR_ERROR|2, "child process exited abnormally");
					return 1;
				}
				if (exitstatus == 0)
					break;	/* yay :) */
				extra_bytes++;
				if (extra_bytes > 256)
				{
					error(ERROR_ERROR|2, "giving up");
					return 1;
				}
			}
		}
		/* show note in iffe output via inherited FD 9 */
		notebuf = sfstropen();
		sfprintf(notebuf," %d ...",extra_bytes);
		note = sfstruse(notebuf);
		write(9,note,strlen(note));
		sfclose(notebuf);
		/* add #define to header via stdout */
		sfprintf(sfstdout,
			"#define _arg_extrabytes\t%d\t/* extra bytes per argument for arguments list */\n",
			extra_bytes);
		return 0;
	}
}end fail{
	echo "#define _arg_extrabytes	sizeof(char*)	/* BUG: test failed; assuming pointer size */"
}end

tst	note{ determining size of PID variables }end output{
	#include <ast.h>
	#include <sfio.h>
	int main(void)
	{
		const int s = sizeof(pid_t);
		char *a;
		Sfio_t *b;
		/*
		 * For $$, $PPID, ${.sh.pid} and ${.sh.ppid} to work correctly, the size
		 * of pid_t must be one of the integer sizes supported by nv_getval().
		 * If there is ever a system where this test returns 1, then both this
		 * test and nv_getval() must learn a new integer size attribute.
		 */
		if(s==sizeof(int16_t))		/* union Value: sp */
			a="NV_INTEGER|NV_SHORT";
		else if(s==sizeof(int32_t))	/* union Value: lp */
			a="NV_INTEGER";
		else if(s==sizeof(Sflong_t))	/* union Value: llp */
			a="NV_INTEGER|NV_LONG";
		else
			return 1;
		/* write NV_PID attribute definition */
		sfprintf(sfstdout,"#define NV_PID\t(%s)\t/* integer attribute(s) corresponding to sizeof(pid_t)==%d */\n",a,s);
		/* show nice test result output on FD 9 */
		b = sfstropen();
		sfprintf(b," %s (%d bytes) ... ",a,s);
		a = sfstruse(b);
		write(9,a,strlen(a));
		return 0;
	}
}end fail{
	echo "$0: cannot determine size of PID variables" >&2
	exit 1
}end
