diff options
-rw-r--r-- | Makefile | 2 | ||||
-rw-r--r-- | arr.c | 5 | ||||
-rw-r--r-- | base.h | 14 | ||||
-rw-r--r-- | json.c | 558 | ||||
-rw-r--r-- | lsp.c | 170 | ||||
-rw-r--r-- | main.c | 10 | ||||
-rw-r--r-- | process-posix.c | 128 | ||||
-rw-r--r-- | process.h | 16 | ||||
-rw-r--r-- | util.c | 29 |
9 files changed, 890 insertions, 42 deletions
@@ -4,7 +4,7 @@ ALL_CFLAGS=$(CFLAGS) -Wall -Wextra -Wshadow -Wconversion -Wpedantic -pedantic -s LIBS=-lSDL2 -lGL -lm libpcre2-32.a DEBUG_CFLAGS=$(ALL_CFLAGS) -DDEBUG -O0 -g RELEASE_CFLAGS=$(ALL_CFLAGS) -O3 -PROFILE_CFLAGS=$(ALL_CFLAGS) -O3 -DPROFILE=1 +PROFILE_CFLAGS=$(ALL_CFLAGS) -O3 -g -DPROFILE=1 # if you change the directories below, ted won't work. # we don't yet have support for using different data directories GLOBAL_DATA_DIR=/usr/share/ted @@ -60,6 +60,10 @@ static inline u32 arr_len(void *arr) { return arr ? arr_hdr_(arr)->len : 0; } +static inline u32 arr_cap(void *arr) { + return arr ? arr_hdr_(arr)->cap : 0; +} + static inline unsigned arr_lenu(void *arr) { return (unsigned)arr_len(arr); } @@ -114,6 +118,7 @@ static void arr_reserve_(void **arr, size_t member_size, size_t n) { if (!*arr) { // create a new array with capacity n+1 + // why n+1? i dont know i wrote this a while ago ArrHeader *hdr = calloc(1, sizeof(ArrHeader) + (n+1) * member_size); if (hdr) { hdr->cap = (u32)n+1; @@ -111,10 +111,24 @@ typedef unsigned long long ullong; #define WarnUnusedResult #endif +#if __GNUC__ +#define ATTRIBUTE_PRINTF(fmt_idx, arg_idx) __attribute__ ((format(printf, fmt_idx, arg_idx))) +#else +#define ATTRIBUTE_PRINTF(fmt_idx, arg_idx) +#endif + #define Status bool WarnUnusedResult // false = error, true = success #define arr_count(a) (sizeof (a) / sizeof *(a)) + +// usage: if UNLIKELY (x > 2) ... +#if __GNUC__ +#define UNLIKELY(x) (__builtin_expect(x,0)) +#else +#define UNLIKELY(x) (x) +#endif + #ifdef __GNUC__ #define no_warn_start _Pragma("GCC diagnostic push") \ _Pragma("GCC diagnostic ignored \"-Wpedantic\"") \ @@ -0,0 +1,558 @@ +// JSON parser for LSP +// provides FAST(ish) parsing but SLOW lookup +// this is especially fast for small objects + +typedef struct { + u32 pos; + u32 len; +} JSONString; + +typedef struct JSONValue JSONValue; + +typedef struct { + u32 len; + // this is an index into the values array + // values[items..items+len] store the names + // values[items+len..items+2*len] store the values + u32 items; +} JSONObject; + +typedef struct { + u32 len; + // this is an index into the values array + // values[elements..elements+len] are the elements + u32 elements; +} JSONArray; + +enum { + // note: json doesn't actually include undefined. + // this is only for returning things from json_get etc. + JSON_UNDEFINED, + JSON_NULL, + JSON_FALSE, + JSON_TRUE, + JSON_NUMBER, + JSON_STRING, + JSON_OBJECT, + JSON_ARRAY +}; + +struct JSONValue { + u8 type; + union { + double number; + JSONString string; + JSONArray array; + JSONObject object; + } val; +}; + + +typedef struct { + char error[64]; + const char *text; + // root = values[0] + JSONValue *values; +} JSON; + +#define SKIP_WHITESPACE while (json_is_space(text[index])) ++index; + + +static bool json_parse_value(JSON *json, u32 *p_index, JSONValue *val); + +// defining this instead of using isspace seems to be faster +// probably because isspace depends on the locale. +static inline bool json_is_space(char c) { + return c == ' ' || c == '\n' || c == '\r' || c == '\t'; +} + +static void json_debug_print_value(const JSON *json, const JSONValue *value) { + switch (value->type) { + case JSON_UNDEFINED: printf("undefined"); break; + case JSON_NULL: printf("null"); break; + case JSON_FALSE: printf("false"); break; + case JSON_TRUE: printf("true"); break; + case JSON_NUMBER: printf("%g", value->val.number); break; + case JSON_STRING: { + const JSONString *string = &value->val.string; + printf("\"%.*s\"", + (int)string->len, + json->text + string->pos); + } break; + case JSON_ARRAY: { + const JSONArray *array = &value->val.array; + printf("["); + for (u32 i = 0; i < array->len; ++i) { + json_debug_print_value(json, &json->values[array->elements + i]); + printf(", "); + } + printf("]"); + } break; + case JSON_OBJECT: { + const JSONObject *obj = &value->val.object; + printf("{"); + for (u32 i = 0; i < obj->len; ++i) { + json_debug_print_value(json, &json->values[obj->items + i]); + printf(": "); + json_debug_print_value(json, &json->values[obj->items + obj->len + i]); + printf(", "); + } + printf("}"); + } break; + } +} + +// count number of comma-separated values until +// closing ] or } +static u32 json_count(JSON *json, u32 index) { + int bracket_depth = 0; + int brace_depth = 0; + u32 count = 1; + const char *text = json->text; + SKIP_WHITESPACE; + // special case: empty object/array + if (text[index] == '}' || text[index] == ']') + return 0; + +// int mark[5000] = {0}; + for (; ; ++index) { + switch (text[index]) { + case '\0': + return 0; // bad no closing bracket + case '[': + ++bracket_depth; + break; + case ']': + --bracket_depth; + if (bracket_depth < 0) + return count; + break; + case '{': + ++brace_depth; +// mark[index] = 1; + break; + case '}': + --brace_depth; +// mark[index] = 1; + if (brace_depth < 0){ + // useful visualization for debugging +// for (int i = 0; text[i]; ++i ){ +// switch (mark[i]){ +// case 1: printf("\x1b[91m"); break; +// case 2: printf("\x1b[92m"); break; +// } +// printf("%c",text[i]); +// if (mark[i]) printf("\x1b[0m"); +// } +// printf("\n"); + + return count; + } + break; + case ',': + if (bracket_depth == 0 && brace_depth == 0) + ++count; + break; + case '"': { + ++index; // skip opening " + int escaped = 0; + for (; ; ++index) { +// mark[index] = 2; + switch (text[index]) { + case '\0': return 0; // bad no closing quote + case '\\': escaped = !escaped; break; + case '"': + if (!escaped) + goto done; + escaped = false; + break; + default: + escaped = false; + break; + } + } + done:; + } break; + } + } +} + +static bool json_parse_object(JSON *json, u32 *p_index, JSONObject *object) { + u32 index = *p_index; + const char *text = json->text; + ++index; // go past { + u32 count = json_count(json, index); + object->len = count; + object->items = arr_len(json->values); + arr_set_len(json->values, arr_len(json->values) + 2 * count); + + for (u32 i = 0; i < count; ++i) { + if (i > 0) { + if (text[index] != ',') { + strbuf_printf(json->error, "stuff after value in object"); + return false; + } + ++index; + } + SKIP_WHITESPACE; + JSONValue name = {0}, value = {0}; + if (!json_parse_value(json, &index, &name)) + return false; + SKIP_WHITESPACE; + if (text[index] != ':') { + strbuf_printf(json->error, "stuff after name in object"); + return false; + } + ++index; // skip : + SKIP_WHITESPACE; + if (!json_parse_value(json, &index, &value)) + return false; + SKIP_WHITESPACE; + json->values[object->items + i] = name; + json->values[object->items + count + i] = value; + } + + if (text[index] != '}') { + strbuf_printf(json->error, "mismatched brackets or quotes."); + return false; + } + ++index; // skip } + *p_index = index; + return true; +} + +static bool json_parse_array(JSON *json, u32 *p_index, JSONArray *array) { + u32 index = *p_index; + const char *text = json->text; + ++index; // go past [ + u32 count = json_count(json, index); + array->len = count; + array->elements = arr_len(json->values); + + arr_set_len(json->values, arr_len(json->values) + count); + + SKIP_WHITESPACE; + + for (u32 i = 0; i < count; ++i) { + if (i > 0) { + if (text[index] != ',') { + strbuf_printf(json->error, "stuff after element in array"); + return false; + } + ++index; + } + SKIP_WHITESPACE; + JSONValue element = {0}; + if (!json_parse_value(json, &index, &element)) + return false; + SKIP_WHITESPACE; + json->values[array->elements + i] = element; + } + + if (text[index] != ']') { + strbuf_printf(json->error, "mismatched brackets or quotes."); + return false; + } + ++index; // skip ] + *p_index = index; + return true; +} + +static bool json_parse_string(JSON *json, u32 *p_index, JSONString *string) { + u32 index = *p_index; + ++index; // skip opening " + string->pos = index; + const char *text = json->text; + bool escaped = false; + for (; ; ++index) { + switch (text[index]) { + case '"': + if (!escaped) + goto done; + escaped = false; + break; + case '\\': + escaped = !escaped; + break; + case '\0': + strbuf_printf(json->error, "string literal goes to end of JSON"); + return false; + default: + escaped = false; + break; + } + } + done: + string->len = index - string->pos; + ++index; // skip closing " + *p_index = index; + return true; +} + +static bool json_parse_number(JSON *json, u32 *p_index, double *number) { + char *endp = NULL; + const char *text = json->text; + u32 index = *p_index; + *number = strtod(text + index, &endp); + if (endp == text + index) { + strbuf_printf(json->error, "bad number"); + return false; + } + index = (u32)(endp - text); + *p_index = index; + return true; +} + +static bool json_parse_value(JSON *json, u32 *p_index, JSONValue *val) { + const char *text = json->text; + u32 index = *p_index; + SKIP_WHITESPACE; + switch (text[index]) { + case '{': + val->type = JSON_OBJECT; + if (!json_parse_object(json, &index, &val->val.object)) + return false; + break; + case '[': + val->type = JSON_ARRAY; + if (!json_parse_array(json, &index, &val->val.array)) + return false; + break; + case '"': + val->type = JSON_STRING; + if (!json_parse_string(json, &index, &val->val.string)) + return false; + break; + case ANY_DIGIT: + val->type = JSON_NUMBER; + if (!json_parse_number(json, &index, &val->val.number)) + return false; + break; + case 'f': + val->type = JSON_FALSE; + if (!str_has_prefix(&text[index], "false")) + return false; + index += 5; + break; + case 't': + val->type = JSON_TRUE; + if (!str_has_prefix(&text[index], "true")) + return false; + index += 4; + break; + case 'n': + val->type = JSON_NULL; + if (!str_has_prefix(&text[index], "null")) + return false; + index += 4; + break; + default: + strbuf_printf(json->error, "bad value"); + return false; + } + *p_index = index; + return true; +} + +// NOTE: text must live as long as json!!! +static bool json_parse(JSON *json, const char *text) { + memset(json, 0, sizeof *json); + json->text = text; + arr_reserve(json->values, strlen(text) / 8); + arr_addp(json->values); // add root + JSONValue val = {0}; + u32 index = 0; + if (!json_parse_value(json, &index, &val)) + return false; + + SKIP_WHITESPACE; + if (text[index]) { + strbuf_printf(json->error, "extra text after end of root object"); + return false; + } + json->values[0] = val; + return true; +} + +static void json_free(JSON *json) { + arr_free(json->values); + memset(json, 0, sizeof *json); +} + +static bool json_streq(const JSON *json, const JSONString *string, const char *name) { + const char *p = &json->text[string->pos]; + const char *end = p + string->len; + for (; p < end; ++p, ++name) { + if (*name != *p) + return false; + } + return *name == '\0'; +} + +// returns undefined if the property `name` does not exist. +static JSONValue json_object_get(const JSON *json, const JSONObject *object, const char *name) { + const JSONValue *items = &json->values[object->items]; + for (u32 i = 0; i < object->len; ++i) { + const JSONValue *this_name = items++; + if (this_name->type == JSON_STRING && json_streq(json, &this_name->val.string, name)) { + return json->values[object->items + object->len + i]; + } + } + return (JSONValue){0}; +} + +// e.g. if json is { "a" : { "b": 3 }}, then json_get(json, "a.b") = 3. +// returns undefined if there is no such property +static JSONValue json_get(const JSON *json, const char *path) { + char segment[128]; + const char *p = path; + if (!json->values) { + return (JSONValue){0}; + } + JSONValue curr_value = json->values[0]; + while (*p) { + size_t segment_len = strcspn(p, "."); + strn_cpy(segment, sizeof segment, p, segment_len); + if (curr_value.type != JSON_OBJECT) { + return (JSONValue){0}; + } + curr_value = json_object_get(json, &curr_value.val.object, segment); + p += segment_len; + if (*p == '.') ++p; + } + return curr_value; +} + +// turn a json string into a null terminated string. +// this won't be nice if the json string includes \u0000 but that's rare. +// if buf_sz > string->len, the string will fit. +static void json_string_get(const JSON *json, const JSONString *string, char *buf, size_t buf_sz) { + const char *text = json->text; + if (buf_sz == 0) { + assert(0); + return; + } + char *buf_end = buf + buf_sz - 1; + for (u32 i = string->pos, end = string->pos + string->len; i < end && buf < buf_end; ++i) { + if (text[i] != '\\') { + *buf++ = text[i]; + } else { + ++i; + if (i >= end) break; + // escape sequence + switch (text[i]) { + case 'n': *buf++ = '\n'; break; + case 'r': *buf++ = '\r'; break; + case 'b': *buf++ = '\b'; break; + case 't': *buf++ = '\t'; break; + case 'f': *buf++ = '\f'; break; + case '\\': *buf++ = '\\'; break; + case '/': *buf++ = '/'; break; + case '"': *buf++ = '"'; break; + case 'u': { + if ((buf_end - buf) < 4 || i + 5 > end) + goto brk; + ++i; + + char hex[5] = {0}; + hex[0] = text[i++]; + hex[1] = text[i++]; + hex[2] = text[i++]; + hex[3] = text[i++]; + unsigned code_point=0; + sscanf(hex, "%04x", &code_point); + // technically this won't deal with people writing out UTF-16 surrogate halves + // using \u. i dont care. + size_t n = unicode_utf32_to_utf8(buf, code_point); + if (n <= 4) buf += n; + } break; + } + } + } + brk: + *buf = '\0'; +} + + +#if __unix__ +static void json_test_time_large(const char *filename) { + struct timespec start={0},end={0}; + FILE *fp = fopen(filename,"rb"); + if (!fp) { + perror(filename); + return; + } + + fseek(fp,0,SEEK_END); + size_t sz = (size_t)ftell(fp); + char *buf = calloc(1,sz+1); + rewind(fp); + fread(buf, 1, sz, fp); + fclose(fp); + for (int trial = 0; trial < 5; ++trial) { + clock_gettime(CLOCK_MONOTONIC, &start); + JSON json={0}; + bool success = json_parse(&json, buf); + if (!success) { + printf("FAIL: %s\n",json.error); + return; + } + + json_free(&json); + clock_gettime(CLOCK_MONOTONIC, &end); + + + + printf("time: %.1fms\n", + ((double)end.tv_sec*1e3+(double)end.tv_nsec*1e-6) + -((double)start.tv_sec*1e3+(double)start.tv_nsec*1e-6)); + } + +} +static void json_test_time_small(void) { + struct timespec start={0},end={0}; + int trials = 50000000; + clock_gettime(CLOCK_MONOTONIC, &start); + for (int trial = 0; trial < trials; ++trial) { + JSON json={0}; + bool success = json_parse(&json, "{\"hello\":\"there\"}"); + if (!success) { + printf("FAIL: %s\n",json.error); + return; + } + + json_free(&json); + } + clock_gettime(CLOCK_MONOTONIC, &end); + printf("time per trial: %.1fns\n", + (((double)end.tv_sec*1e9+(double)end.tv_nsec) + -((double)start.tv_sec*1e9+(double)start.tv_nsec)) + / trials); + +} +#endif + +static void json_debug_print(const JSON *json) { + printf("%u values (capacity %u, text length %zu)\n", + arr_len(json->values), arr_cap(json->values), strlen(json->text)); + json_debug_print_value(json, &json->values[0]); +} + +// e.g. converts "Hello\nworld" to "Hello\\nworld" +// the return value is the # of bytes in the escaped string. +static size_t json_escape_to(char *out, size_t out_sz, const char *in) { + (void)out;(void)out_sz;(void)in; + // @TODO + abort(); +} + +// e.g. converts "Hello\nworld" to "Hello\\nworld" +// the resulting string should be free'd. +static char *json_escape(const char *str) { + size_t out_sz = 2 * strlen(str) + 1; + char *out = calloc(1, out_sz); + json_escape_to(out, out_sz, str); + return out; +} + +#undef SKIP_WHITESPACE @@ -0,0 +1,170 @@ +typedef enum { + LSP_INITIALIZE, + LSP_OPEN, + LSP_CHANGE, + LSP_CLOSE, +} LSPRequestType; + +typedef struct { + // buffer language + Language language; + // these will be free'd. + char *filename; + char *file_contents; +} LSPRequestOpen; + +typedef struct { + LSPRequestType type; + union { + LSPRequestOpen open; + } data; +} LSPRequest; + +typedef struct { + Process process; + char error[256]; +} LSP; + + +static void send_request_content(LSP *lsp, const char *content) { + char header[128]; + size_t content_size = strlen(content); + strbuf_printf(header, "Content-Length: %zu\r\n\r\n", content_size); + process_write(&lsp->process, header, strlen(header)); + process_write(&lsp->process, content, content_size); +} + +static const char *lsp_language_id(Language lang) { + switch (lang) { + case LANG_CONFIG: + case LANG_TED_CFG: + case LANG_NONE: + return "text"; + case LANG_C: + return "c"; + case LANG_CPP: + return "cpp"; + case LANG_JAVA: + return "java"; + case LANG_JAVASCRIPT: + return "javascript"; + case LANG_MARKDOWN: + return "markdown"; + case LANG_GO: + return "go"; + case LANG_RUST: + return "rust"; + case LANG_PYTHON: + return "python"; + case LANG_HTML: + return "html"; + case LANG_TEX: + return "latex"; + case LANG_COUNT: break; + } + assert(0); + return "text"; +} + +static void send_request(LSP *lsp, const LSPRequest *request) { + static unsigned long long id; + ++id; + + switch (request->type) { + case LSP_INITIALIZE: { + char content[1024]; + strbuf_printf(content, + "{\"jsonrpc\":\"2.0\",\"id\":%llu,\"method\":\"initialize\",\"params\":{" + "\"processId\":%d," + "\"capabilities\":{}" + "}}", id, process_get_id()); + send_request_content(lsp, content); + } break; + case LSP_OPEN: { + const LSPRequestOpen *open = &request->data.open; + char *escaped_filename = json_escape(open->filename); + char *did_open = a_sprintf( + "{\"jsonrpc\":\"2.0\",\"id\":%llu,\"method\":\"textDocument/open\",\"params\":{" + "textDocument:{" + "uri:\"file://%s\"," + "languageId:\"%s\"," + "version:1," + "text:\"", + id, escaped_filename, lsp_language_id(open->language)); + free(escaped_filename); + + size_t did_open_sz = strlen(did_open) + 2 * strlen(open->file_contents) + 16; + did_open = realloc(did_open, did_open_sz); + + size_t n = json_escape_to(did_open + strlen(did_open), + did_open_sz - 10 - strlen(did_open), + open->file_contents); + char *p = did_open + n; + sprintf(p, "\"}}}"); + + free(did_open); + + send_request_content(lsp, did_open); + } break; + default: + // @TODO + abort(); + } +} + +static bool recv_response(LSP *lsp, JSON *json) { + static char response_data[500000]; + // i think this should always read all the response data. + long long bytes_read = process_read(&lsp->process, response_data, sizeof response_data); + if (bytes_read < 0) { + strbuf_printf(lsp->error, "Read error."); + return false; + } + response_data[bytes_read] = '\0'; + + char *body_start = strstr(response_data, "\r\n\r\n"); + if (body_start) { + body_start += 4; + if (!json_parse(json, body_start)) { + strbuf_cpy(lsp->error, json->error); + return false; + } + JSONValue result = json_get(json, "result"); + if (result.type == JSON_UNDEFINED) { + // uh oh + JSONValue error = json_get(json, "error.message"); + if (error.type == JSON_STRING) { + json_string_get(json, &error.val.string, lsp->error, sizeof lsp->error); + } else { + strbuf_printf(lsp->error, "Server error (no message)"); + } + } + } else { + strbuf_printf(lsp->error, "No response body."); + return false; + } + + return true; +} + +bool lsp_create(LSP *lsp, const char *analyzer_command) { + ProcessSettings settings = { + .stdin_blocking = true, + .stdout_blocking = true + }; + process_run_ex(&lsp->process, analyzer_command, &settings); + char init_request[1024]; + strbuf_printf(init_request, + "{\"jsonrpc\":\"2.0\",\"id\":1,\"method\":\"initialize\",\"params\":{" + "\"processId\":%d," + "\"capabilities\":{}" + "}}", + process_get_id()); + send_request_content(lsp, init_request); + JSON json = {0}; + if (!recv_response(lsp, &json)) { + return false; + } + json_debug_print(&json); + return true; +} @@ -105,6 +105,8 @@ bool tag_goto(Ted *ted, char const *tag); #include "command.c" #include "config.c" #include "session.c" +#include "json.c" +#include "lsp.c" #if PROFILE #define PROFILE_TIME(var) double var = time_get_seconds(); @@ -283,6 +285,14 @@ int main(int argc, char **argv) { PROFILE_TIME(init_start) PROFILE_TIME(basic_init_start) + // @TODO TEMPORARY + { + LSP lsp={0}; + if (!lsp_create(&lsp, "rust-analyzer")) + printf("%s\n",lsp.error); + exit(0); + } + #if __unix__ { struct sigaction act = {0}; diff --git a/process-posix.c b/process-posix.c index f35017f..1463487 100644 --- a/process-posix.c +++ b/process-posix.c @@ -6,55 +6,101 @@ struct Process { pid_t pid; - int pipe; + int stdout_pipe; + int stdin_pipe; char error[64]; }; -bool process_run(Process *proc, char const *command) { +int process_get_id(void) { + return getpid(); +} + +bool process_run_ex(Process *proc, const char *command, const ProcessSettings *settings) { memset(proc, 0, sizeof *proc); + int stdin_pipe[2] = {0}, stdout_pipe[2] = {0}; + if (pipe(stdin_pipe) != 0) { + strbuf_printf(proc->error, "%s", strerror(errno)); + return false; + } + if (pipe(stdout_pipe) != 0) { + strbuf_printf(proc->error, "%s", strerror(errno)); + close(stdin_pipe[0]); + close(stdin_pipe[1]); + return false; + } + bool success = false; - int pipefd[2]; - if (pipe(pipefd) == 0) { - pid_t pid = fork(); - if (pid == 0) { - // child process - // put child in its own group. it will be in this group with all of its descendents, - // so by killing everything in the group, we kill all the descendents of this process. - // if we didn't do this, we would just be killing the sh process in process_kill. - setpgid(0, 0); - // send stdout and stderr to pipe - dup2(pipefd[1], STDOUT_FILENO); - dup2(pipefd[1], STDERR_FILENO); - close(pipefd[0]); - close(pipefd[1]); - char *program = "/bin/sh"; - char *argv[] = {program, "-c", (char *)command, NULL}; - if (execv(program, argv) == -1) { - dprintf(STDERR_FILENO, "%s: %s\n", program, strerror(errno)); - exit(127); - } - } else if (pid > 0) { - // parent process - close(pipefd[1]); - fcntl(pipefd[0], F_SETFL, fcntl(pipefd[0], F_GETFL) | O_NONBLOCK); // set pipe to non-blocking - proc->pid = pid; - proc->pipe = pipefd[0]; - success = true; + pid_t pid = fork(); + if (pid == 0) { + // child process + // put child in its own group. it will be in this group with all of its descendents, + // so by killing everything in the group, we kill all the descendents of this process. + // if we didn't do this, we would just be killing the sh process in process_kill. + setpgid(0, 0); + // pipe stuff + dup2(stdout_pipe[1], STDOUT_FILENO); + dup2(stdout_pipe[1], STDERR_FILENO); + dup2(stdin_pipe[0], STDIN_FILENO); + // don't need these file descriptors anymore + close(stdout_pipe[0]); + close(stdout_pipe[1]); + close(stdin_pipe[0]); + close(stdin_pipe[1]); + + char *program = "/bin/sh"; + char *argv[] = {program, "-c", (char *)command, NULL}; + if (execv(program, argv) == -1) { + dprintf(STDERR_FILENO, "%s: %s\n", program, strerror(errno)); + exit(127); } - } else { - strbuf_printf(proc->error, "%s", strerror(errno)); + } else if (pid > 0) { + // parent process + + // we're reading from (the child's) stdout and writing to stdin, + // so we don't need the write end of the stdout pipe or the + // read end of the stdin pipe. + close(stdout_pipe[1]); + close(stdin_pipe[0]); + // set pipes to non-blocking + if (!settings->stdout_blocking) + fcntl(stdout_pipe[0], F_SETFL, fcntl(stdout_pipe[0], F_GETFL) | O_NONBLOCK); + if (!settings->stdin_blocking) + fcntl(stdin_pipe[1], F_SETFL, fcntl(stdin_pipe[1], F_GETFL) | O_NONBLOCK); + proc->pid = pid; + proc->stdout_pipe = stdout_pipe[0]; + proc->stdin_pipe = stdin_pipe[1]; + success = true; } return success; } +bool process_run(Process *proc, char const *command) { + const ProcessSettings settings = {0}; + return process_run_ex(proc, command, &settings); +} + + char const *process_geterr(Process *p) { return *p->error ? p->error : NULL; } +long long process_write(Process *proc, const char *data, size_t size) { + assert(proc->stdin_pipe); // check that process hasn't been killed + ssize_t bytes_written = write(proc->stdin_pipe, data, size); + if (bytes_written >= 0) { + return (long long)bytes_written; + } else if (errno == EAGAIN) { + return 0; + } else { + strbuf_printf(proc->error, "%s", strerror(errno)); + return -2; + } +} + long long process_read(Process *proc, char *data, size_t size) { - assert(proc->pipe); - ssize_t bytes_read = read(proc->pipe, data, size); + assert(proc->stdout_pipe); + ssize_t bytes_read = read(proc->stdout_pipe, data, size); if (bytes_read >= 0) { return (long long)bytes_read; } else if (errno == EAGAIN) { @@ -66,13 +112,19 @@ long long process_read(Process *proc, char *data, size_t size) { } +static void process_close_pipes(Process *proc) { + close(proc->stdout_pipe); + close(proc->stdin_pipe); + proc->stdout_pipe = 0; + proc->stdin_pipe = 0; +} + void process_kill(Process *proc) { kill(-proc->pid, SIGKILL); // kill everything in process group // get rid of zombie process waitpid(proc->pid, NULL, 0); - close(proc->pipe); proc->pid = 0; - proc->pipe = 0; + process_close_pipes(proc); } int process_check_status(Process *proc, char *message, size_t message_size) { @@ -83,7 +135,7 @@ int process_check_status(Process *proc, char *message, size_t message_size) { return 0; } else if (ret > 0) { if (WIFEXITED(wait_status)) { - close(proc->pipe); proc->pipe = 0; + process_close_pipes(proc); int code = WEXITSTATUS(wait_status); if (code == 0) { str_printf(message, message_size, "exited successfully"); @@ -93,14 +145,14 @@ int process_check_status(Process *proc, char *message, size_t message_size) { return -1; } } else if (WIFSIGNALED(wait_status)) { - close(proc->pipe); + process_close_pipes(proc); str_printf(message, message_size, "terminated by signal %d", WTERMSIG(wait_status)); return -1; } return 0; } else { // this process is gone or something? - close(proc->pipe); proc->pipe = 0; + process_close_pipes(proc); str_printf(message, message_size, "process ended unexpectedly"); return -1; } @@ -4,11 +4,27 @@ typedef struct Process Process; +// zero everything except what you're using +typedef struct { + bool stdin_blocking; + bool stdout_blocking; +} ProcessSettings; + +// get process ID of this process +int process_get_id(void); // execute the given command (like if it was passed to system()). // returns false on failure +bool process_run_ex(Process *proc, const char *command, const ProcessSettings *props); +// like process_run_ex, but with the default settings bool process_run(Process *process, char const *command); // returns the error last error produced, or NULL if there was no error. char const *process_geterr(Process *process); +// write to stdin +// returns: +// -2 on error +// or a non-negative number indicating the number of bytes written. +long long process_write(Process *process, const char *data, size_t size); +// read from stdout+stderr // returns: // -2 on error // -1 if no data is available right now @@ -97,6 +97,19 @@ static char *str_dup(char const *src) { #define strbuf_catf(str, ...) assert(sizeof str != 4 && sizeof str != 8), \ str_catf(str, sizeof str, __VA_ARGS__) +#if __unix__ +static char *a_sprintf(const char *fmt, ...) ATTRIBUTE_PRINTF(1, 2); +static char *a_sprintf(const char *fmt, ...) { + va_list args; + va_start(args, fmt); + char *str = NULL; + vasprintf(&str, fmt, args); + return str; +} +#else +#error "@TODO" +#endif + // on 16-bit systems, this is 16383. on 32/64-bit systems, this is 1073741823 // it is unusual to have a string that long. #define STRLEN_SAFE_MAX (UINT_MAX >> 2) @@ -134,9 +147,14 @@ static void str_cat(char *dst, size_t dst_sz, char const *src) { } // safer version of strncpy. dst_sz includes a null terminator. -static void str_cpy(char *dst, size_t dst_sz, char const *src) { - size_t srclen = strlen(src); - size_t n = srclen; // number of bytes to copy +static void strn_cpy(char *dst, size_t dst_sz, char const *src, size_t src_len) { + size_t n = src_len; // number of bytes to copy + for (size_t i = 0; i < n; ++i) { + if (src[i] == '\0') { + n = i; + break; + } + } if (dst_sz == 0) { assert(0); @@ -149,6 +167,11 @@ static void str_cpy(char *dst, size_t dst_sz, char const *src) { dst[n] = 0; } +// safer version of strcpy. dst_sz includes a null terminator. +static void str_cpy(char *dst, size_t dst_sz, char const *src) { + strn_cpy(dst, dst_sz, src, SIZE_MAX); +} + #define strbuf_cpy(dst, src) str_cpy(dst, sizeof dst, src) #define strbuf_cat(dst, src) str_cat(dst, sizeof dst, src) |