<commit_before>/*
 * Copyright 2007-2017 Content Management AG
 * All rights reserved.
 *
 * author: Max Kellermann <mk@cm4all.com>
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * - Redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer.
 *
 * - Redistributions in binary form must reproduce the above copyright
 * notice, this list of conditions and the following disclaimer in the
 * documentation and/or other materials provided with the
 * distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
 * FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
 * OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#include "Stock.hxx"
#include "Launch.hxx"
#include "stock/MapStock.hxx"
#include "stock/Stock.hxx"
#include "stock/Class.hxx"
#include "stock/Item.hxx"
#include "access_log/ChildErrorLog.hxx"
#include "spawn/ChildOptions.hxx"
#include "spawn/ExitListener.hxx"
#include "spawn/Interface.hxx"
#include "pool/tpool.hxx"
#include "pool/StringBuilder.hxx"
#include "event/SocketEvent.hxx"
#include "event/TimerEvent.hxx"
#include "net/log/Datagram.hxx"
#include "io/Logger.hxx"
#include "util/Cancellable.hxx"
#include "util/ConstBuffer.hxx"
#include "util/Cast.hxx"

#include <was/protocol.h>

#include <string>

#include <assert.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <signal.h>
#include <stdlib.h>

static constexpr Event::Duration was_idle_timeout = std::chrono::minutes(5);

struct WasChildParams {
    const char *executable_path;

    ConstBuffer<const char *> args;

    const ChildOptions &options;

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

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

class WasChild final : public StockItem, ExitListener {
    const LLogger logger;

    SpawnService &spawn_service;

    const std::string tag;

    const SocketDescriptor log_socket;
    ChildErrorLog log;

    WasProcess process;
    SocketEvent event;
    TimerEvent idle_timeout_event;

    /**
     * If true, then we're waiting for PREMATURE (after the #WasClient
     * has sent #WAS_COMMAND_STOP).
     */
    bool stopping = false;

    /**
     * The number of bytes received before #WAS_COMMAND_STOP was sent.
     */
    uint64_t input_received;

public:
    explicit WasChild(CreateStockItem c, SpawnService &_spawn_service,
                      const char *_tag, SocketDescriptor _log_socket) noexcept
        :StockItem(c), logger(GetStockName()), spawn_service(_spawn_service),
         tag(_tag != nullptr ? _tag : ""),
         log_socket(_log_socket),
         event(c.stock.GetEventLoop(), BIND_THIS_METHOD(EventCallback)),
         idle_timeout_event(c.stock.GetEventLoop(),
                            BIND_THIS_METHOD(OnIdleTimeout))
    {
        /* mark this object as "unused" so the destructor doesn't
           attempt to kill the process */
        process.pid = -1;
    }

    ~WasChild() noexcept override;

    auto &GetEventLoop() noexcept {
        return event.GetEventLoop();
    }

    bool IsTag(const char *other_tag) const noexcept {
        return tag == other_tag;
    }

    /**
     * Throws on error.
     */
    void Launch(const WasChildParams &params) {
        process = was_launch(spawn_service,
                             GetStockName(),
                             params.executable_path,
                             params.args,
                             params.options,
                             log.EnableClient(GetEventLoop(), log_socket),
                             this);
        event.Open(process.control);
    }

    void SetSite(const char *_site) noexcept {
        log.SetSite(_site);
    }

    void SetUri(const char *_uri) noexcept {
        log.SetUri(_uri);
    }

    const WasProcess &GetProcess() const noexcept {
        return process;
    }

    void Stop(uint64_t _received) noexcept {
        assert(!is_idle);
        assert(!stopping);

        stopping = true;
        input_received = _received;
    }

private:
    enum class ReceiveResult {
        SUCCESS, ERROR, AGAIN,
    };

    /**
     * Receive data on the control channel.
     */
    ReceiveResult ReceiveControl(void *p, size_t size) noexcept;

    /**
     * Receive and discard data on the control channel.
     *
     * @return true on success
     */
    bool DiscardControl(size_t size) noexcept;

    /**
     * Discard the given amount of data from the input pipe.
     *
     * @return true on success
     */
    bool DiscardInput(uint64_t remaining) noexcept;

    /**
     * Attempt to recover after the WAS client sent STOP to the
     * application.  This method waits for PREMATURE and discards
     * excess data from the pipe.
     */
    void RecoverStop() noexcept;

    void EventCallback(unsigned events) noexcept;
    void OnIdleTimeout() noexcept;

public:
    /* virtual methods from class StockItem */
    bool Borrow() noexcept override {
        if (stopping)
            /* we havn't yet recovered from #WAS_COMMAND_STOP - give
               up this child process */
            // TODO: improve recovery for this case
            return false;

        event.Cancel();
        idle_timeout_event.Cancel();
        return true;
    }

    bool Release() noexcept override {
        event.ScheduleRead();
        idle_timeout_event.Schedule(was_idle_timeout);
        unclean = stopping;
        return true;
    }

private:
    /* virtual methods from class ExitListener */
    void OnChildProcessExit(gcc_unused int status) noexcept override {
        process.pid = -1;
    }
};

const char *
WasChildParams::GetStockKey(struct pool &pool) const noexcept
{
    PoolStringBuilder<256> b;
    b.push_back(executable_path);

    for (auto i : args) {
        b.push_back(" ");
        b.push_back(i);
    }

    for (auto i : options.env) {
        b.push_back("$");
        b.push_back(i);
    }

    char options_buffer[16384];
    b.emplace_back(options_buffer,
                   options.MakeId(options_buffer));

    return b(pool);
}

WasChild::ReceiveResult
WasChild::ReceiveControl(void *p, size_t size) noexcept
{
    ssize_t nbytes = recv(process.control.Get(), p, size, MSG_DONTWAIT);
    if (nbytes == (ssize_t)size)
        return ReceiveResult::SUCCESS;

    if (nbytes < 0 && errno == EAGAIN) {
        /* the WAS application didn't send enough data (yet); don't
           bother waiting for more, just give up on this process */
        return ReceiveResult::AGAIN;
    }

    if (nbytes < 0)
        logger(2, "error on idle WAS control connection: ", strerror(errno));
    else if (nbytes > 0)
        logger(2, "unexpected data from idle WAS control connection");
    return ReceiveResult::ERROR;
}

bool
WasChild::DiscardControl(size_t size) noexcept
{
    while (size > 0) {
        char buffer[1024];
        ssize_t nbytes = recv(process.control.Get(), buffer,
                              std::min(size, sizeof(buffer)),
                              MSG_DONTWAIT);
        if (nbytes <= 0)
            return false;

        size -= nbytes;
    }

    return true;
}

inline bool
WasChild::DiscardInput(uint64_t remaining) noexcept
{
    while (remaining > 0) {
        uint8_t buffer[16384];
        size_t size = std::min(remaining, uint64_t(sizeof(buffer)));
        ssize_t nbytes = process.input.Read(buffer, size);
        if (nbytes <= 0)
            return false;

        remaining -= nbytes;
    }

    return true;
}

inline void
WasChild::RecoverStop() noexcept
{
    uint64_t premature;

    while (true) {
        struct was_header header;
        switch (ReceiveControl(&header, sizeof(header))) {
        case ReceiveResult::SUCCESS:
            break;

        case ReceiveResult::ERROR:
            InvokeIdleDisconnect();
            return;

        case ReceiveResult::AGAIN:
            /* wait for more data */
            return;
        }

        switch ((enum was_command)header.command) {
        case WAS_COMMAND_NOP:
            /* ignore */
            continue;

        case WAS_COMMAND_HEADER:
        case WAS_COMMAND_STATUS:
        case WAS_COMMAND_NO_DATA:
        case WAS_COMMAND_DATA:
        case WAS_COMMAND_LENGTH:
        case WAS_COMMAND_STOP:
            /* discard & ignore */
            if (!DiscardControl(header.length)) {
                InvokeIdleDisconnect();
                return;
            }
            continue;

        case WAS_COMMAND_REQUEST:
        case WAS_COMMAND_METHOD:
        case WAS_COMMAND_URI:
        case WAS_COMMAND_SCRIPT_NAME:
        case WAS_COMMAND_PATH_INFO:
        case WAS_COMMAND_QUERY_STRING:
        case WAS_COMMAND_PARAMETER:
            logger(2, "unexpected data from idle WAS control connection '",
                      GetStockName(), "'");
            InvokeIdleDisconnect();
            return;

        case WAS_COMMAND_PREMATURE:
            /* this is what we're waiting for */
            break;
        }

        if (ReceiveControl(&premature, sizeof(premature)) != ReceiveResult::SUCCESS) {
            InvokeIdleDisconnect();
            return;
        }

        break;
    }

    if (premature < input_received) {
        InvokeIdleDisconnect();
        return;
    }

    if (!DiscardInput(premature - input_received)) {
        InvokeIdleDisconnect();
        return;
    }

    stopping = false;
    unclean = false;
}

/*
 * libevent callback
 *
 */

inline void
WasChild::EventCallback(unsigned) noexcept
{
    if (stopping) {
        RecoverStop();
        return;
    }

    char buffer;
    ssize_t nbytes = recv(process.control.Get(), &buffer, sizeof(buffer),
                          MSG_DONTWAIT);
    if (nbytes < 0)
        logger(2, "error on idle WAS control connection: ",
               strerror(errno));
    else if (nbytes > 0)
        logger(2, "unexpected data from idle WAS control connection");

    InvokeIdleDisconnect();
}

inline void
WasChild::OnIdleTimeout() noexcept
{
    InvokeIdleDisconnect();
}

/*
 * stock class
 *
 */

class WasStock final : StockClass {
    SpawnService &spawn_service;
    const SocketDescriptor log_socket;
    StockMap stock;

public:
    explicit WasStock(EventLoop &event_loop, SpawnService &_spawn_service,
                      const SocketDescriptor _log_socket,
                      unsigned limit, unsigned max_idle) noexcept
        :spawn_service(_spawn_service),
         log_socket(_log_socket),
         stock(event_loop, *this, limit, max_idle) {}

    StockMap &GetStock() noexcept {
        return stock;
    }

    void FadeTag(const char *tag) noexcept {
        stock.FadeIf([tag](const StockItem &item){
                const auto &child = (const WasChild &)item;
                return child.IsTag(tag);
            });
    }

private:
    /* virtual methods from class StockClass */
    void Create(CreateStockItem c, void *info, struct pool &caller_pool,
                CancellablePointer &cancel_ptr) override;
};

void
WasStock::Create(CreateStockItem c,
                 void *info,
                 gcc_unused struct pool &caller_pool,
                 gcc_unused CancellablePointer &cancel_ptr)
{
    WasChildParams *params = (WasChildParams *)info;

    assert(params != nullptr);
    assert(params->executable_path != nullptr);

    auto *child = new WasChild(c, spawn_service, params->options.tag,
                               log_socket);

    try {
        child->Launch(*params);
    } catch (...) {
        child->InvokeCreateError(std::current_exception());
        return;
    }

    child->InvokeCreateSuccess();
}

WasChild::~WasChild() noexcept
{
    if (process.pid >= 0)
        spawn_service.KillChildProcess(process.pid);
}

/*
 * interface
 *
 */

StockMap *
was_stock_new(unsigned limit, unsigned max_idle,
              EventLoop &event_loop, SpawnService &spawn_service,
              SocketDescriptor log_socket) noexcept
{
    auto *stock = new WasStock(event_loop, spawn_service, log_socket,
                               limit, max_idle);
    return &stock->GetStock();
}

