/*
 *  Phusion Passenger - https://www.phusionpassenger.com/
 *  Copyright (c) 2010-2025 Asynchronous B.V.
 *
 *  "Passenger", "Phusion Passenger" and "Union Station" are registered
 *  trademarks of Asynchronous B.V.
 *
 *  Permission is hereby granted, free of charge, to any person obtaining a copy
 *  of this software and associated documentation files (the "Software"), to deal
 *  in the Software without restriction, including without limitation the rights
 *  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 *  copies of the Software, and to permit persons to whom the Software is
 *  furnished to do so, subject to the following conditions:
 *
 *  The above copyright notice and this permission notice shall be included in
 *  all copies or substantial portions of the Software.
 *
 *  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 *  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 *  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 *  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 *  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 *  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 *  THE SOFTWARE.
 */

#ifndef _GNU_SOURCE
	#define _GNU_SOURCE // according to Linux man page for syscall()
#endif

#include <sys/types.h>
#include <sys/resource.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <unistd.h>
#include <signal.h>
#include <fcntl.h>
#include <stdlib.h>
#include <dirent.h>
#include <poll.h>
#ifdef __linux__
	#include <sys/syscall.h>
	#include <features.h>
#endif

#include <string>
#include <cstddef>
#include <cstring>
#include <climits>
#include <cerrno>

#include <ProcessManagement/Utils.h>
#include <Utils/AsyncSignalSafeUtils.h>

#if defined(__NetBSD__) || defined(__OpenBSD__) || defined(__sun) || defined(__FreeBSD__)
	// Introduced in Solaris 9. Let's hope nobody actually uses
	// a version that doesn't support this.
	#define HAS_CLOSEFROM
#endif

