/*
 * Copyright (C) 2012-2014 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include <limits.h>
#include <sys/cdefs.h>
#include <sys/prctl.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <sys/un.h>
#include <unistd.h>

#include <mutex>
#include <thread>

#include <android/os/logcat/ILogcatManagerService.h>
#include <binder/IServiceManager.h>
#include <cutils/sockets.h>
#include <private/android_filesystem_config.h>
#include <private/android_logger.h>

#include <IOUringSocketHandler/IOUringSocketHandler.h>
#include <android-base/logging.h>
#include <android_logd_flags.h>

#include "LogBuffer.h"
#include "LogListener.h"
#include "LogPermissions.h"

static bool uring_enabled_ = false;

LogListener::LogListener(LogBuffer* buf) : socket_(GetLogSocket()), logbuf_(buf) {}

bool LogListener::StartListener() {
    if (socket_ <= 0) {
        return false;
    }
    auto thread = std::thread(&LogListener::ThreadFunction, this);
    thread.detach();
    return true;
}

bool LogListener::InitializeUring() {
    if (!IOUringSocketHandler::IsIouringSupported()) {
        return false;
    }

    const int numBuffers = 32;

    auto temp_listener = std::make_unique<IOUringSocketHandler>(socket_);
    if (!temp_listener->SetupIoUring(numBuffers)) {
        return false;
    }

    if (!temp_listener->AllocateAndRegisterBuffers(
                numBuffers, sizeof(android_log_header_t) + LOGGER_ENTRY_MAX_PAYLOAD + 1)) {
        return false;
    }

    if (!temp_listener->EnqueueMultishotRecvmsg()) {
        return false;
    }

    uring_listener_ = std::move(temp_listener);
    return true;
}

void LogListener::ThreadFunction() {
    prctl(PR_SET_NAME, "logd.writer");

    uring_enabled_ = android::logd::flags::enable_iouring() && InitializeUring();

    while (true) {
        if (uring_enabled_) {
            HandleDataUring();
        } else {
            HandleDataSync();
        }
    }
}

std::mutex logcatManagerCheckLock;
android::sp<android::os::logcat::ILogcatManagerService> logcatManager;

void OnNotableMessage(const int type, const uid_t uid, const pid_t pid, const char* msg, const size_t msg_len) {
    using namespace android;
    using android::os::logcat::ILogcatManagerService;

    for (int i = 0; i < 2; ++i) {
        sp<ILogcatManagerService> lm = nullptr;
        {
            std::lock_guard<std::mutex> guard(logcatManagerCheckLock);
            if (logcatManager == nullptr) {
                logcatManager = interface_cast<ILogcatManagerService>(
                        defaultServiceManager()->checkService(String16("logcat")));

                if (logcatManager == nullptr) {
                    // system_server hasn't started yet
                    return;
                }
            }
        }

        static_assert(sizeof(char) == sizeof(uint8_t));
        auto msg_u8 = reinterpret_cast<const uint8_t*>(msg);
        std::vector<uint8_t> msgVec(msg_u8, msg_u8 + msg_len);

        binder::Status status = logcatManager->onNotableMessage(type, uid, pid, msgVec);

        if (status.isOk()) {
            return;
        }

        {
            std::lock_guard<std::mutex> guard(logcatManagerCheckLock);
            // happens after system_server restart, which makes logcatManager reference stale
            logcatManager = nullptr;
        }
    }
}

void LogListener::HandleDataUring() {
    void* payload = nullptr;
    size_t payload_len = 0;
    struct ucred* cred = nullptr;

    uring_listener_->ReceiveData(&payload, payload_len, &cred);
    if ((payload != nullptr) && (payload_len > (ssize_t)(sizeof(android_log_header_t)))) {
        ProcessBuffer(cred, payload, payload_len);
    }
    uring_listener_->ReleaseBuffer();
}

void LogListener::HandleDataSync() {
    // + 1 to ensure null terminator if MAX_PAYLOAD buffer is received
    __attribute__((uninitialized)) char
            buffer[sizeof(android_log_header_t) + LOGGER_ENTRY_MAX_PAYLOAD + 1];
    struct iovec iov = {buffer, sizeof(buffer) - 1};

    alignas(4) char control[CMSG_SPACE(sizeof(struct ucred))];
    struct msghdr hdr = {
        nullptr, 0, &iov, 1, control, sizeof(control), 0,
    };

    ssize_t n = recvmsg(socket_, &hdr, 0);
    if (n <= (ssize_t)(sizeof(android_log_header_t))) {
        return;
    }

    // To clear the entire buffer would be safe, but this contributes to 1.68%
    // overhead under logging load. We are safe because we check counts, but
    // still need to clear null terminator
    buffer[n] = 0;

    struct ucred* cred = nullptr;

    struct cmsghdr* cmsg = CMSG_FIRSTHDR(&hdr);
    while (cmsg != nullptr) {
        if (cmsg->cmsg_level == SOL_SOCKET &&
            cmsg->cmsg_type == SCM_CREDENTIALS) {
            cred = (struct ucred*)CMSG_DATA(cmsg);
            break;
        }
        cmsg = CMSG_NXTHDR(&hdr, cmsg);
    }

    ProcessBuffer(cred, buffer, n);
}

void LogListener::ProcessBuffer(struct ucred* cred, void* buffer, ssize_t n) {
    if (cred == nullptr) {
        return;
    }

    if (cred->uid == AID_LOGD) {
        // ignore log messages we send to ourself.
        // Such log messages are often generated by libraries we depend on
        // which use standard Android logging.
        return;
    }

    android_log_header_t* header =
        reinterpret_cast<android_log_header_t*>(buffer);
    log_id_t logId = static_cast<log_id_t>(header->id);
    if (!__android_log_id_is_valid(logId) || logId == LOG_ID_KERNEL) {
        return;
    }

    if (logId == LOG_ID_SECURITY) {
        if (!__android_log_security()) {
            return;
        }
        if (!clientCanWriteSecurityLog(cred->uid, cred->gid, cred->pid)) {
            return;
        }
    }

    char* msg = ((char*)buffer) + sizeof(android_log_header_t);
    n -= sizeof(android_log_header_t);

    // NB: hdr.msg_flags & MSG_TRUNC is not tested, silently passing a
    // truncated message to the logs.

    logbuf_->Log(logId, header->realtime, cred->uid, cred->pid, header->tid, msg,
                 ((size_t)n <= UINT16_MAX) ? (uint16_t)n : UINT16_MAX);
}

int LogListener::GetLogSocket() {
    static const char socketName[] = "logdw";
    int sock = android_get_control_socket(socketName);

    if (sock < 0) {  // logd started up in init.sh
        sock = socket_local_server(
            socketName, ANDROID_SOCKET_NAMESPACE_RESERVED, SOCK_DGRAM);

        int on = 1;
        if (setsockopt(sock, SOL_SOCKET, SO_PASSCRED, &on, sizeof(on))) {
            return -1;
        }
    }
    return sock;
}
