// SPDX-License-Identifier: GPL-2.0-or-later
/*
 * Copyright (c) 2017 Cyril Hrubis <chrubis@suse.cz>
 */

#define _GNU_SOURCE
#include <unistd.h>
#include <stdlib.h>
#include <errno.h>
#include <sched.h>
#include <sys/ptrace.h>
#include "config.h"
#ifdef HAVE_SYS_FANOTIFY_H
# include <sys/fanotify.h>
#endif
#define TST_NO_DEFAULT_MAIN
#include "tst_test.h"
#include "lapi/setns.h"
#include "tst_safe_macros.h"
#include "lapi/personality.h"
#include "lapi/pidfd.h"

int safe_access(const char *file, const int lineno,
	    const char *pathname, int mode)
{
	int rval;

	rval = access(pathname, mode);

	if (rval == -1) {
		tst_brk_(file, lineno, TBROK | TERRNO,
			"access(%s,%d) failed", pathname, mode);
	} else if (rval) {
		tst_brk_(file, lineno, TBROK | TERRNO,
			"Invalid access(%s,%d) return value %d", pathname,
			mode, rval);
	}

	return rval;
}

int safe_setpgid(const char *file, const int lineno, pid_t pid, pid_t pgid)
{
	int rval;

	rval = setpgid(pid, pgid);

	if (rval == -1) {
		tst_brk_(file, lineno, TBROK | TERRNO,
			"setpgid(%i, %i) failed", pid, pgid);
	} else if (rval) {
		tst_brk_(file, lineno, TBROK | TERRNO,
			"Invalid setpgid(%i, %i) return value %d", pid, pgid,
			rval);
	}

	return rval;
}

pid_t safe_getpgid(const char *file, const int lineno, pid_t pid)
{
	pid_t pgid;

	pgid = getpgid(pid);

	if (pgid == -1) {
		tst_brk_(file, lineno, TBROK | TERRNO, "getpgid(%i) failed",
			pid);
	} else if (pgid < 0) {
		tst_brk_(file, lineno, TBROK | TERRNO,
			"Invalid getpgid(%i) return value %d", pid, pgid);
	}

	return pgid;
}

int safe_setgroups(const char *file, const int lineno, size_t size, const gid_t *list)
{
	int rval;

	rval = setgroups(size, list);

	if (rval == -1) {
		tst_brk_(file, lineno, TBROK | TERRNO,
			"setgroups(%zu, %p) failed", size, list);
	} else if (rval) {
		tst_brk_(file, lineno, TBROK | TERRNO,
			"Invalid setgroups(%zu, %p) return value %d", size,
			list, rval);
	}

	return rval;
}

int safe_getgroups(const char *file, const int lineno, int size, gid_t list[])
{
	int rval;

	rval = getgroups(size, list);

	if (rval == -1) {
		tst_brk_(file, lineno, TBROK | TERRNO,
			"getgroups(%i, %p)", size, list);
	} else if (rval < 0) {
		tst_brk_(file, lineno, TBROK | TERRNO,
			"Invalid getgroups(%i, %p) return value %d", size,
			list, rval);
	}

	return rval;
}

int safe_personality(const char *filename, unsigned int lineno,
		    unsigned long persona)
{
	int prev_persona = personality(persona);

	if (prev_persona == -1) {
		tst_brk_(filename, lineno, TBROK | TERRNO,
			 "persona(%ld) failed", persona);
	} else if (prev_persona < 0) {
		tst_brk_(filename, lineno, TBROK | TERRNO,
			 "Invalid persona(%ld) return value %d", persona,
			 prev_persona);
	}

	return prev_persona;
}

int safe_pidfd_open(const char *file, const int lineno, pid_t pid,
		   unsigned int flags)
{
	int rval;

	rval = pidfd_open(pid, flags);

	if (rval == -1) {
		tst_brk_(file, lineno, TBROK | TERRNO,
			 "pidfd_open(%i, %i) failed", pid, flags);
	} else if (rval < 0) {
		tst_brk_(file, lineno, TBROK | TERRNO,
			 "Invalid pidfd_open(%i, %i) return value %d",
			 pid, flags, rval);
	}

	return rval;
}