void
was_stock_free(StockMap *_stock) noexcept
{
    auto *stock = (WasStock *)&_stock->GetClass();
    delete stock;
}

void
was_stock_fade_tag(StockMap &_stock, const char *tag) noexcept
{
    auto &stock = (WasStock &)_stock.GetClass();
    stock.FadeTag(tag);
}

void
was_stock_get(StockMap *hstock, struct pool *pool,
              const ChildOptions &options,
              const char *executable_path,
              ConstBuffer<const char *> args,
              StockGetHandler &handler,
              CancellablePointer &cancel_ptr) noexcept
{
    const AutoRewindPool auto_rewind(*tpool);

    auto params = NewFromPool<WasChildParams>(*pool, executable_path, args,
                                              options);

    hstock->Get(*pool, params->GetStockKey(*tpool), params,
                handler, cancel_ptr);
}

void
was_stock_item_set_site(StockItem &item, const char *site) noexcept
{
    auto &child = (WasChild &)item;
    child.SetSite(site);
}

void
was_stock_item_set_uri(StockItem &item, const char *uri) noexcept
{
    auto &child = (WasChild &)item;
    child.SetUri(uri);
}

const WasProcess &
was_stock_item_get(const StockItem &item) noexcept
{
    auto *child = (const WasChild *)&item;

    return child->GetProcess();
}

void
was_stock_item_stop(StockItem &item, uint64_t received) noexcept
{
    auto &child = (WasChild &)item;
    child.Stop(received);
}
<commit_msg>was/Stock: rethrow instead of calling InvokeCreateError()<commit_after>/*
 * Copyright 2007-2017 Content Management AG
 * All rights reserved.
 *
 * author: Max Kellermann <mk@cm4all.com>
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * - Redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer.
 *
 * - Redistributions in binary form must reproduce the above copyright
 * notice, this list of conditions and the following disclaimer in the
 * documentation and/or other materials provided with the
 * distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
 * FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
 * OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#include "Stock.hxx"
#include "Launch.hxx"
#include "stock/MapStock.hxx"
#include "stock/Stock.hxx"
#include "stock/Class.hxx"
#include "stock/Item.hxx"
#include "access_log/ChildErrorLog.hxx"
#include "spawn/ChildOptions.hxx"
#include "spawn/ExitListener.hxx"
#include "spawn/Interface.hxx"
#include "pool/tpool.hxx"
#include "pool/StringBuilder.hxx"
#include "event/SocketEvent.hxx"
#include "event/TimerEvent.hxx"
#include "net/log/Datagram.hxx"
#include "io/Logger.hxx"
#include "util/Cancellable.hxx"
#include "util/ConstBuffer.hxx"
#include "util/Cast.hxx"

#include <was/protocol.h>

#include <string>

#include <assert.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <signal.h>
#include <stdlib.h>

static constexpr Event::Duration was_idle_timeout = std::chrono::minutes(5);

struct WasChildParams {
    const char *executable_path;

    ConstBuffer<const char *> args;

    const ChildOptions &options;

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

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

class WasChild final : public StockItem, ExitListener {
    const LLogger logger;

    SpawnService &spawn_service;

    const std::string tag;

    const SocketDescriptor log_socket;
    ChildErrorLog log;

    WasProcess process;
    SocketEvent event;
    TimerEvent idle_timeout_event;

    /**
     * If true, then we're waiting for PREMATURE (after the #WasClient
     * has sent #WAS_COMMAND_STOP).
     */
    bool stopping = false;

    /**
     * The number of bytes received before #WAS_COMMAND_STOP was sent.
     */
    uint64_t input_received;

public:
    explicit WasChild(CreateStockItem c, SpawnService &_spawn_service,
                      const char *_tag, SocketDescriptor _log_socket) noexcept
        :StockItem(c), logger(GetStockName()), spawn_service(_spawn_service),
         tag(_tag != nullptr ? _tag : ""),
         log_socket(_log_socket),
         event(c.stock.GetEventLoop(), BIND_THIS_METHOD(EventCallback)),
         idle_timeout_event(c.stock.GetEventLoop(),
                            BIND_THIS_METHOD(OnIdleTimeout))
    {
        /* mark this object as "unused" so the destructor doesn't
           attempt to kill the process */
        process.pid = -1;
    }

    ~WasChild() noexcept override;

    auto &GetEventLoop() noexcept {
        return event.GetEventLoop();
    }

    bool IsTag(const char *other_tag) const noexcept {
        return tag == other_tag;
    }

    /**
     * Throws on error.
     */
    void Launch(const WasChildParams &params) {
        process = was_launch(spawn_service,
                             GetStockName(),
                             params.executable_path,
                             params.args,
                             params.options,
                             log.EnableClient(GetEventLoop(), log_socket),
                             this);
        event.Open(process.control);
    }

    void SetSite(const char *_site) noexcept {
        log.SetSite(_site);
    }

    void SetUri(const char *_uri) noexcept {
        log.SetUri(_uri);
    }

    const WasProcess &GetProcess() const noexcept {
        return process;
    }

    void Stop(uint64_t _received) noexcept {
        assert(!is_idle);
        assert(!stopping);

        stopping = true;
        input_received = _received;
    }

private:
    enum class ReceiveResult {
        SUCCESS, ERROR, AGAIN,
    };

    /**
     * Receive data on the control channel.
     */
    ReceiveResult ReceiveControl(void *p, size_t size) noexcept;

    /**
     * Receive and discard data on the control channel.
     *
     * @return true on success
     */
    bool DiscardControl(size_t size) noexcept;

    /**
     * Discard the given amount of data from the input pipe.
     *
     * @return true on success
     */
    bool DiscardInput(uint64_t remaining) noexcept;

    /**
     * Attempt to recover after the WAS client sent STOP to the
     * application.  This method waits for PREMATURE and discards
     * excess data from the pipe.
     */
    void RecoverStop() noexcept;

    void EventCallback(unsigned events) noexcept;
    void OnIdleTimeout() noexcept;

public:
    /* virtual methods from class StockItem */
    bool Borrow() noexcept override {
        if (stopping)
            /* we havn't yet recovered from #WAS_COMMAND_STOP - give
               up this child process */
            // TODO: improve recovery for this case
            return false;

        event.Cancel();
        idle_timeout_event.Cancel();
        return true;
    }

    bool Release() noexcept override {
        event.ScheduleRead();
        idle_timeout_event.Schedule(was_idle_timeout);
        unclean = stopping;
        return true;
    }

private:
    /* virtual methods from class ExitListener */
    void OnChildProcessExit(gcc_unused int status) noexcept override {
        process.pid = -1;
    }
};

const char *
WasChildParams::GetStockKey(struct pool &pool) const noexcept
{
    PoolStringBuilder<256> b;
    b.push_back(executable_path);

    for (auto i : args) {
        b.push_back(" ");
        b.push_back(i);
    }

    for (auto i : options.env) {
        b.push_back("$");
        b.push_back(i);
    }

    char options_buffer[16384];
    b.emplace_back(options_buffer,
                   options.MakeId(options_buffer));

    return b(pool);
}

WasChild::ReceiveResult
WasChild::ReceiveControl(void *p, size_t size) noexcept
{
    ssize_t nbytes = recv(process.control.Get(), p, size, MSG_DONTWAIT);
    if (nbytes == (ssize_t)size)
        return ReceiveResult::SUCCESS;

    if (nbytes < 0 && errno == EAGAIN) {
        /* the WAS application didn't send enough data (yet); don't
           bother waiting for more, just give up on this process */
        return ReceiveResult::AGAIN;
    }

    if (nbytes < 0)
        logger(2, "error on idle WAS control connection: ", strerror(errno));
    else if (nbytes > 0)
        logger(2, "unexpected data from idle WAS control connection");
    return ReceiveResult::ERROR;
}

bool
WasChild::DiscardControl(size_t size) noexcept
{
    while (size > 0) {
        char buffer[1024];
        ssize_t nbytes = recv(process.control.Get(), buffer,
                              std::min(size, sizeof(buffer)),
                              MSG_DONTWAIT);
        if (nbytes <= 0)
            return false;

        size -= nbytes;
    }

    return true;
}

inline bool
WasChild::DiscardInput(uint64_t remaining) noexcept
{
    while (remaining > 0) {
        uint8_t buffer[16384];
        size_t size = std::min(remaining, uint64_t(sizeof(buffer)));
        ssize_t nbytes = process.input.Read(buffer, size);
        if (nbytes <= 0)
            return false;

        remaining -= nbytes;
    }

    return true;
}

inline void
WasChild::RecoverStop() noexcept
{
    uint64_t premature;

    while (true) {
        struct was_header header;
        switch (ReceiveControl(&header, sizeof(header))) {
        case ReceiveResult::SUCCESS:
            break;

        case ReceiveResult::ERROR:
            InvokeIdleDisconnect();
            return;

        case ReceiveResult::AGAIN:
            /* wait for more data */
            return;
        }

        switch ((enum was_command)header.command) {
        case WAS_COMMAND_NOP:
            /* ignore */
            continue;

        case WAS_COMMAND_HEADER:
        case WAS_COMMAND_STATUS:
        case WAS_COMMAND_NO_DATA:
        case WAS_COMMAND_DATA:
        case WAS_COMMAND_LENGTH:
        case WAS_COMMAND_STOP:
            /* discard & ignore */
            if (!DiscardControl(header.length)) {
                InvokeIdleDisconnect();
                return;
            }
            continue;

        case WAS_COMMAND_REQUEST:
        case WAS_COMMAND_METHOD:
        case WAS_COMMAND_URI:
        case WAS_COMMAND_SCRIPT_NAME:
        case WAS_COMMAND_PATH_INFO:
        case WAS_COMMAND_QUERY_STRING:
        case WAS_COMMAND_PARAMETER:
            logger(2, "unexpected data from idle WAS control connection '",
                      GetStockName(), "'");
            InvokeIdleDisconnect();
            return;

        case WAS_COMMAND_PREMATURE:
            /* this is what we're waiting for */
            break;
        }

        if (ReceiveControl(&premature, sizeof(premature)) != ReceiveResult::SUCCESS) {
            InvokeIdleDisconnect();
            return;
        }

        break;
    }

    if (premature < input_received) {
        InvokeIdleDisconnect();
        return;
    }

    if (!DiscardInput(premature - input_received)) {
        InvokeIdleDisconnect();
        return;
    }

    stopping = false;
    unclean = false;
}

/*
 * libevent callback
 *
 */

inline void
WasChild::EventCallback(unsigned) noexcept
{
    if (stopping) {
        RecoverStop();
        return;
    }

    char buffer;
    ssize_t nbytes = recv(process.control.Get(), &buffer, sizeof(buffer),
                          MSG_DONTWAIT);
    if (nbytes < 0)
        logger(2, "error on idle WAS control connection: ",
               strerror(errno));
    else if (nbytes > 0)
        logger(2, "unexpected data from idle WAS control connection");

    InvokeIdleDisconnect();
}

inline void
WasChild::OnIdleTimeout() noexcept
{
    InvokeIdleDisconnect();
}

/*
 * stock class
 *
 */

class WasStock final : StockClass {
    SpawnService &spawn_service;
    const SocketDescriptor log_socket;
    StockMap stock;

public:
    explicit WasStock(EventLoop &event_loop, SpawnService &_spawn_service,
                      const SocketDescriptor _log_socket,
                      unsigned limit, unsigned max_idle) noexcept
        :spawn_service(_spawn_service),
         log_socket(_log_socket),
         stock(event_loop, *this, limit, max_idle) {}

