/*--------------------------------------------------------------------
 * Copyright © 2016 James Hunt <jamesodhunt@ubuntu.com>.
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *--------------------------------------------------------------------
 */

#include "platform-freebsd.h"

static struct procenv_map signal_map_freebsd[] = {

	mk_map_entry (SIGABRT),
	mk_map_entry (SIGALRM),
	mk_map_entry (SIGBUS),

	{ SIGCHLD, "SIGCHLD|SIGCLD" },

	mk_map_entry (SIGCONT),
	mk_map_entry (SIGFPE),
	mk_map_entry (SIGHUP),
	mk_map_entry (SIGILL),
	mk_map_entry (SIGINT),
	mk_map_entry (SIGKILL),
	mk_map_entry (SIGPIPE),
	mk_map_entry (SIGQUIT),
	mk_map_entry (SIGSEGV),
	mk_map_entry (SIGSTOP),
	mk_map_entry (SIGTERM),
	mk_map_entry (SIGTRAP),
	mk_map_entry (SIGTSTP),
	mk_map_entry (SIGTTIN),
	mk_map_entry (SIGTTOU),
	mk_map_entry (SIGUSR1),
	mk_map_entry (SIGUSR2),
	mk_map_entry (SIGIO),
	mk_map_entry (SIGPROF),
	mk_map_entry (SIGSYS),
	mk_map_entry (SIGURG),
	mk_map_entry (SIGVTALRM),
	mk_map_entry (SIGWINCH),
	mk_map_entry (SIGXCPU),
	mk_map_entry (SIGXFSZ),
	mk_map_entry (SIGEMT),
	mk_map_entry (SIGINFO),
	mk_map_entry (SIGTHR),
	mk_map_entry (SIGLIBRT),

	{ 0, NULL },
};

static struct procenv_map64 mntopt_map_freebsd[] = {

	{ MNT_ACLS         , "acls" },
	{ MNT_ASYNC        , "asynchronous" },
	{ MNT_EXPORTED     , "NFS-exported" },
	{ MNT_GJOURNAL     , "gjournal" },
	{ MNT_LOCAL        , "local" },
	{ MNT_MULTILABEL   , "multilabel" },
	{ MNT_NFS4ACLS     , "nfsv4acls" },
	{ MNT_NOATIME      , "noatime" },
	{ MNT_NOCLUSTERR   , "noclusterr" },
	{ MNT_NOCLUSTERW   , "noclusterw" },
	{ MNT_NOEXEC       , "noexec" },
	{ MNT_NOSUID       , "nosuid" },
	{ MNT_NOSYMFOLLOW  , "nosymfollow" },
	{ MNT_QUOTA        , "with quotas" },
	{ MNT_RDONLY       , "read-only" },
	{ MNT_SOFTDEP      , "soft-updates" },
	{ MNT_SUIDDIR      , "suiddir" },
#if defined (MNT_SUJ)
	{ MNT_SUJ          , "journaled soft-updates" },
#endif
	{ MNT_SYNCHRONOUS  , "synchronous" },
	{ MNT_UNION        , "union" },

	{ 0, NULL }
};

static struct procenv_map if_flag_map_freebsd[] = {
	mk_map_entry (IFF_UP),
	mk_map_entry (IFF_BROADCAST),
	mk_map_entry (IFF_DEBUG),
	mk_map_entry (IFF_LOOPBACK),
	mk_map_entry (IFF_POINTOPOINT),
	mk_map_entry (IFF_RUNNING),
	mk_map_entry (IFF_NOARP),
	mk_map_entry (IFF_PROMISC),
	mk_map_entry (IFF_ALLMULTI),
	mk_map_entry (IFF_SIMPLEX),
	mk_map_entry (IFF_MULTICAST),

	{ 0, NULL }
};

static void
get_user_misc_freebsd (struct procenv_user *user,
		struct procenv_misc *misc)
{
	char                 errors[_POSIX2_LINE_MAX];
	kvm_t               *kvm;
	struct kinfo_proc   *proc;
	int                  ignored;

