/* -*- c-set-style: "K&R"; c-basic-offset: 8 -*-
 *
 * This file is part of PRoot.
 *
 * Copyright (C) 2014 STMicroelectronics
 *
 * 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 2 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, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
 * 02110-1301 USA.
 */

#include <sys/ptrace.h> /* ptrace(2), PTRACE_*, */
#include <sys/types.h>  /* pid_t, size_t, */
#include <stdlib.h>     /* NULL, */
#include <stddef.h>     /* offsetof(), */
#include <sys/user.h>   /* struct user*, */
#include <errno.h>      /* errno, */
#include <assert.h>     /* assert(3), */
#include <sys/wait.h>   /* waitpid(2), */
#include <string.h>     /* memcpy(3), */
#include <stdint.h>     /* uint*_t, */
#include <sys/uio.h>    /* process_vm_*, struct iovec, */
#include <unistd.h>     /* sysconf(3), */
#include <sys/mman.h>   /* mmap(2), munmap(2), MAP_*, */

#include "tracee/mem.h"
#include "tracee/abi.h"
#include "syscall/heap.h"
#include "arch.h"            /* word_t, NO_MISALIGNED_ACCESS */
#include "build.h"           /* HAVE_PROCESS_VM,  */
#include "cli/notice.h"

/**
 * Load the word at the given @address, potentially *not* aligned.
 */
static inline word_t load_word(const void *address)
{
#ifdef NO_MISALIGNED_ACCESS
	if (((word_t)address) % sizeof(word_t) == 0)
		return *(word_t *)address;
	else {
		word_t value;
		memcpy(&value, address, sizeof(word_t));
		return value;
	}
#else
	return *(word_t *)address;
#endif
}

/**
 * Store the word with the given @value to the given @address,
 * potentially *not* aligned.
 */
static inline void store_word(void *address, word_t value)
{
#ifdef NO_MISALIGNED_ACCESS
	if (((word_t)address) % sizeof(word_t) == 0)
		*((word_t *)address) = value;
	else
		memcpy(address, &value, sizeof(word_t));
#else
	*((word_t *)address) = value;
#endif
}

/**
 * Copy @size bytes from the buffer @src_tracer to the address
 * @dest_tracee within the memory space of the @tracee process. It
 * returns -errno if an error occured, otherwise 0.
 */
int write_data(const Tracee *tracee, word_t dest_tracee, const void *src_tracer, word_t size)
{
	word_t *src  = (word_t *)src_tracer;
	word_t *dest = (word_t *)dest_tracee;

	long   status;
	word_t word, i, j;
	word_t nb_trailing_bytes;
	word_t nb_full_words;

	uint8_t *last_dest_word;
	uint8_t *last_src_word;

	if (belongs_to_heap_prealloc(tracee, dest_tracee))
		return -EFAULT;

#if defined(HAVE_PROCESS_VM)
	struct iovec local;
	struct iovec remote;

	local.iov_base = src;
	local.iov_len  = size;

	remote.iov_base = dest;
	remote.iov_len  = size;

	status = process_vm_writev(tracee->pid, &local, 1, &remote, 1, 0);
	if ((size_t) status == size)
		return 0;
	/* Fallback to ptrace if something went wrong.  */

#endif /* HAVE_PROCESS_VM */

	nb_trailing_bytes = size % sizeof(word_t);
	nb_full_words     = (size - nb_trailing_bytes) / sizeof(word_t);

	/* Copy one word by one word, except for the last one. */
	for (i = 0; i < nb_full_words; i++) {
		status = ptrace(PTRACE_POKEDATA, tracee->pid, dest + i, load_word(&src[i]));
		if (status < 0) {
			notice(tracee, WARNING, SYSTEM, "ptrace(POKEDATA)");
			return -EFAULT;
		}
	}

	if (nb_trailing_bytes == 0)
		return 0;

	/* Copy the bytes in the last word carefully since we have to
	 * overwrite only the relevant ones. */

	word = ptrace(PTRACE_PEEKDATA, tracee->pid, dest + i, NULL);
	if (errno != 0) {
		notice(tracee, WARNING, SYSTEM, "ptrace(PEEKDATA)");
		return -EFAULT;
	}

	last_dest_word = (uint8_t *)&word;
	last_src_word  = (uint8_t *)&src[i];

	for (j = 0; j < nb_trailing_bytes; j++)
		last_dest_word[j] = last_src_word[j];

	status = ptrace(PTRACE_POKEDATA, tracee->pid, dest + i, word);
	if (status < 0) {
		notice(tracee, WARNING, SYSTEM, "ptrace(POKEDATA)");
		return -EFAULT;
	}

	return 0;
}