    StockMap &GetStock() noexcept {
        return stock;
    }

    void FadeTag(const char *tag) noexcept {
        stock.FadeIf([tag](const StockItem &item){
                const auto &child = (const WasChild &)item;
                return child.IsTag(tag);
            });
    }

private:
    /* virtual methods from class StockClass */
    void Create(CreateStockItem c, void *info, struct pool &caller_pool,
                CancellablePointer &cancel_ptr) override;
};

void
WasStock::Create(CreateStockItem c,
                 void *info,
                 gcc_unused struct pool &caller_pool,
                 gcc_unused CancellablePointer &cancel_ptr)
{
    WasChildParams *params = (WasChildParams *)info;

    assert(params != nullptr);
    assert(params->executable_path != nullptr);

    auto *child = new WasChild(c, spawn_service, params->options.tag,
                               log_socket);

    try {
        child->Launch(*params);
    } catch (...) {
        delete child;
        throw;
    }

    child->InvokeCreateSuccess();
}

WasChild::~WasChild() noexcept
{
    if (process.pid >= 0)
        spawn_service.KillChildProcess(process.pid);
}

/*
 * interface
 *
 */

StockMap *
was_stock_new(unsigned limit, unsigned max_idle,
              EventLoop &event_loop, SpawnService &spawn_service,
              SocketDescriptor log_socket) noexcept
{
    auto *stock = new WasStock(event_loop, spawn_service, log_socket,
                               limit, max_idle);
    return &stock->GetStock();
}

void
was_stock_free(StockMap *_stock) noexcept
{
    auto *stock = (WasStock *)&_stock->GetClass();
    delete stock;
}

void
was_stock_fade_tag(StockMap &_stock, const char *tag) noexcept
{
    auto &stock = (WasStock &)_stock.GetClass();
    stock.FadeTag(tag);
}

void
was_stock_get(StockMap *hstock, struct pool *pool,
              const ChildOptions &options,
              const char *executable_path,
              ConstBuffer<const char *> args,
              StockGetHandler &handler,
              CancellablePointer &cancel_ptr) noexcept
{
    const AutoRewindPool auto_rewind(*tpool);

    auto params = NewFromPool<WasChildParams>(*pool, executable_path, args,
                                              options);

    hstock->Get(*pool, params->GetStockKey(*tpool), params,
                handler, cancel_ptr);
}

void
was_stock_item_set_site(StockItem &item, const char *site) noexcept
{
    auto &child = (WasChild &)item;
    child.SetSite(site);
}

void
was_stock_item_set_uri(StockItem &item, const char *uri) noexcept
{
    auto &child = (WasChild &)item;
    child.SetUri(uri);
}

const WasProcess &
was_stock_item_get(const StockItem &item) noexcept
{
    auto *child = (const WasChild *)&item;

    return child->GetProcess();
}

void
was_stock_item_stop(StockItem &item, uint64_t received) noexcept
{
    auto &child = (WasChild &)item;
    child.Stop(received);
}
<|endoftext|>


<commit_before>#include <cstring>
#include <string>
#include <map>
#include <set>

#include <unistd.h>
#include <fcntl.h>
#include <sys/socket.h>
#include <netinet/in.h>

#include "webserver.h"

#include "global.h"
#include "judge.h"
#include "scoreboard.h"

using namespace std;

struct Request {
  string line;
  map<string, string> headers;
  Request(int sd) {
    // read socket
    vector<char> buf; {
      char c;
      for (bool done = false; !done;) {
        while (!done && read(sd, &c, 1) == 1) {
          buf.push_back(c);
          if (c == '\n' && buf.size() >= 4 && buf[buf.size()-4] == '\r') {
            done = true;
          }
        }
      }
    }
    int i = 0;

    // parse request line
    for (; buf[i] != '\r'; i++) line += buf[i];
    i += 2;

    // parse headers
    while (true) {
      string line;
      for (; buf[i] != '\r'; i++) line += buf[i];
      i += 2;
      if (line == "") break;
      int j = 0;
      string header;
      for (; j < int(line.size()) && line[j] != ':'; j++) header += line[j];
      string content;
      for (j += 2; j < int(line.size()); j++) content += line[j];
      headers[header] = content;
    }
  }
};

struct Client {
  sockaddr_in addr;
  int sd;
};

static pthread_mutex_t login_mutex = PTHREAD_MUTEX_INITIALIZER;
static map<in_addr_t, pair<string, string>> teambyip;
static map<string, set<in_addr_t>> teambylogin;

static void showlogin(int sd) {
  string form =
    "HTTP/1.1 200 OK\r\n"
    "Connection: close\r\r"
    "Content-Type: text/html\r\n"
    "\r\n"
    "\n"
    "  \n"
    "    \n"
    "  \n"
    "  \n"
    "
Pimenta Judgezzz~*~*
\n"
    "
Login
\n"
    "

\n"
    "
\n"
    "
\n"
    "        Team:	\n"
    "        <input type=\"text\" id=\"team\" autofocus/>	\n"
    "
\n"
    "
\n"
    "        Password:	\n"
    "        <input type=\"password\" id=\"pass\" />	\n"
    "
\n"
    "
<button onclick=\"sendform()\">Login</button>
\n"
    "
\n"
    "  \n"
    "\n"
  ;
  write(sd, form.c_str(), form.size());
}

static string login(const string& team, const string& password) {
  FILE* fp = fopen("teams.txt", "r");
  if (!fp) return "";
  string name;
  for (fgetc(fp); name == "" && !feof(fp); fgetc(fp)) {
    string ntmp;
    for (char c = fgetc(fp); c != '"'; c = fgetc(fp)) ntmp += c;
    fgetc(fp);
    string tmp;
    for (char c = fgetc(fp); c != ' '; c = fgetc(fp)) tmp += c;
    string pass;
    for (char c = fgetc(fp); c != '\n'; c = fgetc(fp)) pass += c;
    if (team == tmp && password == pass) name = ntmp;
  }
  fclose(fp);
  return name;
}

static void* zenity(void* ptr) {
  string* sptr = (string*)ptr;
  system("zenity --warning --text=\"%s\"", sptr->c_str());
  delete sptr;
  return nullptr;
}

static void statement(int sd) {
  Settings settings;
  string response;
  bool send = false;
  if (time(nullptr) < settings.begin) {
    response =
      "HTTP/1.1 403 Forbidden\r\n"
      "Connection: close\r\r"
      "\r\n"
    ;
  }
  else {
    response =
      "HTTP/1.1 200 OK\r\n"
      "Connection: close\r\r"
      "Content-Type: application/octet-stream\r\n"
      "Content-Disposition: attachment; filename=\"contest.pdf\"\r\n"
      "\r\n"
    ;
    send = true;
  }
  write(sd, response.c_str(), response.size());
  if (!send) return;
  FILE* fp = fopen("contest.pdf", "rb");
  if (!fp) return;
  char* buf = new char[1<<20];
  for (size_t rd = 0; (rd = fread(buf, 1, 1 << 20, fp)) > 0;) {
    write(sd, buf, rd);
  }
  delete[] buf;
  fclose(fp);
}

static void* client(void* ptr) {
  Client* cptr = (Client*)ptr;
  Request req(cptr->sd);

  // login
  pthread_mutex_lock(&login_mutex);
  if (teambyip.find(cptr->addr.sin_addr.s_addr) == teambyip.end()) {
    if (req.line[0] == 'P' && req.line.find("login") != string::npos) {
      string teamname = login(req.headers["Team"], req.headers["Password"]);
      if (teamname == "") write(cptr->sd, "Invalid team/password!", 22);
      else {
        auto& ips = teambylogin[req.headers["Team"]];
        ips.insert(cptr->addr.sin_addr.s_addr);
        if (ips.size() > 1) {
          string msg =
            "team "+req.headers["Team"]+" logging with multiple ips:\n"
          ;
          for (in_addr_t x : ips) msg += (to<string>(x)+"\n");
          pthread_t thread;
          pthread_create(&thread, nullptr, zenity, new string(msg));
          pthread_detach(thread);
        }
        teambyip[cptr->addr.sin_addr.s_addr].first = teamname;
        teambyip[cptr->addr.sin_addr.s_addr].second = req.headers["Team"];
        write(cptr->sd, "k", 1);
      }
    }
    else showlogin(cptr->sd);
    pthread_mutex_unlock(&login_mutex);
  }
  // logout
  else if (req.line.find("logout") != string::npos) {
    teambyip.erase(cptr->addr.sin_addr.s_addr);
    pthread_mutex_unlock(&login_mutex);
    write(cptr->sd, "Logged out.", 11);
  }
  else {
    auto& team = teambyip[cptr->addr.sin_addr.s_addr];
    pthread_mutex_unlock(&login_mutex);

    // fetch uri
    stringstream ss;
    ss << req.line;
    string uri;
    ss >> uri;
    ss >> uri;

    // post
    if (req.line[0] == 'P') {
      if (uri.find("submission") != string::npos) {
        Judge::attempt(
          cptr->sd,
          team.first, team.second,
          req.headers["File-name"], to<int>(req.headers["File-size"])
        );
      }
      else {
        //TODO clarifs.
      }
    }
    // data request
    else if (uri.find("?") != string::npos) {
      if (uri.find("teamname") != string::npos) {
        write(cptr->sd, team.first.c_str(), team.first.size());
      }
      else if (uri.find("scoreboard") != string::npos) {
        Scoreboard::send(cptr->sd);
      }
      else if (uri.find("statement") != string::npos) {
        statement(cptr->sd);
      }
    }
    // file request
    else {
      string ctype;
      if (uri.find(".css") != string::npos) {
        ctype = "text/css";
      }
      else if (uri.find(".js") != string::npos) {
        ctype = "application/javascript";
      }
      else if (uri.find(".gif") != string::npos) {
        ctype = "application/octet-stream";
      }
      else {
        ctype = "text/html";
        uri = "/index.html";
      }
      if (ctype != "") {
        string header =
          "HTTP/1.1 200 OK\r\n"
          "Connection: close\r\r"
          "Content-Type: "+ctype+"\r\n"
          "\r\n"
        ;
        FILE* fp = fopen(("www"+uri).c_str(), "rb");
        if (fp) {
          char* buf = new char[1<<10];
          int sz;
          write(cptr->sd, header.c_str(), header.size());
          while ((sz = fread(buf, 1, 1<<10, fp)) > 0) write(cptr->sd, buf, sz);
          delete[] buf;
          fclose(fp);
        }
      }
    }
  }

  close(cptr->sd);
  delete cptr;
  return nullptr;
}

static void* server(void*) {
  // create socket
  int sd = socket(AF_INET, SOCK_STREAM, 0);
  int opt = 1;
  setsockopt(sd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof opt);
  fcntl(sd, F_SETFL, FNDELAY);

  // set addr
  sockaddr_in addr;
  memset(&addr, 0, sizeof addr);
  addr.sin_family = AF_INET;
  addr.sin_port = htons(to<uint16_t>(Global::arg[2]));
  addr.sin_addr.s_addr = htonl(INADDR_ANY);
  bind(sd, (sockaddr*)&addr, sizeof addr);

  // listen
  listen(sd, SOMAXCONN);
  while (Global::alive()) {
    socklen_t alen;
    Client c;
    c.sd = accept(sd, (sockaddr*)&c.addr, &alen);
    if (c.sd < 0) { usleep(25000); continue; }
    pthread_t thread;
    pthread_create(&thread, nullptr, client, new Client(c));
    pthread_detach(thread);
  }

  // close
  close(sd);
  return nullptr;
}

