#include <sys/syscall.h>
#include <signal.h>
#include <string.h>
#include <unistd.h>

#include "types.h"
#include "parasite-syscall.h"
#include "parasite.h"
#include "vdso.h"

static void exit_on(int ret, int err_fd, char *reason)
{
	if (ret) {
		syscall(__NR_write, err_fd, reason, strlen(reason));
		syscall(__NR_exit, ret);
		__builtin_unreachable();
	}
}
/*
 * Because of restrictions of ARCH_MAP_VDSO_* API, new vDSO blob
 * can be mapped only if there is no vDSO blob present for a process.
 * This is a helper process, it unmaps 64-bit vDSO and maps 32-bit vDSO.
 * Then it copies vDSO blob to shared with CRIU mapping.
 *
 * The purpose is to fill compat vdso's symtable (vdso_compat_rt).
 * It's an optimization to fill symtable only once at CRIU restore
 * for all restored tasks.
 *
 * @native		- 64-bit vDSO blob (for easy unmap)
 * @pipe_fd		- to get size of compat blob from /proc/.../maps
 * @err_fd		- to print error messages
 * @vdso_buf, buf_size	- shared with CRIU buffer
 *
 * WARN: This helper shouldn't call pr_err() or any syscall with
 *	 Glibc's wrapper function - it may very likely blow up.
 */
void compat_vdso_helper(struct vdso_maps *native, int pipe_fd, int err_fd, void *vdso_buf, size_t buf_size)
{
	void *vdso_addr;
	long vdso_size;
	long ret;

	if (native->vdso_start != VDSO_BAD_ADDR) {
		ret = syscall(__NR_munmap, native->vdso_start, native->sym.vdso_size);
		exit_on(ret, err_fd, "Error: Failed to unmap native vdso\n");
	}

	if (native->vvar_start != VVAR_BAD_ADDR) {
		ret = syscall(__NR_munmap, native->vvar_start, native->sym.vvar_size);
		exit_on(ret, err_fd, "Error: Failed to unmap native vvar\n");
	}

	ret = syscall(__NR_arch_prctl, ARCH_MAP_VDSO_32, native->vdso_start);
	if (ret < 0)
		exit_on(ret, err_fd, "Error: ARCH_MAP_VDSO failed\n");

	vdso_size = ret;
	if (vdso_size > buf_size)
		exit_on(-1, err_fd, "Error: Compatible vdso's size is bigger than reserved buf\n");

	/* Stop so CRIU could parse smaps to find 32-bit vdso's size */
	ret = syscall(__NR_kill, syscall(__NR_getpid), SIGSTOP);
	exit_on(ret, err_fd, "Error: Can't stop myself with SIGSTOP (having a good time)\n");

	ret = syscall(__NR_read, pipe_fd, &vdso_addr, sizeof(void *));
	if (ret != sizeof(void *))
		exit_on(-1, err_fd, "Error: Can't read size of mapped vdso from pipe\n");

	memcpy(vdso_buf, vdso_addr, vdso_size);

	syscall(__NR_exit, 0);
}
