// SPDX-License-Identifier: GPL-3.0-or-later

#include "journal.h"

bool is_path_unix_socket(const char *path) {
    // Check if the path is valid
    if(!path || !*path)
        return false;

    struct stat statbuf;

    // Use stat to check if the file exists and is a socket
    if (stat(path, &statbuf) == -1)
        // The file does not exist or cannot be accessed
        return false;

    // Check if the file is a socket
    if (S_ISSOCK(statbuf.st_mode))
        return true;

    return false;
}

bool is_stderr_connected_to_journal(void) {
    const char *journal_stream = getenv("JOURNAL_STREAM");
    if (!journal_stream)
        return false; // JOURNAL_STREAM is not set

    struct stat stderr_stat;
    if (fstat(STDERR_FILENO, &stderr_stat) < 0)
        return false; // Error in getting stderr info

    // Parse device and inode from JOURNAL_STREAM
    char *endptr;
    long journal_dev = strtol(journal_stream, &endptr, 10);
    if (*endptr != ':')
        return false; // Format error in JOURNAL_STREAM

    long journal_ino = strtol(endptr + 1, NULL, 10);

    return (stderr_stat.st_dev == (dev_t)journal_dev) && (stderr_stat.st_ino == (ino_t)journal_ino);
}

int journal_direct_fd(const char *path) {
    if(!path || !*path)
        path = JOURNAL_DIRECT_SOCKET;

    if(!is_path_unix_socket(path))
        return -1;

    int fd = socket(AF_UNIX, SOCK_DGRAM| DEFAULT_SOCKET_FLAGS, 0);
    if (fd < 0) return -1;

    sock_setcloexec(fd);

    struct sockaddr_un addr;
    memset(&addr, 0, sizeof(struct sockaddr_un));
    addr.sun_family = AF_UNIX;
    strncpy(addr.sun_path, path, sizeof(addr.sun_path) - 1);

    // Connect the socket (optional, but can simplify send operations)
    if (connect(fd, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
        close(fd);
        return -1;
    }

    return fd;
}

static inline bool journal_send_with_memfd(int fd __maybe_unused, const char *msg __maybe_unused, size_t msg_len __maybe_unused) {
#if defined(__NR_memfd_create) && defined(MFD_ALLOW_SEALING) && defined(F_ADD_SEALS) && defined(F_SEAL_SHRINK) && defined(F_SEAL_GROW) && defined(F_SEAL_WRITE)
    // Create a memory file descriptor
    int memfd = (int)syscall(__NR_memfd_create, "journald", MFD_ALLOW_SEALING);
    if (memfd < 0) return false;

    // Write data to the memfd
    if (write(memfd, msg, msg_len) != (ssize_t)msg_len) {
        close(memfd);
        return false;
    }

    // Seal the memfd to make it immutable
    if (fcntl(memfd, F_ADD_SEALS, F_SEAL_SHRINK | F_SEAL_GROW | F_SEAL_WRITE) < 0) {
        close(memfd);
        return false;
    }

    struct iovec iov = {0};
    struct msghdr msghdr = {0};
    struct cmsghdr *cmsghdr;
    char cmsgbuf[CMSG_SPACE(sizeof(int))];

    msghdr.msg_iov = &iov;
    msghdr.msg_iovlen = 1;
    msghdr.msg_control = cmsgbuf;
    msghdr.msg_controllen = sizeof(cmsgbuf);

    cmsghdr = CMSG_FIRSTHDR(&msghdr);
    if(!cmsghdr) {
        close(memfd);
        return false;
    }

    cmsghdr->cmsg_level = SOL_SOCKET;
    cmsghdr->cmsg_type = SCM_RIGHTS;
    cmsghdr->cmsg_len = CMSG_LEN(sizeof(int));
    memcpy(CMSG_DATA(cmsghdr), &memfd, sizeof(int));

    ssize_t r = sendmsg(fd, &msghdr, 0);

    close(memfd);
    return r >= 0;
#else
    return false;
#endif
}

bool journal_direct_send(int fd, const char *msg, size_t msg_len) {
    // Send the datagram
    if (send(fd, msg, msg_len, 0) < 0) {
        if(errno != EMSGSIZE)
            return false;

        // datagram is too large, fallback to memfd
        if(!journal_send_with_memfd(fd, msg, msg_len))
            return false;
    }

    return true;
}

void journal_construct_path(char *dst, size_t dst_len, const char *host_prefix, const char *namespace_str) {
    if(!host_prefix)
        host_prefix = "";

    if(namespace_str)
        snprintfz(dst, dst_len, "%s/run/systemd/journal.%s/socket",
                  host_prefix, namespace_str);
    else
        snprintfz(dst, dst_len, "%s" JOURNAL_DIRECT_SOCKET,
                  host_prefix);
}