namespace WebServer {

void fire() {
  Global::fire(server);
}

} // namespace WebServer
<commit_msg>Fixing bug<commit_after>#include <cstring>
#include <string>
#include <map>
#include <set>

#include <unistd.h>
#include <fcntl.h>
#include <sys/socket.h>
#include <netinet/in.h>

#include "webserver.h"

#include "global.h"
#include "judge.h"
#include "scoreboard.h"

using namespace std;

struct Request {
  string line;
  map<string, string> headers;
  Request(int sd) {
    // read socket
    vector<char> buf; {
      char c;
      for (bool done = false; !done;) {
        while (!done && read(sd, &c, 1) == 1) {
          buf.push_back(c);
          if (c == '\n' && buf.size() >= 4 && buf[buf.size()-4] == '\r') {
            done = true;
          }
        }
      }
    }
    int i = 0;

    // parse request line
    for (; buf[i] != '\r'; i++) line += buf[i];
    i += 2;

    // parse headers
    while (true) {
      string line;
      for (; buf[i] != '\r'; i++) line += buf[i];
      i += 2;
      if (line == "") break;
      int j = 0;
      string header;
      for (; j < int(line.size()) && line[j] != ':'; j++) header += line[j];
      string content;
      for (j += 2; j < int(line.size()); j++) content += line[j];
      headers[header] = content;
    }
  }
};

struct Client {
  sockaddr_in addr;
  int sd;
};

static pthread_mutex_t login_mutex = PTHREAD_MUTEX_INITIALIZER;
static map<in_addr_t, pair<string, string>> teambyip;
static map<string, set<in_addr_t>> teambylogin;

static void showlogin(int sd) {
  string form =
    "HTTP/1.1 200 OK\r\n"
    "Connection: close\r\r"
    "Content-Type: text/html\r\n"
    "\r\n"
    "\n"
    "  \n"
    "    \n"
    "  \n"
    "  \n"
    "
Pimenta Judgezzz~*~*
\n"
    "
Login
\n"
    "

\n"
    "
\n"
    "
\n"
    "        Team:	\n"
    "        <input type=\"text\" id=\"team\" autofocus/>	\n"
    "
\n"
    "
\n"
    "        Password:	\n"
    "        <input type=\"password\" id=\"pass\" />	\n"
    "
\n"
    "
<button onclick=\"sendform()\">Login</button>
\n"
    "
\n"
    "  \n"
    "\n"
  ;
  write(sd, form.c_str(), form.size());
}

static string login(const string& team, const string& password) {
  FILE* fp = fopen("teams.txt", "r");
  if (!fp) return "";
  string name;
  for (fgetc(fp); name == "" && !feof(fp); fgetc(fp)) {
    string ntmp;
    for (char c = fgetc(fp); c != '"'; c = fgetc(fp)) ntmp += c;
    fgetc(fp);
    string tmp;
    for (char c = fgetc(fp); c != ' '; c = fgetc(fp)) tmp += c;
    string pass;
    for (char c = fgetc(fp); c != '\n'; c = fgetc(fp)) pass += c;
    if (team == tmp && password == pass) name = ntmp;
  }
  fclose(fp);
  return name;
}

static void* zenity(void* ptr) {
  string* sptr = (string*)ptr;
  system("zenity --warning --text=\"%s\"", sptr->c_str());
  delete sptr;
  return nullptr;
}

static void statement(int sd) {
  Settings settings;
  string response;
  bool send = false;
  if (time(nullptr) < settings.begin) {
    response =
      "HTTP/1.1 403 Forbidden\r\n"
      "Connection: close\r\r"
      "\r\n"
    ;
  }
  else {
    response =
      "HTTP/1.1 200 OK\r\n"
      "Connection: close\r\r"
      "Content-Type: application/octet-stream\r\n"
      "Content-Disposition: attachment; filename=\"contest.pdf\"\r\n"
      "\r\n"
    ;
    send = true;
  }
  write(sd, response.c_str(), response.size());
  if (!send) return;
  FILE* fp = fopen("contest.pdf", "rb");
  if (!fp) return;
  char* buf = new char[1<<20];
  for (size_t rd = 0; (rd = fread(buf, 1, 1 << 20, fp)) > 0;) {
    write(sd, buf, rd);
  }
  delete[] buf;
  fclose(fp);
}

static void* client(void* ptr) {
  Client* cptr = (Client*)ptr;
  Request req(cptr->sd);

  // login
  pthread_mutex_lock(&login_mutex);
  if (teambyip.find(cptr->addr.sin_addr.s_addr) == teambyip.end()) {
    if (req.line[0] == 'P' && req.line.find("login") != string::npos) {
      string teamname = login(req.headers["Team"], req.headers["Password"]);
      if (teamname == "") write(cptr->sd, "Invalid team/password!", 22);
      else {
        auto& ips = teambylogin[req.headers["Team"]];
        ips.insert(cptr->addr.sin_addr.s_addr);
        if (ips.size() > 1) {
          string msg =
            "team "+req.headers["Team"]+" logging with multiple ips:\n"
          ;
          for (in_addr_t x : ips) msg += (to<string>(x)+"\n");
          pthread_t thread;
          pthread_create(&thread, nullptr, zenity, new string(msg));
          pthread_detach(thread);
        }
        teambyip[cptr->addr.sin_addr.s_addr].first = teamname;
        teambyip[cptr->addr.sin_addr.s_addr].second = req.headers["Team"];
        write(cptr->sd, "k", 1);
      }
    }
    else showlogin(cptr->sd);
    pthread_mutex_unlock(&login_mutex);
  }
  // logout
  else if (req.line.find("logout") != string::npos) {
    teambyip.erase(cptr->addr.sin_addr.s_addr);
    pthread_mutex_unlock(&login_mutex);
    showlogin(cptr->sd);
  }
  else {
    auto& team = teambyip[cptr->addr.sin_addr.s_addr];
    pthread_mutex_unlock(&login_mutex);

    // fetch uri
    stringstream ss;
    ss << req.line;
    string uri;
    ss >> uri;
    ss >> uri;

    // post
    if (req.line[0] == 'P') {
      if (uri.find("submission") != string::npos) {
        Judge::attempt(
          cptr->sd,
          team.first, team.second,
          req.headers["File-name"], to<int>(req.headers["File-size"])
        );
      }
      else {
        //TODO clarifs.
      }
    }
    // data request
    else if (uri.find("?") != string::npos) {
      if (uri.find("teamname") != string::npos) {
        write(cptr->sd, team.first.c_str(), team.first.size());
      }
      else if (uri.find("scoreboard") != string::npos) {
        Scoreboard::send(cptr->sd);
      }
      else if (uri.find("statement") != string::npos) {
        statement(cptr->sd);
      }
    }
    // file request
    else {
      string ctype;
      if (uri.find(".css") != string::npos) {
        ctype = "text/css";
      }
      else if (uri.find(".js") != string::npos) {
        ctype = "application/javascript";
      }
      else if (uri.find(".gif") != string::npos) {
        ctype = "application/octet-stream";
      }
      else {
        ctype = "text/html";
        uri = "/index.html";
      }
      if (ctype != "") {
        string header =
          "HTTP/1.1 200 OK\r\n"
          "Connection: close\r\r"
          "Content-Type: "+ctype+"\r\n"
          "\r\n"
        ;
        FILE* fp = fopen(("www"+uri).c_str(), "rb");
        if (fp) {
          char* buf = new char[1<<10];
          int sz;
          write(cptr->sd, header.c_str(), header.size());
          while ((sz = fread(buf, 1, 1<<10, fp)) > 0) write(cptr->sd, buf, sz);
          delete[] buf;
          fclose(fp);
        }
      }
    }
  }

  close(cptr->sd);
  delete cptr;
  return nullptr;
}

static void* server(void*) {
  // create socket
  int sd = socket(AF_INET, SOCK_STREAM, 0);
  int opt = 1;
  setsockopt(sd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof opt);
  fcntl(sd, F_SETFL, FNDELAY);

  // set addr
  sockaddr_in addr;
  memset(&addr, 0, sizeof addr);
  addr.sin_family = AF_INET;
  addr.sin_port = htons(to<uint16_t>(Global::arg[2]));
  addr.sin_addr.s_addr = htonl(INADDR_ANY);
  bind(sd, (sockaddr*)&addr, sizeof addr);

  // listen
  listen(sd, SOMAXCONN);
  while (Global::alive()) {
    socklen_t alen;
    Client c;
    c.sd = accept(sd, (sockaddr*)&c.addr, &alen);
    if (c.sd < 0) { usleep(25000); continue; }
    pthread_t thread;
    pthread_create(&thread, nullptr, client, new Client(c));
    pthread_detach(thread);
  }

  // close
  close(sd);
  return nullptr;
}

namespace WebServer {

void fire() {
  Global::fire(server);
}

} // namespace WebServer
<|endoftext|>


<commit_before>#include <inventory.h>
#include <entities.h>
#include <ui.h>

#define ITEM_COUNT 5	// Total number of items that actually exist

extern Player *player;
extern GLuint invUI;

static const Item item[ITEM_COUNT]= {
	#include "../config/items.h"
};

static GLuint itemtex[ITEM_COUNT];

void itemDraw(Player *p,ITEM_ID id);

void initInventorySprites(void){
	unsigned int i;
	for(i = 0;i < ITEM_COUNT;i++){
		itemtex[i] = Texture::loadTexture(getItemTexturePath((ITEM_ID)i));
	}
}

char *getItemTexturePath(ITEM_ID id){
	return item[id].textureLoc;
}

Item::Item(ITEM_ID i, const char *n, ITEM_TYPE t, float w, float h, int m, const char *tl){
	id = i;
	type = t;
	width = w;
	height = h;
	maxStackSize = m;

	name 		= new char[strlen(n)+1];
	textureLoc 	= new char[strlen(tl)+1];

	strcpy(name,n);
	strcpy(textureLoc,tl);

	//tex= new Texturec(1,textureLoc);
}

Inventory::Inventory(unsigned int s){
	sel=0;
	size=s;
	inv = new struct item_t[size];
	memset(inv,0,size*sizeof(struct item_t));
}

Inventory::~Inventory(void){
	delete[] inv;
}

void Inventory::setSelection(unsigned int s){
	sel=s;
}

int Inventory::addItem(ITEM_ID id,unsigned char count){
	//std::cout << id << "," << inv[os].id << std::endl;

	for(unsigned int i = 0; i < size; i++){
		if(id == inv[i].id){
			inv[i].count += count;
			break;
		}else if(id){
			inv[os].id = id;
			inv[os].count = count;
			os++;
			break;
		}
	}

#ifdef DEBUG
	DEBUG_printf("Gave player %u more %s(s)(ID: %d).\n",count,item[id].name,item[id].id);
#endif // DEBUG

	return 0;
}

int Inventory::takeItem(ITEM_ID id,unsigned char count){
	for(unsigned int i = 0;i < size;i++){
		if(inv[i].id == id){
#ifdef DEBUG
			DEBUG_printf("Took %u of player's %s(s).\n",count,item[i].name);
#endif // DEBUG
			inv[i].count-=count;
			return 0;
		}
	}
	return -1;
}

