/*
 * Copyright (C) 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 "LogStatistics.h"

#include <ctype.h>
#include <fcntl.h>
#include <inttypes.h>
#include <pwd.h>
#include <stdio.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>

#include <atomic>
#include <list>
#include <vector>

#include <android-base/logging.h>
#include <android-base/strings.h>
#include <private/android_logger.h>

#include "LogBufferElement.h"

static const uint64_t hourSec = 60 * 60;
static const uint64_t monthSec = 31 * 24 * hourSec;

std::atomic<size_t> LogStatistics::SizesTotal;

static std::string TagNameKey(const LogStatisticsElement& element) {
    if (IsBinary(element.log_id)) {
        uint32_t tag = element.tag;
        if (tag) {
            const char* cp = android::tagToName(tag);
            if (cp) {
                return std::string(cp);
            }
        }
        return android::base::StringPrintf("[%" PRIu32 "]", tag);
    }
    const char* msg = element.msg;
    ++msg;
    uint16_t len = element.msg_len;
    len = (len <= 1) ? 0 : strnlen(msg, len - 1);
    if (!len) {
        return "<NULL>";
    }
    return std::string(msg, len);
}

LogStatistics::LogStatistics(bool track_total_size,
                             std::optional<log_time> start_time)
    : track_total_size_(track_total_size) {
    log_time now(CLOCK_REALTIME);
    log_id_for_each(id) {
        mSizes[id] = 0;
        mElements[id] = 0;
        mSizesTotal[id] = 0;
        mElementsTotal[id] = 0;
        if (start_time) {
            mOldest[id] = *start_time;
            mNewest[id] = *start_time;
        } else {
            mOldest[id] = now;
            mNewest[id] = now;
        }
    }
}

namespace android {

size_t sizesTotal() {
    return LogStatistics::sizesTotal();
}

// caller must own and free character string
char* pidToName(pid_t pid) {
    char* retval = nullptr;
    if (pid == 0) {  // special case from auditd/klogd for kernel
        retval = strdup("logd");
    } else {
        char buffer[512];
        snprintf(buffer, sizeof(buffer), "/proc/%u/cmdline", pid);
        int fd = open(buffer, O_RDONLY | O_CLOEXEC);
        if (fd >= 0) {
            ssize_t ret = read(fd, buffer, sizeof(buffer));
            if (ret > 0) {
                buffer[sizeof(buffer) - 1] = '\0';
                // frameworks intermediate state
                if (fastcmp<strcmp>(buffer, "<pre-initialized>")) {
                    retval = strdup(buffer);
                }
            }
            close(fd);
        }
    }
    return retval;
}
}

void LogStatistics::AddTotal(log_id_t log_id, uint16_t size) {
    auto lock = std::lock_guard{lock_};

    mSizesTotal[log_id] += size;
    SizesTotal += size;
    ++mElementsTotal[log_id];
}

void LogStatistics::Add(LogStatisticsElement element) {
    auto lock = std::lock_guard{lock_};

    if (!track_total_size_) {
        element.total_len = element.msg_len;
    }

    log_id_t log_id = element.log_id;
    uint16_t size = element.total_len;
    mSizes[log_id] += size;
    ++mElements[log_id];

    mSizesTotal[log_id] += size;
    SizesTotal += size;
    ++mElementsTotal[log_id];

    log_time stamp(element.realtime);
    if (mNewest[log_id] < stamp) {
        // A major time update invalidates the statistics :-(
        log_time diff = stamp - mNewest[log_id];
        mNewest[log_id] = stamp;

        if (diff.tv_sec > hourSec) {
            // approximate Do-Your-Best fixup
            diff += mOldest[log_id];
            if ((diff > stamp) && ((diff - stamp).tv_sec < hourSec)) {
                diff = stamp;
            }
            if (diff <= stamp) {
                mOldest[log_id] = diff;
            }
        }
    }

    if (log_id == LOG_ID_KERNEL) {
        return;
    }

    uidTable[log_id].Add(element.uid, element);
    if (element.uid == AID_SYSTEM) {
        pidSystemTable[log_id].Add(element.pid, element);
    }
}

void LogStatistics::Subtract(LogStatisticsElement element) {
    auto lock = std::lock_guard{lock_};

    if (!track_total_size_) {
        element.total_len = element.msg_len;
    }

    log_id_t log_id = element.log_id;
    uint16_t size = element.total_len;
    mSizes[log_id] -= size;
    --mElements[log_id];

    if (mOldest[log_id] < element.realtime) {
        mOldest[log_id] = element.realtime;
    }

    if (log_id == LOG_ID_KERNEL) {
        return;
    }

    uidTable[log_id].Subtract(element.uid, element);
    if (element.uid == AID_SYSTEM) {
        pidSystemTable[log_id].Subtract(element.pid, element);
    }
}

// caller must own and free character string
const char* LogStatistics::UidToNameLocked(uid_t uid) const {
    // Local hard coded favourites
    if (uid == AID_LOGD) {
        return strdup("auditd");
    }

    // Android system
    if (uid < AID_APP) {
        // in bionic, thread safe as long as we copy the results
        struct passwd* pwd = getpwuid(uid);
        if (pwd) {
            return strdup(pwd->pw_name);
        }
    }

    // Parse /data/system/packages.list
    uid_t userId = uid % AID_USER_OFFSET;
    const char* name = android::uidToName(userId);
    if (!name && (userId > (AID_SHARED_GID_START - AID_APP))) {
        name = android::uidToName(userId - (AID_SHARED_GID_START - AID_APP));
    }
    if (name) {
        return name;
    }

    // Android application
    if (uid >= AID_APP) {
        struct passwd* pwd = getpwuid(uid);
        if (pwd) {
            return strdup(pwd->pw_name);
        }
    }

    // report uid -> pid(s) -> pidToName if unique
    for (pidTable_t::const_iterator it = pidTable.begin(); it != pidTable.end();
         ++it) {
        const PidEntry& entry = it->second;

        if (entry.uid() == uid) {
            const char* nameTmp = entry.name();

            if (nameTmp) {
                if (!name) {
                    name = strdup(nameTmp);
                } else if (fastcmp<strcmp>(name, nameTmp)) {
                    free(const_cast<char*>(name));
                    name = nullptr;
                    break;
                }
            }
        }
    }

    // No one
    return name;
}

// Prune at most 10% of the log entries or maxPrune, whichever is less.
bool LogStatistics::ShouldPrune(log_id id, unsigned long max_size,
                                unsigned long* prune_rows) const {
    static constexpr size_t kMinPrune = 4;
    static constexpr size_t kMaxPrune = 256;

    auto lock = std::lock_guard{lock_};
    size_t sizes = mSizes[id];
    if (sizes <= max_size) {
        return false;
    }
    size_t size_over = sizes - ((max_size * 9) / 10);
    size_t elements = mElements[id];
    size_t min_elements = elements / 100;
    if (min_elements < kMinPrune) {
        min_elements = kMinPrune;
    }
    *prune_rows = elements * size_over / sizes;
    if (*prune_rows < min_elements) {
        *prune_rows = min_elements;
    }
    if (*prune_rows > kMaxPrune) {
        *prune_rows = kMaxPrune;
    }

    return true;
}

std::string UidEntry::formatHeader(const std::string& name, log_id_t id) const {
    bool isprune = worstUidEnabledForLogid(id);
    return formatLine(android::base::StringPrintf(name.c_str(),
                                                  android_log_id_to_name(id)),
                      std::string("Size"),
                      std::string(isprune ? "+/-  Pruned" : "")) +
           formatLine(std::string("UID   PACKAGE"), std::string("BYTES"),
                      std::string(isprune ? "NUM" : ""));
}

// Helper to truncate name, if too long, and add name dressings
void LogStatistics::FormatTmp(const char* nameTmp, uid_t uid, std::string& name, std::string& size,
                              size_t nameLen) const {
    const char* allocNameTmp = nullptr;
    if (!nameTmp) nameTmp = allocNameTmp = UidToNameLocked(uid);
    if (nameTmp) {
        size_t lenSpace = std::max(nameLen - name.length(), (size_t)1);
        size_t len = EntryBase::TOTAL_LEN - EntryBase::PRUNED_LEN - size.length() - name.length() -
                     lenSpace - 2;
        size_t lenNameTmp = strlen(nameTmp);
        while ((len < lenNameTmp) && (lenSpace > 1)) {
            ++len;
            --lenSpace;
        }
        name += android::base::StringPrintf("%*s", (int)lenSpace, "");
        if (len < lenNameTmp) {
            name += "...";
            nameTmp += lenNameTmp - std::max(len - 3, (size_t)1);
        }
        name += nameTmp;
        free(const_cast<char*>(allocNameTmp));
    }
}

std::string UidEntry::format(const LogStatistics& stat, log_id_t id, uid_t uid) const
        REQUIRES(stat.lock_) {
    std::string name = android::base::StringPrintf("%u", uid);
    std::string size = android::base::StringPrintf("%zu", getSizes());

    stat.FormatTmp(nullptr, uid, name, size, 6);

    std::string pruned = "";
    std::string output = formatLine(name, size, pruned);

    if (uid != AID_SYSTEM) {
        return output;
    }

    static const size_t maximum_sorted_entries = 32;
    std::array<const pid_t*, maximum_sorted_entries> sorted_pids;
    std::array<const PidEntry*, maximum_sorted_entries> sorted_entries;
    stat.pidSystemTable[id].MaxEntries(uid, 0, sorted_pids, sorted_entries);

    std::string byPid;
    size_t index;
    for (index = 0; index < maximum_sorted_entries; ++index) {
        const PidEntry* entry = sorted_entries[index];
        if (!entry) {
            break;
        }
        if (entry->getSizes() <= (getSizes() / 100)) {
            break;
        }
        byPid += entry->format(stat, id, *sorted_pids[index]);
    }
    if (index > 1) {  // print this only if interesting
        std::string ditto("\" ");
        output += formatLine(std::string("  PID/UID   COMMAND LINE"), ditto, std::string(""));
        output += byPid;
    }

    return output;
}

std::string PidEntry::formatHeader(const std::string& name,
                                   log_id_t /* id */) const {
    return formatLine(name, std::string("Size"), std::string("Pruned")) +
           formatLine(std::string("  PID/UID   COMMAND LINE"),
                      std::string("BYTES"), std::string("NUM"));
}