	assert (user);
	assert (misc);

	kvm = kvm_openfiles (NULL, _PATH_DEVNULL, NULL, O_RDONLY, errors);
	if (! kvm)
		die ("unable to open kvm");

	proc = kvm_getprocs (kvm, KERN_PROC_PID, user->pid, &ignored);
	if (! proc)
		die ("failed to get process info");

	misc->in_jail = (proc->ki_flag & P_JAILED) ? true : false;
	strcpy (user->proc_name, proc->ki_comm);

	if (kvm_close (kvm) < 0)
		die ("failed to close kvm");
}

static void
show_fd_capabilities_freebsd (int fd)
{
	int           ret;
	u_int         mode;
	cap_rights_t  rights;

	ret = cap_getmode (&mode);
	if (ret < 0) {
		/* No Capsicum support */
		goto out;
	}

	ret = cap_rights_get (fd, &rights);
	if (ret < 0) {
		/* Cannot query capabilities */
		goto out;
	}

	show_capsicum_cap (rights, CAP_ACCEPT);
	show_capsicum_cap (rights, CAP_ACL_CHECK);
	show_capsicum_cap (rights, CAP_ACL_DELETE);
	show_capsicum_cap (rights, CAP_ACL_GET);
	show_capsicum_cap (rights, CAP_ACL_SET);
	show_capsicum_cap (rights, CAP_BIND);
#if __FreeBSD__ > 9
	show_capsicum_cap (rights, CAP_BINDAT);
	show_capsicum_cap (rights, CAP_CHFLAGSAT);
#endif
	show_capsicum_cap (rights, CAP_CONNECT);
#if __FreeBSD__ > 9
	show_capsicum_cap (rights, CAP_CONNECTAT);
#endif
	show_capsicum_cap (rights, CAP_CREATE);
#if __FreeBSD__ > 9
	show_capsicum_cap (rights, CAP_EVENT);
#endif
	show_capsicum_cap (rights, CAP_EXTATTR_DELETE);
	show_capsicum_cap (rights, CAP_EXTATTR_GET);
	show_capsicum_cap (rights, CAP_EXTATTR_LIST);
	show_capsicum_cap (rights, CAP_EXTATTR_SET);
	show_capsicum_cap (rights, CAP_FCHDIR);
	show_capsicum_cap (rights, CAP_FCHFLAGS);
	show_capsicum_cap (rights, CAP_FCHMOD);
#if __FreeBSD__ > 9
	show_capsicum_cap (rights, CAP_FCHMODAT);
#endif
	show_capsicum_cap (rights, CAP_FCHOWN);
#if __FreeBSD__ > 9
	show_capsicum_cap (rights, CAP_FCHOWNAT);
#endif
	show_capsicum_cap (rights, CAP_FCNTL);
	show_capsicum_cap (rights, CAP_FEXECVE);
	show_capsicum_cap (rights, CAP_FLOCK);
	show_capsicum_cap (rights, CAP_FPATHCONF);
	show_capsicum_cap (rights, CAP_FSCK);
	show_capsicum_cap (rights, CAP_FSTAT);
#if __FreeBSD__ > 9
	show_capsicum_cap (rights, CAP_FSTATAT);
#endif
	show_capsicum_cap (rights, CAP_FSTATFS);
	show_capsicum_cap (rights, CAP_FSYNC);
	show_capsicum_cap (rights, CAP_FTRUNCATE);
	show_capsicum_cap (rights, CAP_FUTIMES);
#if __FreeBSD__ > 9
	show_capsicum_cap (rights, CAP_FUTIMESAT);
#endif
	show_capsicum_cap (rights, CAP_GETPEERNAME);
	show_capsicum_cap (rights, CAP_GETSOCKNAME);
	show_capsicum_cap (rights, CAP_GETSOCKOPT);
	show_capsicum_cap (rights, CAP_IOCTL);
#if __FreeBSD__ > 9
	show_capsicum_cap (rights, CAP_KQUEUE);
	show_capsicum_cap (rights, CAP_KQUEUE_CHANGE);
	show_capsicum_cap (rights, CAP_KQUEUE_EVENT);
#if defined (CAP_LINKAT)
	show_capsicum_cap (rights, CAP_LINKAT);
#endif
#if defined (CAP_LINKAT_SOURCE)
	show_capsicum_cap (rights, CAP_LINKAT_SOURCE);
#endif
#if defined (CAP_LINKAT_TARGET)
	show_capsicum_cap (rights, CAP_LINKAT_TARGET);
#endif

#endif
	show_capsicum_cap (rights, CAP_LISTEN);
	show_capsicum_cap (rights, CAP_LOOKUP);
	show_capsicum_cap (rights, CAP_MAC_GET);
	show_capsicum_cap (rights, CAP_MAC_SET);
#if __FreeBSD__ > 9
	show_capsicum_cap (rights, CAP_MKDIRAT);
	show_capsicum_cap (rights, CAP_MKFIFOAT);
	show_capsicum_cap (rights, CAP_MKNODAT);
	show_capsicum_cap (rights, CAP_MMAP);
	show_capsicum_cap (rights, CAP_MMAP_R);
	show_capsicum_cap (rights, CAP_MMAP_RW);
	show_capsicum_cap (rights, CAP_MMAP_RWX);
	show_capsicum_cap (rights, CAP_MMAP_RX);
	show_capsicum_cap (rights, CAP_MMAP_W);
	show_capsicum_cap (rights, CAP_MMAP_WX);
	show_capsicum_cap (rights, CAP_MMAP_X);
#endif
	show_capsicum_cap (rights, CAP_PDGETPID);
	show_capsicum_cap (rights, CAP_PDKILL);
	show_capsicum_cap (rights, CAP_PDWAIT);
	show_capsicum_cap (rights, CAP_PEELOFF);
	show_capsicum_cap (rights, CAP_POLL_EVENT);
#if __FreeBSD__ > 9
	show_capsicum_cap (rights, CAP_PREAD);
	show_capsicum_cap (rights, CAP_PWRITE);
#endif
	show_capsicum_cap (rights, CAP_READ);
#if __FreeBSD__ > 9
	show_capsicum_cap (rights, CAP_RECV);
#if defined (CAP_RENAMEAT)
	show_capsicum_cap (rights, CAP_RENAMEAT);
#endif
#if defined (CAP_RENAMEAT_SOURCE)
	show_capsicum_cap (rights, CAP_RENAMEAT_SOURCE);
#endif
#if defined (CAP_RENAMEAT_TARGET)
	show_capsicum_cap (rights, CAP_RENAMEAT_TARGET);
#endif
#endif
	show_capsicum_cap (rights, CAP_SEEK);
#if __FreeBSD__ > 9
	show_capsicum_cap (rights, CAP_SEEK_TELL);
#endif
	show_capsicum_cap (rights, CAP_SEM_GETVALUE);
	show_capsicum_cap (rights, CAP_SEM_POST);
	show_capsicum_cap (rights, CAP_SEM_WAIT);
#if __FreeBSD__ > 9
	show_capsicum_cap (rights, CAP_SEND);
#endif
	show_capsicum_cap (rights, CAP_SETSOCKOPT);
	show_capsicum_cap (rights, CAP_SHUTDOWN);
#if __FreeBSD__ > 9
	show_capsicum_cap (rights, CAP_SOCK_CLIENT);
	show_capsicum_cap (rights, CAP_SOCK_SERVER);
	show_capsicum_cap (rights, CAP_SYMLINKAT);
#endif
	show_capsicum_cap (rights, CAP_TTYHOOK);
#if __FreeBSD__ > 9
	show_capsicum_cap (rights, CAP_UNLINKAT);
#endif
	show_capsicum_cap (rights, CAP_WRITE);

out:
	/* clang requires this */
	return;
}