void Inventory::draw(void){
	static unsigned int lop = 0;
	static unsigned int numSlot = 7;
	static std::vector<int>dfp(numSlot);
	static std::vector<Ray>iray(numSlot);
	static std::vector<vec2>curCoord(numSlot);
	static int range = 200;
	static int itemWide = 45;
	float angleB = (float)180/(float)numSlot;
	float angle = float(angleB/2.0f);
	unsigned int a = 0;
	unsigned int end = 0;
	static vec2 mouseStart = {0,0};
	for(auto &r : iray){
		r.start.x = player->loc.x + (player->width/2);
		r.start.y = player->loc.y + (player->height/2);
		curCoord[a] = r.start;
		//dfp[a] = 0;
		a++;
	}a=0;
	if(invOpening){
		end = 0;
		for(auto &d : dfp){
			if(a != 0){
				if(dfp[a-1]>50)d+=1.65*deltaTime;
			}else{
				d += 1.65*deltaTime;
			}
			if(d >= range)
				d = range;
			a++;
		}a=0;
		if(end < numSlot)invOpen=true;
	}else if(!invOpening){
		for(auto &d : dfp){
			if(d > 0){
				d-=1.65*deltaTime;
			}else end++;
		}
		if(end >= numSlot)invOpen=false;
	}
	if(invOpen){
		for(auto &r : iray){
			angle=180-(angleB*a) - angleB/2.0f;
			curCoord[a].x += float((dfp[a]) * cos(angle*PI/180));
			curCoord[a].y += float((dfp[a]) * sin(angle*PI/180));
			r.end = curCoord[a];

			glColor4f(0.0f, 0.0f, 0.0f, ((float)dfp[a]/(float)range)*0.4f);
 			glBegin(GL_QUADS);
				glVertex2i(r.end.x-(itemWide/2),			r.end.y-(itemWide/2));
				glVertex2i(r.end.x-(itemWide/2)+itemWide,	r.end.y-(itemWide/2));
				glVertex2i(r.end.x-(itemWide/2)+itemWide,	r.end.y-(itemWide/2)+itemWide);
				glVertex2i(r.end.x-(itemWide/2),			r.end.y-(itemWide/2)+itemWide);
			glEnd();

			if(inv[a].count > 0){
				glEnable(GL_TEXTURE_2D);
				glBindTexture(GL_TEXTURE_2D, itemtex[inv[a].id]);
				glColor4f(1.0f, 1.0f, 1.0f, ((float)dfp[a]/(float)range)*0.8f);
	 			glBegin(GL_QUADS);
					glTexCoord2i(0,1);glVertex2i(r.end.x-(itemWide/2),			r.end.y-(itemWide/2));
					glTexCoord2i(1,1);glVertex2i(r.end.x-(itemWide/2)+itemWide,	r.end.y-(itemWide/2));
					glTexCoord2i(1,0);glVertex2i(r.end.x-(itemWide/2)+itemWide,	r.end.y-(itemWide/2)+itemWide);
					glTexCoord2i(0,0);glVertex2i(r.end.x-(itemWide/2),			r.end.y-(itemWide/2)+itemWide);
				glEnd();
				glDisable(GL_TEXTURE_2D);
				ui::putText(r.end.x-(itemWide/2)+(itemWide*.85),r.end.y-(itemWide/1.75),"%d",inv[a].count);
			}
			a++;
		}
	}else if(invHover){
		static int highlight = 0;
		std::cout << ui::mouse.x << "," << mouseStart.x  << "," << mouseSel << std::endl;
		if(!mouseSel){
			mouseStart.x = ui::mouse.x;
			highlight = sel;
			mouseSel=true;
		}else{
			if(ui::mouse.x >= mouseStart.x){
				highlight = (ui::mouse.x - mouseStart.x)/45;
				if(highlight>numSlot)highlight=numSlot;
				if(SDL_GetMouseState(NULL, NULL) & SDL_BUTTON(SDL_BUTTON_RIGHT)){
					sel = highlight;
					mouseSel=false;
					invHover=false;
					selected = true;
				}
			}
			if(ui::mouse.x < mouseStart.x){
				highlight = (mouseStart.x - ui::mouse.x)/45;
				if(highlight<0)highlight=0;
				if(SDL_GetMouseState(NULL, NULL) & SDL_BUTTON(SDL_BUTTON_RIGHT)){
					sel = highlight;
					mouseSel=false;
					invHover=false;
					selected = true;
				}
			}
		}
		for(auto &r : iray){
			angle=180-(angleB*a) - angleB/2.0f;
			curCoord[a].x += float(range) * cos(angle*PI/180);
			curCoord[a].y += float(range) * sin(angle*PI/180);
			r.end = curCoord[a];

			glColor4f(0.0f, 0.0f, 0.0f, 0.1f);
 			glBegin(GL_QUADS);
				glVertex2i(r.end.x-(itemWide/2),			r.end.y-(itemWide/2));
				glVertex2i(r.end.x-(itemWide/2)+itemWide,	r.end.y-(itemWide/2));
				glVertex2i(r.end.x-(itemWide/2)+itemWide,	r.end.y-(itemWide/2)+itemWide);
				glVertex2i(r.end.x-(itemWide/2),			r.end.y-(itemWide/2)+itemWide);
			glEnd();

			if(inv[a].count > 0){
				glEnable(GL_TEXTURE_2D);
				glBindTexture(GL_TEXTURE_2D, itemtex[inv[a].id]);
				glColor4f(1.0f, 1.0f, 1.0f, a == highlight ? 0.4f : 0.2f);
	 			glBegin(GL_QUADS);
					glTexCoord2i(0,1);glVertex2i(r.end.x-(itemWide/2),			r.end.y-(itemWide/2));
					glTexCoord2i(1,1);glVertex2i(r.end.x-(itemWide/2)+itemWide,	r.end.y-(itemWide/2));
					glTexCoord2i(1,0);glVertex2i(r.end.x-(itemWide/2)+itemWide,	r.end.y-(itemWide/2)+itemWide);
					glTexCoord2i(0,0);glVertex2i(r.end.x-(itemWide/2),			r.end.y-(itemWide/2)+itemWide);
				glEnd();
				glDisable(GL_TEXTURE_2D);
				ui::putText(r.end.x-(itemWide/2)+(itemWide*.85),r.end.y-(itemWide/1.75),"%d",inv[a].count);
			}
			a++;
		}
	}
	if(inv[sel].count)itemDraw(player,inv[sel].id);
	lop++;
}

void itemDraw(Player *p,ITEM_ID id){
	if(!id)return;
	glEnable(GL_TEXTURE_2D);
	glBindTexture(GL_TEXTURE_2D,itemtex[id]);
	glColor4ub(255,255,255,255);
	glBegin(GL_QUADS);
		glTexCoord2i(0,1);glVertex2f(p->loc.x,				 p->loc.y);
		glTexCoord2i(1,1);glVertex2f(p->loc.x+item[id].width,p->loc.y);
		glTexCoord2i(1,0);glVertex2f(p->loc.x+item[id].width,p->loc.y+item[id].height);
		glTexCoord2i(0,0);glVertex2f(p->loc.x,				 p->loc.y+item[id].height);
	glEnd();
	glDisable(GL_TEXTURE_2D);
}

int Inventory::useItem(void){
	ITEM_ID id = item[inv[sel].id].id;
	switch(id){
	case FLASHLIGHT:
		player->light ^= true;
		break;
	default:
		//ui::dialogBox(item[id].name,NULL,"You cannot use this item.");
		break;
	}
	return 0;
}

<commit_msg>fixes for wall<commit_after>#include <inventory.h>
#include <entities.h>
#include <ui.h>

#define ITEM_COUNT 5	// Total number of items that actually exist

extern Player *player;
extern GLuint invUI;

static const Item item[ITEM_COUNT]= {
	#include "../config/items.h"
};

static GLuint itemtex[ITEM_COUNT];

void itemDraw(Player *p,ITEM_ID id);

void initInventorySprites(void){
	unsigned int i;
	for(i = 0;i < ITEM_COUNT;i++){
		itemtex[i] = Texture::loadTexture(getItemTexturePath((ITEM_ID)i));
	}
}

char *getItemTexturePath(ITEM_ID id){
	return item[id].textureLoc;
}

Item::Item(ITEM_ID i, const char *n, ITEM_TYPE t, float w, float h, int m, const char *tl){
	id = i;
	type = t;
	width = w;
	height = h;
	maxStackSize = m;

	name 		= new char[strlen(n)+1];
	textureLoc 	= new char[strlen(tl)+1];

	strcpy(name,n);
	strcpy(textureLoc,tl);

	//tex= new Texturec(1,textureLoc);
}

Inventory::Inventory(unsigned int s){
	sel=0;
	size=s;
	inv = new struct item_t[size];
	memset(inv,0,size*sizeof(struct item_t));
}

Inventory::~Inventory(void){
	delete[] inv;
}

void Inventory::setSelection(unsigned int s){
	sel=s;
}

int Inventory::addItem(ITEM_ID id,unsigned char count){
	//std::cout << id << "," << inv[os].id << std::endl;

	for(unsigned int i = 0; i < size; i++){
		if(id == inv[i].id){
			inv[i].count += count;
			break;
		}else if(id){
			inv[os].id = id;
			inv[os].count = count;
			os++;
			break;
		}
	}

#ifdef DEBUG
	DEBUG_printf("Gave player %u more %s(s)(ID: %d).\n",count,item[id].name,item[id].id);
#endif // DEBUG

	return 0;
}

int Inventory::takeItem(ITEM_ID id,unsigned char count){
	for(unsigned int i = 0;i < size;i++){
		if(inv[i].id == id){
#ifdef DEBUG
			DEBUG_printf("Took %u of player's %s(s).\n",count,item[i].name);
#endif // DEBUG
			inv[i].count-=count;
			return 0;
		}
	}
	return -1;
}