std::string PidEntry::format(const LogStatistics& stat, log_id_t, pid_t pid) const
        REQUIRES(stat.lock_) {
    std::string name = android::base::StringPrintf("%5u/%u", pid, uid_);
    std::string size = android::base::StringPrintf("%zu", getSizes());

    stat.FormatTmp(name_, uid_, name, size, 12);

    std::string pruned = "";
    return formatLine(name, size, pruned);
}

std::string TidEntry::formatHeader(const std::string& name,
                                   log_id_t /* id */) const {
    return formatLine(name, std::string("Size"), std::string("Pruned")) +
           formatLine(std::string("  TID/UID   COMM"), std::string("BYTES"),
                      std::string("NUM"));
}

std::string TidEntry::format(const LogStatistics& stat, log_id_t, pid_t tid) const
        REQUIRES(stat.lock_) {
    std::string name = android::base::StringPrintf("%5u/%u", tid, uid_);
    std::string size = android::base::StringPrintf("%zu", getSizes());

    stat.FormatTmp(name_, uid_, name, size, 12);

    std::string pruned = "";
    return formatLine(name, size, pruned);
}

static std::string formatMsec(uint64_t val) {
    static const unsigned subsecDigits = 3;
    static const uint64_t sec = MS_PER_SEC;

    static const uint64_t minute = 60 * sec;
    static const uint64_t hour = 60 * minute;
    static const uint64_t day = 24 * hour;

    std::string output;
    if (val < sec) return output;

    if (val >= day) {
        output = android::base::StringPrintf("%" PRIu64 "d ", val / day);
        val = (val % day) + day;
    }
    if (val >= minute) {
        if (val >= hour) {
            output += android::base::StringPrintf("%" PRIu64 ":",
                                                  (val / hour) % (day / hour));
        }
        output += android::base::StringPrintf(
            (val >= hour) ? "%02" PRIu64 ":" : "%" PRIu64 ":",
            (val / minute) % (hour / minute));
    }
    output +=
        android::base::StringPrintf((val >= minute) ? "%02" PRIu64 : "%" PRIu64,
                                    (val / sec) % (minute / sec));
    val %= sec;
    unsigned digits = subsecDigits;
    while (digits && ((val % 10) == 0)) {
        val /= 10;
        --digits;
    }
    if (digits) {
        output += android::base::StringPrintf(".%0*" PRIu64, digits, val);
    }
    return output;
}

