1
0
forked from 0ad/0ad

# more res/file refactoring (split up archive provider and builder)

also remove some stupid "returns ERR_OK or negative error code"
comments, which is now guaranteed by LibError return type anyway.

This was SVN commit r3807.
This commit is contained in:
janwas 2006-04-24 05:20:14 +00:00
parent 64ef19475d
commit 64ecf79c6a
15 changed files with 330 additions and 297 deletions

View File

@ -704,239 +704,3 @@ LibError afile_unmap(File* f)
H_DEREF(af->ha, Archive, a);
return file_unmap(&a->f);
}
//-----------------------------------------------------------------------------
// archive builder
//-----------------------------------------------------------------------------
static inline bool file_type_is_uncompressible(const char* fn)
{
const char* ext = path_extension(fn);
// this is a selection of file types that are certainly not
// further compressible. we need not include every type under the sun -
// this is only a slight optimization that avoids wasting time
// compressing files. the real decision as to cmethod is made based
// on attained compression ratio.
static const char* uncompressible_exts[] =
{
"zip", "rar",
"jpg", "jpeg", "png",
"ogg", "mp3"
};
for(uint i = 0; i < ARRAY_SIZE(uncompressible_exts); i++)
{
if(!stricmp(ext+1, uncompressible_exts[i]))
return true;
}
return false;
}
struct CompressParams
{
bool attempt_compress;
uintptr_t ctx;
u32 crc;
};
#include <zlib.h>
static LibError compress_cb(uintptr_t cb_ctx, const void* block, size_t size, size_t* bytes_processed)
{
CompressParams* p = (CompressParams*)cb_ctx;
// comp_feed already makes note of total #bytes fed, and we need
// vfs_io to return the uc size (to check if all data was read).
*bytes_processed = size;
// update checksum
p->crc = crc32(p->crc, (const Bytef*)block, (uInt)size);
if(p->attempt_compress)
(void)comp_feed(p->ctx, block, size);
return INFO_CB_CONTINUE;
}
// final decision on whether to store the file as compressed,
// given the observed compressed/uncompressed sizes.
static bool should_store_compressed(size_t ucsize, size_t csize)
{
const float ratio = (float)ucsize / csize;
const ssize_t bytes_saved = (ssize_t)ucsize - (ssize_t)csize;
UNUSED2(bytes_saved);
// tiny - store compressed regardless of savings.
// rationale:
// - CPU cost is negligible and overlapped with IO anyway;
// - reading from compressed files uses less memory because we
// don't need to allocate space for padding in the final buffer.
if(ucsize < 512)
return true;
// large high-entropy file - store uncompressed.
// rationale:
// - any bigger than this and CPU time becomes a problem: it isn't
// necessarily hidden by IO time anymore.
if(ucsize >= 32*KiB && ratio < 1.02f)
return false;
// TODO: any other cases?
// we currently store everything else compressed.
return true;
}
static LibError read_and_compress_file(const char* atom_fn, uintptr_t ctx,
ArchiveEntry& ent, void*& file_contents, FileIOBuf& buf) // out
{
struct stat s;
RETURN_ERR(vfs_stat(atom_fn, &s));
const size_t ucsize = s.st_size;
// skip 0-length files.
// rationale: zip.cpp needs to determine whether a CDFH entry is
// a file or directory (the latter are written by some programs but
// not needed - they'd only pollute the file table).
// it looks like checking for ucsize=csize=0 is the safest way -
// relying on file attributes (which are system-dependent!) is
// even less safe.
// we thus skip 0-length files to avoid confusing them with dirs.
if(!ucsize)
return INFO_SKIPPED;
const bool attempt_compress = !file_type_is_uncompressible(atom_fn);
if(attempt_compress)
{
RETURN_ERR(comp_reset(ctx));
RETURN_ERR(comp_alloc_output(ctx, ucsize));
}
// read file into newly allocated buffer. if attempt_compress, also
// compress the file into another buffer while waiting for IOs.
size_t ucsize_read;
const uint flags = 0;
CompressParams params = { attempt_compress, ctx, 0 };
RETURN_ERR(vfs_load(atom_fn, buf, ucsize_read, flags, compress_cb, (uintptr_t)&params));
debug_assert(ucsize_read == ucsize);
// if we compressed the file trial-wise, check results and
// decide whether to store as such or not (based on compression ratio)
bool store_compressed = false;
void* cdata = 0; size_t csize = 0;
if(attempt_compress)
{
LibError ret = comp_finish(ctx, &cdata, &csize);
if(ret < 0)
{
file_buf_free(buf);
return ret;
}
store_compressed = should_store_compressed(ucsize, csize);
}
// store file info
ent.ucsize = (off_t)ucsize;
ent.mtime = s.st_mtime;
// .. ent.ofs is set by zip_archive_add_file
ent.flags = 0;
ent.atom_fn = atom_fn;
ent.crc = params.crc;
if(store_compressed)
{
ent.method = CM_DEFLATE;
ent.csize = (off_t)csize;
file_contents = cdata;
}
else
{
ent.method = CM_NONE;
ent.csize = (off_t)ucsize;
file_contents = (void*)buf;
}
// note: no need to free cdata - it is owned by the
// compression context and can be reused.
return ERR_OK;
}
LibError archive_build_init(const char* P_archive_filename, Filenames V_fns,
ArchiveBuildState* ab)
{
RETURN_ERR(zip_archive_create(P_archive_filename, &ab->za));
ab->ctx = comp_alloc(CT_COMPRESSION, CM_DEFLATE);
ab->V_fns = V_fns;
// count number of files (needed to estimate progress)
for(ab->num_files = 0; ab->V_fns[ab->num_files]; ab->num_files++) {}
ab->i = 0;
return ERR_OK;
}
#include "ps/Loader.h"
int archive_build_continue(ArchiveBuildState* ab)
{
const double end_time = get_time() + 200e-3;
for(;;)
{
const char* V_fn = ab->V_fns[ab->i];
if(!V_fn)
break;
ArchiveEntry ent; void* file_contents; FileIOBuf buf;
if(read_and_compress_file(V_fn, ab->ctx, ent, file_contents, buf) == ERR_OK)
{
(void)zip_archive_add_file(ab->za, &ent, file_contents);
(void)file_buf_free(buf);
}
ab->i++;
LDR_CHECK_TIMEOUT((int)ab->i, (int)ab->num_files);
}
// note: this is currently known to fail if there are no files in the list
// - zlib.h says: Z_DATA_ERROR is returned if freed prematurely.
// safe to ignore.
comp_free(ab->ctx); ab->ctx = 0;
(void)zip_archive_finish(ab->za);
return ERR_OK;
}
void archive_build_cancel(ArchiveBuildState* ab)
{
// note: the GUI may call us even though no build was ever in progress.
// be sure to make all steps no-op if <ab> is zeroed (initial state) or
// no build is in progress.
comp_free(ab->ctx); ab->ctx = 0;
if(ab->za)
(void)zip_archive_finish(ab->za);
memset(ab, 0, sizeof(*ab));
}
LibError archive_build(const char* P_archive_filename, Filenames V_fns)
{
ArchiveBuildState ab;
RETURN_ERR(archive_build_init(P_archive_filename, V_fns, &ab));
for(;;)
{
int ret = archive_build_continue(&ab);
RETURN_ERR(ret);
if(ret == ERR_OK)
return ERR_OK;
}
}