void Inventory::draw(void){
	static unsigned int lop = 0;
	static unsigned int numSlot = 7;
	static std::vector<int>dfp(numSlot);
	static std::vector<Ray>iray(numSlot);
	static std::vector<vec2>curCoord(numSlot);
	static int range = 200;
	static int itemWide = 45;
	float angleB = (float)180/(float)numSlot;
	float angle = float(angleB/2.0f);
	unsigned int a = 0;
	unsigned int end = 0;
	static vec2 mouseStart = {0,0};
	for(auto &r : iray){
		r.start.x = player->loc.x + (player->width/2);
		r.start.y = player->loc.y + (player->height/2);
		curCoord[a] = r.start;
		//dfp[a] = 0;
		a++;
	}a=0;
	if(invOpening){
		end = 0;
		for(auto &d : dfp){
			if(a != 0){
				if(dfp[a-1]>50)d+=1.65*deltaTime;
			}else{
				d += 1.65*deltaTime;
			}
			if(d >= range)
				d = range;
			a++;
		}a=0;
		if(end < numSlot)invOpen=true;
	}else if(!invOpening){
		for(auto &d : dfp){
			if(d > 0){
				d-=1.65*deltaTime;
			}else end++;
		}
		if(end >= numSlot)invOpen=false;
	}
	if(invOpen){
		for(auto &r : iray){
			angle=180-(angleB*a) - angleB/2.0f;
			curCoord[a].x += float((dfp[a]) * cos(angle*PI/180));
			curCoord[a].y += float((dfp[a]) * sin(angle*PI/180));
			r.end = curCoord[a];

			glColor4f(0.0f, 0.0f, 0.0f, ((float)dfp[a]/(float)range)*0.4f);
 			glBegin(GL_QUADS);
				glVertex2i(r.end.x-(itemWide/2),			r.end.y-(itemWide/2));
				glVertex2i(r.end.x-(itemWide/2)+itemWide,	r.end.y-(itemWide/2));
				glVertex2i(r.end.x-(itemWide/2)+itemWide,	r.end.y-(itemWide/2)+itemWide);
				glVertex2i(r.end.x-(itemWide/2),			r.end.y-(itemWide/2)+itemWide);
			glEnd();

			if(inv[a].count > 0){
				glEnable(GL_TEXTURE_2D);
				glBindTexture(GL_TEXTURE_2D, itemtex[inv[a].id]);
				glColor4f(1.0f, 1.0f, 1.0f, ((float)dfp[a]/(float)range)*0.8f);
	 			glBegin(GL_QUADS);
					glTexCoord2i(0,1);glVertex2i(r.end.x-(itemWide/2),			r.end.y-(itemWide/2));
					glTexCoord2i(1,1);glVertex2i(r.end.x-(itemWide/2)+itemWide,	r.end.y-(itemWide/2));
					glTexCoord2i(1,0);glVertex2i(r.end.x-(itemWide/2)+itemWide,	r.end.y-(itemWide/2)+itemWide);
					glTexCoord2i(0,0);glVertex2i(r.end.x-(itemWide/2),			r.end.y-(itemWide/2)+itemWide);
				glEnd();
				glDisable(GL_TEXTURE_2D);
				ui::putText(r.end.x-(itemWide/2)+(itemWide*.85),r.end.y-(itemWide/1.75),"%d",inv[a].count);
			}
			a++;
		}
	}else if(invHover){
		static unsigned int highlight = 0;
		std::cout << ui::mouse.x << "," << mouseStart.x  << "," << mouseSel << std::endl;
		if(!mouseSel){
			mouseStart.x = ui::mouse.x;
			highlight = sel;
			mouseSel=true;
		}else{
			if(ui::mouse.x >= mouseStart.x){
				highlight = (ui::mouse.x - mouseStart.x)/45;
				if(highlight>numSlot)highlight=numSlot;
				if(SDL_GetMouseState(NULL, NULL) & SDL_BUTTON(SDL_BUTTON_RIGHT)){
					sel = highlight;
					mouseSel=false;
					invHover=false;
					selected = true;
				}
			}
			if(ui::mouse.x < mouseStart.x){
				highlight = (mouseStart.x - ui::mouse.x)/45;
				//if(highlight<0)highlight=0;
				if(SDL_GetMouseState(NULL, NULL) & SDL_BUTTON(SDL_BUTTON_RIGHT)){
					sel = highlight;
					mouseSel=false;
					invHover=false;
					selected = true;
				}
			}
		}
		for(auto &r : iray){
			angle=180-(angleB*a) - angleB/2.0f;
			curCoord[a].x += float(range) * cos(angle*PI/180);
			curCoord[a].y += float(range) * sin(angle*PI/180);
			r.end = curCoord[a];

			glColor4f(0.0f, 0.0f, 0.0f, 0.1f);
 			glBegin(GL_QUADS);
				glVertex2i(r.end.x-(itemWide/2),			r.end.y-(itemWide/2));
				glVertex2i(r.end.x-(itemWide/2)+itemWide,	r.end.y-(itemWide/2));
				glVertex2i(r.end.x-(itemWide/2)+itemWide,	r.end.y-(itemWide/2)+itemWide);
				glVertex2i(r.end.x-(itemWide/2),			r.end.y-(itemWide/2)+itemWide);
			glEnd();

			if(inv[a].count > 0){
				glEnable(GL_TEXTURE_2D);
				glBindTexture(GL_TEXTURE_2D, itemtex[inv[a].id]);
				glColor4f(1.0f, 1.0f, 1.0f, a == highlight ? 0.4f : 0.2f);
	 			glBegin(GL_QUADS);
					glTexCoord2i(0,1);glVertex2i(r.end.x-(itemWide/2),			r.end.y-(itemWide/2));
					glTexCoord2i(1,1);glVertex2i(r.end.x-(itemWide/2)+itemWide,	r.end.y-(itemWide/2));
					glTexCoord2i(1,0);glVertex2i(r.end.x-(itemWide/2)+itemWide,	r.end.y-(itemWide/2)+itemWide);
					glTexCoord2i(0,0);glVertex2i(r.end.x-(itemWide/2),			r.end.y-(itemWide/2)+itemWide);
				glEnd();
				glDisable(GL_TEXTURE_2D);
				ui::putText(r.end.x-(itemWide/2)+(itemWide*.85),r.end.y-(itemWide/1.75),"%d",inv[a].count);
			}
			a++;
		}
	}
	if(inv[sel].count)itemDraw(player,inv[sel].id);
	lop++;
}

void itemDraw(Player *p,ITEM_ID id){
	if(!id)return;
	glEnable(GL_TEXTURE_2D);
	glBindTexture(GL_TEXTURE_2D,itemtex[id]);
	glColor4ub(255,255,255,255);
	glBegin(GL_QUADS);
		glTexCoord2i(0,1);glVertex2f(p->loc.x,				 p->loc.y);
		glTexCoord2i(1,1);glVertex2f(p->loc.x+item[id].width,p->loc.y);
		glTexCoord2i(1,0);glVertex2f(p->loc.x+item[id].width,p->loc.y+item[id].height);
		glTexCoord2i(0,0);glVertex2f(p->loc.x,				 p->loc.y+item[id].height);
	glEnd();
	glDisable(GL_TEXTURE_2D);
}

int Inventory::useItem(void){
	ITEM_ID id = item[inv[sel].id].id;
	switch(id){
	case FLASHLIGHT:
		player->light ^= true;
		break;
	default:
		//ui::dialogBox(item[id].name,NULL,"You cannot use this item.");
		break;
	}
	return 0;
}

<|endoftext|>


<commit_before>/*
===========================================================================
Daemon BSD Source Code
Copyright (c) 2013-2016, Daemon Developers
All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
    * Redistributions of source code must retain the above copyright
      notice, this list of conditions and the following disclaimer.
    * Redistributions in binary form must reproduce the above copyright
      notice, this list of conditions and the following disclaimer in the
      documentation and/or other materials provided with the distribution.
    * Neither the name of the Daemon developers nor the
      names of its contributors may be used to endorse or promote products
      derived from this software without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL DAEMON DEVELOPERS BE LIABLE FOR ANY
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
===========================================================================
*/

#include "CommandBuffer.h"

namespace IPC {

    void CommandBuffer::Init(void* memory, size_t size) {
        if (size < DATA_OFFSET) {
            Sys::Drop("Buffer is too small, size is %i", size);
        }

        this->size = size - DATA_OFFSET;
        base = reinterpret_cast<char*>(memory);
        writerOffset = SAFETY_OFFSET;
        readerOffset = 0;
    }

    void CommandBuffer::Reset() {
        // The writer starts at the safety offset to have the invariant at init.
        reinterpret_cast<SharedWriterData*>(base + WRITER_OFFSET)->offset.store(SAFETY_OFFSET);
        reinterpret_cast<SharedReaderData*>(base + READER_OFFSET)->offset.store(0);
    }

    void CommandBuffer::LoadWriterData() {
        writerOffset = reinterpret_cast<SharedWriterData*>(base + WRITER_OFFSET)->offset.load(std::memory_order_acquire);
        if (writerOffset >= size) {
            Sys::Drop("Invalid writerOffset %i for size %i", writerOffset, size);
        }
    }

    void CommandBuffer::LoadReaderData() {
        readerOffset = reinterpret_cast<SharedReaderData*>(base + READER_OFFSET)->offset.load(std::memory_order_acquire);
        if (readerOffset >= size) {
            Sys::Drop("Invalid readerOffset %i for size %i", readerOffset, size);
        }
    }

    size_t CommandBuffer::GetMaxReadLength() const {
        return FromRead(writerOffset) - SAFETY_OFFSET;
    }

    size_t CommandBuffer::GetMaxWriteLength() const {
        return FromWrite(readerOffset) - SAFETY_OFFSET;
    }

    bool CommandBuffer::CanRead(size_t len) const {
        return FromRead(writerOffset) - SAFETY_OFFSET >= len;
    }

    bool CommandBuffer::CanWrite(size_t len) const {
        return FromWrite(readerOffset) - SAFETY_OFFSET >= len;
    }

    void CommandBuffer::Read(char* out, size_t len, size_t offset) {
        // Add an additional SAFETY_OFFSET to catch what the write pointer has written.
        InternalRead(readerOffset + offset + SAFETY_OFFSET, out, len);
    }

    void CommandBuffer::Write(const char* in, size_t len, size_t offset) {
        InternalWrite(writerOffset + offset, in, len);
    }

    void CommandBuffer::AdvanceReadPointer(size_t offset) {
        // TODO assert that offset is < size
        // Realign the offset to be a multiple of 4
        offset = (offset + 3) & ~3;
        readerOffset = Normalize(readerOffset + offset);
        reinterpret_cast<SharedReaderData*>(base + READER_OFFSET)->offset.store(readerOffset, std::memory_order_release);
    }

    void CommandBuffer::AdvanceWritePointer(size_t offset) {
        // TODO assert that offset is < size
        // Realign the offset to be a multiple of 4
        offset = (offset + 3) & ~3;
        writerOffset = Normalize(writerOffset + offset);
        reinterpret_cast<SharedWriterData*>(base + WRITER_OFFSET)->offset.store(writerOffset, std::memory_order_release);
    }

    size_t CommandBuffer::GetSize() const {
        return size;
    }

    size_t CommandBuffer::Normalize(size_t offset) const {
        if (offset >= size) {
            offset -= size;
        }
        if (offset >= size) {
            offset -= size;
        }
        //TODO assert < size
        return offset;
    }

    size_t CommandBuffer::FromRead(size_t offset) const {
        if (offset < readerOffset) {
            offset += size;
        }
        return offset - readerOffset;
    }

    size_t CommandBuffer::FromWrite(size_t offset) const {
        if (offset < writerOffset) {
            offset += size;
        }
        return offset - writerOffset;
    }

    void CommandBuffer::InternalRead(size_t offset, char* out, size_t len) {
        char* data = base + DATA_OFFSET;
        offset = Normalize(offset);
        size_t canRead = size - offset;
        if (len >= canRead) {
            memcpy(out, data + offset, canRead);
            len -= canRead;
            out += canRead;
            memcpy(out, data, len);
        } else {
            memcpy(out, data + offset, len);
        }
    }

    void CommandBuffer::InternalWrite(size_t offset, const char* in, size_t len) {
        char* data = base + DATA_OFFSET;
        offset = Normalize(offset);
        size_t canWrite = size - offset;
        if (len >= canWrite) {
            memcpy(data + offset, in, canWrite);
            len -= canWrite;
            in += canWrite;
            memcpy(data, in, len);
        } else {
            memcpy(data + offset, in, len);
        }
    }

} // namespace IPC
<commit_msg>[fmt] Use correct format specifier for size<commit_after>/*
===========================================================================
Daemon BSD Source Code
Copyright (c) 2013-2016, Daemon Developers
All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
    * Redistributions of source code must retain the above copyright
      notice, this list of conditions and the following disclaimer.
    * Redistributions in binary form must reproduce the above copyright
      notice, this list of conditions and the following disclaimer in the
      documentation and/or other materials provided with the distribution.
    * Neither the name of the Daemon developers nor the
      names of its contributors may be used to endorse or promote products
      derived from this software without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL DAEMON DEVELOPERS BE LIABLE FOR ANY
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
===========================================================================
*/

