/*
 * Created by jiaqiang.ye@lnxall.com
 *
 * Simple Re-implementation of NOHUP
 *
 * 2023/09/18
 */

#ifndef _GNU_SOURCE
#define _GNU_SOURCE 1
#endif
#ifndef _XOPEN_SOURCE
#define _XOPEN_SOURCE 500
#endif

#include <errno.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <fcntl.h>
#include <unistd.h>
#include <signal.h>

static int clear_cloexec(int pfd)
{
	int flags, ret;

	ret = fcntl(pfd, F_GETFD, 0);
	if (ret < 0)
		return -1;

	flags = ret;
	flags &= ~FD_CLOEXEC;
	if (flags == ret)
		return 0;

	ret = fcntl(pfd, F_SETFD, flags);
	return ret;
}

static int replace_stdio(const char * out)
{
	int error;
	int nfd, ofd;
	const char * null = "/dev/null";

	error = 0;
	nfd = ofd = -1;

	nfd = open(null, O_RDWR);
	if (nfd == -1) {
		fprintf(stderr, "Error, cannot open %s: %s\n",
			null, strerror(errno));
		fflush(stderr);
		return -1;
	}

	ofd = nfd;
	if (out != NULL) {
		ofd = open(out, O_WRONLY | O_APPEND, 0644);
		if (ofd == -1)
			ofd = open(out, O_WRONLY | O_CREAT | O_APPEND, 0644);
		if (ofd == -1) {
			error = errno;
			close(nfd);
			fprintf(stderr, "Error, failed to open %s: %s\n",
				out, strerror(error));
			fflush(stderr);
			return -1;
		}
	}

	error = 0;
	if (nfd != STDIN_FILENO && dup2(nfd, STDIN_FILENO) == -1)
		error--;
	clear_cloexec(STDIN_FILENO);

	if (ofd != STDOUT_FILENO && dup2(ofd, STDOUT_FILENO) == -1)
		error--;
	clear_cloexec(STDOUT_FILENO);

	if (ofd != STDERR_FILENO && dup2(ofd, STDERR_FILENO) == -1)
		error--;
	clear_cloexec(STDERR_FILENO);

	if (nfd > STDERR_FILENO)
		close(nfd);
	if (ofd != nfd && ofd > STDERR_FILENO)
		close(ofd);
	return error;
}

int main(int argc, char *argv[])
{
	int idx;
	pid_t newp;
	const char * newarg;
	const char * outFile = NULL;

	signal(SIGINT, SIG_IGN);
	signal(SIGHUP, SIG_IGN);
	signal(SIGTERM, SIG_IGN);
	outFile = getenv("NOTHUP_OUT");
	if (outFile && outFile[0] == '\0')
		outFile = NULL;

	newarg = argv[1];
	if (argc == 1 || newarg == NULL || newarg[0] == '\0') {
		exit(23);
	}

	if (replace_stdio(NULL) < 0) {
		exit(24);
	}
	/* close file descriptors inherited from parent process */
	for (idx = 3; idx < 1024; ++idx) {
		close(idx);
	}

	newp = fork();
	if (newp > 0)
		_exit(0);

	signal(SIGINT, SIG_IGN);
	signal(SIGHUP, SIG_IGN);
	signal(SIGTERM, SIG_IGN);
	if (setpgrp() == -1) {
		fprintf(stderr, "Error, create new process group: %s\n",
			strerror(errno));
		fflush(stderr);
	} else {
		setpgid(getpid(), 0);
	}

	newp = setsid();
	if (newp == (pid_t) -1l) {
		fprintf(stderr, "Error, failed to create a new session: %s\n",
			strerror(errno));
		fflush(stderr);
	}

	if (replace_stdio(NULL) < 0) {
		exit(25);
	}

	newp = fork();
	if (newp == 0) {
		if (replace_stdio(outFile) < 0) {
			exit(26);
		}
		if (*newarg == '/')
			execv(newarg, (char * const *) &argv[1]);
		else
			execvp(newarg, (char * const *) &argv[1]);
		_exit(27);
	} else if (newp > 0) {
		pid_t p;
		int sta = 0;
		while ((p = waitpid(newp, &sta, 0)) != newp) {
			if (p == -1 && errno != EINTR)
				break;
			sta = 0;
		}
		return sta;
	} else {
		return 28;
	}
}
