/*
   Copyright (C) 2006-2016,2026 Con Kolivas
   Copyright (C) 2011 Peter Hyman
   Copyright (C) 1998 Andrew Tridgell

   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, see <http://www.gnu.org/licenses/>.
*/
#ifndef LRZIP_UTIL_H
#define LRZIP_UTIL_H

#include "lrzip_private.h"
#include <errno.h>
#include <limits.h>
#include <stdint.h>
#include <semaphore.h>
#include <stdarg.h>
#include <unistd.h>
#include <fcntl.h>

void register_infile(rzip_control *control, const char *name, char delete);
void register_outfile(rzip_control *control, const char *name, char delete);
void unlink_files(rzip_control *control);
void register_outputfile(rzip_control *control, FILE *f);

/* Process exit statuses — all fatal failures go through fatal_exit(). */
#define LRZIP_EXIT_SUCCESS	0	/* completed successfully / help / version */
#define LRZIP_EXIT_FAILURE	1	/* runtime, I/O, or archive error */
#define LRZIP_EXIT_USAGE	2	/* invalid command-line options */

void fatal_exit(rzip_control *control);
/* Failure when there is likely to be a meaningful error in perror */
static inline void __attribute__((format(printf, 5, 6))) fatal(const rzip_control *control, unsigned int line, const char *file, const char *func, const char *format, ...)
{
	va_list ap;

	va_start(ap, format);
	if (!control->log_cb) {
		vfprintf(stderr, format, ap);
		perror(NULL);
	} else
		control->log_cb(control->log_data, 0, line, file, func, format, ap);
	va_end(ap);
	fatal_exit((rzip_control*)control);
}
#ifdef fatal
# undef fatal
#endif
#define fatal(...) fatal(control, __LINE__, __FILE__, __func__, __VA_ARGS__)
#define fatal_return(stuff, ...) do { \
	fatal stuff; \
	return __VA_ARGS__; \
} while (0)
#define fatal_goto(stuff, label) do { \
	fatal stuff; \
	goto label; \
} while (0)
static inline void __attribute__((format(printf, 5, 6))) failure(const rzip_control *control, unsigned int line, const char *file, const char *func, const char *format, ...)
{
	va_list ap;

	va_start(ap, format);
	if (!control->log_cb)
		vfprintf(stderr, format, ap);
	else
		control->log_cb(control->log_data, 0, line, file, func, format, ap);
	va_end(ap);
	fatal_exit((rzip_control*)control);
}
#ifdef failure
# undef failure
#endif
#define failure(...) failure(control, __LINE__, __FILE__, __func__, __VA_ARGS__)
#define failure_return(stuff, ...) do { \
	failure stuff; \
	return __VA_ARGS__; \
} while (0)
#define failure_goto(stuff, label) do { \
	failure stuff; \
	goto label; \
} while (0)
void setup_overhead(rzip_control *control);
void setup_ram(rzip_control *control);
void round_to_page(i64 *size);
size_t round_up_page(rzip_control *control, size_t len);
bool get_rand(rzip_control *control, uchar *buf, int len);

/* Safe allocation/copy length for untrusted sizes (malicious archives).
 * maxram <= 0 means only non-negative and size_t-fit are checked. */
static inline bool lrzip_size_ok(i64 n, i64 maxram)
{
	if (n < 1)
		return false;
	if ((uint64_t)n > (uint64_t)SIZE_MAX)
		return false;
	if (maxram > 0 && n > maxram)
		return false;
	return true;
}

/* rzip match/literal token lengths are always emitted ≤ 0xFFFF. */
#define LRZIP_MAX_TOKEN_LEN	0xFFFF
bool read_config(rzip_control *control);
void lrz_stretch(rzip_control *control);
void lrz_stretch2(rzip_control *control);
bool lrz_crypt(const rzip_control *control, uchar *buf, i64 len, const uchar *salt, int encrypt);

/* Secure wipe */
void lrz_secure_wipe(void *p, size_t n);

/* Suite-3: derive aead_key_hdr / aead_key_data from password + CryptoDesc salt */
bool lrz_aead_kdf_setup(rzip_control *control);