template <typename TKey, typename TEntry>
std::string LogStatistics::FormatTable(const LogHashtable<TKey, TEntry>& table, uid_t uid,
                                       pid_t pid, const std::string& name, log_id_t id) const
        REQUIRES(lock_) {
    static const size_t maximum_sorted_entries = 32;
    std::string output;
    std::array<const TKey*, maximum_sorted_entries> sorted_keys;
    std::array<const TEntry*, maximum_sorted_entries> sorted_entries;
    table.MaxEntries(uid, pid, sorted_keys, sorted_entries);
    bool header_printed = false;
    for (size_t index = 0; index < maximum_sorted_entries; ++index) {
        const TEntry* entry = sorted_entries[index];
        if (!entry) {
            break;
        }
        if (entry->getSizes() <= (sorted_entries[0]->getSizes() / 100)) {
            break;
        }
        if (!header_printed) {
            output += "\n\n";
            output += entry->formatHeader(name, id);
            header_printed = true;
        }
        output += entry->format(*this, id, *sorted_keys[index]);
    }
    return output;
}

std::string LogStatistics::ReportInteresting() const {
    auto lock = std::lock_guard{lock_};

    std::vector<std::string> items;

    log_id_for_each(i) { items.emplace_back(std::to_string(mElements[i])); }

    log_id_for_each(i) { items.emplace_back(std::to_string(mSizes[i])); }

    log_id_for_each(i) {
        items.emplace_back(std::to_string(overhead_[i] ? *overhead_[i] : mSizes[i]));
    }

    log_id_for_each(i) {
        uint64_t oldest = mOldest[i].msec() / 1000;
        uint64_t newest = mNewest[i].msec() / 1000;

        int span = newest - oldest;

        items.emplace_back(std::to_string(span));
    }

    return android::base::Join(items, ",");
}

