/* SPDX-License-Identifier: LGPL-2.1-or-later */
/*
 * Copyright (C) 2020, Google Inc.
 *
 * File I/O operations
 */

#include <libcamera/base/file.h>

#include <errno.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>

#include <libcamera/base/log.h>
#include <libcamera/base/shared_fd.h>

/**
 * \file base/file.h
 * \brief File I/O operations
 */

namespace libcamera {

LOG_DEFINE_CATEGORY(File)

/**
 * \class File
 * \brief Interface for I/O operations on files
 *
 * The File class provides an interface to perform I/O operations on files. It
 * wraps opening, closing and mapping files in memory, and handles the cleaning
 * of allocated resources.
 *
 * File instances are usually constructed with a file name, but the name can be
 * set later through the setFileName() function. Instances are not automatically
 * opened when constructed, and shall be opened explictly with open().
 *
 * Files can be mapped to the process memory with map(). Mapped regions can be
 * unmapped manually with munmap(), and are automatically unmapped when the File
 * is destroyed or when it is used to reference another file with setFileName().
 */

/**
 * \enum File::MapFlag
 * \brief Flags for the File::map() function
 * \var File::MapFlag::NoOption
 * \brief No option (used as default value)
 * \var File::MapFlag::Private
 * \brief The memory region is mapped as private, changes are not reflected in
 * the file constents
 */

/**
 * \typedef File::MapFlags
 * \brief A bitwise combination of File::MapFlag values
 */

/**
 * \enum File::OpenModeFlag
 * \brief Mode in which a file is opened
 * \var File::OpenModeFlag::NotOpen
 * \brief The file is not open
 * \var File::OpenModeFlag::ReadOnly
 * \brief The file is open for reading
 * \var File::OpenModeFlag::WriteOnly
 * \brief The file is open for writing
 * \var File::OpenModeFlag::ReadWrite
 * \brief The file is open for reading and writing
 */

/**
 * \typedef File::OpenMode
 * \brief A bitwise combination of File::OpenModeFlag values
 */

/**
 * \brief Construct a File to represent the file \a name
 * \param[in] name The file name
 *
 * Upon construction the File object is closed and shall be opened with open()
 * before performing I/O operations.
 */
File::File(const std::string &name)
	: name_(name), mode_(OpenModeFlag::NotOpen), error_(0)
{
}

/**
 * \brief Construct a File without an associated name
 *
 * Before being used for any purpose, the file name shall be set with
 * setFileName().
 */
File::File()
	: mode_(OpenModeFlag::NotOpen), error_(0)
{
}

/**
 * \brief Destroy a File instance
 *
 * Any memory mapping associated with the File is unmapped, and the File is
 * closed if it is open.
 */
File::~File()
{
	unmapAll();
	close();
}

/**
 * \fn const std::string &File::fileName() const
 * \brief Retrieve the file name
 * \return The file name
 */

/**
 * \brief Set the name of the file
 * \param[in] name The name of the file
 *
 * The \a name can contain an absolute path, a relative path or no path at all.
 * Calling this function on an open file results in undefined behaviour.
 *
 * Any memory mapping associated with the File is unmapped.
 */
void File::setFileName(const std::string &name)
{
	if (isOpen()) {
		LOG(File, Error)
			<< "Can't set file name on already open file " << name_;
		return;
	}

	unmapAll();

	name_ = name;
}

/**
 * \brief Check if the file specified by fileName() exists
 *
 * This function checks if the file specified by fileName() exists. The File
 * instance doesn't need to be open to check for file existence, and this
 * function may return false even if the file is open, if it was deleted from
 * the file system.
 *
 * \return True if the the file exists, false otherwise
 */
bool File::exists() const
{
	return exists(name_);
}

/**
 * \brief Open the file in the given mode
 * \param[in] mode The open mode
 *
 * This function opens the file specified by fileName() in \a mode. If the file
 * doesn't exist and the mode is WriteOnly or ReadWrite, this function will
 * attempt to create the file with initial permissions set to 0666 (modified by
 * the process' umask).
 *
 * The file is opened with the O_CLOEXEC flag, and will be closed automatically
 * when a new binary is executed with one of the exec(3) functions.
 *
 * The error() status is updated.
 *
 * \return True on success, false otherwise
 */
bool File::open(File::OpenMode mode)
{
	if (isOpen()) {
		LOG(File, Error) << "File " << name_ << " is already open";
		return false;
	}

	int flags = static_cast<OpenMode::Type>(mode & OpenModeFlag::ReadWrite) - 1;
	if (mode & OpenModeFlag::WriteOnly)
		flags |= O_CREAT;

	fd_ = UniqueFD(::open(name_.c_str(), flags | O_CLOEXEC, 0666));
	if (!fd_.isValid()) {
		error_ = -errno;
		return false;
	}

	mode_ = mode;
	error_ = 0;
	return true;
}

/**
 * \fn bool File::isOpen() const
 * \brief Check if the file is open
 * \return True if the file is open, false otherwise
 */

/**
 * \fn OpenMode File::openMode() const
 * \brief Retrieve the file open mode
 * \return The file open mode
 */

/**
 * \brief Close the file
 *
 * This function closes the File. If the File is not open, it performs no
 * operation. Memory mappings created with map() are not destroyed when the
 * file is closed.
 */
void File::close()
{
	if (!fd_.isValid())
		return;

	fd_.reset();
	mode_ = OpenModeFlag::NotOpen;
}

/**
 * \fn int File::error() const
 * \brief Retrieve the file error status
 *
 * This function retrieves the error status from the last file open or I/O
 * operation. The error status is a negative number as defined by errno.h. If
 * no error occurred, this function returns 0.
 *
 * \return The file error status
 */

/**
 * \brief Retrieve the file size
 *
 * This function retrieves the size of the file on the filesystem. The File
 * instance shall be open to retrieve its size. The error() status is not
 * modified, error codes are returned directly on failure.
 *
 * \return The file size in bytes on success, or a negative error code otherwise
 */
ssize_t File::size() const
{
	if (!isOpen())
		return -EINVAL;

	struct stat st;
	int ret = fstat(fd_.get(), &st);
	if (ret < 0)
		return -errno;

	return st.st_size;
}

/**
 * \brief Return current read or write position
 *
 * If the file is closed, this function returns 0.
 *
 * \return The current read or write position
 */
off_t File::pos() const
{
	if (!isOpen())
		return 0;

	return lseek(fd_.get(), 0, SEEK_CUR);
}

/**
 * \brief Set the read or write position
 * \param[in] pos The desired position
 * \return The resulting offset from the beginning of the file on success, or a
 * negative error code otherwise
 */
off_t File::seek(off_t pos)
{
	if (!isOpen())
		return -EINVAL;

	off_t ret = lseek(fd_.get(), pos, SEEK_SET);
	if (ret < 0)
		return -errno;

	return ret;
}

/**
 * \brief Read data from the file
 * \param[in] data Memory to read data into
 *
 * Read at most \a data.size() bytes from the file into \a data.data(), and
 * return the number of bytes read. If less data than requested is available,
 * the returned byte count may be smaller than the requested size. If no more
 * data is available, 0 is returned.
 *
 * The position of the file as returned by pos() is advanced by the number of
 * bytes read. If an error occurs, the position isn't modified.
 *
 * \return The number of bytes read on success, or a negative error code
 * otherwise
 */
ssize_t File::read(const Span<uint8_t> &data)
{
	if (!isOpen())
		return -EINVAL;

	size_t readBytes = 0;
	ssize_t ret = 0;

	/* Retry in case of interrupted system calls. */
	while (readBytes < data.size()) {
		ret = ::read(fd_.get(), data.data() + readBytes,
			     data.size() - readBytes);
		if (ret <= 0)
			break;

		readBytes += ret;
	}

	if (ret < 0 && !readBytes)
		return -errno;

	return readBytes;
}

/**
 * \brief Write data to the file
 * \param[in] data Memory containing data to be written
 *
 * Write at most \a data.size() bytes from \a data.data() to the file, and
 * return the number of bytes written. If the file system doesn't have enough
 * space for the data, the returned byte count may be less than requested.
 *
 * The position of the file as returned by pos() is advanced by the number of
 * bytes written. If an error occurs, the position isn't modified.
 *
 * \return The number of bytes written on success, or a negative error code
 * otherwise
 */
ssize_t File::write(const Span<const uint8_t> &data)
{
	if (!isOpen())
		return -EINVAL;

	size_t writtenBytes = 0;

	/* Retry in case of interrupted system calls. */
	while (writtenBytes < data.size()) {
		ssize_t ret = ::write(fd_.get(), data.data() + writtenBytes,
				      data.size() - writtenBytes);
		if (ret <= 0)
			break;

		writtenBytes += ret;
	}

	if (data.size() && !writtenBytes)
		return -errno;

	return writtenBytes;
}

/**
 * \brief Map a region of the file in the process memory
 * \param[in] offset The region offset within the file
 * \param[in] size The region sise
 * \param[in] flags The mapping flags
 *
 * This function maps a region of \a size bytes of the file starting at \a
 * offset into the process memory. The File instance shall be open, but may be
 * closed after mapping the region. Mappings stay valid when the File is
 * closed, and are destroyed automatically when the File is deleted.
 *
 * If \a size is a negative value, this function maps the region starting at \a
 * offset until the end of the file.
 *
 * The mapping memory protection is controlled by the file open mode, unless \a
 * flags contains MapFlag::Private in which case the region is mapped in
 * read/write mode.
 *
 * The error() status is updated.
 *
 * \return The mapped memory on success, or an empty span otherwise
 */
Span<uint8_t> File::map(off_t offset, ssize_t size, File::MapFlags flags)
{
	if (!isOpen()) {
		error_ = -EBADF;
		return {};
	}

	if (size < 0) {
		size = File::size();
		if (size < 0) {
			error_ = size;
			return {};
		}

		size -= offset;
	}

	int mmapFlags = flags & MapFlag::Private ? MAP_PRIVATE : MAP_SHARED;

	int prot = 0;
	if (mode_ & OpenModeFlag::ReadOnly)
		prot |= PROT_READ;
	if (mode_ & OpenModeFlag::WriteOnly)
		prot |= PROT_WRITE;
	if (flags & MapFlag::Private)
		prot |= PROT_WRITE;

	void *map = mmap(NULL, size, prot, mmapFlags, fd_.get(), offset);
	if (map == MAP_FAILED) {
		error_ = -errno;
		return {};
	}

	maps_.emplace(map, size);

	error_ = 0;
	return { static_cast<uint8_t *>(map), static_cast<size_t>(size) };
}

/**
 * \brief Unmap a region mapped with map()
 * \param[in] addr The region address
 *
 * The error() status is updated.
 *
 * \return True on success, or false if an error occurs
 */
bool File::unmap(uint8_t *addr)
{
	auto iter = maps_.find(static_cast<void *>(addr));
	if (iter == maps_.end()) {
		error_ = -ENOENT;
		return false;
	}

	int ret = munmap(addr, iter->second);
	if (ret < 0) {
		error_ = -errno;
		return false;
	}

	maps_.erase(iter);

	error_ = 0;
	return true;
}

void File::unmapAll()
{
	for (const auto &map : maps_)
		munmap(map.first, map.second);

	maps_.clear();
}

/**
 * \brief Check if the file specified by \a name exists
 * \param[in] name The file name
 * \return True if the file exists, false otherwise
 */
bool File::exists(const std::string &name)
{
	struct stat st;
	int ret = stat(name.c_str(), &st);
	if (ret < 0)
		return false;

	/* Directories can not be handled here, even if they exist. */
	return !S_ISDIR(st.st_mode);
}

} /* namespace libcamera */
