<commit_before>/*
 * Launch and manage FastCGI child processes.
 *
 * author: <NAME> <<EMAIL>>
 */

#include "Stock.hxx"
#include "Quark.hxx"
#include "stock/MapStock.hxx"
#include "stock/Stock.hxx"
#include "stock/Class.hxx"
#include "stock/Item.hxx"
#include "child_stock.hxx"
#include "spawn/Prepared.hxx"
#include "spawn/ChildOptions.hxx"
#include "spawn/JailConfig.hxx"
#include "gerrno.h"
#include "pool.hxx"
#include "event/SocketEvent.hxx"
#include "event/Duration.hxx"
#include "util/ConstBuffer.hxx"

#include <daemon/log.h>

#include <glib.h>

#include <string>

#include <assert.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <string.h>

#ifdef __linux
#include <sched.h>
#endif

struct FcgiStock {
    StockMap hstock;
    StockMap *child_stock;

    FcgiStock(unsigned limit, unsigned max_idle,
              EventLoop &event_loop, SpawnService &spawn_service);

    ~FcgiStock() {
        child_stock_free(child_stock);
    }

    EventLoop &GetEventLoop() {
        return hstock.GetEventLoop();
    }

    void FadeAll() {
        hstock.FadeAll();
        child_stock->FadeAll();
    }
};

struct FcgiChildParams {
    const char *executable_path;

    ConstBuffer<const char *> args;

    const ChildOptions &options;

    FcgiChildParams(const char *_executable_path,
                    ConstBuffer<const char *> _args,
                    const ChildOptions &_options)
        :executable_path(_executable_path), args(_args),
         options(_options) {}

    const char *GetStockKey(struct pool &pool) const;
};

struct FcgiConnection final : HeapStockItem {
    std::string jail_home_directory;

    JailConfig jail_config;

    StockItem *child = nullptr;

    int fd = -1;
    SocketEvent event;

    /**
     * Is this a fresh connection to the FastCGI child process?
     */
    bool fresh = true;

    /**
     * Shall the FastCGI child process be killed?
     */
    bool kill = false;

    /**
     * Was the current request aborted by the fcgi_client caller?
     */
    bool aborted = false;

    explicit FcgiConnection(EventLoop &event_loop, CreateStockItem c)
        :HeapStockItem(c),
         event(event_loop, BIND_THIS_METHOD(OnSocketEvent)) {}

    ~FcgiConnection() override;

    /* virtual methods from class StockItem */
    bool Borrow(gcc_unused void *ctx) override;
    bool Release(gcc_unused void *ctx) override;

private:
    void OnSocketEvent(short events);
};

const char *
FcgiChildParams::GetStockKey(struct pool &pool) const
{
    const char *key = executable_path;

    for (auto i : args)
        key = p_strcat(&pool, key, " ", i, nullptr);

    for (auto i : options.env)
        key = p_strcat(&pool, key, "$", i, nullptr);

    char options_buffer[4096];
    *options.MakeId(options_buffer) = 0;
    if (*options_buffer != 0)
        key = p_strcat(&pool, key, options_buffer, nullptr);

    return key;
}

/*
 * libevent callback
 *
 */

void
FcgiConnection::OnSocketEvent(short events)
{
    if ((events & EV_TIMEOUT) == 0) {
        char buffer;
        ssize_t nbytes = recv(fd, &buffer, sizeof(buffer), MSG_DONTWAIT);
        if (nbytes < 0)
            daemon_log(2, "error on idle FastCGI connection '%s': %s\n",
                       GetStockName(), strerror(errno));
        else if (nbytes > 0)
            daemon_log(2, "unexpected data from idle FastCGI connection '%s'\n",
                       GetStockName());
    }

    InvokeIdleDisconnect();
    pool_commit();
}

/*
 * child_stock class
 *
 */

static bool
fcgi_child_stock_prepare(void *info, UniqueFileDescriptor &&fd,
                         PreparedChildProcess &p, GError **error_r)
{
    const auto &params = *(const FcgiChildParams *)info;
    const ChildOptions &options = params.options;

    p.SetStdin(std::move(fd));

    /* the FastCGI protocol defines a channel for stderr, so we could
       close its "real" stderr here, but many FastCGI applications
       don't use the FastCGI protocol to send error messages, so we
       just keep it open */

    int null_fd = open("/dev/null", O_WRONLY|O_CLOEXEC|O_NOCTTY);
    if (null_fd >= 0)
        p.stdout_fd = null_fd;

    p.Append(params.executable_path);
    for (auto i : params.args)
        p.Append(i);

    return options.CopyTo(p, true, nullptr, error_r);
}

static const ChildStockClass fcgi_child_stock_class = {
    .socket_type = nullptr,
    .prepare = fcgi_child_stock_prepare,
};

/*
 * stock class
 *
 */

