429 lines
11 KiB
C++
429 lines
11 KiB
C++
#include "filesystem.h"
|
|
#include <dirent.h>
|
|
#include <sys/stat.h>
|
|
#include <fcntl.h>
|
|
#include <unistd.h>
|
|
#include <sys/syscall.h>
|
|
#include <sys/wait.h>
|
|
#include <sys/mman.h>
|
|
#include <linux/limits.h>
|
|
#include <unistd.h>
|
|
#include <time.h>
|
|
#include <errno.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <spawn.h>
|
|
#include <poll.h>
|
|
|
|
|
|
void (*Error)(const char *, ...);
|
|
|
|
void *VReserve(size_t size) {
|
|
void *result = mmap(0, size, PROT_NONE, MAP_PRIVATE | MAP_ANONYMOUS, -1, (off_t)0);
|
|
return result == (void *)-1 ? 0 : result;
|
|
}
|
|
|
|
bool VCommit(void *p, size_t size) {
|
|
int result = mprotect(p, size, PROT_READ | PROT_WRITE);
|
|
return result == 0;
|
|
}
|
|
|
|
bool VRelease(void *p, size_t size) {
|
|
int result = munmap(p, size);
|
|
return result == 0;
|
|
}
|
|
|
|
bool VDecommit(void *p, size_t size) {
|
|
mprotect(p, size, PROT_NONE);
|
|
madvise(p, size, MADV_DONTNEED);
|
|
return true;
|
|
}
|
|
|
|
void InitOS(void (*error_proc)(const char *, ...)) {
|
|
Error = error_proc;
|
|
}
|
|
|
|
String ReadFile(Allocator al, String path) {
|
|
Scratch scratch(al);
|
|
String null_term = Copy(scratch, path);
|
|
String result = {};
|
|
FILE *f = fopen(null_term.data, "rb");
|
|
if (f) {
|
|
fseek(f, 0, SEEK_END);
|
|
result.len = ftell(f);
|
|
fseek(f, 0, SEEK_SET);
|
|
|
|
result.data = (char *)AllocSize(al, result.len + 1);
|
|
fread(result.data, result.len, 1, f);
|
|
result.data[result.len] = 0;
|
|
|
|
fclose(f);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
bool WriteFile(String path, String data) {
|
|
Scratch scratch;
|
|
String null_term = Copy(scratch, path);
|
|
bool result = false;
|
|
FILE *f = fopen((const char *)null_term.data, "w");
|
|
if (f) {
|
|
size_t written = fwrite(data.data, 1, data.len, f);
|
|
if (written == data.len) {
|
|
result = true;
|
|
}
|
|
fclose(f);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
bool DeleteFile(String path) {
|
|
Scratch scratch;
|
|
String null_term = Copy(scratch, path);
|
|
int result = unlink(null_term.data);
|
|
return result == 0;
|
|
}
|
|
|
|
MakeDirResult MakeDir(String path) {
|
|
Scratch scratch;
|
|
String null_term = Copy(scratch, path);
|
|
int error = mkdir(null_term.data, 0755);
|
|
MakeDirResult result = MakeDirResult_Success;
|
|
if (error != 0) {
|
|
result = MakeDirResult_ErrorOther;
|
|
if (errno == EEXIST) result = MakeDirResult_Exists;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
int64_t GetFileModTime(String path) {
|
|
Scratch scratch;
|
|
String null_term = Copy(scratch, path);
|
|
struct stat attrib = {};
|
|
stat(null_term.data, &attrib);
|
|
struct timespec ts = attrib.st_mtim;
|
|
int64_t result = (((int64_t)ts.tv_sec) * 1000000ll) + ((int64_t)ts.tv_nsec) / 1000ll;
|
|
return result;
|
|
}
|
|
|
|
String GetAbsolutePath(Allocator al, String path) {
|
|
Scratch scratch(al);
|
|
String null_term = Copy(scratch, path);
|
|
char *buffer = AllocArray(al, char, PATH_MAX);
|
|
realpath(null_term.data, buffer);
|
|
String result = buffer;
|
|
return buffer;
|
|
}
|
|
|
|
bool FileExists(String path) {
|
|
Scratch scratch;
|
|
String null_term = Copy(scratch, path);
|
|
bool result = false;
|
|
if (access((char *)null_term.data, F_OK) == 0) {
|
|
result = true;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
bool IsDir(String path) {
|
|
Scratch scratch;
|
|
String null_term = Copy(scratch, path);
|
|
struct stat s;
|
|
if (stat(null_term.data, &s) != 0)
|
|
return false;
|
|
return S_ISDIR(s.st_mode);
|
|
}
|
|
|
|
bool IsFile(String path) {
|
|
Scratch scratch;
|
|
String null_term = Copy(scratch, path);
|
|
struct stat s;
|
|
if (stat(null_term.data, &s) != 0)
|
|
return false;
|
|
return S_ISREG(s.st_mode);
|
|
}
|
|
|
|
bool IsAbsolute(String path) {
|
|
bool result = path.len && path.data[0] == '/';
|
|
return result;
|
|
}
|
|
|
|
String GetWorkingDir(Allocator al) {
|
|
char *buffer = AllocArray(al, char, PATH_MAX);
|
|
char *cwd = getcwd(buffer, PATH_MAX);
|
|
return cwd;
|
|
}
|
|
|
|
String GetExePath(Allocator al) {
|
|
char *buffer = AllocArray(al, char, PATH_MAX);
|
|
readlink("/proc/self/exe", buffer, PATH_MAX);
|
|
return buffer;
|
|
}
|
|
|
|
String GetExeDir(Allocator al) {
|
|
Scratch scratch(al);
|
|
String exe_path = GetExePath(scratch);
|
|
String dir = ChopLastSlash(exe_path);
|
|
String result = Copy(al, dir);
|
|
return result;
|
|
}
|
|
|
|
double get_time_in_micros(void) {
|
|
struct timespec spec;
|
|
clock_gettime(CLOCK_MONOTONIC, &spec);
|
|
return (((double)spec.tv_sec) * 1000000) + (((double)spec.tv_nsec) / 1000);
|
|
}
|
|
|
|
bool IsValid(const FileIter &it) {
|
|
return it.is_valid;
|
|
}
|
|
|
|
void Advance(FileIter *it) {
|
|
struct dirent *file = NULL;
|
|
while ((file = readdir((DIR *)it->dir)) != NULL) {
|
|
if (file->d_name[0] == '.' && file->d_name[1] == '.' && file->d_name[2] == 0) {
|
|
continue;
|
|
}
|
|
if (file->d_name[0] == '.' && file->d_name[1] == 0) {
|
|
continue;
|
|
}
|
|
|
|
it->is_directory = file->d_type == DT_DIR;
|
|
it->filename = Copy(it->allocator, file->d_name);
|
|
|
|
const char *dir_char_ending = it->is_directory ? "/" : "";
|
|
const char *separator = it->path.data[it->path.len - 1] == '/' ? "" : "/";
|
|
it->relative_path = Format(it->allocator, "%.*s%s%s%s", FmtString(it->path), separator, file->d_name, dir_char_ending);
|
|
it->absolute_path = GetAbsolutePath(it->allocator, it->relative_path);
|
|
if (it->is_directory) it->absolute_path = Format(it->allocator, "%.*s/", FmtString(it->absolute_path));
|
|
it->is_valid = true;
|
|
return;
|
|
}
|
|
it->is_valid = false;
|
|
closedir((DIR *)it->dir);
|
|
}
|
|
|
|
FileIter IterateFiles(Allocator alo, String path) {
|
|
FileIter it = {};
|
|
it.allocator = alo;
|
|
it.path = path;
|
|
Scratch scratch(alo);
|
|
String null_term = Copy(scratch, path);
|
|
it.dir = (void *)opendir((char *)null_term.data);
|
|
if (it.dir) {
|
|
Advance(&it);
|
|
}
|
|
return it;
|
|
}
|
|
|
|
// struct Process {
|
|
// bool is_valid;
|
|
// int exit_code;
|
|
// char platform[6 * 8];
|
|
//
|
|
// int64_t view_id; // text editor view
|
|
// bool scroll_to_end;
|
|
// };
|
|
|
|
struct UnixProcess {
|
|
pid_t pid;
|
|
int child_stdout_read;
|
|
int stdin_write;
|
|
};
|
|
|
|
Array<char *> SplitCommand(Allocator allocator, String command_line) {
|
|
Array<char *> cmd = {allocator};
|
|
|
|
String curr = {};
|
|
for (int i = 0; i < command_line.len; i += 1) {
|
|
if (command_line.data[i] == ' ') {
|
|
if (curr.len > 0) {
|
|
Add(&cmd, Copy(allocator, curr).data);
|
|
curr = {};
|
|
}
|
|
continue;
|
|
}
|
|
if (curr.len == 0) {
|
|
curr.data = command_line.data + i;
|
|
}
|
|
curr.len += 1;
|
|
}
|
|
|
|
if (curr.len > 0) {
|
|
Add(&cmd, Copy(allocator, curr).data);
|
|
}
|
|
|
|
return cmd;
|
|
}
|
|
|
|
Process SpawnProcess(String command_line, String working_dir, String write_stdin, Array<String> enviroment) {
|
|
Scratch scratch;
|
|
const int PIPE_READ = 0;
|
|
const int PIPE_WRITE = 1;
|
|
bool error = false;
|
|
|
|
printf("cmd = %.*s\n", FmtString(command_line));
|
|
printf("cwd = %.*s\n", FmtString(working_dir));
|
|
|
|
char *buffer = AllocArray(scratch, char, 4096);
|
|
chdir(working_dir.data);
|
|
getcwd(buffer, 4096);
|
|
defer { chdir(buffer); };
|
|
|
|
|
|
Process process = {};
|
|
UnixProcess *plat = (UnixProcess *)&process.platform;
|
|
Array<char *> args = SplitCommand(scratch, command_line);
|
|
Array<char *> env = {scratch};
|
|
|
|
For (enviroment) {
|
|
Add(&env, Copy(scratch, it).data);
|
|
}
|
|
|
|
int stdout_desc[2] = {};
|
|
int stdin_desc[2] = {};
|
|
posix_spawn_file_actions_t actions = {};
|
|
|
|
if (posix_spawn_file_actions_init(&actions) != 0) {
|
|
perror("posix_spawn_file_actions_init");
|
|
return process;
|
|
}
|
|
defer {
|
|
posix_spawn_file_actions_destroy(&actions);
|
|
};
|
|
|
|
if (pipe(stdout_desc) == -1) {
|
|
perror("pipe");
|
|
return process;
|
|
}
|
|
defer {
|
|
if (error) {
|
|
close(stdout_desc[PIPE_READ]);
|
|
close(stdout_desc[PIPE_WRITE]);
|
|
} else {
|
|
close(stdout_desc[PIPE_WRITE]);
|
|
}
|
|
};
|
|
|
|
if (pipe(stdin_desc) == -1) {
|
|
perror("pipe");
|
|
return process;
|
|
}
|
|
defer {
|
|
if (error) {
|
|
close(stdin_desc[PIPE_READ]);
|
|
close(stdin_desc[PIPE_WRITE]);
|
|
} else {
|
|
close(stdin_desc[PIPE_READ]);
|
|
}
|
|
};
|
|
|
|
error = posix_spawn_file_actions_addclose(&actions, stdout_desc[PIPE_READ]) != 0;
|
|
if (error) {
|
|
perror("posix_spawn_file_actions_addclose");
|
|
return process;
|
|
}
|
|
|
|
error = posix_spawn_file_actions_adddup2(&actions, stdout_desc[PIPE_WRITE], STDOUT_FILENO) != 0;
|
|
if (error) {
|
|
perror("posix_spawn_file_actions_adddup2 STDOUT_FILENO");
|
|
return process;
|
|
}
|
|
|
|
error = posix_spawn_file_actions_adddup2(&actions, stdout_desc[PIPE_WRITE], STDERR_FILENO) != 0;
|
|
if (error) {
|
|
perror("posix_spawn_file_actions_adddup2 STDERR_FILENO");
|
|
return process;
|
|
}
|
|
|
|
error = posix_spawn_file_actions_addclose(&actions, stdin_desc[PIPE_WRITE]) != 0;
|
|
if (error) {
|
|
perror("posix_spawn_file_actions_addclose");
|
|
return process;
|
|
}
|
|
|
|
error = posix_spawn_file_actions_adddup2(&actions, stdin_desc[PIPE_READ], STDIN_FILENO) != 0;
|
|
if (error) {
|
|
perror("posix_spawn_file_actions_adddup2 STDIN_FILENO");
|
|
return process;
|
|
}
|
|
|
|
pid_t process_pid = 0;
|
|
error = posix_spawnp(&process_pid, args[0], &actions, NULL, args.data, env.data) != 0;
|
|
if (error) {
|
|
perror("failed to create process\n");
|
|
return process;
|
|
}
|
|
|
|
|
|
process.is_valid = true;
|
|
plat->child_stdout_read = stdout_desc[PIPE_READ];
|
|
plat->stdin_write = stdin_desc[PIPE_WRITE];
|
|
|
|
if (write_stdin.len) {
|
|
WriteStdin(&process, write_stdin);
|
|
CloseStdin(&process);
|
|
}
|
|
|
|
|
|
|
|
return process;
|
|
}
|
|
|
|
bool IsValid(Process *process) {
|
|
UnixProcess *plat = (UnixProcess *)&process->platform;
|
|
if (process->is_valid == false) {
|
|
return false;
|
|
}
|
|
|
|
int status = 0;
|
|
pid_t result = waitpid(plat->pid, &status, WNOHANG);
|
|
if (result >= 0) {
|
|
if (WIFSIGNALED(status) || WIFEXITED(status)) {
|
|
process->exit_code = WEXITSTATUS(status);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void KillProcess(Process *process) {
|
|
Assert(process->is_valid);
|
|
UnixProcess *plat = (UnixProcess *)process->platform;
|
|
kill(plat->pid, SIGKILL);
|
|
process->exit_code = -1;
|
|
}
|
|
|
|
String PollStdout(Allocator allocator, Process *process, bool force_read) {
|
|
Assert(process->is_valid);
|
|
UnixProcess *plat = (UnixProcess *)process->platform;
|
|
|
|
String result = {};
|
|
result.data = AllocArray(allocator, char, 16 * 4096);
|
|
|
|
pollfd p = {};
|
|
p.fd = plat->child_stdout_read;
|
|
p.events = POLLIN;
|
|
int res = poll(&p, 1, 0);
|
|
if (res == 1 || force_read) {
|
|
result.len = read(plat->child_stdout_read, result.data, 4 * 4096);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
void WriteStdin(Process *process, String string) {
|
|
if (string.len == 0) return;
|
|
Assert(process->is_valid);
|
|
|
|
UnixProcess *plat = (UnixProcess *)process->platform;
|
|
ssize_t size = write(plat->stdin_write, string.data, string.len);
|
|
|
|
Assert(size == string.len);
|
|
}
|
|
|
|
void CloseStdin(Process *process) {
|
|
UnixProcess *plat = (UnixProcess *)process->platform;
|
|
close(plat->stdin_write);
|
|
}
|