#include "CommandBuffer.h"

namespace IPC {

    void CommandBuffer::Init(void* memory, size_t size) {
        if (size < DATA_OFFSET) {
            Sys::Drop("Buffer is too small, size is %u", (unsigned)size);
        }

        this->size = size - DATA_OFFSET;
        base = reinterpret_cast<char*>(memory);
        writerOffset = SAFETY_OFFSET;
        readerOffset = 0;
    }

    void CommandBuffer::Reset() {
        // The writer starts at the safety offset to have the invariant at init.
        reinterpret_cast<SharedWriterData*>(base + WRITER_OFFSET)->offset.store(SAFETY_OFFSET);
        reinterpret_cast<SharedReaderData*>(base + READER_OFFSET)->offset.store(0);
    }

    void CommandBuffer::LoadWriterData() {
        writerOffset = reinterpret_cast<SharedWriterData*>(base + WRITER_OFFSET)->offset.load(std::memory_order_acquire);
        if (writerOffset >= size) {
            Sys::Drop("Invalid writerOffset %i for size %i", writerOffset, size);
        }
    }

    void CommandBuffer::LoadReaderData() {
        readerOffset = reinterpret_cast<SharedReaderData*>(base + READER_OFFSET)->offset.load(std::memory_order_acquire);
        if (readerOffset >= size) {
            Sys::Drop("Invalid readerOffset %i for size %i", readerOffset, size);
        }
    }

    size_t CommandBuffer::GetMaxReadLength() const {
        return FromRead(writerOffset) - SAFETY_OFFSET;
    }

    size_t CommandBuffer::GetMaxWriteLength() const {
        return FromWrite(readerOffset) - SAFETY_OFFSET;
    }

    bool CommandBuffer::CanRead(size_t len) const {
        return FromRead(writerOffset) - SAFETY_OFFSET >= len;
    }

    bool CommandBuffer::CanWrite(size_t len) const {
        return FromWrite(readerOffset) - SAFETY_OFFSET >= len;
    }

    void CommandBuffer::Read(char* out, size_t len, size_t offset) {
        // Add an additional SAFETY_OFFSET to catch what the write pointer has written.
        InternalRead(readerOffset + offset + SAFETY_OFFSET, out, len);
    }

    void CommandBuffer::Write(const char* in, size_t len, size_t offset) {
        InternalWrite(writerOffset + offset, in, len);
    }

    void CommandBuffer::AdvanceReadPointer(size_t offset) {
        // TODO assert that offset is < size
        // Realign the offset to be a multiple of 4
        offset = (offset + 3) & ~3;
        readerOffset = Normalize(readerOffset + offset);
        reinterpret_cast<SharedReaderData*>(base + READER_OFFSET)->offset.store(readerOffset, std::memory_order_release);
    }

    void CommandBuffer::AdvanceWritePointer(size_t offset) {
        // TODO assert that offset is < size
        // Realign the offset to be a multiple of 4
        offset = (offset + 3) & ~3;
        writerOffset = Normalize(writerOffset + offset);
        reinterpret_cast<SharedWriterData*>(base + WRITER_OFFSET)->offset.store(writerOffset, std::memory_order_release);
    }

    size_t CommandBuffer::GetSize() const {
        return size;
    }

    size_t CommandBuffer::Normalize(size_t offset) const {
        if (offset >= size) {
            offset -= size;
        }
        if (offset >= size) {
            offset -= size;
        }
        //TODO assert < size
        return offset;
    }

    size_t CommandBuffer::FromRead(size_t offset) const {
        if (offset < readerOffset) {
            offset += size;
        }
        return offset - readerOffset;
    }

    size_t CommandBuffer::FromWrite(size_t offset) const {
        if (offset < writerOffset) {
            offset += size;
        }
        return offset - writerOffset;
    }

    void CommandBuffer::InternalRead(size_t offset, char* out, size_t len) {
        char* data = base + DATA_OFFSET;
        offset = Normalize(offset);
        size_t canRead = size - offset;
        if (len >= canRead) {
            memcpy(out, data + offset, canRead);
            len -= canRead;
            out += canRead;
            memcpy(out, data, len);
        } else {
            memcpy(out, data + offset, len);
        }
    }

    void CommandBuffer::InternalWrite(size_t offset, const char* in, size_t len) {
        char* data = base + DATA_OFFSET;
        offset = Normalize(offset);
        size_t canWrite = size - offset;
        if (len >= canWrite) {
            memcpy(data + offset, in, canWrite);
            len -= canWrite;
            in += canWrite;
            memcpy(data, in, len);
        } else {
            memcpy(data + offset, in, len);
        }
    }

} // namespace IPC
<|endoftext|>


<commit_before>/*
 * Copyright (C) 2015 ScyllaDB
 */

/*
 * This file is part of Scylla.
 *
 * Scylla is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * Scylla is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with Scylla.  If not, see <http://www.gnu.org/licenses/>.
 */

#pragma once

#include "disk_types.hh"
#include "core/enum.hh"
#include "bytes.hh"
#include "gc_clock.hh"
#include "tombstone.hh"
#include "streaming_histogram.hh"
#include "estimated_histogram.hh"
#include "column_name_helper.hh"
#include "sstables/key.hh"
#include "db/commitlog/replay_position.hh"
#include <vector>
#include <unordered_map>
#include <type_traits>

// While the sstable code works with char, bytes_view works with int8_t
// (signed char). Rather than change all the code, let's do a cast.
static inline bytes_view to_bytes_view(const temporary_buffer<char>& b) {
    using byte = bytes_view::value_type;
    return bytes_view(reinterpret_cast<const byte*>(b.get()), b.size());
}

namespace sstables {

struct option {
    disk_string<uint16_t> key;
    disk_string<uint16_t> value;

    template <typename Describer>
    auto describe_type(Describer f) { return f(key, value); }
};

struct filter {
    uint32_t hashes;
    disk_array<uint32_t, uint64_t> buckets;

    template <typename Describer>
    auto describe_type(Describer f) { return f(hashes, buckets); }

    // Create an always positive filter if nothing else is specified.
    filter() : hashes(0), buckets({}) {}
    explicit filter(int hashes, std::deque<uint64_t> buckets) : hashes(hashes), buckets({std::move(buckets)}) {}
};

class index_entry {
    temporary_buffer<char> _key;
    uint64_t _position;
    temporary_buffer<char> _promoted_index;
public:

    bytes_view get_key_bytes() const {
        return to_bytes_view(_key);
    }

    key_view get_key() const {
        return { get_key_bytes() };
    }

    uint64_t position() const {
        return _position;
    }

    index_entry(temporary_buffer<char>&& key, uint64_t position, temporary_buffer<char>&& promoted_index)
        : _key(std::move(key)), _position(position), _promoted_index(std::move(promoted_index)) {}

};

struct summary_entry {
    bytes key;
    uint64_t position;

    key_view get_key() const {
        return { key };
    }

    bool operator==(const summary_entry& x) const {
        return position ==  x.position && key == x.key;
    }
};

// Note: Sampling level is present in versions ka and higher. We ATM only support ka,
// so it's always there. But we need to make this conditional if we ever want to support
// other formats.
struct summary_ka {
    struct header {
        // The minimum possible amount of indexes per group (sampling level)
        uint32_t min_index_interval;
        // The number of entries in the Summary File
        uint32_t size;
        // The memory to be consumed to map the whole Summary into memory.
        uint64_t memory_size;
        // The actual sampling level.
        uint32_t sampling_level;
        // The number of entries the Summary *would* have if the sampling
        // level would be equal to min_index_interval.
        uint32_t size_at_full_sampling;
    } header;
    // The position in the Summary file for each of the indexes.
    // NOTE1 that its actual size is determined by the "size" parameter, not
    // by its preceding size_at_full_sampling
    // NOTE2: They are laid out in *MEMORY* order, not BE.
    // NOTE3: The sizes in this array represent positions in the memory stream,
    // not the file. The memory stream effectively begins after the header,
    // so every position here has to be added of sizeof(header).
    std::deque<uint32_t> positions;   // can be large, so use a deque instead of a vector
    std::deque<summary_entry> entries;

    disk_string<uint32_t> first_key;
    disk_string<uint32_t> last_key;

    // Used to determine when a summary entry should be added based on min_index_interval.
    // NOTE: keys_written isn't part of on-disk format of summary.
    size_t keys_written;

    // NOTE4: There is a structure written by Cassandra into the end of the Summary
    // file, after the field last_key, that we haven't understand yet, but we know
    // that its content isn't related to the summary itself.
    // The structure is basically as follow:
    // struct { disk_string<uint16_t>; uint32_t; uint64_t; disk_string<uint16_t>; }
    // Another interesting fact about this structure is that it is apparently always
    // filled with the same data. It's too early to judge that the data is useless.
    // However, it was tested that Cassandra loads successfully a Summary file with
    // this structure removed from it. Anyway, let's pay attention to it.

    /*
     * Returns total amount of memory used by the summary
     * Similar to origin off heap size
     */
    uint64_t memory_footprint() const {
        return sizeof(summary_entry) * entries.size() + sizeof(uint32_t) * positions.size() + sizeof(*this);
    }

    explicit operator bool() const {
        return entries.size();
    }
};
using summary = summary_ka;

struct metadata {
    virtual ~metadata() {}
};

struct validation_metadata : public metadata {
    disk_string<uint16_t> partitioner;
    double filter_chance;

    size_t serialized_size() {
        return sizeof(uint16_t) + partitioner.value.size() + sizeof(filter_chance);
    }

    template <typename Describer>
    auto describe_type(Describer f) { return f(partitioner, filter_chance); }
};

struct compaction_metadata : public metadata {
    disk_array<uint32_t, uint32_t> ancestors;
    disk_array<uint32_t, uint8_t> cardinality;

    size_t serialized_size() {
        return sizeof(uint32_t) + (ancestors.elements.size() * sizeof(uint32_t)) +
            sizeof(uint32_t) + (cardinality.elements.size() * sizeof(uint8_t));
    }

    template <typename Describer>
    auto describe_type(Describer f) { return f(ancestors, cardinality); }
};

struct ka_stats_metadata : public metadata {
    estimated_histogram estimated_row_size;
    estimated_histogram estimated_column_count;
    db::replay_position position;
    int64_t min_timestamp;
    int64_t max_timestamp;
    int32_t max_local_deletion_time;
    double compression_ratio;
    streaming_histogram estimated_tombstone_drop_time;
    uint32_t sstable_level;
    uint64_t repaired_at;
    disk_array<uint32_t, disk_string<uint16_t>> min_column_names;
    disk_array<uint32_t, disk_string<uint16_t>> max_column_names;
    bool has_legacy_counter_shards;

