// SPDX-License-Identifier: GPL-3.0-or-later

#ifndef NETDATA_SPAWN_H
#define NETDATA_SPAWN_H 1

#include "../daemon/common.h"

#define SPAWN_SERVER_COMMAND_LINE_ARGUMENT "--special-spawn-server"

typedef enum spawn_protocol {
    SPAWN_PROT_EXEC_CMD = 0,
    SPAWN_PROT_SPAWN_RESULT,
    SPAWN_PROT_CMD_EXIT_STATUS
} spawn_prot_t;

struct spawn_prot_exec_cmd {
    uint16_t command_length;
    char command_to_run[];
};

struct spawn_prot_spawn_result {
    pid_t exec_pid; /* 0 if failed to spawn */
    time_t exec_run_timestamp; /* time of successfully spawning the command */
};

struct spawn_prot_cmd_exit_status {
    int exec_exit_status;
};

struct spawn_prot_header {
    spawn_prot_t opcode;
    void *handle;
};

#undef SPAWN_DEBUG /* define to enable debug prints */

#define SPAWN_MAX_OUTSTANDING (32768)

#define SPAWN_CMD_PROCESSED         0x00000001
#define SPAWN_CMD_IN_PROGRESS       0x00000002
#define SPAWN_CMD_FAILED_TO_SPAWN   0x00000004
#define SPAWN_CMD_DONE              0x00000008

struct spawn_cmd_info {
    avl avl;

    /* concurrency control per command */
    uv_mutex_t mutex;
    uv_cond_t cond; /* users block here until command has finished */

    uint64_t serial;
    char *command_to_run;
    int exit_status;
    pid_t pid;
    unsigned long flags;
    time_t exec_run_timestamp; /* time of successfully spawning the command */
};

/* spawn command queue */
struct spawn_queue {
    avl_tree cmd_tree;

    /* concurrency control of command queue */
    uv_mutex_t mutex;
    uv_cond_t cond;

    volatile unsigned size;
    uint64_t latest_serial;
};

struct write_context {
    uv_write_t write_req;
    struct spawn_prot_header header;
    struct spawn_prot_cmd_exit_status exit_status;
    struct spawn_prot_spawn_result spawn_result;
    struct spawn_prot_exec_cmd payload;
};

extern int spawn_thread_error;
extern int spawn_thread_shutdown;
extern uv_async_t spawn_async;

void spawn_init(void);
void spawn_server(void);
void spawn_client(void *arg);
void destroy_spawn_cmd(struct spawn_cmd_info *cmdinfo);
uint64_t spawn_enq_cmd(char *command_to_run);
void spawn_wait_cmd(uint64_t serial, int *exit_status, time_t *exec_run_timestamp);
void spawn_deq_cmd(struct spawn_cmd_info *cmdinfo);
struct spawn_cmd_info *spawn_get_unprocessed_cmd(void);
int create_spawn_server(uv_loop_t *loop, uv_pipe_t *spawn_channel, uv_process_t *process);

/*
 * Copies from the source buffer to the protocol buffer. It advances the source buffer by the amount copied. It
 * subtracts the amount copied from the source length.
 */
static inline void copy_to_prot_buffer(char *prot_buffer, unsigned *prot_buffer_len, unsigned max_to_copy,
                                       char **source, unsigned *source_len)
{
    unsigned to_copy;

    to_copy = MIN(max_to_copy, *source_len);
    memcpy(prot_buffer + *prot_buffer_len, *source, to_copy);
    *prot_buffer_len += to_copy;
    *source += to_copy;
    *source_len -= to_copy;
}

#endif //NETDATA_SPAWN_H
