/*
 * Copyright 2018 Bloomberg Finance LP
 *
 * 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 <buildboxcasd_daemon.h>

#include <exception>
#include <fcntl.h>
#include <filesystem>
#include <google/rpc/code.pb.h>
#include <grpcpp/channel.h>
#include <iostream>
#include <memory>
#include <signal.h>
#include <stdexcept>
#include <string>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <thread>
#include <ThreadPool.h>
#include <unistd.h>

#include <grpcpp/server.h>
#include <grpcpp/server_context.h>

#include <buildboxcasd_fslocalactionstorage.h>
#include <buildboxcommon_casclient.h>
#include <buildboxcommon_commandline.h>
#include <buildboxcommon_connectionoptions_commandline.h>
#include <buildboxcommon_logging.h>
#include <buildboxcommon_logging_commandline.h>
#include <buildboxcommon_notify.h>
#include <buildboxcommon_stringutils.h>
#include <buildboxcommon_systemutils.h>
#include <buildboxcommon_version.h>

#include <buildboxcasd_server.h>
#include <buildboxcommon_fslocalcas.h>
#include <buildboxcommon_lrulocalcas.h>
#include <unordered_set>
#include <vector>

namespace buildboxcasd {
namespace {

constexpr unsigned int DEFAULT_MAX_NUM_DIGEST_THREADS = 16;
constexpr unsigned int DEFAULT_MAX_NUM_IO_THREADS = 32;

std::pair<std::vector<std::string>,
          std::unordered_map<std::string, std::string>>
parseInstanceNameConfigs(const std::vector<std::string> &instanceNameOpts)
{
    std::vector<std::string> instances;
    std::unordered_map<std::string, std::string> instanceMappings;

    for (const auto &opt : instanceNameOpts) {
        if (opt.empty()) {
            instances.emplace_back("");
            continue;
        }
        const auto parts = buildboxcommon::StringUtils::split(opt, ":");
        if (parts.size() == 1) {
            instances.emplace_back(opt);
        }
        else if (parts.size() == 2) {
            // instance name mapping
            instances.emplace_back(parts[0]);
            instanceMappings.emplace(parts[0], parts[1]);
        }
        else {
            BUILDBOXCOMMON_THROW_EXCEPTION(std::invalid_argument,
                                           "Invalid instance option: " << opt);
        }
    }

    return {instances, instanceMappings};
}

std::optional<int64_t> parseSize(const std::string &value)
{
    std::optional<int64_t> size = std::nullopt;
    try {
        size = buildboxcommon::StringUtils::parseSize(value);
        return size;
    }
    catch (const std::invalid_argument &e) {
        BUILDBOX_LOG_DEBUG("Failed to parse size: " << e.what());
        return std::nullopt;
    }
}

std::optional<double> parsePercentage(const std::string &value)
{
    const char *s = value.c_str();
    char *endptr = nullptr;
    constexpr int BASE = 10;
    long percentage = strtol(s, &endptr, BASE);

    constexpr int PERCENTAGE_BASE = 100;
    if (percentage <= 0 || percentage >= PERCENTAGE_BASE ||
        strcmp(endptr, "%") != 0) {
        return std::nullopt;
    }
    return (double)percentage / PERCENTAGE_BASE;
}

} // namespace

bool Daemon::configure(const buildboxcommon::CommandLine &cml,
                       const std::string &cachePath)
{
    // If no new options are specified then we use compatibility mode
    bool compatMode =
        !cml.exists("remote") && !cml.exists("local-server-instance") &&
        !cml.exists("proxy-instance") &&
        (cml.exists("instance") || cml.exists("server-instance"));

    if (!buildboxcommon::ConnectionOptionsCommandLine::configureChannel(
            cml, "", &d_cas_server)) {
        std::cerr << "Error configuring CAS channel\n";
        return false;
    }
    if (!buildboxcommon::ConnectionOptionsCommandLine::configureChannel(
            cml, "", &d_ac_server)) {
        std::cerr << "Error configuring ActionCache channel\n";
        return false;
    }
    if (!buildboxcommon::ConnectionOptionsCommandLine::configureChannel(
            cml, "", &d_ra_server)) {
        std::cerr << "Error configuring RemoteAsset channel\n";
        return false;
    }
    if (!buildboxcommon::ConnectionOptionsCommandLine::configureChannel(
            cml, "", &d_exec_server)) {
        std::cerr << "Error configuring Execution channel\n";
        return false;
    }

    if (compatMode) {
        // In compatibility mode --instance is not used for connection options,
        // undo parsing by ::configureChannel.
        d_cas_server.d_instanceName = "";
        d_ac_server.d_instanceName = "";
        d_ra_server.d_instanceName = "";
        d_exec_server.d_instanceName = "";
    }

    if (!buildboxcommon::ConnectionOptionsCommandLine::updateChannelOptions(
            cml, "cas-", &d_cas_server)) {
        std::cerr << "Error configuring CAS channel\n";
        return false;
    }

    if (!buildboxcommon::ConnectionOptionsCommandLine::updateChannelOptions(
            cml, "ac-", &d_ac_server)) {
        std::cerr << "Error configuring ActionCache channel\n";
        return false;
    }
    if (!buildboxcommon::ConnectionOptionsCommandLine::updateChannelOptions(
            cml, "ra-", &d_ra_server)) {
        std::cerr << "Error configuring Remote Asset channel\n";
        return false;
    }
    if (!buildboxcommon::ConnectionOptionsCommandLine::updateChannelOptions(
            cml, "exec-", &d_exec_server)) {
        std::cerr << "Error configuring Execution channel\n";
        return false;
    }

    bool is_proxy = d_cas_server.d_url != "" || d_ac_server.d_url != "" ||
                    d_ra_server.d_url != "" || d_exec_server.d_url != "";

    if (is_proxy && d_cas_server.d_url.empty()) {
        std::cerr << "Cannot create proxy without remote CAS server\n";
        return false;
    }

    if (compatMode) {
        if (cml.exists("instance")) {
            std::string new_arg_name;
            if (is_proxy) {
                d_proxyInstanceNames = {cml.getString("instance")};
                new_arg_name = "--proxy-instance";
            }
            else {
                d_localServerInstanceNames = {cml.getString("instance")};
                new_arg_name = "--local-server-instance";
            }
            std::cerr
                << "WARNING\n"
                << "WARNING --instance without --remote is treated "
                << "as " << new_arg_name << ".\n"
                << "WARNING This behavior is obsolete and will be removed.\n"
                << "WARNING Replace --instance with " << new_arg_name << ".\n"
                << "WARNING\n";
        }

        if (cml.exists("server-instance")) {
            std::cerr << "WARNING\n"
                      << "WARNING --server-instance is obsolete and will be "
                         "removed.\n"
                      << "WARNING Replace --server-instance with "
                         "--local-server-instance.\n"
                      << "WARNING\n";
            d_localServerInstanceNames = {cml.getString("server-instance")};
        }
    }
    else {
        // In new mode --server-instance is not allowed
        if (cml.exists("server-instance")) {
            std::cerr << "--server-instance is not available with --remote "
                         "and other new options.\n"
                      << "Replace it with --local-server-instance.\n";
            return false;
        }

        if (is_proxy) {
            if (cml.exists("proxy-instance")) {
                auto [instances, instanceMappings] =
                    parseInstanceNameConfigs(cml.getVS("proxy-instance"));
                d_proxyInstanceNames = instances;
                d_instanceMappings = instanceMappings;
            }
            else {
                d_proxyInstanceNames = {""};
            }

            if (cml.exists("local-server-instance")) {
                d_localServerInstanceNames =
                    cml.getVS("local-server-instance");
            }
        }
        else {
            if (cml.exists("proxy-instance")) {
                std::cerr
                    << "--proxy-instance cannot be used if no remotes "
                       "are specified,\n"
                    << "as buildbox-casd is not running in proxy mode.\n";
                return false;
            }

            if (cml.exists("local-server-instance")) {
                d_localServerInstanceNames =
                    cml.getVS("local-server-instance");
            }
            else {
                d_localServerInstanceNames = std::vector<std::string>({""});
            }
        }
    }

    if (cml.exists("bind")) {
        d_bind_addresses = cml.getVS("bind");
    }
    if (cml.exists("unix-socket-group")) {
        const std::string group = cml.getString("unix-socket-group");
        try {
            // Attempt to directly parse it as gid
            d_socket_gid = static_cast<gid_t>(std::stoul(group));
        }
        catch (...) {
            // Convert group name to gid. An exception can be thrown if this
            // also fails.
            d_socket_gid = buildboxcommon::SystemUtils::getGidFromName(group);
        }
    }
    if (cml.exists("unix-socket-mode")) {
        const std::string modeStr = cml.getString("unix-socket-mode");
        try {
            const mode_t mode =
                static_cast<mode_t>(std::stoul(modeStr, 0, 8)) & 0777;
            d_socket_mode = mode;
        }
        catch (std::exception &e) {
            BUILDBOX_LOG_ERROR(
                "Invalid unix mode for unix domain socket: " << modeStr);
            throw;
        }
    }

    if (cml.exists("permit-keepalive-time")) {
        d_permitKeepaliveTime = cml.getInt("permit-keepalive-time");
        if (d_permitKeepaliveTime < 0) {
            std::cerr << "Invalid value for --permit-keepalive-time "
                      << d_permitKeepaliveTime.value() << "\n";
            return false;
        }
    }

    if (cml.exists("shutdown-deadline")) {
        d_shutdownDeadline = cml.getInt("shutdown-deadline");
        if (d_shutdownDeadline < 0) {
            std::cerr << "Invalid value for --shutdown-deadline "
                      << d_shutdownDeadline.value() << "\n";
            return false;
        }
    }

    if (cml.exists("buildbox-run")) {
        d_runnerCommand = cml.getString("buildbox-run");
        d_extraRunArgs = cml.getVS("runner-arg");
        d_maxJobs = cml.getInt("jobs");
        if (d_maxJobs < 1) {
            std::cerr << "Invalid value for --jobs " << d_maxJobs << "\n";
            return false;
        }
        if (cml.exists("exec-hybrid-queue-limit")) {
            d_execHybridQueueLimit = cml.getInt("exec-hybrid-queue-limit");
            if (d_execHybridQueueLimit < 0) {
                std::cerr << "Invalid value for --exec-hybrid-queue-limit "
                          << d_execHybridQueueLimit.value() << "\n";
                return false;
            }
        }
        d_enableFallbackToLocalExecution =
            cml.getBool("exec-fallback-to-local");
        d_cacheFailures = cml.getBool("cache-failures");
    }
    else if (cml.getBool("exec-fallback-to-local") ||
             cml.exists("exec-hybrid-queue-limit")) {
        std::cerr << "Cannot use options --exec-hybrid-queue-limit or "
                     "--exec-fallback-to-local without "
                     "--buildbox-run\n";
        return false;
    }

    DigestFunction_Value digestFunctionValue =
        cml.exists("digest-function")
            ? buildboxcommon::DigestGenerator::stringToDigestFunction(
                  cml.getString("digest-function"))
            : static_cast<buildboxcommon::DigestFunction_Value>(
                  BUILDBOXCOMMON_DIGEST_FUNCTION_VALUE);
    buildboxcommon::DigestGenerator::init(digestFunctionValue);

    d_quotaHigh = parseSize(cml.getString("quota-high", ""));

    const std::string quotaLowString = cml.getString("quota-low", "");
    if (!quotaLowString.empty() && quotaLowString.back() == '%') {
        const auto quotaLowRatio = parsePercentage(quotaLowString);
        if (!quotaLowRatio.has_value()) {
            std::cerr << "Invalid percentage for --quota-low: '"
                      << quotaLowString << "'\n";
            return false;
        }
        d_quotaLowRatio = quotaLowRatio.value();
    }
    else {
        const auto quotaLow = parseSize(quotaLowString);
        if (quotaLow.has_value()) {
            if (!d_quotaHigh.has_value()) {
                std::cerr << "--quota-low must be specified as a percentage "
                             "when used without --quota-high\n";
                return false;
            }
            d_quotaLowRatio = static_cast<double>(quotaLow.value()) /
                              static_cast<double>(d_quotaHigh.value());
        }
        else {
            std::cerr << "Invalid value for --quota-low: '" << quotaLowString
                      << "'\n";
            return false;
        }
    }

    if (cml.exists("read-only-remote")) {
        d_read_only_remote = true;
    }
    else {
        d_read_only_remote = false;
    }

    const auto reservedSpace = parseSize(cml.getString("reserved"));
    if (!reservedSpace.has_value()) {
        std::cerr << "Invalid value for --reserved: '"
                  << cml.getString("reserved") << "'\n";
        return false;
    }
    d_reserved_space = reservedSpace.value();

    d_protect_session_blobs = cml.getBool("protect-session-blobs");
    d_failFastDuringCleanup = cml.getBool("fail-fast-during-cleanup");
    d_proxy_findmissingblobs_cache_ttl_seconds =
        static_cast<unsigned int>(cml.getInt("findmissingblobs-cache-ttl"));
    d_proxyAcValidationCacheTtlSeconds =
        static_cast<unsigned int>(cml.getInt("ac-validation-cache-ttl"));

    buildboxcommon::LogLevel logLevel = buildboxcommon::LogLevel::ERROR;
    if (!buildboxcommon::parseLoggingOptions(cml, logLevel)) {
        return false;
    }
    BUILDBOX_LOG_SET_LEVEL(logLevel);
    d_log_level = logLevel;

    d_local_cache_path = cachePath;
    if (d_local_cache_path.empty()) {
        std::cerr << "Local cache path is missing\n";
        return false;
    }

    d_allow_external_file_moves = cml.getBool("capture-allow-file-moves");
    d_storeWritableObjects = cml.getBool("store-writable-objects", false);

    if (cml.exists("num-digest-threads")) {
        int numDigestThreads = cml.getInt("num-digest-threads");
        if (numDigestThreads < 0) {
            std::cerr << "Invalid value for --num-digest-threads "
                      << d_numDigestThreads << "\n";
            return false;
        }
        d_numDigestThreads = numDigestThreads;
    }
    else {
        d_numDigestThreads = std::min(std::thread::hardware_concurrency(),
                                      DEFAULT_MAX_NUM_DIGEST_THREADS);
    }

    if (cml.exists("num-io-threads")) {
        auto num_io_threads = cml.getInt("num-io-threads");
        if (num_io_threads < 0) {
            std::cerr << "Invalid value for --num-io-threads "
                      << num_io_threads << "\n";
            return false;
        }
        d_numIOThreads = num_io_threads;
    }
    else {
        d_numIOThreads = std::min(std::thread::hardware_concurrency() * 2,
                                  DEFAULT_MAX_NUM_IO_THREADS);
    }

    d_allowedPreUnstageCommands =
        std::make_shared<std::unordered_set<std::string>>();
    if (cml.exists("allow-pre-unstage-command")) {
        const auto commands = cml.getVS("allow-pre-unstage-command");
        for (const auto &command : commands) {
            if (!std::filesystem::path(command).is_absolute()) {
                std::cerr << "--allow-pre-unstage-command PATH must be an "
                             "absolute path, got "
                          << command << '\n';
                return false;
            }
            d_allowedPreUnstageCommands->insert(command);
        }
    }

    return true;
} // namespace buildboxcasd

void Daemon::logCommandLine() const
{

    std::ostringstream oss;
    oss << "Starting buildbox-casd " << buildboxcommon::VERSION
        << " with cache at \"" << this->d_local_cache_path << "\"";
    if (!d_proxyInstanceNames.empty()) {
        const auto proxyInstanceNames =
            buildboxcommon::StringUtils::join(d_proxyInstanceNames, ",");
        oss << ", proxy-instance(s) = \"" << proxyInstanceNames << "\"";
    }
    if (!d_localServerInstanceNames.empty()) {
        const auto localInstanceNames =
            buildboxcommon::StringUtils::join(d_localServerInstanceNames, ",");
        oss << ", local-server-instance(s) = \"" << localInstanceNames << "\"";
    }
    oss << ", bind = \""
        << buildboxcommon::StringUtils::join(d_bind_addresses, ",") << "\"";
    if (d_quotaHigh.has_value()) {
        oss << ", quota-high = \"" << d_quotaHigh.value() << "\"";
    }
    constexpr int PERCENTAGE_BASE = 100;
    oss << ", quota-low = \"" << (d_quotaLowRatio * PERCENTAGE_BASE)
        << "%\", reserved = \"" << d_reserved_space
        << "\", protect_session_blobs = " << std::boolalpha
        << d_protect_session_blobs
        << ", fail_fast_during_cleanup = " << d_failFastDuringCleanup
        << ", findmissingblobs-cache-ttl = "
        << d_proxy_findmissingblobs_cache_ttl_seconds << ", log-level = \""
        << d_log_level << "\", CAS-client = [" << d_cas_server
        << "], Remote-Asset-client = [" << d_ra_server
        << "], ActionCache-client = [" << d_ac_server
        << "], Execution-client = [" << d_exec_server << "]";

    if (!d_runnerCommand.empty()) {
        oss << ", buildbox-run = \"" << d_runnerCommand << "\"";
        if (!d_extraRunArgs.empty()) {
            oss << ", runner-arg = ["
                << buildboxcommon::StringUtils::join(d_extraRunArgs, ", ")
                << "]";
        }
        oss << ", jobs = " << d_maxJobs;
        oss << ", cache-failures = " << (d_cacheFailures ? "true" : "false");
    }

    if (d_execHybridQueueLimit.has_value()) {
        oss << ", exec-hybrid-queue-limit = "
            << d_execHybridQueueLimit.value();
    }
    if (d_enableFallbackToLocalExecution) {
        oss << ", exec-fallback-to-local = true";
    }

    BUILDBOX_LOG_INFO(oss.str());
}

void Daemon::runDaemon()
{
    logCommandLine();

    const std::string unix_prefix = "unix:";
    if (d_bind_addresses.empty()) {
        const auto path = (d_local_cache_path.back() == '/')
                              ? d_local_cache_path
                              : d_local_cache_path + "/";

        d_bind_addresses.emplace_back(unix_prefix + path + "casd.sock");
    }

    // Creating an FsLocalCas instance:
    std::unique_ptr<buildboxcommon::LocalCas> fs_storage =
        std::make_unique<buildboxcommon::FsLocalCas>(
            this->d_local_cache_path, this->d_allow_external_file_moves,
            this->d_storeWritableObjects,
            /* trackDiskUsage */ d_quotaHigh.has_value());

    // Creating an FsLocalActionStorage instance:
    std::shared_ptr<FsLocalActionStorage> actioncache_storage =
        std::make_shared<FsLocalActionStorage>(this->d_local_cache_path);

    std::shared_ptr<buildboxcommon::LocalCas> storage;

    // Creating LocalCas wrapper for LRU expiry
    auto lru_storage = std::make_shared<buildboxcommon::LruLocalCas>(
        std::move(fs_storage), d_quotaLowRatio, d_quotaHigh, d_reserved_space,
        d_failFastDuringCleanup);

    if (d_protect_session_blobs) {
        // Do not delete blobs created or used in this session
        lru_storage->protectActiveBlobs(std::chrono::system_clock::now());
    }

    storage = lru_storage;

    auto asset_storage =
        std::make_shared<FsLocalAssetStorage>(this->d_local_cache_path);

    std::shared_ptr<LocalExecutionScheduler> executionScheduler;
    if (!d_runnerCommand.empty()) {
        executionScheduler = std::make_shared<LocalExecutionScheduler>(
            d_bind_addresses.front(), d_runnerCommand, d_extraRunArgs,
            d_maxJobs);
        executionScheduler->setCacheFailures(d_cacheFailures);
    }

    std::shared_ptr<ThreadPool> digestThreadPool, ioThreadPool;
    if (d_numDigestThreads > 0) {
        digestThreadPool =
            std::make_shared<ThreadPool>(d_numDigestThreads, "digest");
    }
    if (d_numIOThreads > 0) {
        ioThreadPool = std::make_shared<ThreadPool>(d_numIOThreads, "io");
    }

    d_server = std::make_shared<Server>(
        storage, asset_storage, actioncache_storage, executionScheduler,
        digestThreadPool, ioThreadPool, d_allowedPreUnstageCommands);

    if (!this->d_proxyInstanceNames.empty()) { // Proxy
        BUILDBOX_LOG_INFO("Creating CAS proxy server, remote CAS address = \""
                          << d_cas_server.d_url << "\"");
        std::optional<buildboxcommon::ConnectionOptions> ra_endpoint;
        if (!d_ra_server.d_url.empty()) {
            BUILDBOX_LOG_INFO("Creating Remote Asset proxy server, Remote "
                              "Asset server address = \""
                              << d_ra_server.d_url << "\"");
            ra_endpoint = d_ra_server;
        }
        std::optional<buildboxcommon::ConnectionOptions> ac_endpoint;
        if (!d_ac_server.d_url.empty()) { // ActionCache proxy
            BUILDBOX_LOG_INFO("Creating ActionCache proxy server, cache "
                              "server address = \""
                              << d_ac_server.d_url << "\"");
            ac_endpoint = d_ac_server;
        }
        std::optional<buildboxcommon::ConnectionOptions> execution_endpoint;
        if (!d_exec_server.d_url.empty()) { // Execution proxy
            BUILDBOX_LOG_INFO("Creating Execution proxy server, execution "
                              "server address = \""
                              << d_exec_server.d_url << "\"");
            execution_endpoint = d_exec_server;
        }

        d_server->addProxyInstances(
            d_proxyInstanceNames, d_instanceMappings, d_cas_server,
            ra_endpoint, ac_endpoint, execution_endpoint, d_read_only_remote,
            static_cast<int>(d_proxy_findmissingblobs_cache_ttl_seconds),
            static_cast<int>(d_proxyAcValidationCacheTtlSeconds),
            d_execHybridQueueLimit, d_enableFallbackToLocalExecution);
    }

    // Optionally, add an additional local server instance:

    for (const auto &instanceName : d_localServerInstanceNames) {
        BUILDBOX_LOG_INFO("Creating local server instance with name \""
                          << instanceName << "\"");
        d_server->addLocalServerInstance(instanceName);
    }

    const std::string tmp_suffix = ".tmp";
    for (const auto &bindAddress : d_bind_addresses) {
        if (bindAddress.find(unix_prefix) == 0) {
            // Use temporary socket path during initialization to avoid
            // exposing uninitialized socket to other processes. This allows
            // race-free connections from client processes.
            const std::string socketPath =
                bindAddress.substr(unix_prefix.size());
            d_socket_paths.emplace_back(socketPath);

            d_server->addListeningPort(bindAddress + tmp_suffix);
        }
        else {
            // HTTP
            d_server->addListeningPort(bindAddress);
        }
    }

    if (d_permitKeepaliveTime.has_value()) {
        d_server->permitKeepaliveTime(d_permitKeepaliveTime.value());
    }

    // Ignore SIGPIPE in case of using sockets + grpc without MSG_NOSIGNAL
    // support configured
    struct sigaction new_sig{};
    new_sig.sa_handler = SIG_IGN;
    new_sig.sa_flags = 0;
    sigfillset(&new_sig.sa_mask);
    if (sigaction(SIGPIPE, &new_sig, NULL) == -1) {
        BUILDBOX_LOG_ERROR("Failed to set SIGPIPE handler");
        exit(1);
    }

    d_server->start();

    for (const auto &socketPath : d_socket_paths) {
        const auto tmpSocketPath = socketPath + tmp_suffix;
        // Change group ownership
        if (d_socket_gid.has_value()) {
            if (chown(tmpSocketPath.c_str(), -1, d_socket_gid.value()) != 0) {
                BUILDBOXCOMMON_THROW_EXCEPTION(
                    std::runtime_error, "Failed to chown unix socket at "
                                            << tmpSocketPath << ", error: "
                                            << std::strerror(errno));
            }
        }
        // Change permission
        if (d_socket_mode.has_value()) {
            BUILDBOX_LOG_INFO("Updating unix socket permission to: "
                              << d_socket_mode.value());
            if (chmod(tmpSocketPath.c_str(), d_socket_mode.value()) != 0) {
                BUILDBOXCOMMON_THROW_EXCEPTION(
                    std::runtime_error, "Failed to chown unix socket at "
                                            << tmpSocketPath << ", error: "
                                            << std::strerror(errno));
            }
        }
        // Socket has been fully initialized. Rename socket to final path.
        if (rename(tmpSocketPath.c_str(), socketPath.c_str()) != 0) {
            BUILDBOXCOMMON_THROW_EXCEPTION(std::runtime_error,
                                           "Failed to rename unix socket at "
                                               << tmpSocketPath << ", error: "
                                               << std::strerror(errno));
        }
    }

    buildboxcommon::systemd_notify_socket_send_ready();

    BUILDBOX_LOG_INFO(
        "Server listening on "
        << buildboxcommon::StringUtils::join(d_bind_addresses, ","));
}

void Daemon::stop()
{
    // Use local variable to avoid race condition with nullptr check
    std::shared_ptr<Server> server = d_server;

    if (server != nullptr) {
        if (d_shutdownDeadline.has_value()) {
            server->shutdown(
                gpr_time_add(gpr_now(GPR_CLOCK_REALTIME),
                             gpr_time_from_seconds(d_shutdownDeadline.value(),
                                                   GPR_TIMESPAN)));
        }
        else {
            server->shutdown();
        }
        server->wait();
        d_server = nullptr;
    }

    d_shutdown = true;

    for (const auto &socketPath : d_socket_paths) {
        // Clean up socket. This is not handled by gRPC as we use a
        // temporary path during initialization.
        unlink(socketPath.c_str());
    }
}

} // namespace buildboxcasd
