#ifndef _WIN32
#define _POSIX_C_SOURCE 200809L
#endif
#include "platform.h"
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#ifdef _WIN32
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <direct.h>
#include <io.h>
#include <process.h>
#else
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#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