int safe_setregid(const char *file, const int lineno,
		  gid_t rgid, gid_t egid)
{
	int rval;

	rval = setregid(rgid, egid);

	if (rval == -1) {
		tst_brk_(file, lineno, TBROK | TERRNO,
			 "setregid(%li, %li) failed", (long)rgid, (long)egid);
	} else if (rval) {
		tst_brk_(file, lineno, TBROK | TERRNO,
			 "Invalid setregid(%li, %li) return value %d",
			 (long)rgid, (long)egid, rval);
	}

	return rval;
}

int safe_setreuid(const char *file, const int lineno,
		  uid_t ruid, uid_t euid)
{
	int rval;

	rval = setreuid(ruid, euid);

	if (rval == -1) {
		tst_brk_(file, lineno, TBROK | TERRNO,
			 "setreuid(%li, %li) failed", (long)ruid, (long)euid);
	} else if (rval) {
		tst_brk_(file, lineno, TBROK | TERRNO,
			 "Invalid setreuid(%li, %li) return value %d",
			 (long)ruid, (long)euid, rval);
	}

	return rval;
}

int safe_setresgid(const char *file, const int lineno,
	gid_t rgid, gid_t egid, gid_t sgid)
{
	int ret;

	ret = setresgid(rgid, egid, sgid);

	if (ret == -1) {
		tst_brk_(file, lineno, TBROK | TERRNO,
			"setregid(%li, %li, %li) failed", (long)rgid,
			(long)egid, (long)sgid);
	} else if (ret) {
		tst_brk_(file, lineno, TBROK | TERRNO,
			"Invalid setregid(%li, %li, %li) return value %d",
			(long)rgid, (long)egid, (long)sgid, ret);
	}

	return ret;
}

int safe_setresuid(const char *file, const int lineno,
	uid_t ruid, uid_t euid, uid_t suid)
{
	int ret;

	ret = setresuid(ruid, euid, suid);

	if (ret == -1) {
		tst_brk_(file, lineno, TBROK | TERRNO,
			"setreuid(%li, %li, %li) failed", (long)ruid,
			(long)euid, (long)suid);
	} else if (ret) {
		tst_brk_(file, lineno, TBROK | TERRNO,
			"Invalid setreuid(%li, %li, %li) return value %d",
			(long)ruid, (long)euid, (long)suid, ret);
	}

	return ret;
}

int safe_sigaction(const char *file, const int lineno,
                   int signum, const struct sigaction *act,
                   struct sigaction *oldact)
{
	int rval;

	rval = sigaction(signum, act, oldact);

	if (rval == -1) {
		tst_brk_(file, lineno, TBROK | TERRNO,
			"sigaction(%s (%d), %p, %p) failed",
			tst_strsig(signum), signum, act, oldact);
	} else if (rval) {
		tst_brk_(file, lineno, TBROK | TERRNO,
			"Invalid sigaction(%s (%d), %p, %p) return value %d",
			tst_strsig(signum), signum, act, oldact, rval);
	}

	return rval;
}

int safe_sigaddset(const char *file, const int lineno,
                    sigset_t *sigs, int signo)
{
	int rval;

	rval = sigaddset(sigs, signo);

	if (rval == -1) {
		tst_brk_(file, lineno, TBROK | TERRNO,
			"sigaddset() %s (%i) failed", tst_strsig(signo),
			signo);
	} else if (rval) {
		tst_brk_(file, lineno, TBROK | TERRNO,
			"Invalid sigaddset() %s (%i) return value %d",
			tst_strsig(signo), signo, rval);
	}

	return rval;
}

int safe_sigdelset(const char *file, const int lineno,
                    sigset_t *sigs, int signo)
{
	int rval;

	rval = sigdelset(sigs, signo);

	if (rval == -1) {
		tst_brk_(file, lineno, TBROK | TERRNO,
			"sigdelset() %s (%i) failed", tst_strsig(signo),
			signo);
	} else if (rval) {
		tst_brk_(file, lineno, TBROK | TERRNO,
			"Invalid sigdelset() %s (%i) return value %d",
			tst_strsig(signo), signo, rval);
	}

	return rval;
}

int safe_sigemptyset(const char *file, const int lineno,
                      sigset_t *sigs)
{
	int rval;

	rval = sigemptyset(sigs);

	if (rval == -1) {
		tst_brk_(file, lineno, TBROK | TERRNO, "sigemptyset() failed");
	} else if (rval) {
		tst_brk_(file, lineno, TBROK | TERRNO,
			"Invalid sigemptyset() return value %d", rval);
	}

	return rval;
}

