/* SPDX-License-Identifier: LGPL-2.1+ */

#include <unistd.h>

#include "cryptsetup-keyfile.h"
#include "fd-util.h"
#include "format-util.h"
#include "memory-util.h"
#include "path-util.h"
#include "stat-util.h"
#include "strv.h"

#define KEY_FILE_SIZE_MAX (16U*1024U*1024U) /* 16 MiB */

int load_key_file(
                const char *key_file,
                char **search_path,
                size_t key_file_size,
                uint64_t key_file_offset,
                void **ret_key,
                size_t *ret_key_size) {

        _cleanup_(erase_and_freep) char *buffer = NULL;
        _cleanup_free_ char *discovered_path = NULL;
        _cleanup_close_ int fd = -1;
        ssize_t n;
        int r;

        assert(key_file);
        assert(ret_key);
        assert(ret_key_size);

        if (strv_isempty(search_path) || path_is_absolute(key_file)) {
                fd = open(key_file, O_RDONLY|O_CLOEXEC);
                if (fd < 0)
                        return log_error_errno(errno, "Failed to load key file '%s': %m", key_file);
        } else {
                char **i;

                STRV_FOREACH(i, search_path) {
                        _cleanup_free_ char *joined;

                        joined = path_join(*i, key_file);
                        if (!joined)
                                return log_oom();

                        fd = open(joined, O_RDONLY|O_CLOEXEC);
                        if (fd >= 0) {
                                discovered_path = TAKE_PTR(joined);
                                break;
                        }
                        if (errno != ENOENT)
                                return log_error_errno(errno, "Failed to load key file '%s': %m", joined);
                }

                if (!discovered_path) {
                        /* Search path supplied, but file not found, report by returning NULL, but not failing */
                        *ret_key = NULL;
                        *ret_key_size = 0;
                        return 0;
                }

                assert(fd >= 0);
                key_file = discovered_path;
        }

        if (key_file_size == 0) {
                struct stat st;

                if (fstat(fd, &st) < 0)
                        return log_error_errno(errno, "Failed to stat key file '%s': %m", key_file);

                r = stat_verify_regular(&st);
                if (r < 0)
                        return log_error_errno(r, "Key file is not a regular file: %m");

                if (st.st_size == 0)
                        return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Key file is empty, refusing.");
                if ((uint64_t) st.st_size > KEY_FILE_SIZE_MAX) {
                        char buf1[FORMAT_BYTES_MAX], buf2[FORMAT_BYTES_MAX];
                        return log_error_errno(SYNTHETIC_ERRNO(ERANGE),
                                               "Key file larger (%s) than allowed maximum size (%s), refusing.",
                                               format_bytes(buf1, sizeof(buf1), st.st_size),
                                               format_bytes(buf2, sizeof(buf2), KEY_FILE_SIZE_MAX));
                }

                if (key_file_offset >= (uint64_t) st.st_size)
                        return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Key file offset too large for file, refusing.");

                key_file_size = st.st_size - key_file_offset;
        }

        buffer = malloc(key_file_size);
        if (!buffer)
                return log_oom();

        if (key_file_offset > 0)
                n = pread(fd, buffer, key_file_size, key_file_offset);
        else
                n = read(fd, buffer, key_file_size);
        if (n < 0)
                return log_error_errno(errno, "Failed to read key file '%s': %m", key_file);
        if (n == 0)
                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Empty encrypted key found, refusing.");

        *ret_key = TAKE_PTR(buffer);
        *ret_key_size = (size_t) n;

        return 1;
}