std::string LogStatistics::Format(uid_t uid, pid_t pid, unsigned int logMask) const {
    auto lock = std::lock_guard{lock_};

    static const uint16_t spaces_total = 19;

    // Report on total logging, current and for all time

    std::string output = "size/num";
    size_t oldLength;
    int16_t spaces = 1;

    log_id_for_each(id) {
        if (!(logMask & (1 << id))) continue;
        oldLength = output.length();
        if (spaces < 0) spaces = 0;
        output += android::base::StringPrintf("%*s%s", spaces, "",
                                              android_log_id_to_name(id));
        spaces += spaces_total + oldLength - output.length();
    }
    if (spaces < 0) spaces = 0;
    output += android::base::StringPrintf("%*sTotal", spaces, "");

    static const char TotalStr[] = "\nTotal";
    spaces = 10 - strlen(TotalStr);
    output += TotalStr;

    size_t totalSize = 0;
    size_t totalEls = 0;
    log_id_for_each(id) {
        if (!(logMask & (1 << id))) continue;
        oldLength = output.length();
        if (spaces < 0) spaces = 0;
        size_t szs = mSizesTotal[id];
        totalSize += szs;
        size_t els = mElementsTotal[id];
        totalEls += els;
        output +=
            android::base::StringPrintf("%*s%zu/%zu", spaces, "", szs, els);
        spaces += spaces_total + oldLength - output.length();
    }
    if (spaces < 0) spaces = 0;
    output += android::base::StringPrintf("%*s%zu/%zu", spaces, "", totalSize,
                                          totalEls);

    static const char NowStr[] = "\nNow";
    spaces = 10 - strlen(NowStr);
    output += NowStr;

    totalSize = 0;
    totalEls = 0;
    log_id_for_each(id) {
        if (!(logMask & (1 << id))) continue;

        size_t els = mElements[id];
        oldLength = output.length();
        if (spaces < 0) spaces = 0;
        size_t szs = mSizes[id];
        totalSize += szs;
        totalEls += els;
        output += android::base::StringPrintf("%*s%zu/%zu", spaces, "", szs, els);
        spaces -= output.length() - oldLength;
        spaces += spaces_total;
    }
    if (spaces < 0) spaces = 0;
    output += android::base::StringPrintf("%*s%zu/%zu", spaces, "", totalSize,
                                          totalEls);

    static const char SpanStr[] = "\nLogspan";
    spaces = 10 - strlen(SpanStr);
    output += SpanStr;

    // Total reports the greater of the individual maximum time span, or the
    // validated minimum start and maximum end time span if it makes sense.
    uint64_t minTime = UINT64_MAX;
    uint64_t maxTime = 0;
    uint64_t maxSpan = 0;
    totalSize = 0;

    log_id_for_each(id) {
        if (!(logMask & (1 << id))) continue;

        // validity checking
        uint64_t oldest = mOldest[id].msec();
        uint64_t newest = mNewest[id].msec();
        if (newest <= oldest) {
            spaces += spaces_total;
            continue;
        }

        uint64_t span = newest - oldest;
        if (span > (monthSec * MS_PER_SEC)) {
            spaces += spaces_total;
            continue;
        }

        // total span
        if (minTime > oldest) minTime = oldest;
        if (maxTime < newest) maxTime = newest;
        if (span > maxSpan) maxSpan = span;
        totalSize += span;

        oldLength = output.length();
        output += android::base::StringPrintf("%*s%s", spaces, "", formatMsec(span).c_str());
        spaces -= output.length() - oldLength;
        spaces += spaces_total;
    }
    if ((maxTime > minTime) && ((maxTime -= minTime) < totalSize) &&
        (maxTime > maxSpan)) {
        maxSpan = maxTime;
    }
    if (spaces < 0) spaces = 0;
    output += android::base::StringPrintf("%*s%s", spaces, "",
                                          formatMsec(maxSpan).c_str());

    static const char OverheadStr[] = "\nOverhead";
    spaces = 10 - strlen(OverheadStr);
    output += OverheadStr;

    totalSize = 0;
    log_id_for_each(id) {
        if (!(logMask & (1 << id))) continue;

        size_t els = mElements[id];
        if (els) {
            oldLength = output.length();
            if (spaces < 0) spaces = 0;
            size_t szs = 0;
            if (overhead_[id]) {
                szs = *overhead_[id];
            } else {
                CHECK(track_total_size_);
                szs = mSizes[id];
            }
            totalSize += szs;
            output += android::base::StringPrintf("%*s%zu", spaces, "", szs);
            spaces -= output.length() - oldLength;
        }
        spaces += spaces_total;
    }
    totalSize += sizeOf();
    if (spaces < 0) spaces = 0;
    output += android::base::StringPrintf("%*s%zu", spaces, "", totalSize);

    // Report on Chattiest

    std::string name;

    // Chattiest by application (UID)
    log_id_for_each(id) {
        if (!(logMask & (1 << id))) continue;

        name = (uid == AID_ROOT) ? "Chattiest UIDs in %s log buffer:"
                                 : "Logging for your UID in %s log buffer:";
        output += FormatTable(uidTable[id], uid, pid, name, id);
    }

    return output;
}

namespace android {

uid_t pidToUid(pid_t pid) {
    char path[32];
    snprintf(path, sizeof(path), "/proc/%u", pid);
    struct stat sb;
    return !stat(path, &sb) ? sb.st_uid : AID_LOGD;
}

}

uid_t LogStatistics::PidToUid(pid_t pid) {
    auto lock = std::lock_guard{lock_};
    return pidTable.Add(pid)->second.uid();
}

// caller must free character string
const char* LogStatistics::PidToName(pid_t pid) const {
    auto lock = std::lock_guard{lock_};
    // An inconvenient truth ... getName() can alter the object
    pidTable_t& writablePidTable = const_cast<pidTable_t&>(pidTable);
    const char* name = writablePidTable.Add(pid)->second.name();
    if (!name) {
        return nullptr;
    }
    return strdup(name);
}