View File

@ -139,37 +139,6 @@ extern LibError afile_map(File* f, void*& p, size_t& size);
extern LibError afile_unmap(File* f);
//
// archive creation
//
// array of pointers to VFS filenames (including path), terminated by a
// NULL entry.
typedef const char** Filenames;
// rationale: this is fairly lightweight and simple, so we don't bother
// making it opaque.
struct ArchiveBuildState
{
ZipArchive* za;
uintptr_t ctx;
Filenames V_fns;
size_t num_files; // number of filenames in V_fns (excluding final 0)
size_t i;
};
extern LibError archive_build_init(const char* P_archive_filename, Filenames V_fns,
ArchiveBuildState* ab);
// create an archive (overwriting previous file) and fill it with the given
// files. compression method is chosen intelligently based on extension and
// file entropy / achieved compression ratio.
extern int archive_build_continue(ArchiveBuildState* ab);
extern void archive_build_cancel(ArchiveBuildState* ab);
extern LibError archive_build(const char* P_archive_filename, Filenames V_fns);
//
// interface for backends

View File

@ -0,0 +1,257 @@
/**
* =========================================================================
* File : archive_builder.cpp
* Project : 0 A.D.
* Description :
*
* @author Jan.Wassenberg@stud.uni-karlsruhe.de
* =========================================================================
*/
/*
* Copyright (c) 2003-2005 Jan Wassenberg
*
* Redistribution and/or modification are also permitted under the
* terms of the GNU General Public License as published by the
* Free Software Foundation (version 2 or later, at your option).
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
*/
#include "precompiled.h"
#include "lib.h"
#include "lib/timer.h"
#include "file_internal.h"
// un-nice dependencies:
#include "ps/Loader.h"
#include <zlib.h>
static inline bool file_type_is_uncompressible(const char* fn)
{
const char* ext = path_extension(fn);
// this is a selection of file types that are certainly not
// further compressible. we need not include every type under the sun -
// this is only a slight optimization that avoids wasting time
// compressing files. the real decision as to cmethod is made based
// on attained compression ratio.
static const char* uncompressible_exts[] =
{
"zip", "rar",
"jpg", "jpeg", "png",
"ogg", "mp3"
};
for(uint i = 0; i < ARRAY_SIZE(uncompressible_exts); i++)
{
if(!stricmp(ext+1, uncompressible_exts[i]))
return true;
}
return false;
}
struct CompressParams
{
bool attempt_compress;
uintptr_t ctx;
u32 crc;
};
static LibError compress_cb(uintptr_t cb_ctx, const void* block, size_t size, size_t* bytes_processed)
{
CompressParams* p = (CompressParams*)cb_ctx;
// comp_feed already makes note of total #bytes fed, and we need
// vfs_io to return the uc size (to check if all data was read).
*bytes_processed = size;
// update checksum
p->crc = crc32(p->crc, (const Bytef*)block, (uInt)size);
if(p->attempt_compress)
(void)comp_feed(p->ctx, block, size);
return INFO_CB_CONTINUE;
}
// final decision on whether to store the file as compressed,
// given the observed compressed/uncompressed sizes.
static bool should_store_compressed(size_t ucsize, size_t csize)
{
const float ratio = (float)ucsize / csize;
const ssize_t bytes_saved = (ssize_t)ucsize - (ssize_t)csize;
UNUSED2(bytes_saved);
// tiny - store compressed regardless of savings.
// rationale:
// - CPU cost is negligible and overlapped with IO anyway;
// - reading from compressed files uses less memory because we
// don't need to allocate space for padding in the final buffer.
if(ucsize < 512)
return true;
// large high-entropy file - store uncompressed.
// rationale:
// - any bigger than this and CPU time becomes a problem: it isn't
// necessarily hidden by IO time anymore.
if(ucsize >= 32*KiB && ratio < 1.02f)
return false;
// TODO: any other cases?
// we currently store everything else compressed.
return true;
}
static LibError read_and_compress_file(const char* atom_fn, uintptr_t ctx,
ArchiveEntry& ent, void*& file_contents, FileIOBuf& buf) // out
{
struct stat s;
RETURN_ERR(vfs_stat(atom_fn, &s));
const size_t ucsize = s.st_size;
// skip 0-length files.
// rationale: zip.cpp needs to determine whether a CDFH entry is
// a file or directory (the latter are written by some programs but
// not needed - they'd only pollute the file table).
// it looks like checking for ucsize=csize=0 is the safest way -
// relying on file attributes (which are system-dependent!) is
// even less safe.
// we thus skip 0-length files to avoid confusing them with dirs.
if(!ucsize)
return INFO_SKIPPED;
const bool attempt_compress = !file_type_is_uncompressible(atom_fn);
if(attempt_compress)
{
RETURN_ERR(comp_reset(ctx));
RETURN_ERR(comp_alloc_output(ctx, ucsize));
}
// read file into newly allocated buffer. if attempt_compress, also
// compress the file into another buffer while waiting for IOs.
size_t ucsize_read;
const uint flags = 0;
CompressParams params = { attempt_compress, ctx, 0 };
RETURN_ERR(vfs_load(atom_fn, buf, ucsize_read, flags, compress_cb, (uintptr_t)&params));
debug_assert(ucsize_read == ucsize);
// if we compressed the file trial-wise, check results and
// decide whether to store as such or not (based on compression ratio)
bool store_compressed = false;
void* cdata = 0; size_t csize = 0;
if(attempt_compress)
{
LibError ret = comp_finish(ctx, &cdata, &csize);
if(ret < 0)
{
file_buf_free(buf);
return ret;
}
store_compressed = should_store_compressed(ucsize, csize);
}
// store file info
ent.ucsize = (off_t)ucsize;
ent.mtime = s.st_mtime;
// .. ent.ofs is set by zip_archive_add_file
ent.flags = 0;
ent.atom_fn = atom_fn;
ent.crc = params.crc;
if(store_compressed)
{
ent.method = CM_DEFLATE;
ent.csize = (off_t)csize;
file_contents = cdata;
}
else
{
ent.method = CM_NONE;
ent.csize = (off_t)ucsize;
file_contents = (void*)buf;
}
// note: no need to free cdata - it is owned by the
// compression context and can be reused.
return ERR_OK;
}
LibError archive_build_init(const char* P_archive_filename, Filenames V_fns,
ArchiveBuildState* ab)
{
RETURN_ERR(zip_archive_create(P_archive_filename, &ab->za));
ab->ctx = comp_alloc(CT_COMPRESSION, CM_DEFLATE);
ab->V_fns = V_fns;
// count number of files (needed to estimate progress)
for(ab->num_files = 0; ab->V_fns[ab->num_files]; ab->num_files++) {}
ab->i = 0;
return ERR_OK;
}
int archive_build_continue(ArchiveBuildState* ab)
{
const double end_time = get_time() + 200e-3;
for(;;)
{
const char* V_fn = ab->V_fns[ab->i];
if(!V_fn)
break;
ArchiveEntry ent; void* file_contents; FileIOBuf buf;
if(read_and_compress_file(V_fn, ab->ctx, ent, file_contents, buf) == ERR_OK)
{
(void)zip_archive_add_file(ab->za, &ent, file_contents);
(void)file_buf_free(buf);
}
ab->i++;
LDR_CHECK_TIMEOUT((int)ab->i, (int)ab->num_files);
}
// note: this is currently known to fail if there are no files in the list
// - zlib.h says: Z_DATA_ERROR is returned if freed prematurely.
// safe to ignore.
comp_free(ab->ctx); ab->ctx = 0;
(void)zip_archive_finish(ab->za);
return ERR_OK;
}
void archive_build_cancel(ArchiveBuildState* ab)
{
// note: the GUI may call us even though no build was ever in progress.
// be sure to make all steps no-op if <ab> is zeroed (initial state) or
// no build is in progress.
comp_free(ab->ctx); ab->ctx = 0;
if(ab->za)
(void)zip_archive_finish(ab->za);
memset(ab, 0, sizeof(*ab));
}
LibError archive_build(const char* P_archive_filename, Filenames V_fns)
{
ArchiveBuildState ab;
RETURN_ERR(archive_build_init(P_archive_filename, V_fns, &ab));
for(;;)
{
int ret = archive_build_continue(&ab);
RETURN_ERR(ret);
if(ret == ERR_OK)
return ERR_OK;
}
}