/* AEAD seal/open: key_id 0 = header key, 1 = data key.
 * On seal: writes nonce||ct||tag into out (out_len must be >= 12+pt_len+16).
 * On open: in is nonce||ct||tag; pt_out length is in_len - 28. */
#define LRZ_AEAD_KEY_HDR	0
#define LRZ_AEAD_KEY_DATA	1
bool lrz_aead_seal(rzip_control *control, int key_id,
		   const uchar *aad, size_t aad_len,
		   const uchar *pt, size_t pt_len,
		   uchar *out, size_t *out_len);
bool lrz_aead_open(rzip_control *control, int key_id,
		   const uchar *aad, size_t aad_len,
		   const uchar *in, size_t in_len,
		   uchar *pt_out, size_t *pt_len);

/* On-disk overhead helpers for encrypt modes */
static inline i64 lrz_enc_prefix_len(const rzip_control *control)
{
	if (!ENCRYPT)
		return 0;
	if (ENCRYPT_AEAD)
		return LRZ_AEAD_NONCE_LEN;
	return SALT_LEN;
}

static inline i64 lrz_enc_suffix_len(const rzip_control *control)
{
	if (!ENCRYPT)
		return 0;
	if (ENCRYPT_AEAD)
		return LRZ_AEAD_TAG_LEN;
	return 0;
}

/* Full on-disk size for an encrypted stream header record (25-byte clear). */
static inline i64 lrz_enc_header_disk_len(const rzip_control *control)
{
	if (!ENCRYPT)
		return 25;
	return lrz_enc_prefix_len(control) + 25 + lrz_enc_suffix_len(control);
}

#define LRZ_DECRYPT	(0)
#define LRZ_ENCRYPT	(1)

static inline bool lrz_encrypt(const rzip_control *control, uchar *buf, i64 len, const uchar *salt)
{
	return lrz_crypt(control, buf, len, salt, LRZ_ENCRYPT);
}

static inline bool lrz_decrypt(const rzip_control *control, uchar *buf, i64 len, const uchar *salt)
{
	return lrz_crypt(control, buf, len, salt, LRZ_DECRYPT);
}

/* ck specific wrappers for true unnamed semaphore usage on platforms
 * that support them and for apple which does not. We use a single byte across
 * a pipe to emulate semaphore behaviour there. */
#ifdef __APPLE__
static inline void cksem_init(const rzip_control *control, cksem_t *cksem)
{
	int flags, fd, i;

	if (pipe(cksem->pipefd) == -1)
		fatal("Failed pipe errno=%d", errno);

	/* Make the pipes FD_CLOEXEC to allow them to close should we call
	 * execv on restart. */
	for (i = 0; i < 2; i++) {
		fd = cksem->pipefd[i];
		flags = fcntl(fd, F_GETFD, 0);
		flags |= FD_CLOEXEC;
		if (fcntl(fd, F_SETFD, flags) == -1)
			fatal("Failed to fcntl errno=%d", errno);
	}
}

static inline void cksem_post(const rzip_control *control, cksem_t *cksem)
{
	const char buf = 1;
	int ret;

	ret = write(cksem->pipefd[1], &buf, 1);
	if (unlikely(ret == 0))
		fatal("Failed to write in cksem_post errno=%d", errno);
}

static inline void cksem_wait(const rzip_control *control, cksem_t *cksem)
{
	char buf;
	int ret;

	ret = read(cksem->pipefd[0], &buf, 1);
	if (unlikely(ret == 0))
		fatal("Failed to read in cksem_post errno=%d", errno);
}
#else
static inline void cksem_init(const rzip_control *control, cksem_t *cksem)
{
	int ret;
	if ((ret = sem_init(cksem, 0, 0)))
		fatal("Failed to sem_init ret=%d errno=%d", ret, errno);
}

static inline void cksem_post(const rzip_control *control, cksem_t *cksem)
{
	if (unlikely(sem_post(cksem)))
		fatal("Failed to sem_post errno=%d cksem=0x%p", errno, cksem);
}

static inline void cksem_wait(const rzip_control *control, cksem_t *cksem)
{
	if (unlikely(sem_wait(cksem)))
		fatal("Failed to sem_wait errno=%d cksem=0x%p", errno, cksem);
}
#endif

#endif
