// Copyright (c) Meta Platforms, Inc. and affiliates.
// SPDX-License-Identifier: LGPL-2.1-or-later

/**
 * @file
 *
 * Memory reading interface.
 *
 * See @ref MemoryReader.
 */

#ifndef DRGN_MEMORY_READER_H
#define DRGN_MEMORY_READER_H

#include "binary_search_tree.h"
#include "drgn_internal.h"

/**
 * @ingroup Internals
 *
 * @defgroup MemoryReader Memory reader
 *
 * Memory reading interface.
 *
 * @ref drgn_memory_reader provides a common interface for registering regions
 * of memory in a program and reading from memory.
 *
 * @ref drgn_memory_reader does not have a notion of the maximum address or
 * address overflow/wrap-around. Those must be handled at a higher layer.
 *
 * @{
 */

DEFINE_BINARY_SEARCH_TREE_TYPE(drgn_memory_segment_tree,
			       struct drgn_memory_segment);

/**
 * Memory reader.
 *
 * A memory reader maps the segments of memory in an address space to callbacks
 * which can be used to read memory from those segments.
 */
struct drgn_memory_reader {
	/** Virtual memory segments. */
	struct drgn_memory_segment_tree virtual_segments;
	/** Physical memory segments. */
	struct drgn_memory_segment_tree physical_segments;
};

/**
 * Initialize a @ref drgn_memory_reader.
 *
 * The reader is initialized with no segments.
 */
void drgn_memory_reader_init(struct drgn_memory_reader *reader);

/** Deinitialize a @ref drgn_memory_reader. */
void drgn_memory_reader_deinit(struct drgn_memory_reader *reader);

/** Return whether a @ref drgn_memory_reader has no segments. */
bool drgn_memory_reader_empty(struct drgn_memory_reader *reader);

/**
 * Add a segment to a @ref drgn_memory_reader.
 *
 * @param[in] reader Memory reader.
 * @param[in] min_address Start address (inclusive).
 * @param[in] max_address End address (inclusive). Must be `>= min_address`.
 * @param[in] read_fn Callback to read from segment.
 * @param[in] arg Argument to pass to @p read_fn.
 * @param[in] physical Whether to add a physical memory segment.
 * @return @c NULL on success, non-@c NULL on error.
 */
struct drgn_error *
drgn_memory_reader_add_segment(struct drgn_memory_reader *reader,
			       uint64_t min_address, uint64_t max_address,
			       drgn_memory_read_fn read_fn, void *arg,
			       bool physical);

/**
 * Read from a @ref drgn_memory_reader.
 *
 * @param[in] reader Memory reader.
 * @param[out] buf Buffer to read into.
 * @param[in] address Starting address in memory to read.
 * @param[in] count Number of bytes to read. `address + count - 1` must be
 * `<= UINT64_MAX`
 * @param[in] physical Whether @c address is physical.
 * @return @c NULL on success, non-@c NULL on error.
 */
struct drgn_error *drgn_memory_reader_read(struct drgn_memory_reader *reader,
					   void *buf, uint64_t address,
					   size_t count, bool physical);

/** Argument for @ref drgn_read_memory_file(). */
struct drgn_memory_file_segment {
	/** Offset in the file where the segment starts. */
	uint64_t file_offset;
	/**
	 * Size of the segment in the file. This may be less than the size of
	 * the segment in memory.
	 */
	uint64_t file_size;
	/** File descriptor. */
	int fd;
	/**
	 * If @c true, EIO is treated as a fault. Otherwise, it is treated as an
	 * OS error.
	 */
	bool eio_is_fault;
	/**
	 * If @c true, reads between @ref file_size and the size of the segment
	 * in memory will be returned as zeroes. Otherwise, such reads will
	 * result in a fault.
	 */
	bool zerofill;
};

/** @ref drgn_memory_read_fn which reads from a file. */
struct drgn_error *drgn_read_memory_file(void *buf, uint64_t address,
					 size_t count, uint64_t offset,
					 void *arg, bool physical);

/** @} */

#endif /* DRGN_MEMORY_READER_H */
