2024-04-18 12:59:11 +00:00
|
|
|
#pragma once
|
|
|
|
#include "dqn.h"
|
|
|
|
|
2024-08-01 03:34:36 +00:00
|
|
|
#include <dirent.h> // readdir, opendir, closedir
|
|
|
|
|
2024-03-25 05:11:57 +00:00
|
|
|
/*
|
2024-01-31 12:49:23 +00:00
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
//
|
|
|
|
// $$$$$$\ $$$$$$\ $$$$$$$\ $$$$$$\ $$$$$$\ $$$$$$\ $$\ $$\
|
|
|
|
// $$ __$$\ $$ __$$\ $$ __$$\ $$ __$$\ $$ __$$\ \_$$ _|$$ | $$ |
|
|
|
|
// $$ / $$ |$$ / \__| $$ | $$ |$$ / $$ |$$ / \__| $$ | \$$\ $$ |
|
|
|
|
// $$ | $$ |\$$$$$$\ $$$$$$$ |$$ | $$ |\$$$$$$\ $$ | \$$$$ /
|
|
|
|
// $$ | $$ | \____$$\ $$ ____/ $$ | $$ | \____$$\ $$ | $$ $$<
|
|
|
|
// $$ | $$ |$$\ $$ | $$ | $$ | $$ |$$\ $$ | $$ | $$ /\$$\
|
|
|
|
// $$$$$$ |\$$$$$$ | $$ | $$$$$$ |\$$$$$$ |$$$$$$\ $$ / $$ |
|
|
|
|
// \______/ \______/ \__| \______/ \______/ \______|\__| \__|
|
|
|
|
//
|
2024-03-25 05:11:57 +00:00
|
|
|
// dqn_os_posix.cpp
|
2024-01-31 12:49:23 +00:00
|
|
|
//
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////
|
2024-03-25 05:11:57 +00:00
|
|
|
*/
|
2024-01-31 12:49:23 +00:00
|
|
|
|
2024-03-25 05:11:57 +00:00
|
|
|
// NOTE: [$VMEM] Dqn_OSMem
|
|
|
|
// //////////////////////////////////////////////////////////////////////////
|
2024-01-31 12:49:23 +00:00
|
|
|
static uint32_t Dqn_OS_MemConvertPageToOSFlags_(uint32_t protect)
|
|
|
|
{
|
|
|
|
DQN_ASSERT((protect & ~Dqn_OSMemPage_All) == 0);
|
|
|
|
DQN_ASSERT(protect != 0);
|
|
|
|
uint32_t result = 0;
|
|
|
|
|
|
|
|
if (protect & (Dqn_OSMemPage_NoAccess | Dqn_OSMemPage_Guard)) {
|
|
|
|
result = PROT_NONE;
|
|
|
|
} else {
|
|
|
|
if (protect & Dqn_OSMemPage_Read)
|
|
|
|
result = PROT_READ;
|
|
|
|
if (protect & Dqn_OSMemPage_Write)
|
|
|
|
result = PROT_WRITE;
|
|
|
|
}
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
DQN_API void *Dqn_OS_MemReserve(Dqn_usize size, Dqn_OSMemCommit commit, uint32_t page_flags)
|
|
|
|
{
|
|
|
|
unsigned long os_page_flags = Dqn_OS_MemConvertPageToOSFlags_(page_flags);
|
|
|
|
|
|
|
|
if (commit == Dqn_OSMemCommit_Yes)
|
|
|
|
os_page_flags |= (PROT_READ | PROT_WRITE);
|
|
|
|
|
2024-03-25 05:11:57 +00:00
|
|
|
void *result = mmap(nullptr, size, os_page_flags, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
|
2024-01-31 12:49:23 +00:00
|
|
|
if (result == MAP_FAILED)
|
|
|
|
result = nullptr;
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
DQN_API bool Dqn_OS_MemCommit(void *ptr, Dqn_usize size, uint32_t page_flags)
|
|
|
|
{
|
|
|
|
bool result = false;
|
|
|
|
if (!ptr || size == 0)
|
|
|
|
return false;
|
|
|
|
|
|
|
|
unsigned long os_page_flags = Dqn_OS_MemConvertPageToOSFlags_(page_flags);
|
|
|
|
result = mprotect(ptr, size, os_page_flags) == 0;
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
DQN_API void Dqn_OS_MemDecommit(void *ptr, Dqn_usize size)
|
|
|
|
{
|
|
|
|
mprotect(ptr, size, PROT_NONE);
|
|
|
|
madvise(ptr, size, MADV_FREE);
|
|
|
|
}
|
|
|
|
|
|
|
|
DQN_API void Dqn_OS_MemRelease(void *ptr, Dqn_usize size)
|
|
|
|
{
|
|
|
|
munmap(ptr, size);
|
|
|
|
}
|
|
|
|
|
|
|
|
DQN_API int Dqn_OS_MemProtect(void *ptr, Dqn_usize size, uint32_t page_flags)
|
|
|
|
{
|
|
|
|
if (!ptr || size == 0)
|
|
|
|
return 0;
|
|
|
|
|
2024-03-25 05:11:57 +00:00
|
|
|
static Dqn_Str8 const ALIGNMENT_ERROR_MSG = DQN_STR8(
|
|
|
|
"Page protection requires pointers to be page aligned because we "
|
|
|
|
"can only guard memory at a multiple of the page boundary.");
|
|
|
|
DQN_ASSERTF(Dqn_IsPowerOfTwoAligned(DQN_CAST(uintptr_t) ptr, g_dqn_library->os_page_size),
|
|
|
|
"%s",
|
|
|
|
ALIGNMENT_ERROR_MSG.data);
|
|
|
|
DQN_ASSERTF(
|
|
|
|
Dqn_IsPowerOfTwoAligned(size, g_dqn_library->os_page_size), "%s", ALIGNMENT_ERROR_MSG.data);
|
2024-01-31 12:49:23 +00:00
|
|
|
|
|
|
|
unsigned long os_page_flags = Dqn_OS_MemConvertPageToOSFlags_(page_flags);
|
|
|
|
int result = mprotect(ptr, size, os_page_flags);
|
|
|
|
DQN_ASSERTF(result == 0, "mprotect failed (%d)", errno);
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
// NOTE: [$DATE] Date //////////////////////////////////////////////////////////////////////////////
|
|
|
|
DQN_API Dqn_OSDateTime Dqn_OS_DateLocalTimeNow()
|
|
|
|
{
|
2024-03-25 05:11:57 +00:00
|
|
|
Dqn_OSDateTime result = {};
|
2024-01-31 12:49:23 +00:00
|
|
|
struct timespec ts;
|
|
|
|
clock_gettime(CLOCK_REALTIME, &ts);
|
|
|
|
|
|
|
|
// NOTE: localtime_r is used because it is thread safe
|
|
|
|
// See: https://linux.die.net/man/3/localtime
|
|
|
|
// According to POSIX.1-2004, localtime() is required to behave as though
|
|
|
|
// tzset(3) was called, while localtime_r() does not have this requirement.
|
|
|
|
// For portable code tzset(3) should be called before localtime_r().
|
|
|
|
for (static bool once = true; once; once = false)
|
|
|
|
tzset();
|
|
|
|
|
|
|
|
struct tm time = {};
|
|
|
|
localtime_r(&ts.tv_sec, &time);
|
|
|
|
|
|
|
|
result.hour = time.tm_hour;
|
|
|
|
result.minutes = time.tm_min;
|
|
|
|
result.seconds = time.tm_sec;
|
|
|
|
|
2024-03-25 05:11:57 +00:00
|
|
|
result.day = DQN_CAST(uint8_t) time.tm_mday;
|
|
|
|
result.month = DQN_CAST(uint8_t) time.tm_mon + 1;
|
|
|
|
result.year = 1900 + DQN_CAST(int16_t) time.tm_year;
|
2024-01-31 12:49:23 +00:00
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
DQN_API uint64_t Dqn_OS_DateUnixTime()
|
|
|
|
{
|
|
|
|
uint64_t result = time(nullptr);
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
2024-02-25 11:37:14 +00:00
|
|
|
DQN_API uint64_t Dqn_OS_DateToUnixTime(Dqn_OSDateTime date)
|
|
|
|
{
|
|
|
|
DQN_ASSERT(Dqn_OS_DateIsValid(date));
|
|
|
|
struct tm timeinfo = {};
|
|
|
|
timeinfo.tm_year = date.year - 1900;
|
|
|
|
timeinfo.tm_mon = date.month - 1;
|
|
|
|
timeinfo.tm_mday = date.day;
|
|
|
|
timeinfo.tm_hour = date.hour;
|
|
|
|
timeinfo.tm_min = date.minutes;
|
|
|
|
timeinfo.tm_sec = date.seconds;
|
|
|
|
uint64_t result = mktime(&timeinfo);
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
2024-04-18 12:59:11 +00:00
|
|
|
DQN_API Dqn_OSDateTime Dqn_OS_DateUnixTimeToDate(uint64_t time)
|
|
|
|
{
|
|
|
|
time_t posix_time = DQN_CAST(time_t) time;
|
|
|
|
struct tm posix_date = *gmtime(&posix_time);
|
|
|
|
Dqn_OSDateTime result = {};
|
|
|
|
result.year = posix_date.tm_year + 1900;
|
|
|
|
result.month = posix_date.tm_mon + 1;
|
|
|
|
result.day = posix_date.tm_mday;
|
|
|
|
result.hour = posix_date.tm_hour;
|
|
|
|
result.minutes = posix_date.tm_min;
|
|
|
|
result.seconds = posix_date.tm_sec;
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
2024-01-31 12:49:23 +00:00
|
|
|
DQN_API bool Dqn_OS_SecureRNGBytes(void *buffer, uint32_t size)
|
|
|
|
{
|
2024-01-31 13:03:06 +00:00
|
|
|
#if defined(DQN_PLATFORM_EMSCRIPTEN)
|
2024-03-25 05:11:57 +00:00
|
|
|
(void)buffer;
|
|
|
|
(void)size;
|
2024-01-31 13:03:06 +00:00
|
|
|
return false;
|
|
|
|
#else
|
2024-01-31 12:49:23 +00:00
|
|
|
if (!buffer || size < 0)
|
|
|
|
return false;
|
|
|
|
|
|
|
|
if (size == 0)
|
|
|
|
return true;
|
|
|
|
|
|
|
|
DQN_ASSERTF(size <= 32,
|
2024-03-25 05:11:57 +00:00
|
|
|
"We can increase this by chunking the buffer and filling 32 bytes at a time. *Nix "
|
|
|
|
"guarantees 32 "
|
2024-01-31 12:49:23 +00:00
|
|
|
"bytes can always be fulfilled by this system at a time");
|
2024-03-25 05:11:57 +00:00
|
|
|
// TODO(doyle):
|
|
|
|
// https://github.com/jedisct1/libsodium/blob/master/src/libsodium/randombytes/sysrandom/randombytes_sysrandom.c
|
2024-01-31 12:49:23 +00:00
|
|
|
// TODO(doyle): https://man7.org/linux/man-pages/man2/getrandom.2.html
|
|
|
|
uint32_t read_bytes = 0;
|
|
|
|
do {
|
2024-03-25 05:11:57 +00:00
|
|
|
read_bytes =
|
|
|
|
getrandom(buffer, size, 0); // NOTE: EINTR can not be triggered if size <= 32 bytes
|
2024-01-31 12:49:23 +00:00
|
|
|
} while (read_bytes != size || errno == EAGAIN);
|
|
|
|
return true;
|
2024-01-31 13:03:06 +00:00
|
|
|
#endif
|
2024-01-31 12:49:23 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
DQN_API Dqn_Str8 Dqn_OS_EXEPath(Dqn_Arena *arena)
|
|
|
|
{
|
|
|
|
Dqn_Str8 result = {};
|
|
|
|
if (!arena)
|
|
|
|
return result;
|
|
|
|
|
|
|
|
int required_size_wo_null_terminator = 0;
|
|
|
|
for (int try_size = 128;; try_size *= 2) {
|
2024-03-25 05:11:57 +00:00
|
|
|
auto scoped_arena = Dqn_ArenaTempMemScope(arena);
|
|
|
|
char *try_buf = Dqn_Arena_NewArray(arena, char, try_size, Dqn_ZeroMem_No);
|
|
|
|
int bytes_written = readlink("/proc/self/exe", try_buf, try_size);
|
2024-01-31 12:49:23 +00:00
|
|
|
if (bytes_written == -1) {
|
|
|
|
// Failed, we're unable to determine the executable directory
|
|
|
|
break;
|
|
|
|
} else if (bytes_written == try_size) {
|
|
|
|
// Try again, if returned size was equal- we may of prematurely
|
|
|
|
// truncated according to the man pages
|
|
|
|
continue;
|
|
|
|
} else {
|
|
|
|
// readlink will give us the path to the executable. Once we
|
|
|
|
// determine the correct buffer size required to get the full file
|
|
|
|
// path, we do some post-processing on said string and extract just
|
|
|
|
// the directory.
|
|
|
|
|
|
|
|
// TODO(dqn): It'd be nice if there's some way of keeping this
|
|
|
|
// try_buf around, memcopy the byte and trash the try_buf from the
|
|
|
|
// arena. Instead we just get the size and redo the call one last
|
|
|
|
// time after this "calculate" step.
|
2024-03-25 05:11:57 +00:00
|
|
|
DQN_ASSERTF(bytes_written < try_size,
|
|
|
|
"bytes_written can never be greater than the try size, function writes at "
|
|
|
|
"most try_size");
|
2024-01-31 12:49:23 +00:00
|
|
|
required_size_wo_null_terminator = bytes_written;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (required_size_wo_null_terminator) {
|
|
|
|
Dqn_ArenaTempMem temp_mem = Dqn_Arena_TempMemBegin(arena);
|
2024-03-25 05:11:57 +00:00
|
|
|
char *exe_path =
|
|
|
|
Dqn_Arena_NewArray(arena, char, required_size_wo_null_terminator + 1, Dqn_ZeroMem_No);
|
2024-01-31 12:49:23 +00:00
|
|
|
exe_path[required_size_wo_null_terminator] = 0;
|
|
|
|
|
|
|
|
int bytes_written = readlink("/proc/self/exe", exe_path, required_size_wo_null_terminator);
|
|
|
|
if (bytes_written == -1) {
|
|
|
|
// Note that if read-link fails again can be because there's
|
|
|
|
// a potential race condition here, our exe or directory could have
|
|
|
|
// been deleted since the last call, so we need to be careful.
|
|
|
|
Dqn_Arena_TempMemEnd(temp_mem);
|
|
|
|
} else {
|
|
|
|
result = Dqn_Str8_Init(exe_path, required_size_wo_null_terminator);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
DQN_API uint64_t Dqn_OS_PerfCounterFrequency()
|
|
|
|
{
|
|
|
|
// NOTE: On Linux we use clock_gettime(CLOCK_MONOTONIC_RAW) which
|
|
|
|
// increments at nanosecond granularity.
|
|
|
|
uint64_t result = 1'000'000'000;
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
DQN_API uint64_t Dqn_OS_PerfCounterNow()
|
|
|
|
{
|
|
|
|
struct timespec ts;
|
|
|
|
clock_gettime(CLOCK_MONOTONIC_RAW, &ts);
|
|
|
|
uint64_t result = DQN_CAST(uint64_t) ts.tv_sec * 1'000'000'000 + DQN_CAST(uint64_t) ts.tv_nsec;
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
#if !defined(DQN_NO_OS_FILE_API)
|
|
|
|
DQN_API Dqn_OSPathInfo Dqn_OS_PathInfo(Dqn_Str8 path)
|
|
|
|
{
|
|
|
|
Dqn_OSPathInfo result = {};
|
|
|
|
if (!Dqn_Str8_HasData(path))
|
|
|
|
return result;
|
|
|
|
|
|
|
|
struct stat file_stat;
|
|
|
|
if (lstat(path.data, &file_stat) != -1) {
|
|
|
|
result.exists = true;
|
|
|
|
result.size = file_stat.st_size;
|
|
|
|
result.last_access_time_in_s = file_stat.st_atime;
|
|
|
|
result.last_write_time_in_s = file_stat.st_mtime;
|
|
|
|
// TODO(dqn): Seems linux does not support creation time via stat. We
|
|
|
|
// shoddily deal with this.
|
2024-03-25 05:11:57 +00:00
|
|
|
result.create_time_in_s =
|
|
|
|
DQN_MIN(result.last_access_time_in_s, result.last_write_time_in_s);
|
2024-08-01 03:34:36 +00:00
|
|
|
|
|
|
|
if (S_ISDIR(file_stat.st_mode)) {
|
|
|
|
result.type = Dqn_OSPathInfoType_Directory;
|
|
|
|
} else if (S_ISREG(file_stat.st_mode)) {
|
|
|
|
result.type = Dqn_OSPathInfoType_File;
|
|
|
|
}
|
2024-01-31 12:49:23 +00:00
|
|
|
}
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
2024-02-11 07:23:13 +00:00
|
|
|
DQN_API bool Dqn_OS_PathDelete(Dqn_Str8 path)
|
|
|
|
{
|
|
|
|
bool result = false;
|
|
|
|
if (Dqn_Str8_HasData(path))
|
|
|
|
result = remove(path.data) == 0;
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
2024-01-31 12:49:23 +00:00
|
|
|
DQN_API bool Dqn_OS_FileExists(Dqn_Str8 path)
|
|
|
|
{
|
|
|
|
bool result = false;
|
|
|
|
if (!Dqn_Str8_HasData(path))
|
|
|
|
return result;
|
|
|
|
|
|
|
|
struct stat stat_result;
|
|
|
|
if (lstat(path.data, &stat_result) != -1)
|
|
|
|
result = S_ISREG(stat_result.st_mode) || S_ISLNK(stat_result.st_mode);
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
2024-02-11 07:23:13 +00:00
|
|
|
DQN_API bool Dqn_OS_CopyFile(Dqn_Str8 src, Dqn_Str8 dest, bool overwrite, Dqn_ErrorSink *error)
|
2024-01-31 12:49:23 +00:00
|
|
|
{
|
|
|
|
bool result = false;
|
|
|
|
#if defined(DQN_PLATFORM_EMSCRIPTEN)
|
2024-02-11 07:23:13 +00:00
|
|
|
Dqn_ErrorSink_MakeF(error, 1, "Unsupported on Emscripten because of their VFS model");
|
2024-01-31 12:49:23 +00:00
|
|
|
#else
|
2024-02-11 07:23:13 +00:00
|
|
|
int src_fd = open(src.data, O_RDONLY);
|
|
|
|
if (src_fd == -1) {
|
|
|
|
int error_code = errno;
|
|
|
|
Dqn_ErrorSink_MakeF(error,
|
|
|
|
error_code,
|
|
|
|
"Failed to open file '%.*s' for copying: (%d) %s",
|
|
|
|
DQN_STR_FMT(src),
|
|
|
|
error_code,
|
|
|
|
strerror(error_code));
|
|
|
|
return result;
|
2024-01-31 12:49:23 +00:00
|
|
|
}
|
2024-03-25 05:11:57 +00:00
|
|
|
DQN_DEFER
|
|
|
|
{
|
2024-01-31 12:49:23 +00:00
|
|
|
close(src_fd);
|
2024-02-11 07:23:13 +00:00
|
|
|
};
|
2024-01-31 12:49:23 +00:00
|
|
|
|
2024-02-11 07:23:13 +00:00
|
|
|
int dest_fd = open(dest.data, O_WRONLY | O_CREAT | (overwrite ? O_TRUNC : 0));
|
|
|
|
if (dest_fd == -1) {
|
|
|
|
int error_code = errno;
|
|
|
|
Dqn_ErrorSink_MakeF(error,
|
|
|
|
error_code,
|
|
|
|
"Failed to open file destination '%.*s' for copying to: (%d) %s",
|
|
|
|
DQN_STR_FMT(src),
|
|
|
|
error_code,
|
|
|
|
strerror(error_code));
|
|
|
|
return result;
|
|
|
|
}
|
2024-03-25 05:11:57 +00:00
|
|
|
DQN_DEFER
|
|
|
|
{
|
2024-01-31 12:49:23 +00:00
|
|
|
close(dest_fd);
|
2024-02-11 07:23:13 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
struct stat stat_existing;
|
2024-03-25 05:11:57 +00:00
|
|
|
int fstat_result = fstat(src_fd, &stat_existing);
|
2024-02-11 07:23:13 +00:00
|
|
|
if (fstat_result == -1) {
|
|
|
|
int error_code = errno;
|
|
|
|
Dqn_ErrorSink_MakeF(error,
|
|
|
|
error_code,
|
|
|
|
"Failed to query file size of '%.*s' for copying: (%d) %s",
|
|
|
|
DQN_STR_FMT(src),
|
|
|
|
error_code,
|
|
|
|
strerror(error_code));
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
ssize_t bytes_written = sendfile64(dest_fd, src_fd, 0, stat_existing.st_size);
|
|
|
|
result = (bytes_written == stat_existing.st_size);
|
|
|
|
if (!result) {
|
2024-03-25 05:11:57 +00:00
|
|
|
int error_code = errno;
|
2024-08-01 03:34:36 +00:00
|
|
|
Dqn_TLSTMem tmem = Dqn_TLS_TMem(nullptr);
|
2024-03-25 05:11:57 +00:00
|
|
|
Dqn_Str8 file_size_str8 =
|
2024-08-01 03:34:36 +00:00
|
|
|
Dqn_U64ToByteSizeStr8(tmem.arena, stat_existing.st_size, Dqn_U64ByteSizeType_Auto);
|
2024-03-25 05:11:57 +00:00
|
|
|
Dqn_Str8 bytes_written_str8 =
|
2024-08-01 03:34:36 +00:00
|
|
|
Dqn_U64ToByteSizeStr8(tmem.arena, bytes_written, Dqn_U64ByteSizeType_Auto);
|
2024-02-11 07:23:13 +00:00
|
|
|
Dqn_ErrorSink_MakeF(error,
|
|
|
|
error_code,
|
2024-03-25 05:11:57 +00:00
|
|
|
"Failed to copy file '%.*s' to '%.*s', we copied %.*s but the file "
|
|
|
|
"size is %.*s: (%d) %s",
|
2024-02-11 07:23:13 +00:00
|
|
|
DQN_STR_FMT(src),
|
|
|
|
DQN_STR_FMT(dest),
|
|
|
|
DQN_STR_FMT(bytes_written_str8),
|
|
|
|
DQN_STR_FMT(file_size_str8),
|
|
|
|
error_code,
|
|
|
|
strerror(error_code));
|
|
|
|
}
|
|
|
|
|
2024-01-31 12:49:23 +00:00
|
|
|
#endif
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
2024-02-11 07:23:13 +00:00
|
|
|
DQN_API bool Dqn_OS_MoveFile(Dqn_Str8 src, Dqn_Str8 dest, bool overwrite, Dqn_ErrorSink *error)
|
2024-01-31 12:49:23 +00:00
|
|
|
{
|
|
|
|
// See: https://github.com/gingerBill/gb/blob/master/gb.h
|
|
|
|
bool result = false;
|
|
|
|
bool file_moved = true;
|
|
|
|
if (link(src.data, dest.data) == -1) {
|
|
|
|
// NOTE: Link can fail if we're trying to link across different volumes
|
|
|
|
// so we fall back to a binary directory.
|
2024-02-11 07:23:13 +00:00
|
|
|
file_moved |= Dqn_OS_CopyFile(src, dest, overwrite, error);
|
2024-01-31 12:49:23 +00:00
|
|
|
}
|
|
|
|
|
2024-02-11 07:23:13 +00:00
|
|
|
if (file_moved) {
|
2024-03-25 05:16:13 +00:00
|
|
|
result = true;
|
2024-02-11 07:23:13 +00:00
|
|
|
int unlink_result = unlink(src.data);
|
|
|
|
if (unlink_result == -1) {
|
|
|
|
int error_code = errno;
|
2024-03-25 05:11:57 +00:00
|
|
|
Dqn_ErrorSink_MakeF(
|
|
|
|
error,
|
|
|
|
error_code,
|
|
|
|
"File '%.*s' was moved but failed to be unlinked from old location: (%d) %s",
|
|
|
|
DQN_STR_FMT(src),
|
|
|
|
error_code,
|
|
|
|
strerror(error_code));
|
2024-02-11 07:23:13 +00:00
|
|
|
}
|
|
|
|
}
|
2024-01-31 12:49:23 +00:00
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
2024-02-11 07:23:13 +00:00
|
|
|
DQN_API bool Dqn_OS_MakeDir(Dqn_Str8 path)
|
2024-01-31 12:49:23 +00:00
|
|
|
{
|
2024-08-01 03:34:36 +00:00
|
|
|
Dqn_TLSTMem tmem = Dqn_TLS_TMem(nullptr);
|
|
|
|
bool result = true;
|
2024-01-31 12:49:23 +00:00
|
|
|
|
2024-03-25 05:11:57 +00:00
|
|
|
// TODO(doyle): Implement this without using the path indexes, it's not
|
2024-01-31 12:49:23 +00:00
|
|
|
// necessary. See Windows implementation.
|
|
|
|
Dqn_usize path_indexes_size = 0;
|
2024-03-25 05:11:57 +00:00
|
|
|
uint16_t path_indexes[64] = {};
|
2024-01-31 12:49:23 +00:00
|
|
|
|
2024-08-01 03:34:36 +00:00
|
|
|
Dqn_Str8 copy = Dqn_Str8_Copy(tmem.arena, path);
|
2024-01-31 12:49:23 +00:00
|
|
|
for (Dqn_usize index = copy.size - 1; index < copy.size; index--) {
|
|
|
|
bool first_char = index == (copy.size - 1);
|
|
|
|
char ch = copy.data[index];
|
|
|
|
if (ch == '/' || first_char) {
|
|
|
|
char temp = copy.data[index];
|
|
|
|
|
|
|
|
if (!first_char)
|
|
|
|
copy.data[index] = 0; // Temporarily null terminate it
|
|
|
|
|
|
|
|
bool is_file = Dqn_OS_FileExists(copy);
|
|
|
|
|
|
|
|
if (!first_char)
|
|
|
|
copy.data[index] = temp; // Undo null termination
|
|
|
|
|
|
|
|
if (is_file) {
|
|
|
|
// NOTE: There's something that exists in at this path, but
|
|
|
|
// it's not a directory. This request to make a directory is
|
|
|
|
// invalid.
|
|
|
|
return false;
|
2024-03-25 05:11:57 +00:00
|
|
|
} else if (Dqn_OS_DirExists(copy)) {
|
|
|
|
// NOTE: We found a directory, we can stop here and start
|
|
|
|
// building up all the directories that didn't exist up to
|
|
|
|
// this point.
|
|
|
|
break;
|
2024-01-31 12:49:23 +00:00
|
|
|
} else {
|
2024-03-25 05:11:57 +00:00
|
|
|
// NOTE: There's nothing that exists at this path, we can
|
|
|
|
// create a directory here
|
|
|
|
path_indexes[path_indexes_size++] = DQN_CAST(uint16_t) index;
|
2024-01-31 12:49:23 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
for (Dqn_usize index = path_indexes_size - 1; result && index < path_indexes_size; index--) {
|
|
|
|
uint16_t path_index = path_indexes[index];
|
2024-03-25 05:11:57 +00:00
|
|
|
char temp = copy.data[path_index];
|
2024-01-31 12:49:23 +00:00
|
|
|
|
2024-03-25 05:11:57 +00:00
|
|
|
if (index != 0)
|
|
|
|
copy.data[path_index] = 0;
|
2024-01-31 12:49:23 +00:00
|
|
|
result |= mkdir(copy.data, 0774) == 0;
|
2024-03-25 05:11:57 +00:00
|
|
|
if (index != 0)
|
|
|
|
copy.data[path_index] = temp;
|
2024-01-31 12:49:23 +00:00
|
|
|
}
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
2024-08-01 03:34:36 +00:00
|
|
|
DQN_API bool Dqn_OS_DirExists(Dqn_Str8 path)
|
|
|
|
{
|
|
|
|
bool result = false;
|
|
|
|
if (!Dqn_Str8_HasData(path))
|
|
|
|
return result;
|
|
|
|
|
|
|
|
struct stat stat_result;
|
|
|
|
if (lstat(path.data, &stat_result) != -1)
|
|
|
|
result = S_ISDIR(stat_result.st_mode);
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
DQN_API bool Dqn_OS_DirIterate(Dqn_Str8 path, Dqn_OS_DirIterator *it)
|
|
|
|
{
|
|
|
|
if (!it->handle) {
|
|
|
|
it->handle = opendir(path.data);
|
|
|
|
if (!it->handle)
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
struct dirent *entry;
|
|
|
|
for (;;) {
|
|
|
|
entry = readdir(DQN_CAST(DIR*)it->handle);
|
|
|
|
if (entry == NULL)
|
|
|
|
break;
|
|
|
|
|
|
|
|
if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
Dqn_usize name_size = Dqn_CStr8_Size(entry->d_name);
|
|
|
|
Dqn_usize clamped_size = DQN_MIN(sizeof(it->buffer) - 1, name_size);
|
|
|
|
DQN_ASSERTF(name_size == clamped_size, "name: %s, name_size: %zu, clamped_size: %zu", entry->d_name, name_size, clamped_size);
|
|
|
|
DQN_MEMCPY(it->buffer, entry->d_name, clamped_size);
|
|
|
|
it->buffer[clamped_size] = 0;
|
|
|
|
it->file_name = Dqn_Str8_Init(it->buffer, clamped_size);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
closedir(DQN_CAST(DIR*)it->handle);
|
|
|
|
it->handle = NULL;
|
|
|
|
it->file_name = {};
|
|
|
|
it->buffer[0] = 0;
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2024-01-31 12:49:23 +00:00
|
|
|
// NOTE: R/W Stream API ////////////////////////////////////////////////////////////////////////////
|
2024-03-25 05:11:57 +00:00
|
|
|
DQN_API Dqn_OSFile Dqn_OS_FileOpen(Dqn_Str8 path,
|
|
|
|
Dqn_OSFileOpen open_mode,
|
|
|
|
uint32_t access,
|
|
|
|
Dqn_ErrorSink *error)
|
2024-01-31 12:49:23 +00:00
|
|
|
{
|
|
|
|
Dqn_OSFile result = {};
|
|
|
|
if (!Dqn_Str8_HasData(path) || path.size <= 0)
|
|
|
|
return result;
|
|
|
|
|
|
|
|
if ((access & ~Dqn_OSFileAccess_All) || ((access & Dqn_OSFileAccess_All) == 0)) {
|
|
|
|
DQN_INVALID_CODE_PATH;
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (access & Dqn_OSFileAccess_Execute) {
|
2024-02-11 07:23:13 +00:00
|
|
|
result.error = true;
|
2024-03-25 05:11:57 +00:00
|
|
|
Dqn_ErrorSink_MakeF(
|
|
|
|
error,
|
|
|
|
1,
|
|
|
|
"Failed to open file '%.*s': File access flag 'execute' is not supported",
|
|
|
|
DQN_STR_FMT(path));
|
2024-01-31 12:49:23 +00:00
|
|
|
DQN_INVALID_CODE_PATH; // TODO: Not supported via fopen
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
// NOTE: fopen interface is not as expressive as the Win32
|
|
|
|
// We will fopen the file beforehand to setup the state/check for validity
|
2024-02-11 07:23:13 +00:00
|
|
|
// before closing and reopening it with the correct request access
|
2024-01-31 12:49:23 +00:00
|
|
|
// permissions.
|
|
|
|
{
|
|
|
|
FILE *handle = nullptr;
|
|
|
|
switch (open_mode) {
|
|
|
|
case Dqn_OSFileOpen_CreateAlways: handle = fopen(path.data, "w"); break;
|
2024-03-25 05:11:57 +00:00
|
|
|
case Dqn_OSFileOpen_OpenIfExist: handle = fopen(path.data, "r"); break;
|
|
|
|
case Dqn_OSFileOpen_OpenAlways: handle = fopen(path.data, "a"); break;
|
2024-01-31 12:49:23 +00:00
|
|
|
default: DQN_INVALID_CODE_PATH; break;
|
|
|
|
}
|
2024-02-11 07:23:13 +00:00
|
|
|
|
|
|
|
if (!handle) { // TODO(doyle): FileOpen flag to string
|
|
|
|
result.error = true;
|
2024-03-25 05:11:57 +00:00
|
|
|
Dqn_ErrorSink_MakeF(error,
|
|
|
|
1,
|
|
|
|
"Failed to open file '%.*s': File could not be opened in requested "
|
|
|
|
"mode 'Dqn_OSFileOpen' flag %d",
|
|
|
|
DQN_STR_FMT(path),
|
|
|
|
open_mode);
|
2024-01-31 12:49:23 +00:00
|
|
|
return result;
|
|
|
|
}
|
|
|
|
fclose(handle);
|
|
|
|
}
|
|
|
|
|
|
|
|
char const *fopen_mode = nullptr;
|
2024-03-25 05:11:57 +00:00
|
|
|
if (access & Dqn_OSFileAccess_AppendOnly)
|
2024-01-31 12:49:23 +00:00
|
|
|
fopen_mode = "a+";
|
2024-03-25 05:11:57 +00:00
|
|
|
else if (access & Dqn_OSFileAccess_Write)
|
2024-01-31 12:49:23 +00:00
|
|
|
fopen_mode = "w+";
|
2024-03-25 05:11:57 +00:00
|
|
|
else if (access & Dqn_OSFileAccess_Read)
|
2024-01-31 12:49:23 +00:00
|
|
|
fopen_mode = "r+";
|
|
|
|
|
|
|
|
FILE *handle = fopen(path.data, fopen_mode);
|
|
|
|
if (!handle) {
|
2024-02-11 07:23:13 +00:00
|
|
|
result.error = true;
|
2024-03-25 05:11:57 +00:00
|
|
|
Dqn_ErrorSink_MakeF(error,
|
|
|
|
1,
|
|
|
|
"Failed to open file '%.*s': File could not be opened with requested "
|
|
|
|
"access mode 'Dqn_OSFileAccess' %d",
|
|
|
|
DQN_STR_FMT(path),
|
|
|
|
fopen_mode);
|
2024-01-31 12:49:23 +00:00
|
|
|
return result;
|
|
|
|
}
|
|
|
|
result.handle = handle;
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
2024-02-11 07:23:13 +00:00
|
|
|
DQN_API bool Dqn_OS_FileRead(Dqn_OSFile *file, void *buffer, Dqn_usize size, Dqn_ErrorSink *error)
|
2024-01-31 12:49:23 +00:00
|
|
|
{
|
2024-02-11 07:23:13 +00:00
|
|
|
if (!file || !file->handle || file->error || !buffer || size <= 0)
|
|
|
|
return false;
|
|
|
|
|
2024-03-25 05:11:57 +00:00
|
|
|
if (fread(buffer, size, 1, DQN_CAST(FILE *) file->handle) != 1) {
|
2024-08-01 03:34:36 +00:00
|
|
|
Dqn_TLSTMem tmem = Dqn_TLS_TMem(nullptr);
|
2024-03-25 05:11:57 +00:00
|
|
|
Dqn_Str8 buffer_size_str8 =
|
2024-08-01 03:34:36 +00:00
|
|
|
Dqn_U64ToByteSizeStr8(tmem.arena, size, Dqn_U64ByteSizeType_Auto);
|
2024-03-25 05:11:57 +00:00
|
|
|
Dqn_ErrorSink_MakeF(
|
|
|
|
error, 1, "Failed to read %.*s from file", DQN_STR_FMT(buffer_size_str8));
|
2024-02-11 07:23:13 +00:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2024-03-25 05:11:57 +00:00
|
|
|
DQN_API bool
|
|
|
|
Dqn_OS_FileWritePtr(Dqn_OSFile *file, void const *buffer, Dqn_usize size, Dqn_ErrorSink *error)
|
2024-02-11 07:23:13 +00:00
|
|
|
{
|
|
|
|
if (!file || !file->handle || file->error || !buffer || size <= 0)
|
2024-01-31 12:49:23 +00:00
|
|
|
return false;
|
2024-03-25 05:11:57 +00:00
|
|
|
bool result =
|
|
|
|
fwrite(buffer, DQN_CAST(Dqn_usize) size, 1 /*count*/, DQN_CAST(FILE *) file->handle) ==
|
|
|
|
1 /*count*/;
|
2024-02-11 07:23:13 +00:00
|
|
|
if (!result) {
|
2024-08-01 03:34:36 +00:00
|
|
|
Dqn_TLSTMem tmem = Dqn_TLS_TMem(nullptr);
|
2024-03-25 05:11:57 +00:00
|
|
|
Dqn_Str8 buffer_size_str8 =
|
2024-08-01 03:34:36 +00:00
|
|
|
Dqn_U64ToByteSizeStr8(tmem.arena, size, Dqn_U64ByteSizeType_Auto);
|
2024-03-25 05:11:57 +00:00
|
|
|
Dqn_ErrorSink_MakeF(
|
|
|
|
error, 1, "Failed to write buffer (%s) to file handle", DQN_STR_FMT(buffer_size_str8));
|
2024-02-11 07:23:13 +00:00
|
|
|
}
|
2024-01-31 12:49:23 +00:00
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
2024-02-11 07:23:13 +00:00
|
|
|
DQN_API void Dqn_OS_FileClose(Dqn_OSFile *file)
|
2024-01-31 12:49:23 +00:00
|
|
|
{
|
2024-02-11 07:23:13 +00:00
|
|
|
if (!file || !file->handle || file->error)
|
2024-01-31 12:49:23 +00:00
|
|
|
return;
|
2024-03-25 05:11:57 +00:00
|
|
|
fclose(DQN_CAST(FILE *) file->handle);
|
2024-01-31 12:49:23 +00:00
|
|
|
*file = {};
|
|
|
|
}
|
|
|
|
#endif // !defined(DQN_NO_OS_FILE_API)
|
|
|
|
|
|
|
|
// NOTE: [$EXEC] Dqn_OSExec ////////////////////////////////////////////////////////////////////////
|
2024-03-19 12:11:00 +00:00
|
|
|
DQN_API void Dqn_OS_Exit(int32_t exit_code)
|
2024-01-31 12:49:23 +00:00
|
|
|
{
|
2024-03-25 05:11:57 +00:00
|
|
|
exit(DQN_CAST(int) exit_code);
|
2024-01-31 12:49:23 +00:00
|
|
|
}
|
|
|
|
|
2024-03-25 05:11:57 +00:00
|
|
|
enum Dqn_OSPipeType_ {
|
|
|
|
Dqn_OSPipeType__Read,
|
|
|
|
Dqn_OSPipeType__Write,
|
|
|
|
Dqn_OSPipeType__Count,
|
|
|
|
};
|
|
|
|
|
|
|
|
DQN_API Dqn_OSExecResult Dqn_OS_ExecWait(Dqn_OSExecAsyncHandle handle,
|
|
|
|
Dqn_Arena *arena,
|
|
|
|
Dqn_ErrorSink *error)
|
2024-01-31 12:49:23 +00:00
|
|
|
{
|
|
|
|
Dqn_OSExecResult result = {};
|
2024-03-25 05:11:57 +00:00
|
|
|
if (!handle.process || handle.os_error_code || handle.exit_code) {
|
|
|
|
if (handle.os_error_code)
|
|
|
|
result.os_error_code = handle.os_error_code;
|
|
|
|
else
|
|
|
|
result.exit_code = handle.exit_code;
|
|
|
|
|
|
|
|
DQN_ASSERT(!handle.stdout_read);
|
|
|
|
DQN_ASSERT(!handle.stdout_write);
|
|
|
|
DQN_ASSERT(!handle.stderr_read);
|
|
|
|
DQN_ASSERT(!handle.stderr_write);
|
2024-01-31 12:49:23 +00:00
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
2024-03-25 05:11:57 +00:00
|
|
|
#if defined(DQN_PLATFORM_EMSCRIPTEN)
|
|
|
|
DQN_INVALID_CODE_PATHF("Unsupported operation");
|
|
|
|
#endif
|
2024-01-31 12:49:23 +00:00
|
|
|
|
2024-03-25 05:11:57 +00:00
|
|
|
static_assert(sizeof(pid_t) <= sizeof(handle.process),
|
|
|
|
"We store the PID opaquely in a register sized pointer");
|
2024-01-31 12:49:23 +00:00
|
|
|
pid_t process = {};
|
|
|
|
DQN_MEMCPY(&process, &handle.process, sizeof(process));
|
|
|
|
for (;;) {
|
|
|
|
int status = 0;
|
|
|
|
if (waitpid(process, &status, 0) < 0) {
|
|
|
|
result.os_error_code = errno;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (WIFEXITED(status)) {
|
|
|
|
result.exit_code = WEXITSTATUS(status);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (WIFSIGNALED(status)) {
|
|
|
|
result.os_error_code = WTERMSIG(status);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
2024-03-25 05:11:57 +00:00
|
|
|
|
|
|
|
int stdout_pipe[Dqn_OSPipeType__Count] = {};
|
|
|
|
int stderr_pipe[Dqn_OSPipeType__Count] = {};
|
|
|
|
DQN_MEMCPY(&stdout_pipe[Dqn_OSPipeType__Read],
|
|
|
|
&handle.stdout_read,
|
|
|
|
sizeof(stdout_pipe[Dqn_OSPipeType__Read]));
|
|
|
|
DQN_MEMCPY(&stdout_pipe[Dqn_OSPipeType__Write],
|
|
|
|
&handle.stdout_write,
|
|
|
|
sizeof(stdout_pipe[Dqn_OSPipeType__Write]));
|
|
|
|
DQN_MEMCPY(&stderr_pipe[Dqn_OSPipeType__Read],
|
|
|
|
&handle.stderr_read,
|
|
|
|
sizeof(stderr_pipe[Dqn_OSPipeType__Read]));
|
|
|
|
DQN_MEMCPY(&stderr_pipe[Dqn_OSPipeType__Write],
|
|
|
|
&handle.stderr_write,
|
|
|
|
sizeof(stderr_pipe[Dqn_OSPipeType__Write]));
|
|
|
|
|
|
|
|
// NOTE: Process has finished, stop the write end of the pipe
|
|
|
|
close(stdout_pipe[Dqn_OSPipeType__Write]);
|
|
|
|
close(stderr_pipe[Dqn_OSPipeType__Write]);
|
|
|
|
|
|
|
|
// NOTE: Read the data from the read end of the pipe
|
|
|
|
if (result.os_error_code == 0) {
|
2024-08-01 03:34:36 +00:00
|
|
|
Dqn_TLSTMem tmem = Dqn_TLS_TMem(arena);
|
2024-03-25 05:11:57 +00:00
|
|
|
if (arena && handle.stdout_read) {
|
|
|
|
char buffer[4096];
|
2024-08-01 03:34:36 +00:00
|
|
|
Dqn_Str8Builder builder = Dqn_Str8Builder_Init(tmem.arena);
|
2024-03-25 05:11:57 +00:00
|
|
|
for (;;) {
|
|
|
|
ssize_t bytes_read =
|
|
|
|
read(stdout_pipe[Dqn_OSPipeType__Read], buffer, sizeof(buffer));
|
|
|
|
if (bytes_read <= 0)
|
|
|
|
break;
|
2024-08-01 03:34:36 +00:00
|
|
|
Dqn_Str8Builder_AddF(&builder, "%.*s", bytes_read, buffer);
|
2024-03-25 05:11:57 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
result.stdout_text = Dqn_Str8Builder_Build(&builder, arena);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (arena && handle.stderr_read) {
|
|
|
|
char buffer[4096];
|
2024-08-01 03:34:36 +00:00
|
|
|
Dqn_Str8Builder builder = Dqn_Str8Builder_Init(tmem.arena);
|
2024-03-25 05:11:57 +00:00
|
|
|
for (;;) {
|
|
|
|
ssize_t bytes_read =
|
|
|
|
read(stderr_pipe[Dqn_OSPipeType__Read], buffer, sizeof(buffer));
|
|
|
|
if (bytes_read <= 0)
|
|
|
|
break;
|
2024-08-01 03:34:36 +00:00
|
|
|
Dqn_Str8Builder_AddF(&builder, "%.*s", bytes_read, buffer);
|
2024-03-25 05:11:57 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
result.stderr_text = Dqn_Str8Builder_Build(&builder, arena);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
close(stdout_pipe[Dqn_OSPipeType__Read]);
|
|
|
|
close(stderr_pipe[Dqn_OSPipeType__Read]);
|
2024-01-31 12:49:23 +00:00
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
2024-03-25 05:11:57 +00:00
|
|
|
DQN_API Dqn_OSExecAsyncHandle Dqn_OS_ExecAsync(Dqn_Slice<Dqn_Str8> cmd_line,
|
|
|
|
Dqn_Str8 working_dir,
|
|
|
|
uint8_t exec_flags,
|
|
|
|
Dqn_ErrorSink *error)
|
2024-01-31 12:49:23 +00:00
|
|
|
{
|
2024-03-25 05:11:57 +00:00
|
|
|
#if defined(DQN_PLATFORM_EMSCRIPTEN)
|
|
|
|
DQN_INVALID_CODE_PATHF("Unsupported operation");
|
|
|
|
#endif
|
|
|
|
|
2024-01-31 12:49:23 +00:00
|
|
|
Dqn_OSExecAsyncHandle result = {};
|
|
|
|
if (cmd_line.size == 0)
|
|
|
|
return result;
|
|
|
|
|
2024-08-01 03:34:36 +00:00
|
|
|
Dqn_TLSTMem tmem = Dqn_TLS_TMem(nullptr);
|
|
|
|
Dqn_Str8 cmd_rendered = Dqn_Slice_Str8Render(tmem.arena, cmd_line, DQN_STR8(" "));
|
2024-03-25 05:11:57 +00:00
|
|
|
int stdout_pipe[Dqn_OSPipeType__Count] = {};
|
|
|
|
int stderr_pipe[Dqn_OSPipeType__Count] = {};
|
|
|
|
|
|
|
|
// NOTE: Open stdout pipe //////////////////////////////////////////////////////////////////////
|
|
|
|
if (Dqn_Bit_IsSet(exec_flags, Dqn_OSExecFlag_SaveStdout)) {
|
|
|
|
if (pipe(stdout_pipe) == -1) {
|
|
|
|
result.os_error_code = errno;
|
2024-03-25 05:33:37 +00:00
|
|
|
Dqn_ErrorSink_MakeF(
|
|
|
|
error,
|
|
|
|
result.os_error_code,
|
|
|
|
"Failed to create stdout pipe to redirect the output of the command '%.*s': %s",
|
|
|
|
DQN_STR_FMT(cmd_rendered),
|
|
|
|
strerror(result.os_error_code));
|
2024-03-25 05:11:57 +00:00
|
|
|
return result;
|
|
|
|
}
|
|
|
|
DQN_ASSERT(stdout_pipe[Dqn_OSPipeType__Read] != 0);
|
|
|
|
DQN_ASSERT(stdout_pipe[Dqn_OSPipeType__Write] != 0);
|
|
|
|
}
|
|
|
|
|
|
|
|
DQN_DEFER
|
|
|
|
{
|
|
|
|
if (result.os_error_code == 0 && result.exit_code == 0)
|
|
|
|
return;
|
|
|
|
close(stdout_pipe[Dqn_OSPipeType__Read]);
|
|
|
|
close(stdout_pipe[Dqn_OSPipeType__Write]);
|
|
|
|
};
|
|
|
|
|
|
|
|
// NOTE: Open stderr pipe //////////////////////////////////////////////////////////////////////
|
|
|
|
if (Dqn_Bit_IsSet(exec_flags, Dqn_OSExecFlag_SaveStderr)) {
|
|
|
|
if (Dqn_Bit_IsSet(exec_flags, Dqn_OSExecFlag_MergeStderrToStdout)) {
|
|
|
|
stderr_pipe[Dqn_OSPipeType__Read] = stdout_pipe[Dqn_OSPipeType__Read];
|
|
|
|
stderr_pipe[Dqn_OSPipeType__Write] = stdout_pipe[Dqn_OSPipeType__Write];
|
|
|
|
} else if (pipe(stderr_pipe) == -1) {
|
|
|
|
result.os_error_code = errno;
|
2024-03-25 05:33:37 +00:00
|
|
|
Dqn_ErrorSink_MakeF(
|
|
|
|
error,
|
|
|
|
result.os_error_code,
|
|
|
|
"Failed to create stderr pipe to redirect the output of the command '%.*s': %s",
|
|
|
|
DQN_STR_FMT(cmd_rendered),
|
|
|
|
strerror(result.os_error_code));
|
2024-03-25 05:11:57 +00:00
|
|
|
return result;
|
|
|
|
}
|
|
|
|
DQN_ASSERT(stderr_pipe[Dqn_OSPipeType__Read] != 0);
|
|
|
|
DQN_ASSERT(stderr_pipe[Dqn_OSPipeType__Write] != 0);
|
|
|
|
}
|
|
|
|
|
2024-03-25 05:33:37 +00:00
|
|
|
DQN_DEFER {
|
2024-03-25 05:11:57 +00:00
|
|
|
if (result.os_error_code == 0 && result.exit_code == 0)
|
|
|
|
return;
|
|
|
|
close(stderr_pipe[Dqn_OSPipeType__Read]);
|
|
|
|
close(stderr_pipe[Dqn_OSPipeType__Write]);
|
|
|
|
};
|
|
|
|
|
2024-01-31 12:49:23 +00:00
|
|
|
pid_t child_pid = fork();
|
|
|
|
if (child_pid < 0) {
|
|
|
|
result.os_error_code = errno;
|
2024-03-25 05:33:37 +00:00
|
|
|
Dqn_ErrorSink_MakeF(
|
|
|
|
error,
|
|
|
|
result.os_error_code,
|
|
|
|
"Failed to fork process to execute the command '%.*s': %s",
|
|
|
|
DQN_STR_FMT(cmd_rendered),
|
|
|
|
strerror(result.os_error_code));
|
2024-01-31 12:49:23 +00:00
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
2024-03-25 05:11:57 +00:00
|
|
|
if (child_pid == 0) { // Child process
|
|
|
|
if (Dqn_Bit_IsSet(exec_flags, Dqn_OSExecFlag_SaveStdout) &&
|
|
|
|
(dup2(stdout_pipe[Dqn_OSPipeType__Write], STDOUT_FILENO) == -1)) {
|
|
|
|
result.os_error_code = errno;
|
2024-03-25 05:33:37 +00:00
|
|
|
Dqn_ErrorSink_MakeF(
|
|
|
|
error,
|
|
|
|
result.os_error_code,
|
|
|
|
"Failed to redirect stdout 'write' pipe for output of command '%.*s': %s",
|
|
|
|
DQN_STR_FMT(cmd_rendered),
|
|
|
|
strerror(result.os_error_code));
|
2024-03-25 05:11:57 +00:00
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (Dqn_Bit_IsSet(exec_flags, Dqn_OSExecFlag_SaveStderr) &&
|
|
|
|
(dup2(stderr_pipe[Dqn_OSPipeType__Write], STDERR_FILENO) == -1)) {
|
|
|
|
result.os_error_code = errno;
|
2024-03-25 05:33:37 +00:00
|
|
|
Dqn_ErrorSink_MakeF(
|
|
|
|
error,
|
|
|
|
result.os_error_code,
|
|
|
|
"Failed to redirect stderr 'read' pipe for output of command '%.*s': %s",
|
|
|
|
DQN_STR_FMT(cmd_rendered),
|
|
|
|
strerror(result.os_error_code));
|
2024-03-25 05:11:57 +00:00
|
|
|
return result;
|
|
|
|
}
|
2024-01-31 12:49:23 +00:00
|
|
|
|
|
|
|
// NOTE: Convert the command into something suitable for execvp
|
2024-03-25 05:11:57 +00:00
|
|
|
char **argv =
|
2024-08-01 03:34:36 +00:00
|
|
|
Dqn_Arena_NewArray(tmem.arena, char *, cmd_line.size + 1 /*null*/, Dqn_ZeroMem_Yes);
|
2024-01-31 12:49:23 +00:00
|
|
|
if (!argv) {
|
|
|
|
result.exit_code = -1;
|
2024-03-25 05:33:37 +00:00
|
|
|
Dqn_ErrorSink_MakeF(
|
|
|
|
error,
|
|
|
|
result.os_error_code,
|
|
|
|
"Failed to create argument values from command line '%.*s': Out of memory",
|
|
|
|
DQN_STR_FMT(cmd_rendered));
|
2024-01-31 12:49:23 +00:00
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
for (Dqn_usize arg_index = 0; arg_index < cmd_line.size; arg_index++) {
|
|
|
|
Dqn_Str8 arg = cmd_line.data[arg_index];
|
2024-08-01 03:34:36 +00:00
|
|
|
argv[arg_index] = Dqn_Str8_Copy(tmem.arena, arg).data; // NOTE: Copy string to guarantee it is null-terminated
|
2024-01-31 12:49:23 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// NOTE: Change the working directory if there is one
|
|
|
|
char *prev_working_dir = nullptr;
|
2024-03-25 05:33:37 +00:00
|
|
|
DQN_DEFER {
|
2024-01-31 12:49:23 +00:00
|
|
|
if (!prev_working_dir)
|
|
|
|
return;
|
2024-03-25 05:11:57 +00:00
|
|
|
if (result.os_error_code == 0) {
|
|
|
|
int chdir_result = chdir(prev_working_dir);
|
|
|
|
(void)chdir_result;
|
|
|
|
}
|
2024-01-31 12:49:23 +00:00
|
|
|
free(prev_working_dir);
|
|
|
|
};
|
|
|
|
|
|
|
|
if (working_dir.size) {
|
|
|
|
prev_working_dir = get_current_dir_name();
|
|
|
|
if (chdir(working_dir.data) == -1) {
|
|
|
|
result.os_error_code = errno;
|
2024-03-25 05:33:37 +00:00
|
|
|
Dqn_ErrorSink_MakeF(
|
|
|
|
error,
|
|
|
|
result.os_error_code,
|
|
|
|