int safe_sigfillset(const char *file, const int lineno,
		     sigset_t *sigs)
{
	int rval;

	rval = sigfillset(sigs);

	if (rval == -1) {
		tst_brk_(file, lineno, TBROK | TERRNO, "sigfillset() failed");
	} else if (rval) {
		tst_brk_(file, lineno, TBROK | TERRNO,
			"Invalid sigfillset() return value %d", rval);
	}

	return rval;
}

static const char *strhow(int how)
{
	switch (how) {
	case SIG_BLOCK:
		return "SIG_BLOCK";
	case SIG_UNBLOCK:
		return "SIG_UNBLOCK";
	case SIG_SETMASK:
		return "SIG_SETMASK";
	default:
		return "???";
	}
}

int safe_sigprocmask(const char *file, const int lineno,
                      int how, sigset_t *set, sigset_t *oldset)
{
	int rval;

	rval = sigprocmask(how, set, oldset);

	if (rval == -1) {
		tst_brk_(file, lineno, TBROK | TERRNO,
			"sigprocmask(%s, %p, %p) failed", strhow(how), set,
			oldset);
	} else if (rval) {
		tst_brk_(file, lineno, TBROK | TERRNO,
			"Invalid sigprocmask(%s, %p, %p) return value %d",
			strhow(how), set, oldset, rval);
	}

	return rval;
}

int safe_sigwait(const char *file, const int lineno,
                  sigset_t *set, int *sig)
{
	int rval;

	rval = sigwait(set, sig);

	if (rval > 0) {
		errno = rval;
		tst_brk_(file, lineno, TBROK | TERRNO,
			"sigwait(%p, %p) failed", set, sig);
	} else if (rval) {
		tst_brk_(file, lineno, TBROK,
			"Invalid sigwait(%p, %p) return value %d", set, sig,
			rval);
	}

	return rval;
}

struct group *safe_getgrnam(const char *file, const int lineno,
			    const char *name)
{
	struct group *rval;

	errno = 0;
	rval = getgrnam(name);
	if (rval == NULL) {
		tst_brk_(file, lineno, TBROK | TERRNO,
			"getgrnam(%s) failed", name);
	}

	return rval;
}

struct group *safe_getgrnam_fallback(const char *file, const int lineno,
				     const char *name, const char *fallback)
{
	struct group *rval;

	errno = 0;
	rval = getgrnam(name);
	if (rval == NULL) {
		tst_res_(file, lineno, TINFO,
			 "getgrnam(%s) failed - try fallback %s",
			 name, fallback);
		rval = safe_getgrnam(file, lineno, fallback);
	}

	return rval;
}

struct group *safe_getgrgid(const char *file, const int lineno, gid_t gid)
{
	struct group *rval;

	errno = 0;
	rval = getgrgid(gid);
	if (rval == NULL) {
		tst_brk_(file, lineno, TBROK | TERRNO,
			"getgrgid(%li) failed", (long)gid);
	}

	return rval;
}

int safe_chroot(const char *file, const int lineno, const char *path)
{
	int rval;

	rval = chroot(path);

	if (rval == -1) {
		tst_brk_(file, lineno, TBROK | TERRNO, "chroot(%s) failed",
			path);
	} else if (rval) {
		tst_brk_(file, lineno, TBROK | TERRNO,
			 "Invalid chroot(%s) return value %d", path, rval);
	}

	return rval;
}

int safe_unshare(const char *file, const int lineno, int flags)
{
	int res;

	res = unshare(flags);

	if (res == -1) {
		if (errno == EINVAL) {
			tst_brk_(file, lineno, TCONF | TERRNO,
				 "unshare(%d) unsupported", flags);
		} else {
			tst_brk_(file, lineno, TBROK | TERRNO,
				 "unshare(%d) failed", flags);
		}
	} else if (res) {
		tst_brk_(file, lineno, TBROK | TERRNO,
			 "Invalid unshare(%d) return value %d", flags, res);
	}

	return res;
}

int safe_setns(const char *file, const int lineno, int fd, int nstype)
{
	int ret;

	ret = setns(fd, nstype);

	if (ret == -1) {
		tst_brk_(file, lineno, TBROK | TERRNO, "setns(%i, %i) failed",
			fd, nstype);
	} else if (ret) {
		tst_brk_(file, lineno, TBROK | TERRNO,
			"Invalid setns(%i, %i) return value %d", fd, nstype,
			ret);
	}

	return ret;
}