View File

@ -0,0 +1,53 @@
/**
* =========================================================================
* File : archive_builder.h
* Project : 0 A.D.
* Description :
*
* @author Jan.Wassenberg@stud.uni-karlsruhe.de
* =========================================================================
*/
/*
* Copyright (c) 2003-2005 Jan Wassenberg
*
* Redistribution and/or modification are also permitted under the
* terms of the GNU General Public License as published by the
* Free Software Foundation (version 2 or later, at your option).
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
*/
#ifndef ARCHIVE_BUILDER_H__
#define ARCHIVE_BUILDER_H__
// array of pointers to VFS filenames (including path), terminated by a
// NULL entry.
typedef const char** Filenames;
// rationale: this is fairly lightweight and simple, so we don't bother
// making it opaque.
struct ArchiveBuildState
{
ZipArchive* za;
uintptr_t ctx;
Filenames V_fns;
size_t num_files; // number of filenames in V_fns (excluding final 0)
size_t i;
};
extern LibError archive_build_init(const char* P_archive_filename, Filenames V_fns,
ArchiveBuildState* ab);
// create an archive (overwriting previous file) and fill it with the given
// files. compression method is chosen intelligently based on extension and
// file entropy / achieved compression ratio.
extern int archive_build_continue(ArchiveBuildState* ab);
extern void archive_build_cancel(ArchiveBuildState* ab);
extern LibError archive_build(const char* P_archive_filename, Filenames V_fns);
#endif // #ifndef ARCHIVE_BUILDER_H__

