/*
 * SPDX-License-Identifier: Apache-2.0
 * Copyright (C) 2020 Raspberry Pi Ltd
 */

#include "macfile.h"
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <sys/socket.h>
#include <security/Authorization.h>
#include <QDebug>
#include <QCoreApplication>

MacFile::MacFile(QObject *parent)
    : QFile(parent)
{

}

/* Prevent that Qt thinks /dev/rdisk does not permit seeks because it does not report size */
bool MacFile::isSequential() const
{
    return false;
}

MacFile::authOpenResult MacFile::authOpen(const QByteArray &filename)
{
    int fd = -1;

    QByteArray right = "sys.openfile.readwrite."+filename;
    AuthorizationItem item = {right, 0, nullptr, 0};
    AuthorizationRights rights = {1, &item};
    AuthorizationFlags flags = kAuthorizationFlagInteractionAllowed |
            kAuthorizationFlagExtendRights |
            kAuthorizationFlagPreAuthorize;
    
    // Create authorization environment with custom prompt
    // This provides better context in the authorization dialog
    QString promptText = QCoreApplication::translate("MacFile", "Raspberry Pi Imager needs to access the disk to write the image.");
    QByteArray promptBytes = promptText.toUtf8();
    const char *promptKey = "prompt";
    AuthorizationItem envItems[] = {
        { promptKey, (size_t)promptBytes.length(), (void*)promptBytes.constData(), 0 }
    };
    AuthorizationEnvironment env = { 1, envItems };
    
    AuthorizationRef authRef;
    if (AuthorizationCreate(&rights, &env, flags, &authRef) != 0)
        return authOpenCancelled;

    AuthorizationExternalForm externalForm;
    if (AuthorizationMakeExternalForm(authRef, &externalForm) != 0)
    {
        AuthorizationFree(authRef, 0);
        return authOpenError;
    }

    const char *cmd = "/usr/libexec/authopen";
    QByteArray mode = QByteArray::number(O_RDWR);
    int pipe[2];
    int stdinpipe[2];
    ::socketpair(AF_UNIX, SOCK_STREAM, 0, pipe);
    ::pipe(stdinpipe);
    pid_t pid = ::fork();
    if (pid == 0)
    {
        // child
        ::close(pipe[0]);
        ::close(stdinpipe[1]);
        ::dup2(pipe[1], STDOUT_FILENO);
        ::dup2(stdinpipe[0], STDIN_FILENO);
        ::execl(cmd, cmd, "-stdoutpipe", "-extauth", "-o", mode.data(), filename.data(), NULL);
        ::exit(-1);
    }
    else
    {
        ::close(pipe[1]);
        ::close(stdinpipe[0]);
        ::write(stdinpipe[1], externalForm.bytes, sizeof(externalForm.bytes));
        ::close(stdinpipe[1]);

        const size_t bufSize = CMSG_SPACE(sizeof(int));
        char buf[bufSize];
        struct iovec io_vec[1];
        io_vec[0].iov_base = buf;
        io_vec[0].iov_len = bufSize;
        const size_t cmsgSize = CMSG_SPACE(sizeof(int));
        char cmsg[cmsgSize];

        struct msghdr msg = {0};
        msg.msg_iov = io_vec;
        msg.msg_iovlen = 1;
        msg.msg_control = cmsg;
        msg.msg_controllen = cmsgSize;

        ssize_t size;
        do {
            size = recvmsg(pipe[0], &msg, 0);
        } while (size == -1 && errno == EINTR);

        qDebug() << "RECEIVED SIZE:" << size;

        if (size > 0) {
            struct cmsghdr *chdr = CMSG_FIRSTHDR(&msg);
            if (chdr && chdr->cmsg_type == SCM_RIGHTS) {
                qDebug() << "SCMRIGHTS";
                fd = *( (int*) (CMSG_DATA(chdr)) );
            }
            else
            {
                qDebug() << "NOT SCMRIGHTS";
            }
        }

        pid_t wpid;
        int status;

        do {
            wpid = ::waitpid(pid, &status, 0);
        } while (wpid == -1 && errno == EINTR);

        if (wpid == -1)
        {
            qDebug() << "waitpid() failed executing authopen";
            return authOpenError;
        }
        if (WEXITSTATUS(status))
        {
            qDebug() << "authopen returned failure code" << WEXITSTATUS(status);
            return authOpenError;
        }

        qDebug() << "fd received:" << fd;
    }
    AuthorizationFree(authRef, 0);

    return open(fd, QIODevice::ReadWrite | QIODevice::ExistingOnly | QIODevice::Unbuffered, QFileDevice::AutoCloseHandle) ? authOpenSuccess : authOpenError;
}

bool MacFile::forceSync()
{
    if (!isOpen()) {
        qDebug() << "Warning: Cannot sync closed file";
        return false;
    }
    
    // Flush Qt's internal buffers first
    if (!flush()) {
        qDebug() << "Warning: flush() failed during forceSync:" << errorString();
        return false;
    }
    
    // Force filesystem sync using fsync
    if (::fsync(handle()) != 0) {
        qDebug() << "Warning: fsync() failed during forceSync, errno:" << errno;
        return false;
    }
    
    return true;
}