/**
 * Gather the @src_tracer_count buffers pointed to by @src_tracer to
 * the address @dest_tracee within the memory space of the @tracee
 * process.  This function returns -errno if an error occured,
 * otherwise 0.
 */
int writev_data(const Tracee *tracee, word_t dest_tracee, const struct iovec *src_tracer, int src_tracer_count)
{
	size_t size;
	int status;
	int i;

	if (belongs_to_heap_prealloc(tracee, dest_tracee))
		return -EFAULT;

#if defined(HAVE_PROCESS_VM)
	struct iovec remote;

	for (i = 0, size = 0; i < src_tracer_count; i++)
		size += src_tracer[i].iov_len;

	remote.iov_base = (word_t *)dest_tracee;
	remote.iov_len  = size;

	status = process_vm_writev(tracee->pid, src_tracer, src_tracer_count, &remote, 1, 0);
	if ((size_t) status == size)
		return 0;
	/* Fallback to iterative-write if something went wrong.  */

#endif /* HAVE_PROCESS_VM */

	for (i = 0, size = 0; i < src_tracer_count; i++) {
		status = write_data(tracee, dest_tracee + size,
				src_tracer[i].iov_base, src_tracer[i].iov_len);
		if (status < 0)
			return status;

		size += src_tracer[i].iov_len;
	}

	return 0;
}

/**
 * Copy @size bytes to the buffer @dest_tracer from the address
 * @src_tracee within the memory space of the @tracee process. It
 * returns -errno if an error occured, otherwise 0.
 */
int read_data(const Tracee *tracee, void *dest_tracer, word_t src_tracee, word_t size)
{
	word_t *src  = (word_t *)src_tracee;
	word_t *dest = (word_t *)dest_tracer;

	word_t nb_trailing_bytes;
	word_t nb_full_words;
	word_t word, i, j;

	uint8_t *last_src_word;
	uint8_t *last_dest_word;

	if (belongs_to_heap_prealloc(tracee, src_tracee))
		return -EFAULT;

#if defined(HAVE_PROCESS_VM)
	long status;
	struct iovec local;
	struct iovec remote;

	local.iov_base = dest;
	local.iov_len  = size;

	remote.iov_base = src;
	remote.iov_len  = size;

	status = process_vm_readv(tracee->pid, &local, 1, &remote, 1, 0);
	if ((size_t) status == size)
		return 0;
	/* Fallback to ptrace if something went wrong.  */

#endif /* HAVE_PROCESS_VM */

	nb_trailing_bytes = size % sizeof(word_t);
	nb_full_words     = (size - nb_trailing_bytes) / sizeof(word_t);

	/* Copy one word by one word, except for the last one. */
	for (i = 0; i < nb_full_words; i++) {
		word = ptrace(PTRACE_PEEKDATA, tracee->pid, src + i, NULL);
		if (errno != 0) {
			notice(tracee, WARNING, SYSTEM, "ptrace(PEEKDATA)");
			return -EFAULT;
		}
		store_word(&dest[i], word);
	}

	if (nb_trailing_bytes == 0)
		return 0;

	/* Copy the bytes from the last word carefully since we have
	 * to not overwrite the bytes lying beyond @dest_tracer. */

	word = ptrace(PTRACE_PEEKDATA, tracee->pid, src + i, NULL);
	if (errno != 0) {
		notice(tracee, WARNING, SYSTEM, "ptrace(PEEKDATA)");
		return -EFAULT;
	}

	last_dest_word = (uint8_t *)&dest[i];
	last_src_word  = (uint8_t *)&word;

	for (j = 0; j < nb_trailing_bytes; j++)
		last_dest_word[j] = last_src_word[j];

	return 0;
}

/**
 * Copy to @dest_tracer at most @max_size bytes from the string
 * pointed to by @src_tracee within the memory space of the @tracee
 * process. This function returns -errno on error, otherwise
 * it returns the number in bytes of the string, including the
 * end-of-string terminator.
 */
