#ifndef _WIN32 #define _POSIX_C_SOURCE 200809L #endif #include "platform.h" #include #include #include #include #ifdef _WIN32 #define WIN32_LEAN_AND_MEAN #include #include #include #include #else #include #include #include #include #endif static bool platform_error(Error* error, const char* action, const char* description) { error->line = 0; snprintf(error->message, sizeof(error->message), "cannot %s %s", action, description); return false; } static bool append_bytes(char** buffer, size_t* length, size_t* capacity, const char* data, size_t count) { if (count > SIZE_MAX - *length - 1) return false; size_t needed = *length + count + 1; if (needed > *capacity) { size_t grown = *capacity == 0 ? 4096 : *capacity; while (grown < needed) { if (grown > SIZE_MAX / 2) { grown = needed; break; } grown *= 2; } char* replacement = realloc(*buffer, grown); if (replacement == NULL) return false; *buffer = replacement; *capacity = grown; } memcpy(*buffer + *length, data, count); *length += count; (*buffer)[*length] = '\0'; return true; } #ifdef _WIN32 typedef struct { char* data; size_t length; size_t capacity; } CommandLine; static bool command_append(CommandLine* line, const char* text, size_t count) { return append_bytes(&line->data, &line->length, &line->capacity, text, count); } /* Quote an argv element using the parsing rules used by the Microsoft C runtime. */ static bool command_argument(CommandLine* line, const char* argument) { bool quote = *argument == '\0' || strpbrk(argument, " \t\"") != NULL; if (!quote) return command_append(line, argument, strlen(argument)); if (!command_append(line, "\"", 1)) return false; size_t slashes = 0; for (const char* p = argument;; p++) { if (*p == '\\') { slashes++; continue; } if (*p == '\"' || *p == '\0') { size_t count = slashes * 2 + (*p == '\"'); for (size_t i = 0; i < count; i++) if (!command_append(line, "\\", 1)) return false; slashes = 0; if (*p == '\0') break; } else { for (size_t i = 0; i < slashes; i++) if (!command_append(line, "\\", 1)) return false; slashes = 0; } if (!command_append(line, p, 1)) return false; } return command_append(line, "\"", 1); } static char* windows_command_line(char* const argv[]) { CommandLine line = {0}; for (size_t i = 0; argv[i] != NULL; i++) { if (i != 0 && !command_append(&line, " ", 1)) goto failure; if (!command_argument(&line, argv[i])) goto failure; } return line.data; failure: free(line.data); return NULL; } bool platform_run(char* const argv[], int* exit_code, Error* error, const char* description) { intptr_t result = _spawnvp(_P_WAIT, argv[0], (const char* const*)argv); if (result == -1) return platform_error(error, "start", description); *exit_code = (int)result; return true; } bool platform_capture(char* const argv[], char** output, int* exit_code, Error* error, const char* description) { SECURITY_ATTRIBUTES security = { .nLength = sizeof(security), .lpSecurityDescriptor = NULL, .bInheritHandle = TRUE, }; HANDLE read_pipe = NULL, write_pipe = NULL; if (!CreatePipe(&read_pipe, &write_pipe, &security, 0) || !SetHandleInformation(read_pipe, HANDLE_FLAG_INHERIT, 0)) { if (read_pipe != NULL) CloseHandle(read_pipe); if (write_pipe != NULL) CloseHandle(write_pipe); return platform_error(error, "create output pipe for", description); } char* command_line = windows_command_line(argv); if (command_line == NULL) { CloseHandle(read_pipe); CloseHandle(write_pipe); return platform_error(error, "build command line for", description); } STARTUPINFOA startup = {.cb = sizeof(startup)}; startup.dwFlags = STARTF_USESTDHANDLES; startup.hStdInput = GetStdHandle(STD_INPUT_HANDLE); startup.hStdOutput = write_pipe; startup.hStdError = write_pipe; PROCESS_INFORMATION process = {0}; BOOL started = CreateProcessA(NULL, command_line, NULL, NULL, TRUE, CREATE_NO_WINDOW, NULL, NULL, &startup, &process); free(command_line); CloseHandle(write_pipe); if (!started) { CloseHandle(read_pipe); return platform_error(error, "start", description); } char* buffer = NULL; size_t length = 0, capacity = 0; char chunk[4096]; DWORD count = 0; bool ok = true; while (ReadFile(read_pipe, chunk, sizeof(chunk), &count, NULL) && count != 0) { if (ok && !append_bytes(&buffer, &length, &capacity, chunk, count)) ok = false; } CloseHandle(read_pipe); WaitForSingleObject(process.hProcess, INFINITE); DWORD result = 1; if (!GetExitCodeProcess(process.hProcess, &result)) ok = false; CloseHandle(process.hThread); CloseHandle(process.hProcess); if (buffer == NULL) buffer = calloc(1, 1); if (!ok || buffer == NULL) { free(buffer); return platform_error(error, "capture output from", description); } *output = buffer; *exit_code = (int)result; return true; } bool platform_current_executable(char* path, size_t capacity) { if (capacity == 0 || capacity > (size_t)MAXDWORD) return false; DWORD count = GetModuleFileNameA(NULL, path, (DWORD)capacity); return count != 0 && count < capacity; } bool platform_temporary_file(char* path, size_t capacity) { char directory[MAX_PATH + 1]; DWORD count = GetTempPathA(sizeof(directory), directory); if (count == 0 || count >= sizeof(directory) || capacity < MAX_PATH + 1) return false; return GetTempFileNameA(directory, "vdt", 0, path) != 0; } bool platform_temporary_directory(char* path, size_t capacity) { if (!platform_temporary_file(path, capacity)) return false; if (!DeleteFileA(path)) return false; return CreateDirectoryA(path, NULL) != 0; } bool platform_delete_file(const char* path) { return DeleteFileA(path) != 0; } bool platform_remove_directory(const char* path) { return RemoveDirectoryA(path) != 0; } bool platform_ensure_directory(const char* path) { if (CreateDirectoryA(path, NULL)) return true; return GetLastError() == ERROR_ALREADY_EXISTS; } char platform_path_separator(void) { return '\\'; } #else bool platform_run(char* const argv[], int* exit_code, Error* error, const char* description) { pid_t child = fork(); if (child < 0) return platform_error(error, "start", description); if (child == 0) { execvp(argv[0], argv); _exit(127); } int status = 0; pid_t waited; do { waited = waitpid(child, &status, 0); } while (waited < 0 && errno == EINTR); if (waited < 0) return platform_error(error, "wait for", description); *exit_code = WIFEXITED(status) ? WEXITSTATUS(status) : 128 + WTERMSIG(status); return true; } bool platform_capture(char* const argv[], char** output, int* exit_code, Error* error, const char* description) { int pipes[2]; if (pipe(pipes) != 0) return platform_error(error, "create output pipe for", description); pid_t child = fork(); if (child < 0) { close(pipes[0]); close(pipes[1]); return platform_error(error, "start", description); } if (child == 0) { close(pipes[0]); dup2(pipes[1], STDOUT_FILENO); dup2(pipes[1], STDERR_FILENO); close(pipes[1]); execvp(argv[0], argv); _exit(127); } close(pipes[1]); char* buffer = NULL; size_t length = 0, capacity = 0; char chunk[4096]; bool ok = true; for (;;) { ssize_t count = read(pipes[0], chunk, sizeof(chunk)); if (count == 0) break; if (count < 0) { if (errno == EINTR) continue; ok = false; break; } if (!append_bytes(&buffer, &length, &capacity, chunk, (size_t)count)) { ok = false; break; } } close(pipes[0]); int status = 0; pid_t waited; do { waited = waitpid(child, &status, 0); } while (waited < 0 && errno == EINTR); if (buffer == NULL) buffer = calloc(1, 1); if (!ok || waited < 0 || buffer == NULL) { free(buffer); return platform_error(error, "capture output from", description); } *output = buffer; *exit_code = WIFEXITED(status) ? WEXITSTATUS(status) : 128 + WTERMSIG(status); return true; } bool platform_current_executable(char* path, size_t capacity) { if (capacity == 0) return false; ssize_t count = readlink("/proc/self/exe", path, capacity - 1); if (count < 0 || (size_t)count >= capacity - 1) return false; path[count] = '\0'; return true; } bool platform_temporary_file(char* path, size_t capacity) { const char pattern[] = "/tmp/vedit-subtitle-XXXXXX"; if (capacity < sizeof(pattern)) return false; memcpy(path, pattern, sizeof(pattern)); int fd = mkstemp(path); if (fd < 0) return false; return close(fd) == 0; } bool platform_temporary_directory(char* path, size_t capacity) { const char pattern[] = "/tmp/vedit-fonts-XXXXXX"; if (capacity < sizeof(pattern)) return false; memcpy(path, pattern, sizeof(pattern)); return mkdtemp(path) != NULL; } bool platform_delete_file(const char* path) { return unlink(path) == 0; } bool platform_remove_directory(const char* path) { return rmdir(path) == 0; } bool platform_ensure_directory(const char* path) { return mkdir(path, 0700) == 0 || errno == EEXIST; } char platform_path_separator(void) { return '/'; } #endif