View File

@ -75,8 +75,8 @@ cassert(sizeof(DirIterator_) <= sizeof(DirIterator));
// prepare to iterate (once) over entries in the given directory.
// returns a negative error code or 0 on success, in which case <d> is
// ready for subsequent dir_next_ent calls and must be freed via dir_close.
// if ERR_OK is returned, <d> is ready for subsequent dir_next_ent calls and
// must be freed via dir_close.
LibError dir_open(const char* P_path, DirIterator* d_)
{
DirIterator_* d = (DirIterator_*)d_;
@ -105,7 +105,7 @@ LibError dir_open(const char* P_path, DirIterator* d_)
// return ERR_DIR_END if all entries have already been returned once,
// another negative error code, or 0 on success, in which case <ent>
// another negative error code, or ERR_OK on success, in which case <ent>
// describes the next (order is unspecified) directory entry.
LibError dir_next_ent(DirIterator* d_, DirEnt* ent)
{

View File

@ -124,12 +124,12 @@ struct DirEnt
#define DIRENT_IS_DIR(p_ent) ((p_ent)->size == -1)
// prepare to iterate (once) over entries in the given directory.
// returns a negative error code or 0 on success, in which case <d> is
// ready for subsequent dir_next_ent calls and must be freed via dir_close.
// if ERR_OK is returned, <d> is ready for subsequent dir_next_ent calls and
// must be freed via dir_close.
extern LibError dir_open(const char* P_path, DirIterator* d);
// return ERR_DIR_END if all entries have already been returned once,
// another negative error code, or 0 on success, in which case <ent>
// another negative error code, or ERR_OK on success, in which case <ent>
// describes the next (order is unspecified) directory entry.
extern LibError dir_next_ent(DirIterator* d, DirEnt* ent);

View File

@ -32,6 +32,7 @@
#include "compression.h"
#include "zip.h"
#include "archive.h"
#include "archive_builder.h"
#include "vfs.h"
#include "vfs_mount.h"

View File

@ -73,8 +73,7 @@ enum TreeLookupFlags
// a higher-priority file of the same name already exists
// (used by VFile_reload when opening for writing).
//
// return 0 on success, or a negative error code
// (in which case output params are undefined).
// output params are only valid if ERR_OK is returned.
extern LibError tree_lookup(const char* path, TFile** ptf, uint flags = 0);
// starting at VFS root, traverse <path> and pass back information
@ -88,8 +87,7 @@ extern LibError tree_lookup(const char* path, TFile** ptf, uint flags = 0);
// <path> can be to a file or dir (in which case it must end in '/',
// to make sure the last component is treated as a directory).
//
// return 0 on success, or a negative error code
// (in which case output params are undefined).
// output params are only valid if ERR_OK is returned.
extern LibError tree_lookup_dir(const char* path, TDir** ptd, uint flags = 0);

View File

@ -35,7 +35,6 @@ Handle ogl_shader_load(const char* fn, GLenum type);
void ogl_shader_free(Handle& h);
// Attach a shader to the given OpenGL program.
// Returns 0 on success and a negative error code otherwise.
LibError ogl_shader_attach(GLhandleARB program, Handle& h);

View File

@ -193,9 +193,8 @@ extern LibError sys_clipboard_free(wchar_t* copy);
// it is no longer needed and can be freed after this call returns.
// hotspot (hx,hy) is the offset from its upper-left corner to the
// position where mouse clicks are registered.
// return: negative error code, or 0 on success. cursor is filled with
// a pointer and undefined on failure. it must be sys_cursor_free-ed
// when no longer needed.
// cursor is only valid when ERR_OK is returned; in that case, it must be
// sys_cursor_free-ed when no longer needed.
extern LibError sys_cursor_create(uint w, uint h, void* bgra_img,
uint hx, uint hy, void** cursor);
@ -229,14 +228,12 @@ extern LibError sys_error_description_r(int err, char* buf, size_t max_chars);
wchar_t* sys_get_module_filename(void* addr, wchar_t* path);
// store full path to the current executable.
// returns 0 or a negative error code.
// useful for determining installation directory, e.g. for VFS.
extern LibError sys_get_executable_name(char* n_path, size_t buf_size);
// have the user specify a directory via OS dialog.
// stores its full path in the given buffer, which must hold at least
// PATH_MAX chars.
// returns 0 on success or a negative error code.
extern LibError sys_pick_directory(char* n_path, size_t buf_size);
// execute the specified function once on each CPU.
@ -244,9 +241,9 @@ extern LibError sys_pick_directory(char* n_path, size_t buf_size);
// is never re-entered) in order of increasing OS CPU ID.
// note: implemented by switching thread affinity masks and forcing
// a reschedule, which is apparently not possible with POSIX.
// return 0 on success or a negative error code on failure
// (e.g. if OS is preventing us from running on some CPUs).
// called from ia32.cpp get_cpu_count
//
// may fail if e.g. OS is preventing us from running on some CPUs.
// called from ia32.cpp get_cpu_count.
extern LibError sys_on_each_cpu(void (*cb)());

View File

@ -92,7 +92,6 @@ void dll_list_init(char* buf, size_t chars)
// read DLL file version and append that and its name to the list.
// return 0 on success or a negative error code.
//
// name should preferably be the complete path to DLL, to make sure
// we don't inadvertently load another one on the library search path.

View File

@ -29,7 +29,6 @@
extern void dll_list_init(char* buf, size_t chars);
// read DLL file version and append that and its name to the list.
// return 0 on success or a negative error code.
//
// name should preferably be the complete path to DLL, to make sure
// we don't inadvertently load another one on the library search path.

View File

@ -128,9 +128,9 @@ LibError win_get_cpu_info()
// is never re-entered) in order of increasing OS CPU ID.
// note: implemented by switching thread affinity masks and forcing
// a reschedule, which is apparently not possible with POSIX.
// return 0 on success or a negative error code on failure
// (e.g. if OS is preventing us from running on some CPUs).
// called from ia32.cpp get_cpu_count
//
// may fail if e.g. OS is preventing us from running on some CPUs.
// called from ia32.cpp get_cpu_count.
LibError sys_on_each_cpu(void (*cb)())
{
const HANDLE hProcess = GetCurrentProcess();

View File

@ -436,7 +436,7 @@ static LibError walk_stack(StackFrameCallback cb, void* user_arg = 0, uint skip
ret = cb(&sf, user_arg);
// callback reports it's done; stop calling it and return that value.
// (can be 0 for success, or a negative error code)
// (can be either success or failure)
if(ret != INFO_CB_CONTINUE)
{
debug_assert(ret <= 0); // shouldn't return > 0

View File

@ -468,9 +468,8 @@ static HCURSOR HCURSOR_from_ptr(void* p)
// it is no longer needed and can be freed after this call returns.
// hotspot (hx,hy) is the offset from its upper-left corner to the
// position where mouse clicks are registered.
// return: negative error code, or 0 on success. cursor is filled with
// a pointer and undefined on failure. it must be sys_cursor_free-ed
// when no longer needed.
// cursor is only valid when ERR_OK is returned; in that case, it must be
// sys_cursor_free-ed when no longer needed.
LibError sys_cursor_create(uint w, uint h, void* bgra_img,
uint hx, uint hy, void** cursor)
{
@ -593,7 +592,6 @@ wchar_t* sys_get_module_filename(void* addr, wchar_t* path)
// store full path to the current executable.
// returns 0 or a negative error code.
// useful for determining installation directory, e.g. for VFS.
inline LibError sys_get_executable_name(char* n_path, size_t buf_size)
{
@ -619,7 +617,6 @@ static int CALLBACK browse_cb(HWND hWnd, unsigned int msg, LPARAM UNUSED(lParam)
// have the user specify a directory via OS dialog.
// stores its full path in the given buffer, which must hold at least
// PATH_MAX chars.
// returns 0 on success or a negative error code.
LibError sys_pick_directory(char* path, size_t buf_size)
{
// bring up dialog; set starting directory to current working dir.