int read_string(const Tracee *tracee, char *dest_tracer, word_t src_tracee, word_t max_size)
{
	word_t *src  = (word_t *)src_tracee;
	word_t *dest = (word_t *)dest_tracer;

	word_t nb_trailing_bytes;
	word_t nb_full_words;
	word_t word, i, j;

	uint8_t *src_word;
	uint8_t *dest_word;

	if (belongs_to_heap_prealloc(tracee, src_tracee))
		return -EFAULT;

#if defined(HAVE_PROCESS_VM)
	/* [process_vm] system calls do not check the memory regions
	 * in the remote process until just before doing the
	 * read/write.  Consequently, a partial read/write [1] may
	 * result if one of the remote_iov elements points to an
	 * invalid memory region in the remote process.  No further
	 * reads/writes will be attempted beyond that point.  Keep
	 * this in mind when attempting to read data of unknown length
	 * (such as C strings that are null-terminated) from a remote
	 * process, by avoiding spanning memory pages (typically 4KiB)
	 * in a single remote iovec element.  (Instead, split the
	 * remote read into two remote_iov elements and have them
	 * merge back into a single write local_iov entry.  The first
	 * read entry goes up to the page boundary, while the second
	 * starts on the next page boundary.).
	 *
	 * [1] Partial transfers apply at the granularity of iovec
	 * elements. These system calls won't perform a partial
	 * transfer that splits a single iovec element.
	 *
	 * -- man 2 process_vm_readv
	 */
	long status;
	size_t size;
	size_t offset;
	struct iovec local;
	struct iovec remote;

	static size_t chunk_size = 0;
	static uintptr_t chunk_mask;

	/* A chunk shall not cross a page boundary.  */
	if (chunk_size == 0) {
		chunk_size = sysconf(_SC_PAGESIZE);
		chunk_size = (chunk_size > 0 && chunk_size < 1024 ? chunk_size : 1024);
		chunk_mask = ~(chunk_size - 1);
	}

	/* Read the string by chunk.  */
	offset = 0;
	do {
		uintptr_t current_chunk = (src_tracee + offset) & chunk_mask;
		uintptr_t next_chunk    = current_chunk + chunk_size;

		/* Compute the number of bytes available up to the
		 * next chunk or up to max_size.  */
		size = next_chunk - (src_tracee + offset);
		size = (size < max_size - offset ? size : max_size - offset);

		local.iov_base = (uint8_t *)dest + offset;
		local.iov_len  = size;

		remote.iov_base = (uint8_t *)src + offset;
		remote.iov_len  = size;

		status = process_vm_readv(tracee->pid, &local, 1, &remote, 1, 0);
		if ((size_t) status != size)
			goto fallback;

		status = strnlen(local.iov_base, size);
		if ((size_t) status < size) {
			size = offset + status + 1;
			assert(size <= max_size);
			return size;
		}

		offset += size;
	} while (offset < max_size);
	assert(offset == max_size);

	/* Fallback to ptrace if something went wrong.  */
fallback:
#endif /* HAVE_PROCESS_VM */

	nb_trailing_bytes = max_size % sizeof(word_t);
	nb_full_words     = (max_size - nb_trailing_bytes) / sizeof(word_t);

	/* Copy one word by one word, except for the last one. */
	for (i = 0; i < nb_full_words; i++) {
		word = ptrace(PTRACE_PEEKDATA, tracee->pid, src + i, NULL);
		if (errno != 0)
			return -EFAULT;

		store_word(&dest[i], word);

		/* Stop once an end-of-string is detected. */
		src_word = (uint8_t *)&word;
		for (j = 0; j < sizeof(word_t); j++)
			if (src_word[j] == '\0')
				return i * sizeof(word_t) + j + 1;
	}

	/* Copy the bytes from the last word carefully since we have
	 * to not overwrite the bytes lying beyond @dest_tracer. */

	word = ptrace(PTRACE_PEEKDATA, tracee->pid, src + i, NULL);
	if (errno != 0)
		return -EFAULT;

	dest_word = (uint8_t *)&dest[i];
	src_word  = (uint8_t *)&word;

	for (j = 0; j < nb_trailing_bytes; j++) {
		dest_word[j] = src_word[j];
		if (src_word[j] == '\0')
			break;
	}

	return i * sizeof(word_t) + j + 1;
}

/**
 * Return the value of the word at the given @address in the @tracee's
 * memory space.  The caller must test errno to check if an error
 * occured.
 */
