From ea70e513d8a927680f2795d4bad9aba788f32d77 Mon Sep 17 00:00:00 2001 From: pommicket Date: Sun, 8 Jan 2023 13:45:57 -0500 Subject: building on windows still needs lots more testing --- lsp.c | 35 ++++++----- main.c | 5 ++ os-posix.c | 7 +-- os-win.c | 193 ++++++++++++++++++++++++++++++++++++++++++++----------------- os.h | 9 +-- 5 files changed, 170 insertions(+), 79 deletions(-) diff --git a/lsp.c b/lsp.c index f1fb452..8f2c1af 100644 --- a/lsp.c +++ b/lsp.c @@ -298,10 +298,11 @@ static bool lsp_receive(LSP *lsp, size_t max_size) { if (status != 0) { bool not_found = #if _WIN32 - #error "@TODO: what status does cmd return if the program is not found?" + false // @TODO #else - info.exit_code == 127; + info.exit_code == 127 #endif + ; if (not_found) { // don't give an error if the server is not found. @@ -530,21 +531,26 @@ LSP *lsp_create(const char *root_dir, const char *command, const char *configura lsp->workspace_folders_mutex = SDL_CreateMutex(); ProcessSettings settings = { - .stdin_blocking = true, - .stdout_blocking = false, - .stderr_blocking = false, .separate_stderr = true, .working_directory = root_dir, }; lsp->process = process_run_ex(command, &settings); - LSPRequest initialize = { - .type = LSP_REQUEST_INITIALIZE - }; - initialize.id = get_request_id(); - // immediately send the request rather than queueing it. - // this is a small request, so it shouldn't be a problem. - write_request(lsp, &initialize); - lsp->communication_thread = SDL_CreateThread(lsp_communication_thread, "LSP communicate", lsp); + const char *error = process_geterr(lsp->process); + if (error) { + lsp_set_error(lsp, "Couldn't start LSP server: %s", error); + lsp->exited = true; + process_kill(&lsp->process); + } else { + + LSPRequest initialize = { + .type = LSP_REQUEST_INITIALIZE + }; + initialize.id = get_request_id(); + // immediately send the request rather than queueing it. + // this is a small request, so it shouldn't be a problem. + write_request(lsp, &initialize); + lsp->communication_thread = SDL_CreateThread(lsp_communication_thread, "LSP communicate", lsp); + } return lsp; } @@ -595,7 +601,8 @@ bool lsp_next_message(LSP *lsp, LSPMessage *message) { void lsp_free(LSP *lsp) { SDL_SemPost(lsp->quit_sem); - SDL_WaitThread(lsp->communication_thread, NULL); + if (lsp->communication_thread) + SDL_WaitThread(lsp->communication_thread, NULL); SDL_DestroyMutex(lsp->messages_mutex); SDL_DestroyMutex(lsp->workspace_folders_mutex); SDL_DestroyMutex(lsp->error_mutex); diff --git a/main.c b/main.c index ff73375..766a612 100644 --- a/main.c +++ b/main.c @@ -1,5 +1,10 @@ /* @TODO: +- test LSP on windows +- what status does cmd return if the program is not found? (lsp.c:301) +- switch to CreateProcessW +- are we freeing process if process_run(_ex) fails? +- test time_last_modified (windows) - some way of opening + closing all C files in directory for clangd textDocument/references to work? - does adding compile_commands.json help? diff --git a/os-posix.c b/os-posix.c index 42c55bb..38e21cf 100644 --- a/os-posix.c +++ b/os-posix.c @@ -223,12 +223,9 @@ Process *process_run_ex(const char *command, const ProcessSettings *settings) { close(stderr_pipe[1]); close(stdin_pipe[0]); // set pipes to non-blocking - if (!settings->stdout_blocking) - set_nonblocking(stdout_pipe[0]); - if (stderr_pipe[0] && !settings->stderr_blocking) + set_nonblocking(stdout_pipe[0]); + if (stderr_pipe[0]) set_nonblocking(stderr_pipe[0]); - if (!settings->stdin_blocking) - set_nonblocking(stdin_pipe[1]); proc->pid = pid; proc->stdout_pipe = stdout_pipe[0]; if (stderr_pipe[0]) diff --git a/os-win.c b/os-win.c index c565a5b..b3c931a 100644 --- a/os-win.c +++ b/os-win.c @@ -1,6 +1,7 @@ // windows implementation of OS functions #include "os.h" +#include "util.h" #include #include #include @@ -109,16 +110,16 @@ int os_get_cwd(char *buf, size_t buflen) { return 1; } -#error "@TODO: test this" -struct timespec time_last_modified(const char *filename) { +struct timespec time_last_modified(const char *path) { struct timespec ts = {0}; FILETIME write_time = {0}; WCHAR wide_path[4100]; - if (MultiByteToWideChar(CP_UTF8, 0, path, -1, wide_path, sizeof wide_path) == 0) + if (MultiByteToWideChar(CP_UTF8, 0, path, -1, wide_path, (int)sizeof wide_path) == 0) return ts; - HANDLE file = CreateFileW(wide_path, GENERIC_WRITE, FILE_SHARE_READ|FILE_SHARE_WRITE|FILE_SHARE_DELETE, NULL, - OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL); - if (file == INVALID_HANDLE) + HANDLE file = CreateFileW(wide_path, GENERIC_READ, + FILE_SHARE_READ|FILE_SHARE_WRITE|FILE_SHARE_DELETE, NULL, + OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); + if (file == INVALID_HANDLE_VALUE) return ts; if (GetFileTime(file, NULL, NULL, &write_time)) { @@ -147,13 +148,11 @@ void time_sleep_ns(u64 ns) { Sleep((DWORD)(ns / 1000000)); } -#error "@TODO: fix process functions to take Process**" -#error "@TODO : implement process_write, separate_stderr, working_directory" -#error "@TODO : make sure process_read & process_write do what they're supposed to for both blocking & non-blocking read/writes." -#include "process.h" - struct Process { - HANDLE pipe_read, pipe_write; + // NOTE: we do need to keep the ends of the pipes we aren't using open too + HANDLE pipe_stdin_read, pipe_stdin_write, + pipe_stdout_read, pipe_stdout_write, + pipe_stderr_read, pipe_stderr_write; HANDLE job; PROCESS_INFORMATION process_info; char error[200]; @@ -167,17 +166,11 @@ static void get_last_error_str(char *out, size_t out_sz) { if (cr) *cr = '\0'; // get rid of carriage return+newline at end of error } -bool process_run(Process *process, const char *command) { - // thanks to https://stackoverflow.com/a/35658917 for the pipe code +Process *process_run_ex(const char *command, const ProcessSettings *settings) { // thanks to https://devblogs.microsoft.com/oldnewthing/20131209-00/?p=2433 for the job code - - bool success = false; - memset(process, 0, sizeof *process); + Process *process = calloc(1, sizeof *process); char *command_line = str_dup(command); - if (!command_line) { - strbuf_printf(process->error, "Out of memory."); - return false; - } + // we need to create a "job" for this, because when you kill a process on windows, // all its children just keep going. so cmd.exe would die, but not the actual build process. // jobs fix this, apparently. @@ -186,28 +179,43 @@ bool process_run(Process *process, const char *command) { JOBOBJECT_EXTENDED_LIMIT_INFORMATION job_info = {0}; job_info.BasicLimitInformation.LimitFlags = JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE; SetInformationJobObject(job, JobObjectExtendedLimitInformation, &job_info, sizeof job_info); - HANDLE pipe_read, pipe_write; + HANDLE pipe_stdin_read = 0, pipe_stdin_write = 0, pipe_stdout_read = 0, + pipe_stdout_write = 0, pipe_stderr_read = 0, pipe_stderr_write = 0; SECURITY_ATTRIBUTES security_attrs = {sizeof(SECURITY_ATTRIBUTES)}; security_attrs.bInheritHandle = TRUE; - if (CreatePipe(&pipe_read, &pipe_write, &security_attrs, 0)) { + bool created_pipes = true; + created_pipes &= CreatePipe(&pipe_stdin_read, &pipe_stdin_write, &security_attrs, 0) != 0; + created_pipes &= CreatePipe(&pipe_stdout_read, &pipe_stdout_write, &security_attrs, 0) != 0; + if (settings->separate_stderr) + created_pipes &= CreatePipe(&pipe_stderr_read, &pipe_stderr_write, &security_attrs, 0) != 0; + + if (created_pipes) { STARTUPINFOA startup = {sizeof(STARTUPINFOA)}; startup.dwFlags = STARTF_USESHOWWINDOW | STARTF_USESTDHANDLES; - startup.hStdOutput = pipe_write; - startup.hStdError = pipe_write; + startup.hStdOutput = pipe_stdout_write; + startup.hStdError = settings->separate_stderr ? pipe_stderr_write : pipe_stdout_write; + startup.hStdInput = pipe_stdin_read; startup.wShowWindow = SW_HIDE; PROCESS_INFORMATION *process_info = &process->process_info; if (CreateProcessA(NULL, command_line, NULL, NULL, TRUE, CREATE_NEW_CONSOLE | CREATE_SUSPENDED, - NULL, NULL, &startup, process_info)) { + NULL, settings->working_directory, &startup, process_info)) { // create a suspended process, add it to the job, then resume (unsuspend) the process if (AssignProcessToJobObject(job, process_info->hProcess)) { if (ResumeThread(process_info->hThread) != (DWORD)-1) { process->job = job; - process->pipe_read = pipe_read; - process->pipe_write = pipe_write; - success = true; + process->pipe_stdin_read = pipe_stdin_read; + process->pipe_stdin_write = pipe_stdin_write; + process->pipe_stdout_read = pipe_stdout_read; + process->pipe_stdout_write = pipe_stdout_write; + process->pipe_stderr_read = pipe_stderr_read; + process->pipe_stderr_write = pipe_stderr_write; + } else { + strbuf_printf(process->error, "Couldn't start thread"); } + } else { + strbuf_printf(process->error, "Couldn't assign process to job object."); } - if (!success) { + if (*process->error) { TerminateProcess(process_info->hProcess, 1); CloseHandle(process_info->hProcess); CloseHandle(process_info->hThread); @@ -217,33 +225,51 @@ bool process_run(Process *process, const char *command) { get_last_error_str(buf, sizeof buf); strbuf_printf(process->error, "Couldn't run `%s`: %s", command, buf); } - free(command_line); - if (!success) { - CloseHandle(pipe_read); - CloseHandle(pipe_write); + if (*process->error) { + if (pipe_stdin_read) CloseHandle(pipe_stdin_read); + if (pipe_stdin_write) CloseHandle(pipe_stdin_write); + if (pipe_stdout_read) CloseHandle(pipe_stdout_read); + if (pipe_stdout_write) CloseHandle(pipe_stdout_write); + if (pipe_stderr_read) CloseHandle(pipe_stderr_read); + if (pipe_stderr_write) CloseHandle(pipe_stderr_write); } } else { char buf[150]; get_last_error_str(buf, sizeof buf); strbuf_printf(process->error, "Couldn't create pipe: %s", buf); } - if (!success) + if (*process->error) CloseHandle(job); } - return success; + free(command_line); + return process; +} + +int process_get_id(void) { + return (int)GetCurrentProcessId(); +} + + +Process *process_run(const char *command) { + const ProcessSettings settings = {0}; + return process_run_ex(command, &settings); } const char *process_geterr(Process *p) { return *p->error ? p->error : NULL; } -long long process_read(Process *process, char *data, size_t size) { +static long long process_read_handle(Process *process, HANDLE pipe, char *data, size_t size) { + if (size > U32_MAX) { + strbuf_printf(process->error, "Too much data to read."); + return -2; + } DWORD bytes_read = 0, bytes_avail = 0, bytes_left = 0; - if (PeekNamedPipe(process->pipe_read, data, (DWORD)size, &bytes_read, &bytes_avail, &bytes_left)) { + if (PeekNamedPipe(pipe, data, (DWORD)size, &bytes_read, &bytes_avail, &bytes_left)) { if (bytes_read == 0) { return -1; } else { - ReadFile(process->pipe_read, data, (DWORD)size, &bytes_read, NULL); // make sure data is removed from pipe + ReadFile(pipe, data, (DWORD)size, &bytes_read, NULL); // make sure data is removed from pipe return bytes_read; } } else { @@ -254,40 +280,99 @@ long long process_read(Process *process, char *data, size_t size) { } } -void process_kill(Process *process) { +long long process_read(Process *process, char *data, size_t size) { + if (!process) { + // already killed + assert(0); + return -2; + } + return process_read_handle(process, process->pipe_stdout_read, data, size); +} + +long long process_read_stderr(Process *process, char *data, size_t size) { + if (!process) { + // already killed + assert(0); + return -2; + } + return process_read_handle(process, process->pipe_stderr_read, data, size); +} + +long long process_write(Process *process, const char *data, size_t size) { + if (!process) { + // already killed + assert(0); + return -2; + } + + if (size > LLONG_MAX) { + strbuf_printf(process->error, "Too much data to read."); + return -2; + } + size_t total_written = 0; + DWORD written = 0; + while (total_written < size) { + bool success = WriteFile(process->pipe_stdin_write, data, + size > U32_MAX ? U32_MAX : (DWORD)size, + &written, + NULL); + if (!success) { + char buf[150]; + get_last_error_str(buf, sizeof buf); + strbuf_printf(process->error, "Couldn't write to pipe: %s", buf); + return -2; + } + total_written += written; + } + return (long long)total_written; +} + +void process_kill(Process **pprocess) { + Process *process = *pprocess; + if (!process) { + // already killed + return; + } CloseHandle(process->job); - CloseHandle(process->pipe_read); - CloseHandle(process->pipe_write); + CloseHandle(process->pipe_stdin_read); + CloseHandle(process->pipe_stdin_write); + CloseHandle(process->pipe_stdout_read); + CloseHandle(process->pipe_stdout_write); + if (process->pipe_stderr_read) CloseHandle(process->pipe_stderr_read); + if (process->pipe_stderr_write) CloseHandle(process->pipe_stderr_write); CloseHandle(process->process_info.hProcess); CloseHandle(process->process_info.hThread); + free(process); + *pprocess = NULL; } -int process_check_status(Process *process, char *message, size_t message_size) { - assert(!message || message_size); +int process_check_status(Process **pprocess, ProcessExitInfo *info) { + Process *process = *pprocess; + if (!process) { + // already killed + return -1; + } HANDLE hProcess = process->process_info.hProcess; DWORD exit_code = 1; if (GetExitCodeProcess(hProcess, &exit_code)) { if (exit_code == STILL_ACTIVE) { - if (message) - *message = '\0'; return 0; } else { - process_kill(process); + process_kill(pprocess); + info->exited = true; + info->exit_code = (int)exit_code; if (exit_code == 0) { - if (message) - str_printf(message, message_size, "exited successfully"); + strbuf_printf(info->message, "exited successfully"); return +1; } else { - if (message) - str_printf(message, message_size, "exited with code %d", (int)exit_code); + strbuf_printf(info->message, "exited with code %d", (int)exit_code); return -1; } } } else { // something has gone wrong. - if (message) - str_printf(message, message_size, "couldn't get process exit status"); - process_kill(process); + strbuf_printf(info->message, "couldn't get process exit status"); + process_kill(pprocess); return -1; } } diff --git a/os.h b/os.h index 0af10cb..d2920db 100644 --- a/os.h +++ b/os.h @@ -84,10 +84,7 @@ typedef struct Process Process; // zero everything except what you're using typedef struct { - bool stdin_blocking; - bool stdout_blocking; bool separate_stderr; - bool stderr_blocking; // not applicable if separate_stderr is false. const char *working_directory; } ProcessSettings; @@ -106,7 +103,7 @@ typedef struct { int process_get_id(void); // execute the given command (like if it was passed to system()), creating a new Process object. // returns a valid process object on failure, but it will have an error, according to process_geterr -Process *process_run_ex(const char *command, const ProcessSettings *props); +Process *process_run_ex(const char *command, const ProcessSettings *settings); // like process_run_ex, but with the default settings Process *process_run(const char *command); // returns the error last error produced, or NULL if there was no error. @@ -115,7 +112,7 @@ const char *process_geterr(Process *process); // returns: // -2 on error // or a non-negative number indicating the number of bytes written. -// If stdin is set to blocking, fewer than `size` bytes will be written only if an error occured. +// Currently, this does a blocking write. long long process_write(Process *process, const char *data, size_t size); // read from stdout+stderr // returns: @@ -123,7 +120,7 @@ long long process_write(Process *process, const char *data, size_t size); // -1 if no data is available right now // 0 on end of file // or a positive number indicating the number of bytes read to data (at most size) -// If stdout is set to blocking, fewer than `size` bytes will be read only if an error occured or end-of-file was reached. +// This does a nonblocking read. long long process_read(Process *process, char *data, size_t size); // like process_read, but reads stderr. // this function ALWAYS RETURNS -2 if separate_stderr is not specified in the ProcessSettings. -- cgit v1.2.3