static void
show_mounts_freebsd (ShowMountType what)
{
	show_mounts_generic_bsd (what, mntopt_map_freebsd);
}

static void
show_cpu_freebsd (void)
{
	long                max;
	kvm_t              *kvm;
	struct kinfo_proc  *proc;
	int                 ignored;
	int                 cpu;
	char                errors[_POSIX2_LINE_MAX];

	assert (user.pid > 0);

	max = get_sysconf (_SC_NPROCESSORS_ONLN);

	kvm = kvm_openfiles (NULL, _PATH_DEVNULL, NULL, O_RDONLY, errors);
	if (! kvm)
		die ("unable to open kvm");

	proc = kvm_getprocs (kvm, KERN_PROC_PID, user.pid, &ignored);
	if (! proc)
		die ("failed to get process info");

	/* cpu values are zero-based */
	cpu = 1 + proc->ki_oncpu;

	if (kvm_close (kvm) < 0)
		die ("failed to close kvm");

	entry ("number", "%u of %lu", cpu, max);
}

/* Who would have thought handling PIDs was so tricky? */
static void
handle_proc_branch_freebsd (void)
{
	int                  count = 0;
	int                  i;
	char                 errors[_POSIX2_LINE_MAX];
	kvm_t               *kvm;
	struct kinfo_proc   *procs;
	struct kinfo_proc   *p;
	pid_t                current;
	int                  done = false;
	char                *str = NULL;
	pid_t                ultimate_parent = 0;

	common_assert ();

	current = getpid ();

	kvm = kvm_openfiles (NULL, _PATH_DEVNULL, NULL, O_RDONLY, errors);
	if (! kvm)
		die ("unable to open kvm");

	procs = kvm_getprocs (kvm, KERN_PROC_PROC, 0, &count);
	if (! procs)
		die ("failed to get process info");

	/* Calculate the lowest PID number which gives us the ultimate
	 * parent of all processes.
	 *
	 * On FreeBSD systems, PID 0 ('[kernel]') is the ultimate
	 * parent rather than PID 1 ('init').
	 *
	 * However, this doesn't work in a FreeBSD jail since in that
	 * environment:
	 *
	 * - there is no init process visible.
	 * - there is no kernel thread visible.
	 * - the ultimate parent PID will either by 1 (the "invisible"
	 *   init process) or 'n' where 'n' is a PID>1 which is also
	 *   "invisible" (since it lives outside the jail in the host
	 *   environment).
	 *
	 * Confused yet?
	 */

	p = &procs[0];
	ultimate_parent = p->ki_pid;

	for (i = 1; i < count; i++) {
		p = &procs[i];
		if (p->ki_pid < ultimate_parent)
			ultimate_parent = p->ki_pid;
	}

	while (! done) {
		for (i = 0; i < count && !done; i++) {
			p = &procs[i];

			if (p->ki_pid == current) {
				if (misc.in_jail) {
					struct kinfo_proc   *p2;
					int                  ppid_found = false;
					int                  j;

					/* Determine if the parent PID
					 * actually exists within the
					 * jail.
					 */
					for (j = 0; j < count; j++) {
						p2 =  &procs[j];

						if (p2->ki_pid == p->ki_ppid) {
							ppid_found = true;
							break;
						}
					}

					if (p->ki_ppid == 1 || (p->ki_ppid && ! ppid_found)) {
						/* Found the "last" PID (whose parent is either
						 * the "invisible init" or which exists outside the jail)
						 * so record it and hop out.
						 */
						appendf (&str, "%d ('%s') %d (%s)",
								(int)current, p->ki_comm,
								p->ki_ppid, UNKNOWN_STR);
						done = true;
						break;
					} else {
						/* Found a valid parent pid */
						appendf (&str, "%d ('%s'), ",
								(int)current, p->ki_comm);
					}

				} else if (! ultimate_parent && current == ultimate_parent) {

					/* Found the "last" PID so record it and hop out */
					appendf (&str, "%d ('%s')",
							(int)current, p->ki_comm);
					done = true;
					break;
				} else {
					/* Found a valid parent pid */
					appendf (&str, "%d ('%s'), ",
							(int)current, p->ki_comm);
				}

				/* Move on */
				current = p->ki_ppid;
			}
		}
	}

	if (kvm_close (kvm) < 0)
		die ("failed to close kvm");

	entry ("ancestry", "%s", str);
	free (str);
}