word_t peek_word(const Tracee *tracee, word_t address)
{
	word_t result = 0;

	if (belongs_to_heap_prealloc(tracee, address)) {
		errno = EFAULT;
		return 0;
	}

#if defined(HAVE_PROCESS_VM)
	int status;
	struct iovec local;
	struct iovec remote;

	local.iov_base = &result;
	local.iov_len  = sizeof_word(tracee);

	remote.iov_base = (void *)address;
	remote.iov_len  = sizeof_word(tracee);

	errno = 0;
	status = process_vm_readv(tracee->pid, &local, 1, &remote, 1, 0);
	if (status > 0)
		return result;
	/* Fallback to ptrace if something went wrong.  */
#endif
	errno = 0;
	result = (word_t) ptrace(PTRACE_PEEKDATA, tracee->pid, address, NULL);

	/* From ptrace(2) manual: "Unfortunately, under Linux,
	 * different variations of this fault will return EIO or
	 * EFAULT more or less arbitrarily."  */
	if (errno == EIO)
		errno = EFAULT;

	/* Use only the 32 LSB when running a 32-bit process on a
	 * 64-bit kernel. */
	if (is_32on64_mode(tracee))
		result &= 0xFFFFFFFF;

	return result;
}

/**
 * Set the word at the given @address in the @tracee's memory space to
 * the given @value.  The caller must test errno to check if an error
 * occured.
 */
void poke_word(const Tracee *tracee, word_t address, word_t value)
{
	word_t tmp;

	if (belongs_to_heap_prealloc(tracee, address)) {
		errno = EFAULT;
		return;
	}

#if defined(HAVE_PROCESS_VM)
	int status;
	struct iovec local;
	struct iovec remote;

	/* Note: &value points to the 32 LSB on 64-bit little-endian
	 * architecture.  */
	local.iov_base = &value;
	local.iov_len  = sizeof_word(tracee);

	remote.iov_base = (void *)address;
	remote.iov_len  = sizeof_word(tracee);

	errno = 0;
	status = process_vm_writev(tracee->pid, &local, 1, &remote, 1, 0);
	if (status > 0)
		return;
	/* Fallback to ptrace if something went wrong.  */
#endif
	/* Don't overwrite the 32 MSB when running a 32-bit process on
	 * a 64-bit kernel. */
	if (is_32on64_mode(tracee)) {
		errno = 0;
		tmp = (word_t) ptrace(PTRACE_PEEKDATA, tracee->pid, address, NULL);
		if (errno != 0)
			return;

		value |= (tmp & 0xFFFFFFFF00000000ULL);
	}

	errno = 0;
	(void) ptrace(PTRACE_POKEDATA, tracee->pid, address, value);

	/* From ptrace(2) manual: "Unfortunately, under Linux,
	 * different variations of this fault will return EIO or
	 * EFAULT more or less arbitrarily."  */
	if (errno == EIO)
		errno = EFAULT;

	return;
}

/**
 * Allocate @size bytes in the @tracee's memory space.  This function
 * returns the address of the allocated memory in the @tracee's memory
 * space, otherwise 0 if an error occured.
 */
word_t alloc_mem(Tracee *tracee, ssize_t size)
{
	word_t stack_pointer;

	/* Get the current value of the stack pointer from the tracee's
	 * USER area. */
	stack_pointer = peek_reg(tracee, CURRENT, STACK_POINTER);

	/* Some ABIs specify an amount of bytes after the stack
	 * pointer that shall not be used by anything but the compiler
	 * (for optimization purpose).  */
	if (stack_pointer == peek_reg(tracee, ORIGINAL, STACK_POINTER))
		size += RED_ZONE_SIZE;

	/* Sanity check. */
	if (   (size > 0 && stack_pointer <= (word_t) size)
	    || (size < 0 && stack_pointer >= ULONG_MAX + size)) {
		notice(tracee, WARNING, INTERNAL, "integer under/overflow detected in %s",
			__FUNCTION__);
		return 0;
	}

	/* Remember the stack grows downward. */
	stack_pointer -= size;

	/* Set the new value of the stack pointer in the tracee's USER
	 * area. */
	poke_reg(tracee, STACK_POINTER, stack_pointer);
	return stack_pointer;
}

/**
 * Clear @size bytes at the given @address in the @tracee's memory
 * space.  This function returns -errno if an error occured, otherwise
 * 0.
 */
int clear_mem(const Tracee *tracee, word_t address, size_t size)
{
	int status;
	void *zeros;

	if (belongs_to_heap_prealloc(tracee, address))
		return -EFAULT;

	zeros = mmap(NULL, size, PROT_READ, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
	if (zeros == MAP_FAILED)
		return -errno;

	status = write_data(tracee, address, zeros, size);
	munmap(zeros, size);
	return status;
}
