/* ukibootctl
   Copyright (C) 2025 Alexander Larsson <alexl@redhat.com>

   This file is free software: you can redistribute it and/or modify
   it under the terms of the GNU Lesser General Public License as
   published by the Free Software Foundation; either version 2.1 of the
   License, or (at your option) any later version.

   This file 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 Lesser General Public License for more details.

   You should have received a copy of the GNU Lesser General Public License
   along with this program.  If not, see <https://www.gnu.org/licenses/>.
*/

#include "config.h"

#include <assert.h>
#include <errno.h>
#include <stdarg.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#ifdef HAVE_MACHINE_ENDIAN_H
#include <machine/endian.h>
#endif

#ifdef HAVE_SYS_ENDIAN_H
#include <sys/endian.h>
#endif

#ifdef HAVE_ENDIAN_H
#include <endian.h>
#endif

extern bool opt_verbose;

#define N_ELEMENTS(arr) (sizeof(arr) / sizeof((arr)[0]))

static inline void
_lcfs_reset_errno_(int *saved_errno)
{
    if (*saved_errno < 0)
	return;
    errno = *saved_errno;
}

// This helper was taken from systemd; it ensures that the value of errno
// is reset.
#define PROTECT_ERRNO                                                                              \
    __attribute__((cleanup(_lcfs_reset_errno_))) __attribute__((unused)) int _saved_errno_ = errno

static inline void
cleanup_freep(void *p)
{
    void **pp = (void **)p;

    if (*pp)
	free(*pp);
}

// A wrapper around close() that takes a pointer to a file descriptor (integer):
// - Never returns an error (and preserves errno)
// - Sets the value to -1 after closing to make cleanup idempotent
static inline void
cleanup_fdp(int *fdp)
{
    PROTECT_ERRNO;
    int fd;

    assert(fdp);

    fd = *fdp;
    if (fd != -1)
	(void)close(fd);
    *fdp = -1;
}

#define arg_unused  __attribute__((unused))
#define attr_packed __attribute__((packed))

#define autofree  __attribute__((cleanup(cleanup_freep)))
#define autoclose __attribute__((cleanup(cleanup_fdp)))

__attribute__((__format__(printf, 1, 2))) extern void
verbose(const char *format, ...);
__attribute__((__format__(printf, 1, 2))) extern void
error(const char *format, ...);
__attribute__((__noreturn__)) __attribute__((__format__(printf, 1, 2))) extern void
fatal(const char *format, ...);
__attribute__((__noreturn__)) extern void
oom(void);

#define attr_malloc	     __attribute__((__malloc__))
#define attr_alloc_size(...) __attribute__((__alloc_size__(__VA_ARGS__)))

attr_malloc attr_alloc_size(1) static inline void *xmalloc(size_t size)
{
    void *p = malloc(size);
    if (!p)
	oom();
    return p;
}

attr_malloc attr_alloc_size(1, 2) static inline void *xcalloc(size_t nmemb, size_t size)
{
    void *p = calloc(nmemb, size);
    if (!p)
	oom();
    return p;
}

attr_alloc_size(2) static inline void *xrealloc(void *ptr, size_t size)
{
    void *p = realloc(ptr, size);
    if (!p)
	oom();
    return p;
}

static inline bool
full_pread(int fd, void *buf, size_t count, off_t offset)
{
    uint8_t *p = buf;
    while (count > 0) {
	ssize_t n = pread(fd, p, count, offset);
	if (n < 0) {
	    error("pread failed: %s\n", strerror(errno));
	    return false;
	}
	if (n == 0) {
	    error("Unexpected end of file\n");
	    return false;
	}
	p += n;
	count -= n;
	offset += n;
    }
    return true;
}

static inline bool
full_write(int fd, const void *buf, size_t count)
{
    const uint8_t *p = buf;
    while (count > 0) {
	ssize_t n = write(fd, p, count);
	if (n < 0) {
	    error("write failed: %s\n", strerror(errno));
	    return false;
	}
	p += n;
	count -= n;
    }
    return true;
}

static inline void *
steal_pointer(void *pp)
{
    void **ptr = (void **)pp;
    void *ref;

    ref = *ptr;
    *ptr = NULL;

    return ref;
}

/* type safety */
#define steal_pointer(pp) (0 ? (*(pp)) : (steal_pointer)(pp))

static inline const char *
gnu_basename(const char *filename)
{
    const char *p = strrchr(filename, '/');
    return p ? p + 1 : filename;
}

extern uint32_t
crc32(const uint8_t *buffer, size_t len);
extern char *
read_proc_cmdline(void);
extern char *
find_proc_cmdline_key(const char *cmdline, const char *key);
