Compare commits

..

15 commits

Author SHA1 Message Date
32f31fe6d6 Merge pull request 'Add the DbOperationArgs set of functions, and fix bug with LMDB' (#62) from lda/Cytoplasm:db-args into master
Reviewed-on: Telodendria/Cytoplasm#62
2024-11-10 18:09:05 +00:00
lda
f3838e775c Merge branch 'master' into db-args 2024-11-09 16:03:20 +00:00
LDA
100d61050f [ADD/FIX] Add DbOPERATIONArgs functions, fix LMDB 2024-11-09 16:59:49 +01:00
b4841fffaa Merge pull request 'Add a function to decode a JSON value directly.' (#60) from lda/Cytoplasm:raw-types into master
Reviewed-on: Telodendria/Cytoplasm#60
2024-10-26 18:49:52 +00:00
LDA
39e81139f0 [ADD] Add JsonValueDecode to decode a simple value 2024-10-25 18:45:54 +02:00
8df5f0f1c1 Merge pull request 'Make Memory API more friendly with alignment' (#59) from lda/Cytoplasm:alignment into master
Reviewed-on: Telodendria/Cytoplasm#59
2024-10-24 11:57:50 +00:00
LDA
708c5daad9 [FIX] Fix memory alignment issues
Some architectures(DEC Alpha as a main outlier, but x86 may behave that
way by setting flags) raise traps on unaligned operations, which can be
either costly(having to talk to the kernel, which may have to emulate
the read) or could cause program termination.

Also adds a basic memory interval for checking if a pointer has any
business living within the heap. Most systems separate those anyways so
it avoids doing potentially dangerous operations.
2024-10-24 11:41:33 +02:00
4f316ff7b3
Documentation generator doesn't support C99 comments. 2024-09-21 16:31:31 -04:00
6827d4fc39
Documentation is enforced. 2024-09-21 16:26:46 -04:00
63bd879101
Run CI on pull requests. 2024-09-21 16:23:06 -04:00
f7c51ee019 Basic work toward compiling on Darwin 2024-09-21 15:47:01 -04:00
af4a142261 Whoops, typo 2024-09-21 15:00:08 -04:00
10c8784f25 . 2024-09-21 14:59:13 -04:00
4a21567bc5 2nd CI attempt 2024-09-21 14:52:41 -04:00
ff094b50f2 First attempt to use new CI runner. (#55)
Reviewed-on: Telodendria/Cytoplasm#55
Co-authored-by: Jordan Bancino <jordan@bancino.net>
Co-committed-by: Jordan Bancino <jordan@bancino.net>
2024-09-21 18:46:43 +00:00
12 changed files with 276 additions and 28 deletions

View file

@ -1,6 +1,6 @@
name: Compile Cytoplasm name: Compile Cytoplasm
run-name: Compile Cytoplasm on ${{ forgejo.actor }} run-name: Compile Cytoplasm on ${{ forgejo.actor }}
on: [push] on: [push, pull_request]
jobs: jobs:
"Compile Cytoplasm": "Compile Cytoplasm":
@ -11,7 +11,7 @@ jobs:
runs-on: ["${{ matrix.os }}", "${{ matrix.arch }}"] runs-on: ["${{ matrix.os }}", "${{ matrix.arch }}"]
steps: steps:
- name: Check out repository - name: Check out repository
uses: actions/checkout@v3 uses: actions/checkout@v4
- name: Configure Cytoplasm - name: Configure Cytoplasm
run: ./configure run: ./configure
- name: Build Cytoplasm - name: Build Cytoplasm

View file

@ -21,6 +21,8 @@ are not even initialized to a default value.
- Make `HttpRouter` decode path parts before matching them on regular expressions. - Make `HttpRouter` decode path parts before matching them on regular expressions.
- Fixed a bug in `ArraySort()` that would crash programs if the passed array has no - Fixed a bug in `ArraySort()` that would crash programs if the passed array has no
elements. elements.
- Adds `Db[OP]Args` functions that are equivalent to their `Db[OP]` counter parts, but
uses an array of string instead of variadic arguments.
## v0.4.0 ## v0.4.0

2
configure vendored
View file

@ -37,7 +37,7 @@ case "$(uname)" in
# These systems typically use GCC. # These systems typically use GCC.
SCRIPT_ARGS="${SCRIPT_ARGS} --cc=gcc" SCRIPT_ARGS="${SCRIPT_ARGS} --cc=gcc"
;; ;;
OpenBSD|FreeBSD) OpenBSD|FreeBSD|Darwin)
# These systems typically use Clang. # These systems typically use Clang.
SCRIPT_ARGS="${SCRIPT_ARGS} --cc=clang" SCRIPT_ARGS="${SCRIPT_ARGS} --cc=clang"
;; ;;

View file

@ -119,6 +119,13 @@ extern void DbMaxCacheSet(Db *, size_t);
*/ */
extern DbRef * DbCreate(Db *, size_t,...); extern DbRef * DbCreate(Db *, size_t,...);
/**
* Behaves like
* .Fn DbCreate ,
* but with an array of strings instead of variadic arguments.
*/
extern DbRef * DbCreateArgs(Db *, Array *);
/** /**
* Lock an existing object in the database. This function will fail * Lock an existing object in the database. This function will fail
* if the object does not exist. It takes a variable number of C * if the object does not exist. It takes a variable number of C
@ -129,6 +136,13 @@ extern DbRef * DbCreate(Db *, size_t,...);
*/ */
extern DbRef * DbLock(Db *, size_t,...); extern DbRef * DbLock(Db *, size_t,...);
/**
* Behaves like
* .Fn DbLock ,
* but with an array of strings instead of variadic arguments.
*/
extern DbRef * DbLockArgs(Db *, Array *);
/** /**
* Behaves like * Behaves like
* .Fn DbLock , * .Fn DbLock ,
@ -138,6 +152,13 @@ extern DbRef * DbLock(Db *, size_t,...);
*/ */
extern DbRef * DbLockIntent(Db *, DbHint, size_t,...); extern DbRef * DbLockIntent(Db *, DbHint, size_t,...);
/**
* Behaves like
* .Fn DbLockIntent ,
* but with an array of strings instead of variadic arguments.
*/
extern DbRef * DbLockIntentArgs(Db *, DbHint, Array *);
/** /**
* Immediately and permanently remove an object from the database. * Immediately and permanently remove an object from the database.
* This function assumes the object is not locked, otherwise undefined * This function assumes the object is not locked, otherwise undefined
@ -145,6 +166,13 @@ extern DbRef * DbLockIntent(Db *, DbHint, size_t,...);
*/ */
extern bool DbDelete(Db *, size_t,...); extern bool DbDelete(Db *, size_t,...);
/**
* Behaves like
* .Fn DbDelete ,
* but with an array of strings instead of variadic arguments.
*/
extern bool DbDeleteArgs(Db *, Array *);
/** /**
* Unlock an object and return it back to the database. This function * Unlock an object and return it back to the database. This function
* immediately syncs the object to the filesystem. The cache is a * immediately syncs the object to the filesystem. The cache is a
@ -162,6 +190,13 @@ extern bool DbUnlock(Db *, DbRef *);
*/ */
extern bool DbExists(Db *, size_t,...); extern bool DbExists(Db *, size_t,...);
/**
* Behaves like
* .Fn DbExists ,
* but with an array of strings instead of variadic arguments.
*/
extern bool DbExistsArgs(Db *, Array *);
/** /**
* List all of the objects at a given path. Unlike the other varargs * List all of the objects at a given path. Unlike the other varargs
* functions, this one does not take a path to a specific object; it * functions, this one does not take a path to a specific object; it
@ -174,6 +209,13 @@ extern bool DbExists(Db *, size_t,...);
*/ */
extern Array * DbList(Db *, size_t,...); extern Array * DbList(Db *, size_t,...);
/**
* Behaves like
* .Fn DbList ,
* but with an array of strings instead of variadic arguments.
*/
extern Array * DbListArgs(Db *, Array *);
/** /**
* Free the list returned by * Free the list returned by
* .Fn DbListFree . * .Fn DbListFree .

View file

@ -297,6 +297,12 @@ extern size_t JsonEncode(HashMap *, Stream *, int);
*/ */
extern HashMap * JsonDecode(Stream *); extern HashMap * JsonDecode(Stream *);
/**
* Decodes a JSON value from thr current input strram and parse it
* into a JsonValue.
*/
extern JsonValue * JsonValueDecode(Stream *);
/** /**
* A convenience function that allows the caller to retrieve and * A convenience function that allows the caller to retrieve and
* arbitrarily deep keys within a JSON object. It takes a root JSON * arbitrarily deep keys within a JSON object. It takes a root JSON

View file

@ -0,0 +1,69 @@
/*
* Copyright (C) 2022-2024 Jordan Bancino <@jordan:bancino.net>
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation files
* (the "Software"), to deal in the Software without restriction,
* including without limitation the rights to use, copy, modify, merge,
* publish, distribute, sublicense, and/or sell copies of the Software,
* and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
#ifndef CYTOPLASM_PLATFORM_H
#define CYTOPLASM_PLATFORM_H
/***
* @Nm Platform
* @Nd A simple macro header that determines what platform the application
* is being built for.
* @Dd September 21, 2024
*/
#if defined(WIN32) || defined(_WIN32) || defined(__WIN32__) || defined(__NT__)
#define PLATFORM_WINDOWS
#ifdef _WIN64
#define PLATFORM_WIN64
#else
#define PLATFORM_WIN32
#endif
#elif __APPLE__
#define PLATFORM_DARWIN
#include <TargetConditionals.h>
#if TARGET_IPHONE_SIMULATOR
#define PLATFORM_IPHONE
#elif TARGET_OS_MACCATALYST
#define PLATFORM_CATALYST
#elif TARGET_OS_IPHONE
#define PLATFORM_IPHONE
#elif TARGET_OS_MAC
#define PLATFORM_MAC
#else
# error "Unknown Apple platform"
#endif
#elif __ANDROID__
#define PLATFORM_ANDROID
#elif __linux__
#define PLATFORM_LINUX
#elif __unix__
#define PLATFORM_UNIX
#elif defined(_POSIX_VERSION)
#define PLATFORM_POSIX
#else
# error "Unknown compiler"
#endif
#endif /* CYTOPLASM_PLATFORM_H */

View file

@ -262,6 +262,16 @@ DbClose(Db * db)
Free(db); Free(db);
} }
DbRef *
DbCreateArgs(Db * db, Array *args)
{
if (!args || !db || !db->create)
{
return NULL;
}
return db->create(db, args);
}
DbRef * DbRef *
DbCreate(Db * db, size_t nArgs,...) DbCreate(Db * db, size_t nArgs,...)
{ {
@ -283,12 +293,22 @@ DbCreate(Db * db, size_t nArgs,...)
return NULL; return NULL;
} }
ret = db->create(db, args); ret = DbCreateArgs(db, args);
ArrayFree(args); ArrayFree(args);
return ret; return ret;
} }
bool
DbDeleteArgs(Db * db, Array *args)
{
if (!args || !db || !db->delete)
{
return false;
}
return db->delete(db, args);
}
bool bool
DbDelete(Db * db, size_t nArgs,...) DbDelete(Db * db, size_t nArgs,...)
{ {
@ -305,12 +325,22 @@ DbDelete(Db * db, size_t nArgs,...)
args = ArrayFromVarArgs(nArgs, ap); args = ArrayFromVarArgs(nArgs, ap);
va_end(ap); va_end(ap);
ret = db->delete(db, args); ret = DbDeleteArgs(db, args);
ArrayFree(args); ArrayFree(args);
return ret; return ret;
} }
DbRef *
DbLockArgs(Db * db, Array *args)
{
if (!args || !db || !db->lockFunc)
{
return NULL;
}
return db->lockFunc(db, DB_HINT_WRITE, args);
}
DbRef * DbRef *
DbLock(Db * db, size_t nArgs,...) DbLock(Db * db, size_t nArgs,...)
{ {
@ -318,6 +348,11 @@ DbLock(Db * db, size_t nArgs,...)
Array *args; Array *args;
DbRef *ret; DbRef *ret;
if (!db)
{
return NULL;
}
va_start(ap, nArgs); va_start(ap, nArgs);
args = ArrayFromVarArgs(nArgs, ap); args = ArrayFromVarArgs(nArgs, ap);
va_end(ap); va_end(ap);
@ -327,13 +362,22 @@ DbLock(Db * db, size_t nArgs,...)
return NULL; return NULL;
} }
ret = db->lockFunc(db, DB_HINT_WRITE, args); ret = DbLockArgs(db, args);
ArrayFree(args); ArrayFree(args);
return ret; return ret;
} }
DbRef *
DbLockIntentArgs(Db * db, DbHint hint, Array *args)
{
if (!db || !args || !db->lockFunc)
{
return NULL;
}
return db->lockFunc(db, hint, args);
}
DbRef * DbRef *
DbLockIntent(Db * db, DbHint hint, size_t nArgs,...) DbLockIntent(Db * db, DbHint hint, size_t nArgs,...)
{ {
@ -350,7 +394,7 @@ DbLockIntent(Db * db, DbHint hint, size_t nArgs,...)
return NULL; return NULL;
} }
ret = db->lockFunc(db, hint, args); ret = DbLockIntentArgs(db, hint, args);
ArrayFree(args); ArrayFree(args);
@ -368,6 +412,16 @@ DbUnlock(Db * db, DbRef * ref)
return db->unlock(db, ref); return db->unlock(db, ref);
} }
bool
DbExistsArgs(Db * db, Array *args)
{
if (!args || !db || !db->exists)
{
return false;
}
return db->exists(db, args);
}
bool bool
DbExists(Db * db, size_t nArgs,...) DbExists(Db * db, size_t nArgs,...)
{ {
@ -388,12 +442,21 @@ DbExists(Db * db, size_t nArgs,...)
return false; return false;
} }
ret = db->exists(db, args); ret = DbExistsArgs(db, args);
ArrayFree(args); ArrayFree(args);
return ret; return ret;
} }
Array *
DbListArgs(Db *db, Array *args)
{
if (!db || !args || !db->list)
{
return NULL;
}
return db->list(db, args);
}
Array * Array *
DbList(Db * db, size_t nArgs,...) DbList(Db * db, size_t nArgs,...)
{ {
@ -410,7 +473,7 @@ DbList(Db * db, size_t nArgs,...)
path = ArrayFromVarArgs(nArgs, ap); path = ArrayFromVarArgs(nArgs, ap);
va_end(ap); va_end(ap);
result = db->list(db, path); result = DbListArgs(db, path);
ArrayFree(path); ArrayFree(path);
return result; return result;

View file

@ -54,7 +54,8 @@ DbDirName(FlatDb * db, Array * args, size_t strip)
{ {
char *tmp; char *tmp;
char *sanitise = StrDuplicate(ArrayGet(args, i)); char *sanitise = StrDuplicate(ArrayGet(args, i));
for (j = 0; j < strlen(sanitise); j++) size_t len = strlen(sanitise);
for (j = 0; j < len; j++)
{ {
sanitise[j] = DbSanitiseChar(sanitise[j]); sanitise[j] = DbSanitiseChar(sanitise[j]);
} }

View file

@ -128,7 +128,9 @@ LMDBKeyHead(MDB_val key)
return NULL; return NULL;
} }
while ((void *) (end - 1) >= key.mv_data && *(end - 1)) /* Doing >= will lead to cases where it is sent straight to the
* start. Don't do that. */
while ((void *) (end - 1) > key.mv_data && *(end - 1))
{ {
end--; end--;
} }

View file

@ -1348,6 +1348,25 @@ JsonDecode(Stream * stream)
return result; return result;
} }
JsonValue *
JsonValueDecode(Stream *stream)
{
JsonValue *result;
JsonParserState state;
state.stream = stream;
state.token = NULL;
JsonTokenSeek(&state);
result = JsonDecodeValue(&state);
if (state.token)
{
Free(state.token);
}
return result;
}
JsonValue * JsonValue *
JsonGet(HashMap * json, size_t nArgs,...) JsonGet(HashMap * json, size_t nArgs,...)