static void
fcgi_stock_create(void *ctx, CreateStockItem c, void *info,
                  struct pool &caller_pool,
                  gcc_unused CancellablePointer &cancel_ptr)
{
    FcgiStock *fcgi_stock = (FcgiStock *)ctx;<commit_msg>fcgi/Stock: use UniqueFileDescriptor for /dev/null<commit_after>/*
 * Launch and manage FastCGI child processes.
 *
 * author: <NAME> <<EMAIL>>
 */

#include "Stock.hxx"
#include "Quark.hxx"
#include "stock/MapStock.hxx"
#include "stock/Stock.hxx"
#include "stock/Class.hxx"
#include "stock/Item.hxx"
#include "child_stock.hxx"
#include "spawn/Prepared.hxx"
#include "spawn/ChildOptions.hxx"
#include "spawn/JailConfig.hxx"
#include "gerrno.h"
#include "pool.hxx"
#include "event/SocketEvent.hxx"
#include "event/Duration.hxx"
#include "system/UniqueFileDescriptor.hxx"
#include "util/ConstBuffer.hxx"

#include <daemon/log.h>

#include <glib.h>

#include <string>

#include <assert.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <string.h>

#ifdef __linux
#include <sched.h>
#endif

struct FcgiStock {
    StockMap hstock;
    StockMap *child_stock;

    FcgiStock(unsigned limit, unsigned max_idle,
              EventLoop &event_loop, SpawnService &spawn_service);

    ~FcgiStock() {
        child_stock_free(child_stock);
    }

    EventLoop &GetEventLoop() {
        return hstock.GetEventLoop();
    }

    void FadeAll() {
        hstock.FadeAll();
        child_stock->FadeAll();
    }
};

struct FcgiChildParams {
    const char *executable_path;

    ConstBuffer<const char *> args;

    const ChildOptions &options;

    FcgiChildParams(const char *_executable_path,
                    ConstBuffer<const char *> _args,
                    const ChildOptions &_options)
        :executable_path(_executable_path), args(_args),
         options(_options) {}

    const char *GetStockKey(struct pool &pool) const;
};

struct FcgiConnection final : HeapStockItem {
    std::string jail_home_directory;

    JailConfig jail_config;

    StockItem *child = nullptr;

    int fd = -1;
    SocketEvent event;

    /**
     * Is this a fresh connection to the FastCGI child process?
     */
    bool fresh = true;

    /**
     * Shall the FastCGI child process be killed?
     */
    bool kill = false;

    /**
     * Was the current request aborted by the fcgi_client caller?
     */
    bool aborted = false;

    explicit FcgiConnection(EventLoop &event_loop, CreateStockItem c)
        :HeapStockItem(c),
         event(event_loop, BIND_THIS_METHOD(OnSocketEvent)) {}

    ~FcgiConnection() override;

    /* virtual methods from class StockItem */
    bool Borrow(gcc_unused void *ctx) override;
    bool Release(gcc_unused void *ctx) override;

private:
    void OnSocketEvent(short events);
};

const char *
FcgiChildParams::GetStockKey(struct pool &pool) const
{
    const char *key = executable_path;

    for (auto i : args)
        key = p_strcat(&pool, key, " ", i, nullptr);

    for (auto i : options.env)
        key = p_strcat(&pool, key, "$", i, nullptr);

    char options_buffer[4096];
    *options.MakeId(options_buffer) = 0;
    if (*options_buffer != 0)
        key = p_strcat(&pool, key, options_buffer, nullptr);

    return key;
}

/*
 * libevent callback
 *
 */

void
FcgiConnection::OnSocketEvent(short events)
{
    if ((events & EV_TIMEOUT) == 0) {
        char buffer;
        ssize_t nbytes = recv(fd, &buffer, sizeof(buffer), MSG_DONTWAIT);
        if (nbytes < 0)
            daemon_log(2, "error on idle FastCGI connection '%s': %s\n",
                       GetStockName(), strerror(errno));
        else if (nbytes > 0)
            daemon_log(2, "unexpected data from idle FastCGI connection '%s'\n",
                       GetStockName());
    }

    InvokeIdleDisconnect();
    pool_commit();
}

/*
 * child_stock class
 *
 */

static bool
fcgi_child_stock_prepare(void *info, UniqueFileDescriptor &&fd,
                         PreparedChildProcess &p, GError **error_r)
{
    const auto &params = *(const FcgiChildParams *)info;
    const ChildOptions &options = params.options;

    p.SetStdin(std::move(fd));

    /* the FastCGI protocol defines a channel for stderr, so we could
       close its "real" stderr here, but many FastCGI applications
       don't use the FastCGI protocol to send error messages, so we
       just keep it open */

    UniqueFileDescriptor null_fd;
    if (null_fd.Open("/dev/null", O_WRONLY))
        p.SetStdin(std::move(null_fd));

    p.Append(params.executable_path);
    for (auto i : params.args)
        p.Append(i);

    return options.CopyTo(p, true, nullptr, error_r);
}

static const ChildStockClass fcgi_child_stock_class = {
    .socket_type = nullptr,
    .prepare = fcgi_child_stock_prepare,
};

/*
 * stock class
 *
 */

static void
fcgi_stock_create(void *ctx, CreateStockItem c, void *info,
                  struct pool &caller_pool,
                  gcc_unused CancellablePointer &cancel_ptr)
{
    FcgiStock *fcgi_stock = (FcgiStock *)ctx;
{'fraction_non_alphanumeric': 0.11560448867245394, 'fraction_numerical': 0.002646622909167902, 'mean_word_length': 2.9378907878282616, 'pattern_counts': {'":': 0, '<': 33, '<?xml version=': 0, '>': 36, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '7347489', 'n_tokens_mistral': 3218, 'n_tokens_neox': 3055, 'n_words': 776}