namespace Passenger {

using namespace std;


#ifdef __APPLE__
	// http://www.opensource.apple.com/source/Libc/Libc-825.26/sys/fork.c
	// This bypasses atfork handlers.
	extern "C" {
		extern pid_t __fork(void);
	}
#endif

pid_t
asyncFork() {
	#if defined(__linux__)
		#if defined(SYS_fork)
			return (pid_t) syscall(SYS_fork);
		#else
			return syscall(SYS_clone, SIGCHLD, 0, 0, 0, 0);
		#endif
	#elif defined(__APPLE__)
		return __fork();
	#else
		return fork();
	#endif
}

void
resetSignalHandlersAndMask() {
	struct sigaction action;
	action.sa_handler = SIG_DFL;
	action.sa_flags   = SA_RESTART;
	sigemptyset(&action.sa_mask);
	sigaction(SIGHUP,  &action, NULL);
	sigaction(SIGINT,  &action, NULL);
	sigaction(SIGQUIT, &action, NULL);
	sigaction(SIGILL,  &action, NULL);
	sigaction(SIGTRAP, &action, NULL);
	sigaction(SIGABRT, &action, NULL);
	#ifdef SIGEMT
		sigaction(SIGEMT,  &action, NULL);
	#endif
	sigaction(SIGFPE,  &action, NULL);
	sigaction(SIGBUS,  &action, NULL);
	sigaction(SIGSEGV, &action, NULL);
	sigaction(SIGSYS,  &action, NULL);
	sigaction(SIGPIPE, &action, NULL);
	sigaction(SIGALRM, &action, NULL);
	sigaction(SIGTERM, &action, NULL);
	sigaction(SIGURG,  &action, NULL);
	sigaction(SIGSTOP, &action, NULL);
	sigaction(SIGTSTP, &action, NULL);
	sigaction(SIGCONT, &action, NULL);
	sigaction(SIGCHLD, &action, NULL);
	#ifdef SIGINFO
		sigaction(SIGINFO, &action, NULL);
	#endif
	sigaction(SIGUSR1, &action, NULL);
	sigaction(SIGUSR2, &action, NULL);

	// We reset the signal mask after resetting the signal handlers,
	// because prior to calling resetSignalHandlersAndMask(), the
	// process might be blocked on some signals. We want those signals
	// to be processed after installing the new signal handlers
	// so that bugs like https://github.com/phusion/passenger/pull/97
	// can be prevented.

	sigset_t signal_set;
	int ret;

	sigemptyset(&signal_set);
	do {
		ret = sigprocmask(SIG_SETMASK, &signal_set, NULL);
	} while (ret == -1 && errno == EINTR);
}

void
disableMallocDebugging() {
	unsetenv("MALLOC_FILL_SPACE");
	unsetenv("MALLOC_PROTECT_BEFORE");
	unsetenv("MallocGuardEdges");
	unsetenv("MallocScribble");
	unsetenv("MallocPreScribble");
	unsetenv("MallocCheckHeapStart");
	unsetenv("MallocCheckHeapEach");
	unsetenv("MallocCheckHeapAbort");
	unsetenv("MallocBadFreeAbort");
	unsetenv("MALLOC_CHECK_");

	const char *libs = getenv("DYLD_INSERT_LIBRARIES");
	if (libs != NULL && strstr(libs, "/usr/lib/libgmalloc.dylib")) {
		string newLibs = libs;
		string::size_type pos = newLibs.find("/usr/lib/libgmalloc.dylib");
		size_t len = strlen("/usr/lib/libgmalloc.dylib");

		// Erase all leading ':' too.
		while (pos > 0 && newLibs[pos - 1] == ':') {
			pos--;
			len++;
		}
		// Erase all trailing ':' too.
		while (pos + len < newLibs.size() && newLibs[pos + len] == ':') {
			len++;
		}

		newLibs.erase(pos, len);
		if (newLibs.empty()) {
			unsetenv("DYLD_INSERT_LIBRARIES");
		} else {
			setenv("DYLD_INSERT_LIBRARIES", newLibs.c_str(), 1);
		}
	}
}

// Async-signal safe way to get the current process's hard file descriptor limit.
static int
getFileDescriptorLimit() {
	long long sysconfResult = sysconf(_SC_OPEN_MAX);

	struct rlimit rl;
	long long rlimitResult;
	if (getrlimit(RLIMIT_NOFILE, &rl) == -1) {
		rlimitResult = 0;
	} else {
		rlimitResult = (long long) rl.rlim_max;
	}

	long result;
	// OS X 10.9 returns LLONG_MAX. It doesn't make sense
	// to use that result so we limit ourselves to the
	// sysconf result.
	if (rlimitResult >= INT_MAX || sysconfResult > rlimitResult) {
		result = sysconfResult;
	} else {
		result = rlimitResult;
	}

	if (result < 0) {
		// Unable to query the file descriptor limit.
		result = 9999;
	} else if (result < 2) {
		// The calls reported broken values.
		result = 2;
	}
	return result;
}

// Async-signal safe function to get the highest file
// descriptor that the process is currently using.
// See also http://stackoverflow.com/questions/899038/getting-the-highest-allocated-file-descriptor
static int
getHighestFileDescriptor(bool asyncSignalSafe) {
#if defined(F_MAXFD)
	int ret;

	do {
		ret = fcntl(0, F_MAXFD);
	} while (ret == -1 && errno == EINTR);
	if (ret == -1) {
		ret = getFileDescriptorLimit();
	}
	return ret;

#else
	int p[2], ret, flags;
	pid_t pid = -1;
	int result = -1;

	/* Since opendir() may not be async signal safe and thus may lock up
	 * or crash, we use it in a child process which we kill if we notice
	 * that things are going wrong.
	 */

	// Make a pipe.
	p[0] = p[1] = -1;
	do {
		ret = pipe(p);
	} while (ret == -1 && errno == EINTR);
	if (ret == -1) {
		goto done;
	}

	// Make the read side non-blocking.
	do {
		flags = fcntl(p[0], F_GETFL);
	} while (flags == -1 && errno == EINTR);
	if (flags == -1) {
		goto done;
	}
	do {
		fcntl(p[0], F_SETFL, flags | O_NONBLOCK);
	} while (ret == -1 && errno == EINTR);
	if (ret == -1) {
		goto done;
	}

	if (asyncSignalSafe) {
		do {
			pid = asyncFork();
		} while (pid == -1 && errno == EINTR);
	} else {
		do {
			pid = fork();
		} while (pid == -1 && errno == EINTR);
	}

	if (pid == 0) {
		// Don't close p[0] here or it might affect the result.

		resetSignalHandlersAndMask();

		struct sigaction action;
		action.sa_handler = _exit;
		action.sa_flags   = SA_RESTART;
		sigemptyset(&action.sa_mask);
		sigaction(SIGSEGV, &action, NULL);
		sigaction(SIGPIPE, &action, NULL);
		sigaction(SIGBUS, &action, NULL);
		sigaction(SIGILL, &action, NULL);
		sigaction(SIGFPE, &action, NULL);
		sigaction(SIGABRT, &action, NULL);

		DIR *dir = NULL;
		#ifdef __APPLE__
			/* /dev/fd can always be trusted on OS X. */
			dir = opendir("/dev/fd");
		#else
			/* On FreeBSD and possibly other operating systems, /dev/fd only
			 * works if fdescfs is mounted. If it isn't mounted then /dev/fd
			 * still exists but always returns [0, 1, 2] and thus can't be
			 * trusted. If /dev and /dev/fd are on different filesystems
			 * then that probably means fdescfs is mounted.
			 */
			struct stat dirbuf1, dirbuf2;
			if (stat("/dev", &dirbuf1) == -1
			 || stat("/dev/fd", &dirbuf2) == -1) {
				_exit(1);
			}
			if (dirbuf1.st_dev != dirbuf2.st_dev) {
				dir = opendir("/dev/fd");
			}
		#endif
		if (dir == NULL) {
			dir = opendir("/proc/self/fd");
			if (dir == NULL) {
				_exit(1);
			}
		}

		struct dirent *ent;
		union {
			int highest;
			char data[sizeof(int)];
		} u;
		u.highest = -1;

		while ((ent = readdir(dir)) != NULL) {
			if (ent->d_name[0] != '.') {
				int number = atoi(ent->d_name);
				if (number > u.highest) {
					u.highest = number;
				}
			}
		}
		if (u.highest != -1) {
			ssize_t ret, written = 0;
			do {
				ret = write(p[1], u.data + written, sizeof(int) - written);
				if (ret == -1) {
					_exit(1);
				}
				written += ret;
			} while (written < (ssize_t) sizeof(int));
		}
		closedir(dir);
		_exit(0);

	} else if (pid == -1) {
		goto done;

	} else {
		close(p[1]); // Do not retry on EINTR: http://news.ycombinator.com/item?id=3363819
		p[1] = -1;

		union {
			int highest;
			char data[sizeof(int)];
		} u;
		ssize_t ret, bytesRead = 0;
		struct pollfd pfd;
		pfd.fd = p[0];
		pfd.events = POLLIN;

		do {
			do {
				// The child process must finish within 30 ms, otherwise
				// we might as well query sysconf.
				ret = poll(&pfd, 1, 30);
			} while (ret == -1 && errno == EINTR);
			if (ret <= 0) {
				goto done;
			}

			do {
				ret = read(p[0], u.data + bytesRead, sizeof(int) - bytesRead);
			} while (ret == -1 && errno == EINTR);
			if (ret == -1) {
				if (errno != EAGAIN) {
					goto done;
				}
			} else if (ret == 0) {
				goto done;
			} else {
				bytesRead += ret;
			}
		} while (bytesRead < (ssize_t) sizeof(int));

		result = u.highest;
		goto done;
	}

done:
	// Do not retry on EINTR: http://news.ycombinator.com/item?id=3363819
	if (p[0] != -1) {
		close(p[0]);
	}
	if (p[1] != -1) {
		close(p[1]);
	}
	if (pid != -1) {
		do {
			ret = kill(pid, SIGKILL);
		} while (ret == -1 && errno == EINTR);
		do {
			ret = waitpid(pid, NULL, 0);
		} while (ret == -1 && errno == EINTR);
	}

	if (result == -1) {
		result = getFileDescriptorLimit();
	}
	return result;
#endif
}

void
closeAllFileDescriptors(int lastToKeepOpen, bool asyncSignalSafe) {
	#if defined(F_CLOSEM)
		int ret;
		do {
			ret = fcntl(lastToKeepOpen + 1, F_CLOSEM);
		} while (ret == -1 && errno == EINTR);
		if (ret != -1) {
			return;
		}
	#elif defined(HAS_CLOSEFROM)
		closefrom(lastToKeepOpen + 1);
		return;
	#endif

	for (int i = getHighestFileDescriptor(asyncSignalSafe); i > lastToKeepOpen; i--) {
		/* Even though we normally shouldn't retry on EINTR
		 * (http://news.ycombinator.com/item?id=3363819)
		 * it's okay to do that here because this function
		 * may only be called in a single-threaded environment.
		 */
		int ret;
		do {
			ret = close(i);
		} while (ret == -1 && errno == EINTR);
	}
}

void
printExecError(const char **command, int errcode) {
	char buf[1024] = { };
	printExecError2(command, errcode, buf, sizeof(buf));
}

void
printExecError2(const char **command, int errcode, char *buf, size_t size) {
	char *pos = buf;
	const char *end = buf + size;

	pos = AsyncSignalSafeUtils::appendData(pos, end, "*** ERROR: cannot execute ");
	pos = AsyncSignalSafeUtils::appendData(pos, end, command[0]);
	pos = AsyncSignalSafeUtils::appendData(pos, end, ": ");
	pos = AsyncSignalSafeUtils::appendData(pos, end,
		AsyncSignalSafeUtils::limitedStrerror(errcode));
	pos = AsyncSignalSafeUtils::appendData(pos, end, " (errno=");
	pos = AsyncSignalSafeUtils::appendInteger<int, 10>(pos, end, errcode);
	pos = AsyncSignalSafeUtils::appendData(pos, end, ")\n");

	AsyncSignalSafeUtils::printError(buf, pos - buf);
}


} // namespace Passenger