View file

@ -42,26 +42,30 @@
#define MEMORY_FILE_SIZE 256 #define MEMORY_FILE_SIZE 256
#define MEM_BOUND_TYPE uint64_t
#define MEM_BOUND 0xDEADBEEFBEEFDEAD
#define MEM_MAGIC 0xDEADBEEFDEADBEEF
struct MemoryInfo struct MemoryInfo
{ {
uint64_t magic; uint64_t magic;
size_t size; size_t size;
size_t unalignedSize;
char file[MEMORY_FILE_SIZE]; char file[MEMORY_FILE_SIZE];
int line; int line;
void *pointer; void *pointer;
MemoryInfo *prev; MemoryInfo *prev;
MemoryInfo *next; MemoryInfo *next;
MEM_BOUND_TYPE leftBoundary;
}; };
#define MEM_BOUND_TYPE uint32_t #define MEM_SIZE_ACTUAL(x) (MemoryAlignBoundary((x) * sizeof(uint8_t)) + sizeof(MEM_BOUND_TYPE))
#define MEM_BOUND 0xDEADBEEF #define MEM_START_BOUNDARY(info) (info->leftBoundary)
#define MEM_MAGIC 0xDEADBEEFDEADBEEF #define MEM_END_BOUNDARY(info) (*(((MEM_BOUND_TYPE *) (((uint8_t *) info->pointer) + info->size)) - 1))
#define MEM_BOUND_LOWER(p) *((MEM_BOUND_TYPE *) p)
#define MEM_BOUND_UPPER(p, x) *(((MEM_BOUND_TYPE *) (((uint8_t *) p) + x)) + 1)
#define MEM_SIZE_ACTUAL(x) (((x) * sizeof(uint8_t)) + (2 * sizeof(MEM_BOUND_TYPE)))
static pthread_mutex_t lock; static pthread_mutex_t lock;
static void (*hook) (MemoryAction, MemoryInfo *, void *) = MemoryDefaultHook; static void (*hook) (MemoryAction, MemoryInfo *, void *) = MemoryDefaultHook;
@ -71,6 +75,19 @@ static size_t allocationsLen = 0;
static MemoryInfo *allocationTail = NULL; static MemoryInfo *allocationTail = NULL;
/* Simple range of "plausible" boundaries for heap, serving as an extra
* check */
static void *heapStart, *heapEnd;
static size_t MemoryAlignBoundary(size_t size)
{
size_t boundSize = sizeof(MEM_BOUND_TYPE);
size_t remainder = size % boundSize;
size_t closest = size / boundSize + !!remainder;
return closest * boundSize;
}
int int
MemoryRuntimeInit(void) MemoryRuntimeInit(void)
{ {
@ -85,6 +102,8 @@ MemoryRuntimeInit(void)
pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE); pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);
ret = pthread_mutex_init(&lock, &attr); ret = pthread_mutex_init(&lock, &attr);
pthread_mutexattr_destroy(&attr); pthread_mutexattr_destroy(&attr);
heapStart = NULL;
heapEnd = NULL;
ret = (ret == 0); ret = (ret == 0);
@ -110,6 +129,15 @@ MemoryInsert(MemoryInfo * a)
a->prev = allocationTail; a->prev = allocationTail;
a->magic = MEM_MAGIC; a->magic = MEM_MAGIC;
if (!heapStart || heapStart > (void *) a)
{
heapStart = a;
}
if (!heapEnd || heapEnd < (void *) a)
{
heapEnd = a;
}
allocationTail = a; allocationTail = a;
allocationsLen++; allocationsLen++;
@ -142,8 +170,9 @@ MemoryDelete(MemoryInfo * a)
static int static int
MemoryCheck(MemoryInfo * a) MemoryCheck(MemoryInfo * a)
{ {
if (MEM_BOUND_LOWER(a->pointer) != MEM_BOUND || if (MEM_START_BOUNDARY(a) != MEM_BOUND ||
MEM_BOUND_UPPER(a->pointer, a->size - (2 * sizeof(MEM_BOUND_TYPE))) != MEM_BOUND) a->magic != MEM_MAGIC ||
MEM_END_BOUNDARY(a) != MEM_BOUND)
{ {
if (hook) if (hook)
{ {
@ -174,13 +203,14 @@ MemoryAllocate(size_t size, const char *file, int line)
p = a + 1; p = a + 1;
memset(p, 0, MEM_SIZE_ACTUAL(size)); memset(p, 0, MEM_SIZE_ACTUAL(size));
MEM_BOUND_LOWER(p) = MEM_BOUND;
MEM_BOUND_UPPER(p, size) = MEM_BOUND;
a->size = MEM_SIZE_ACTUAL(size); a->size = MEM_SIZE_ACTUAL(size);
a->unalignedSize = size;
strncpy(a->file, file, MEMORY_FILE_SIZE - 1); strncpy(a->file, file, MEMORY_FILE_SIZE - 1);
a->line = line; a->line = line;
a->pointer = p; a->pointer = p;
MEM_START_BOUNDARY(a) = MEM_BOUND;
MEM_END_BOUNDARY(a) = MEM_BOUND;
if (!MemoryInsert(a)) if (!MemoryInsert(a))
{ {
@ -195,7 +225,7 @@ MemoryAllocate(size_t size, const char *file, int line)
} }
pthread_mutex_unlock(&lock); pthread_mutex_unlock(&lock);
return ((MEM_BOUND_TYPE *) p) + 1; return p;
} }
void * void *
@ -220,6 +250,7 @@ MemoryReallocate(void *p, size_t size, const char *file, int line)
if (new) if (new)
{ {
a = new; a = new;
a->unalignedSize = size;
a->size = MEM_SIZE_ACTUAL(size); a->size = MEM_SIZE_ACTUAL(size);
strncpy(a->file, file, MEMORY_FILE_SIZE - 1); strncpy(a->file, file, MEMORY_FILE_SIZE - 1);
a->line = line; a->line = line;
@ -228,8 +259,8 @@ MemoryReallocate(void *p, size_t size, const char *file, int line)
a->pointer = a + 1; a->pointer = a + 1;
MemoryInsert(a); MemoryInsert(a);
MEM_BOUND_LOWER(a->pointer) = MEM_BOUND; MEM_START_BOUNDARY(a) = MEM_BOUND;
MEM_BOUND_UPPER(a->pointer, size) = MEM_BOUND; MEM_END_BOUNDARY(a) = MEM_BOUND;
if (hook) if (hook)
{ {
@ -253,7 +284,7 @@ MemoryReallocate(void *p, size_t size, const char *file, int line)
} }
} }
return ((MEM_BOUND_TYPE *) a->pointer) + 1; return a->pointer;
} }
void void
@ -344,8 +375,12 @@ MemoryInfoGet(void *po)
pthread_mutex_lock(&lock); pthread_mutex_lock(&lock);
p = ((MEM_BOUND_TYPE *) po) - 1;
p = ((MemoryInfo *) p) - 1; p = ((MemoryInfo *) p) - 1;
if (p < heapStart || p > heapEnd)
{
pthread_mutex_unlock(&lock);
return NULL;
}
if (((MemoryInfo *)p)->magic != MEM_MAGIC) if (((MemoryInfo *)p)->magic != MEM_MAGIC)
{ {
@ -364,7 +399,7 @@ MemoryInfoGetSize(MemoryInfo * a)
return 0; return 0;
} }
return a->size ? a->size - (2 * sizeof(MEM_BOUND_TYPE)) : 0; return a->size ? a->unalignedSize : 0;
} }
const char * const char *
@ -397,7 +432,7 @@ MemoryInfoGetPointer(MemoryInfo * a)
return NULL; return NULL;
} }
return ((MEM_BOUND_TYPE *) a->pointer) + 1; return a->pointer;
} }
void void

View file

@ -24,6 +24,7 @@
#include <Util.h> #include <Util.h>
#include <Memory.h> #include <Memory.h>
#include <Platform.h>
#include <stdio.h> #include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
@ -89,6 +90,10 @@ UtilTsMillis(void)
return ts; return ts;
} }
#ifdef PLATFORM_DARWIN
#define st_mtim st_mtimespec
#endif
uint64_t uint64_t
UtilLastModified(char *path) UtilLastModified(char *path)
{ {
@ -105,6 +110,10 @@ UtilLastModified(char *path)
return ts; return ts;
} }
#ifdef PLATFORM_DARWIN
#undef st_mtim
#endif
int int
UtilMkdir(const char *dir, const mode_t mode) UtilMkdir(const char *dir, const mode_t mode)
{ {