static PROCENV_CPU_SET_TYPE *
get_cpuset_freebsd (void)
{
	static PROCENV_CPU_SET_TYPE  cpu_set_real;
	PROCENV_CPU_SET_TYPE        *cs = NULL;
	size_t                       size;

	CPU_ZERO (&cpu_set_real);
	cs = &cpu_set_real;

	size = sizeof (PROCENV_CPU_SET_TYPE);

	if (pthread_getaffinity_np (pthread_self (), size, cs))
		return NULL;

	return cs;
}

static void
free_cpuset_freebsd (PROCENV_CPU_SET_TYPE *cs)
{
	(void) cs;
}

static bool cpuset_has_cpu_freebsd (const PROCENV_CPU_SET_TYPE *cs,
		PROCENV_CPU_TYPE cpu)
{
	return CPU_ISSET (cpu, cs);
}

static void
show_clocks_freebsd (void)
{
	show_clock_res (CLOCK_REALTIME);

#if defined CLOCK_REALTIME_COARSE
	show_clock_res (CLOCK_REALTIME_COARSE);
#endif

#if defined CLOCK_REALTIME_HR
	show_clock_res (CLOCK_REALTIME_HR);
#endif

	show_clock_res (CLOCK_REALTIME_PRECISE);
	show_clock_res (CLOCK_REALTIME_FAST);
	show_clock_res (CLOCK_MONOTONIC);

#if defined CLOCK_MONOTONIC_COARSE
	show_clock_res (CLOCK_MONOTONIC_COARSE);
#endif

#if defined CLOCK_MONOTONIC_RAW
	show_clock_res (CLOCK_MONOTONIC_RAW);
#endif

	show_clock_res (CLOCK_MONOTONIC_PRECISE);
	show_clock_res (CLOCK_MONOTONIC_FAST);
	show_clock_res (CLOCK_UPTIME);
	show_clock_res (CLOCK_UPTIME_PRECISE);
	show_clock_res (CLOCK_UPTIME_FAST);
	show_clock_res (CLOCK_VIRTUAL);
	show_clock_res (CLOCK_PROF);
}
	
struct procenv_ops platform_ops =
{
	.driver                        = PROCENV_SET_DRIVER (freebsd),

	.get_user_misc                 = get_user_misc_freebsd,
	.get_kernel_bits               = get_kernel_bits_generic,
	.get_cpuset                    = get_cpuset_freebsd,
	.get_mtu                       = get_mtu_generic,
	.get_time                      = get_time_generic,
	.free_cpuset                   = free_cpuset_freebsd,
	.cpuset_has_cpu                = cpuset_has_cpu_freebsd,

	.signal_map                    = signal_map_freebsd,
	.if_flag_map                   = if_flag_map_freebsd,

	.show_confstrs                 = show_confstrs_generic,
	.show_cpu                      = show_cpu_freebsd,
	.show_clocks                   = show_clocks_freebsd,
	.show_cpu_affinities           = show_cpu_affinities_generic,
	.show_fd_capabilities          = show_fd_capabilities_freebsd,
	.show_fds                      = show_fds_generic,
	.show_mounts                   = show_mounts_freebsd,
	.show_rlimits                  = show_rlimits_generic,

	.handle_proc_branch            = handle_proc_branch_freebsd,
};
