/* Copyright (c) Dovecot authors, see top-level COPYING file */

#include "lib.h"
#include "net.h"
#include "path-util.h"

#include <unistd.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/un.h>

void fd_close_on_exec(int fd, bool set)
{
	int flags;

	flags = fcntl(fd, F_GETFD, 0);
	if (flags < 0)
		i_fatal("fcntl(F_GETFD, %d) failed: %m", fd);

	flags = set ? (flags | FD_CLOEXEC) : (flags & ~FD_CLOEXEC);
	if (fcntl(fd, F_SETFD, flags) < 0)
		i_fatal("fcntl(F_SETFD, %d) failed: %m", fd);
}

void fd_debug_verify_leaks(int first_fd, int last_fd)
{
	struct ip_addr addr, raddr;
	in_port_t port, rport;
	struct stat st;
	int old_errno;
	bool leaks = FALSE;

	for (int fd = first_fd; fd <= last_fd; ++fd) {
		if (fcntl(fd, F_GETFD, 0) == -1 && errno == EBADF)
			continue;

		old_errno = errno;

		if (net_getsockname(fd, &addr, &port) == 0) {
			if (addr.family == AF_UNIX) {
				struct sockaddr_un sa;

				socklen_t socklen = sizeof(sa);

				if (getsockname(fd, (void *)&sa,
						&socklen) < 0)
					sa.sun_path[0] = '\0';

				i_error("Leaked UNIX socket fd %d: %s",
					fd, sa.sun_path);
				leaks = TRUE;
				continue;
			}

			if (net_getpeername(fd, &raddr, &rport) < 0) {
				i_zero(&raddr);
				rport = 0;
			}
			i_error("Leaked socket fd %d: %s:%u -> %s:%u",
				fd, net_ip2addr(&addr), port,
				net_ip2addr(&raddr), rport);
			leaks = TRUE;
			continue;
		}

		if (fstat(fd, &st) == 0) {
			const char *error;
			const char *fname;
			if (t_readlink(t_strdup_printf("/proc/self/fd/%d", fd),
				       &fname, &error) < 0)
				fname = t_strdup_printf("<error: %s>", error);

#ifdef __APPLE__
			/* OSX workaround: gettimeofday() calls shm_open()
			   internally and the fd won't get closed on exec.
			   We'll just skip all ino/dev=0 files and hope they
			   weren't anything else. */
			if (st.st_ino == 0 && st.st_dev == 0)
				continue;
#endif
#ifdef HAVE_SYS_SYSMACROS_H
			i_error("Leaked file %s: fd %d dev %s.%s inode %s",
				fname, fd, dec2str(major(st.st_dev)),
				dec2str(minor(st.st_dev)), dec2str(st.st_ino));
			leaks = TRUE;
			continue;
#else
			i_error("Leaked file %s: fd %d dev %s inode %s",
				fname, fd, dec2str(st.st_dev),
				dec2str(st.st_ino));
			leaks = TRUE;
			continue;
#endif
		}

		i_error("Leaked unknown fd %d (errno = %s)",
			fd, strerror(old_errno));
		leaks = TRUE;
		continue;
	}
	if (leaks)
		i_fatal("fd leak found");
}

void fd_set_nonblock(int fd, bool nonblock)
{
	int flags;

	i_assert(fd > -1);

	flags = fcntl(fd, F_GETFL, 0);
	if (flags < 0)
		i_fatal("fcntl(%d, F_GETFL) failed: %m", fd);

	if (nonblock)
		flags |= O_NONBLOCK;
	else
		flags &= ENUM_NEGATE(O_NONBLOCK);

	if (fcntl(fd, F_SETFL, flags) < 0)
		i_fatal("fcntl(%d, F_SETFL) failed: %m", fd);
}

void fd_close_maybe_stdio(int *fd_in, int *fd_out)
{
	int *fdp[2] = { fd_in, fd_out };

	if (*fd_in == *fd_out)
		*fd_in = -1;

	for (unsigned int i = 0; i < N_ELEMENTS(fdp); i++) {
		if (*fdp[i] == -1)
			;
		else if (*fdp[i] > 1)
			i_close_fd(fdp[i]);
		else if (dup2(dev_null_fd, *fdp[i]) == *fdp[i])
			*fdp[i] = -1;
		else
			i_fatal("dup2(/dev/null, %d) failed: %m", *fdp[i]);
	}
}

#undef i_close_fd_path
void i_close_fd_path(int *fd, const char *path, const char *arg,
		     const char *func, const char *file, int line)
{
	int saved_errno;

	if (*fd == -1)
		return;

	if (unlikely(*fd <= 0)) {
		i_panic("%s: close(%s%s%s) @ %s:%d attempted with fd=%d",
			func, arg,
			(path == NULL) ? "" : " = ",
			(path == NULL) ? "" : path,
			file, line, *fd);
	}

	saved_errno = errno;
	/* Ignore ECONNRESET because we don't really care about it here,
	   as we are closing the socket down in any case. There might be
	   unsent data but nothing we can do about that. */
	if (unlikely(close(*fd) < 0 && errno != ECONNRESET))
		i_error("%s: close(%s%s%s) @ %s:%d failed (fd=%d): %m",
			func, arg,
			(path == NULL) ? "" : " = ",
			(path == NULL) ? "" : path,
			file, line, *fd);
	errno = saved_errno;

	*fd = -1;
}