    template <typename Describer>
    auto describe_type(Describer f) {
        return f(
            estimated_row_size,
            estimated_column_count,
            position,
            min_timestamp,
            max_timestamp,
            max_local_deletion_time,
            compression_ratio,
            estimated_tombstone_drop_time,
            sstable_level,
            repaired_at,
            min_column_names,
            max_column_names,
            has_legacy_counter_shards
        );
    }
};
using stats_metadata = ka_stats_metadata;

// Numbers are found on disk, so they do matter. Also, setting their sizes of
// that of an uint32_t is a bit wasteful, but it simplifies the code a lot
// since we can now still use a strongly typed enum without introducing a
// notion of "disk-size" vs "memory-size".
enum class metadata_type : uint32_t {
    Validation = 0,
    Compaction = 1,
    Stats = 2,
};

static constexpr int DEFAULT_CHUNK_SIZE = 65536;

// checksums are generated using adler32 algorithm.
struct checksum {
    uint32_t chunk_size;
    std::deque<uint32_t> checksums;

    template <typename Describer>
    auto describe_type(Describer f) { return f(chunk_size, checksums); }
};

}

namespace std {

template <>
struct hash<sstables::metadata_type> : enum_hash<sstables::metadata_type> {};

}

namespace sstables {

struct statistics {
    disk_hash<uint32_t, metadata_type, uint32_t> hash;
    std::unordered_map<metadata_type, std::unique_ptr<metadata>> contents;
};

struct deletion_time {
    int32_t local_deletion_time;
    int64_t marked_for_delete_at;

    template <typename Describer>
    auto describe_type(Describer f) { return f(local_deletion_time, marked_for_delete_at); }

    bool live() const {
        return (local_deletion_time == std::numeric_limits<int32_t>::max()) &&
               (marked_for_delete_at == std::numeric_limits<int64_t>::min());
    }

    bool operator==(const deletion_time& d) {
        return local_deletion_time == d.local_deletion_time &&
               marked_for_delete_at == d.marked_for_delete_at;
    }
    bool operator!=(const deletion_time& d) {
        return !(*this == d);
    }
    explicit operator tombstone() {
        return !live() ? tombstone(marked_for_delete_at, gc_clock::time_point(gc_clock::duration(local_deletion_time))) : tombstone();
    }
};

enum class column_mask : uint8_t {
    none = 0x0,
    deletion = 0x01,
    expiration = 0x02,
    counter = 0x04,
    counter_update = 0x08,
    range_tombstone = 0x10,
};

inline column_mask operator&(column_mask m1, column_mask m2) {
    return column_mask(static_cast<uint8_t>(m1) & static_cast<uint8_t>(m2));
}

inline column_mask operator|(column_mask m1, column_mask m2) {
    return column_mask(static_cast<uint8_t>(m1) | static_cast<uint8_t>(m2));
}
}

<commit_msg>sstables: expose promoted index in index entry<commit_after>/*
 * Copyright (C) 2015 ScyllaDB
 */

/*
 * This file is part of Scylla.
 *
 * Scylla is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * Scylla is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with Scylla.  If not, see <http://www.gnu.org/licenses/>.
 */

#pragma once

#include "disk_types.hh"
#include "core/enum.hh"
#include "bytes.hh"
#include "gc_clock.hh"
#include "tombstone.hh"
#include "streaming_histogram.hh"
#include "estimated_histogram.hh"
#include "column_name_helper.hh"
#include "sstables/key.hh"
#include "db/commitlog/replay_position.hh"
#include <vector>
#include <unordered_map>
#include <type_traits>

// While the sstable code works with char, bytes_view works with int8_t
// (signed char). Rather than change all the code, let's do a cast.
static inline bytes_view to_bytes_view(const temporary_buffer<char>& b) {
    using byte = bytes_view::value_type;
    return bytes_view(reinterpret_cast<const byte*>(b.get()), b.size());
}

namespace sstables {

struct option {
    disk_string<uint16_t> key;
    disk_string<uint16_t> value;

    template <typename Describer>
    auto describe_type(Describer f) { return f(key, value); }
};

struct filter {
    uint32_t hashes;
    disk_array<uint32_t, uint64_t> buckets;

    template <typename Describer>
    auto describe_type(Describer f) { return f(hashes, buckets); }

    // Create an always positive filter if nothing else is specified.
    filter() : hashes(0), buckets({}) {}
    explicit filter(int hashes, std::deque<uint64_t> buckets) : hashes(hashes), buckets({std::move(buckets)}) {}
};

class index_entry {
    temporary_buffer<char> _key;
    uint64_t _position;
    temporary_buffer<char> _promoted_index;
public:

    bytes_view get_key_bytes() const {
        return to_bytes_view(_key);
    }

    key_view get_key() const {
        return { get_key_bytes() };
    }

    uint64_t position() const {
        return _position;
    }

    bytes_view get_promoted_index_bytes() const {
        return to_bytes_view(_promoted_index);
    }

    index_entry(temporary_buffer<char>&& key, uint64_t position, temporary_buffer<char>&& promoted_index)
        : _key(std::move(key)), _position(position), _promoted_index(std::move(promoted_index)) {}

};

struct summary_entry {
    bytes key;
    uint64_t position;

    key_view get_key() const {
        return { key };
    }

    bool operator==(const summary_entry& x) const {
        return position ==  x.position && key == x.key;
    }
};

// Note: Sampling level is present in versions ka and higher. We ATM only support ka,
// so it's always there. But we need to make this conditional if we ever want to support
// other formats.
struct summary_ka {
    struct header {
        // The minimum possible amount of indexes per group (sampling level)
        uint32_t min_index_interval;
        // The number of entries in the Summary File
        uint32_t size;
        // The memory to be consumed to map the whole Summary into memory.
        uint64_t memory_size;
        // The actual sampling level.
        uint32_t sampling_level;
        // The number of entries the Summary *would* have if the sampling
        // level would be equal to min_index_interval.
        uint32_t size_at_full_sampling;
    } header;
    // The position in the Summary file for each of the indexes.
    // NOTE1 that its actual size is determined by the "size" parameter, not
    // by its preceding size_at_full_sampling
    // NOTE2: They are laid out in *MEMORY* order, not BE.
    // NOTE3: The sizes in this array represent positions in the memory stream,
    // not the file. The memory stream effectively begins after the header,
    // so every position here has to be added of sizeof(header).
    std::deque<uint32_t> positions;   // can be large, so use a deque instead of a vector
    std::deque<summary_entry> entries;

    disk_string<uint32_t> first_key;
    disk_string<uint32_t> last_key;

    // Used to determine when a summary entry should be added based on min_index_interval.
    // NOTE: keys_written isn't part of on-disk format of summary.
    size_t keys_written;

    // NOTE4: There is a structure written by Cassandra into the end of the Summary
    // file, after the field last_key, that we haven't understand yet, but we know
    // that its content isn't related to the summary itself.
    // The structure is basically as follow:
    // struct { disk_string<uint16_t>; uint32_t; uint64_t; disk_string<uint16_t>; }
    // Another interesting fact about this structure is that it is apparently always
    // filled with the same data. It's too early to judge that the data is useless.
    // However, it was tested that Cassandra loads successfully a Summary file with
    // this structure removed from it. Anyway, let's pay attention to it.

    /*
     * Returns total amount of memory used by the summary
     * Similar to origin off heap size
     */
    uint64_t memory_footprint() const {
        return sizeof(summary_entry) * entries.size() + sizeof(uint32_t) * positions.size() + sizeof(*this);
    }

    explicit operator bool() const {
        return entries.size();
    }
};
using summary = summary_ka;

struct metadata {
    virtual ~metadata() {}
};

struct validation_metadata : public metadata {
    disk_string<uint16_t> partitioner;
    double filter_chance;

    size_t serialized_size() {
        return sizeof(uint16_t) + partitioner.value.size() + sizeof(filter_chance);
    }

    template <typename Describer>
    auto describe_type(Describer f) { return f(partitioner, filter_chance); }
};

struct compaction_metadata : public metadata {
    disk_array<uint32_t, uint32_t> ancestors;
    disk_array<uint32_t, uint8_t> cardinality;

    size_t serialized_size() {
        return sizeof(uint32_t) + (ancestors.elements.size() * sizeof(uint32_t)) +
            sizeof(uint32_t) + (cardinality.elements.size() * sizeof(uint8_t));
    }

    template <typename Describer>
    auto describe_type(Describer f) { return f(ancestors, cardinality); }
};

struct ka_stats_metadata : public metadata {
    estimated_histogram estimated_row_size;
    estimated_histogram estimated_column_count;
    db::replay_position position;
    int64_t min_timestamp;
    int64_t max_timestamp;
    int32_t max_local_deletion_time;
    double compression_ratio;
    streaming_histogram estimated_tombstone_drop_time;
    uint32_t sstable_level;
    uint64_t repaired_at;
    disk_array<uint32_t, disk_string<uint16_t>> min_column_names;
    disk_array<uint32_t, disk_string<uint16_t>> max_column_names;
    bool has_legacy_counter_shards;

    template <typename Describer>
    auto describe_type(Describer f) {
        return f(
            estimated_row_size,
            estimated_column_count,
            position,
            min_timestamp,
            max_timestamp,
            max_local_deletion_time,
            compression_ratio,
            estimated_tombstone_drop_time,
            sstable_level,
            repaired_at,
            min_column_names,
            max_column_names,
            has_legacy_counter_shards
        );
    }
};
using stats_metadata = ka_stats_metadata;

// Numbers are found on disk, so they do matter. Also, setting their sizes of
// that of an uint32_t is a bit wasteful, but it simplifies the code a lot
// since we can now still use a strongly typed enum without introducing a
// notion of "disk-size" vs "memory-size".
enum class metadata_type : uint32_t {
    Validation = 0,
    Compaction = 1,
    Stats = 2,
};

static constexpr int DEFAULT_CHUNK_SIZE = 65536;

// checksums are generated using adler32 algorithm.
struct checksum {
    uint32_t chunk_size;
    std::deque<uint32_t> checksums;

    template <typename Describer>
    auto describe_type(Describer f) { return f(chunk_size, checksums); }
};

}

namespace std {

template <>
struct hash<sstables::metadata_type> : enum_hash<sstables::metadata_type> {};

}

namespace sstables {

struct statistics {
    disk_hash<uint32_t, metadata_type, uint32_t> hash;
    std::unordered_map<metadata_type, std::unique_ptr<metadata>> contents;
};

struct deletion_time {
    int32_t local_deletion_time;
    int64_t marked_for_delete_at;

    template <typename Describer>
    auto describe_type(Describer f) { return f(local_deletion_time, marked_for_delete_at); }

    bool live() const {
        return (local_deletion_time == std::numeric_limits<int32_t>::max()) &&
               (marked_for_delete_at == std::numeric_limits<int64_t>::min());
    }

    bool operator==(const deletion_time& d) {
        return local_deletion_time == d.local_deletion_time &&
               marked_for_delete_at == d.marked_for_delete_at;
    }
    bool operator!=(const deletion_time& d) {
        return !(*this == d);
    }
    explicit operator tombstone() {
        return !live() ? tombstone(marked_for_delete_at, gc_clock::time_point(gc_clock::duration(local_deletion_time))) : tombstone();
    }
};

enum class column_mask : uint8_t {
    none = 0x0,
    deletion = 0x01,
    expiration = 0x02,
    counter = 0x04,
    counter_update = 0x08,
    range_tombstone = 0x10,
};

inline column_mask operator&(column_mask m1, column_mask m2) {
    return column_mask(static_cast<uint8_t>(m1) & static_cast<uint8_t>(m2));
}

inline column_mask operator|(column_mask m1, column_mask m2) {
    return column_mask(static_cast<uint8_t>(m1) | static_cast<uint8_t>(m2));
}
}

<|endoftext|>