long tst_safe_ptrace(const char *file, const int lineno, int req, pid_t pid,
	void *addr, void *data)
{
	long ret;

	errno = 0;
	ret = ptrace(req, pid, addr, data);

	if (ret == -1) {
		tst_brk_(file, lineno, TBROK | TERRNO, "ptrace() failed");
	} else if (ret) {
		tst_brk_(file, lineno, TBROK | TERRNO,
			"Invalid ptrace() return value %ld", ret);
	}

	return ret;
}

int safe_pipe2(const char *file, const int lineno, int fildes[2], int flags)
{
	int ret;

	ret = pipe2(fildes, flags);

	if (ret == -1) {
		tst_brk_(file, lineno, TBROK | TERRNO,
			"pipe2({%d,%d}) failed with flag(%d)", fildes[0],
			fildes[1], flags);
	} else if (ret) {
		tst_brk_(file, lineno, TBROK | TERRNO,
			"Invalid pipe2({%d,%d}, %d) return value %d",
			fildes[0], fildes[1], flags, ret);
	}

	return ret;
}

int safe_dup(const char *file, const int lineno, int oldfd)
{
	int rval;

	rval = dup(oldfd);

	if (rval == -1) {
		tst_brk_(file, lineno, TBROK | TERRNO,
			"dup(%i) failed", oldfd);
	} else if (rval < 0) {
		tst_brk_(file, lineno, TBROK | TERRNO,
			"Invalid dup(%i) return value %d", oldfd, rval);
	}

	return rval;
}

int safe_dup2(const char *file, const int lineno, int oldfd, int newfd)
{
	int rval;

	rval = dup2(oldfd, newfd);

	if (rval == -1) {
		tst_brk_(file, lineno, TBROK | TERRNO,
			 "dup2(%i, %i) failed", oldfd, newfd);
	} else if (rval != newfd) {
		tst_brk_(file, lineno, TBROK | TERRNO,
			 "Invalid dup2(%i, %i) return value %d",
			 oldfd, newfd, rval);
	}

	return rval;
}

void *safe_realloc(const char *file, const int lineno, void *ptr, size_t size)
{
	void *ret;

	ret = realloc(ptr, size);

	if (!ret) {
		tst_brk_(file, lineno, TBROK | TERRNO,
			"realloc(%p, %zu) failed", ptr, size);
	}

	return ret;
}

sighandler_t safe_signal(const char *file, const int lineno,
	int signum, sighandler_t handler)
{
	sighandler_t rval;

	rval = signal(signum, handler);

	if (rval == SIG_ERR) {
		tst_brk_(file, lineno, TBROK | TERRNO,
			"signal(%d,%p) failed",
			signum, handler);
	}

	return rval;
}

void safe_cmd(const char *file, const int lineno, const char *const argv[],
	const char *stdout_path, const char *stderr_path)
{
	int rval;

	switch ((rval = tst_cmd(argv, stdout_path, stderr_path,
		TST_CMD_PASS_RETVAL | TST_CMD_TCONF_ON_MISSING))) {
	case 0:
		break;
	default:
		tst_brk_(file, lineno, TBROK, "%s failed (%d)", argv[0], rval);
	}
}

int safe_msync(const char *file, const int lineno, void *addr,
				size_t length, int flags)
{
	int rval;

	rval = msync(addr, length, flags);

	if (rval == -1) {
		tst_brk_(file, lineno, TBROK | TERRNO,
			"msync(%p, %zu, %d) failed", addr, length, flags);
	} else if (rval) {
		tst_brk_(file, lineno, TBROK | TERRNO,
			"Invalid msync(%p, %zu, %d) return value %d",
			addr, length, flags, rval);
	}

	return rval;
}

void safe_print_file(const char *file, const int lineno, char *path)
{
	FILE *pfile;
	char line[PATH_MAX];

	tst_res(TINFO, "=== %s ===", path);

	pfile = safe_fopen(file, lineno, NULL, path, "r");

	while (fgets(line, sizeof(line), pfile))
		fprintf(stderr, "%s", line);

	safe_fclose(file, lineno, NULL, pfile);
}
