1201 lines
53 KiB
C++
1201 lines
53 KiB
C++
/* ScummVM - Graphic Adventure Engine
|
|
*
|
|
* ScummVM is the legal property of its developers, whose names
|
|
* are too numerous to list here. Please refer to the COPYRIGHT
|
|
* file distributed with this source distribution.
|
|
*
|
|
* This program is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation, either version 3 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* 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. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
*
|
|
*/
|
|
|
|
/* OS-layer functions and macros.
|
|
*
|
|
* This file does not introduce any curses (or other screen-API)
|
|
* dependencies; it can be used for both the interpreter as well as the
|
|
* compiler.
|
|
*/
|
|
|
|
#ifndef GLK_TADS_OS_FROB_TADS
|
|
#define GLK_TADS_OS_FROB_TADS
|
|
|
|
#include "common/fs.h"
|
|
#include "common/stream.h"
|
|
#include "glk/glk_api.h"
|
|
#include "glk/tads/os_filetype.h"
|
|
|
|
namespace Glk {
|
|
namespace TADS {
|
|
|
|
/* Defined for Gargoyle. */
|
|
#define HAVE_STDINT_
|
|
|
|
#if 0
|
|
#include "common.h"
|
|
#endif
|
|
|
|
/* Used by the base code to inhibit "unused parameter" compiler warnings. */
|
|
#ifndef VARUSED
|
|
#define VARUSED(var) (void)var
|
|
#endif
|
|
|
|
/* We assume that the C-compiler is mostly ANSI compatible. */
|
|
#define OSANSI
|
|
|
|
/* Special function qualifier needed for certain types of callback
|
|
* functions. This is for old 16-bit systems; we don't need it and
|
|
* define it to nothing. */
|
|
#define OS_LOADDS
|
|
|
|
/* Unices don't suffer the near/far pointers brain damage (thank God) so
|
|
* we make this a do-nothing macro. */
|
|
#define osfar_t
|
|
|
|
/* This is used to explicitly discard computed values (some compilers
|
|
* would otherwise give a warning like "computed value not used" in some
|
|
* cases). Casting to void should work on every ANSI-Compiler. */
|
|
#define DISCARD (void)
|
|
|
|
/* Copies a struct into another. ANSI C allows the assignment operator
|
|
* to be used with structs. */
|
|
#define OSCPYSTRUCT(x,y) ((x)=(y))
|
|
|
|
/* Link error messages into the application. */
|
|
#define ERR_LINK_MESSAGES
|
|
|
|
/* Program Exit Codes. */
|
|
#define OSEXSUCC 0 /* Successful completion. */
|
|
#define OSEXFAIL 1 /* Failure. */
|
|
|
|
/* Here we configure the osgen layer; refer to tads2/osgen3.c for more
|
|
* information about the meaning of these macros. */
|
|
#define USE_DOSEXT
|
|
#define USE_NULLSTYPE
|
|
|
|
/* Theoretical maximum osmalloc() size.
|
|
* Unix systems have at least a 32-bit memory space. Even on 64-bit
|
|
* systems, 2^32 is a good value, so we don't bother trying to find out
|
|
* an exact value. */
|
|
#define OSMALMAX 0xffffffffL
|
|
|
|
#define OSFNMAX 255
|
|
|
|
/**
|
|
* File handle structure for osfxxx functions
|
|
* Note that we need to define it as a Common::Stream since the type is used by
|
|
* TADS for both reading and writing files
|
|
*/
|
|
typedef Common::Stream osfildef;
|
|
|
|
/* Directory handle for searches via os_open_dir() et al. */
|
|
typedef Common::FSNode *osdirhdl_t;
|
|
|
|
/* file type/mode bits */
|
|
#define OSFMODE_FILE S_IFREG
|
|
#define OSFMODE_DIR S_IFDIR
|
|
#define OSFMODE_CHAR S_IFCHR
|
|
#define OSFMODE_BLK S_IFBLK
|
|
#define OSFMODE_PIPE S_IFIFO
|
|
#ifdef S_IFLNK
|
|
#define OSFMODE_LINK S_IFLNK
|
|
#else
|
|
#define OSFMODE_LINK 0
|
|
#endif
|
|
#ifdef S_IFSOCK
|
|
#define OSFMODE_SOCKET S_IFSOCK
|
|
#else
|
|
#define OSFMODE_SOCKET 0
|
|
#endif
|
|
|
|
/* File attribute bits. */
|
|
#define OSFATTR_HIDDEN 0x0001
|
|
#define OSFATTR_SYSTEM 0x0002
|
|
#define OSFATTR_READ 0x0004
|
|
#define OSFATTR_WRITE 0x0008
|
|
|
|
/* Get a file's stat() type. */
|
|
int osfmode( const char* fname, int follow_links, unsigned long* mode,
|
|
unsigned long* attr );
|
|
|
|
#if 0
|
|
/* The maximum width of a line of text.
|
|
*
|
|
* We ignore this, but the base code needs it defined. If the
|
|
* interpreter is run inside a console or terminal with more columns
|
|
* than the value defined here, weird things will happen, so we go safe
|
|
* and use a large value. */
|
|
#define OS_MAXWIDTH 255
|
|
#endif
|
|
|
|
/* Disable the Tads swap file; computers have plenty of RAM these days.
|
|
*/
|
|
#define OS_DEFAULT_SWAP_ENABLED 0
|
|
|
|
/* TADS 2 macro/function configuration. Modern configurations always
|
|
* use the no-macro versions, so these definitions should always be set
|
|
* as shown below. */
|
|
#define OS_MCM_NO_MACRO
|
|
#define ERR_NO_MACRO
|
|
|
|
/* These values are used for the "mode" parameter of osfseek() to
|
|
* indicate where to seek in the file. */
|
|
#define OSFSK_SET SEEK_SET /* Set position relative to the start of the file. */
|
|
#define OSFSK_CUR SEEK_CUR /* Set position relative to the current file position. */
|
|
#define OSFSK_END SEEK_END /* Set position relative to the end of the file. */
|
|
|
|
|
|
/* ============= Functions follow ================ */
|
|
|
|
/* Allocate a block of memory of the given size in bytes. */
|
|
#define osmalloc malloc
|
|
|
|
/* Free memory previously allocated with osmalloc(). */
|
|
#define osfree free
|
|
|
|
/* Reallocate memory previously allocated with osmalloc() or osrealloc(),
|
|
* changing the block's size to the given number of bytes. */
|
|
#define osrealloc realloc
|
|
|
|
/* Set busy cursor.
|
|
*
|
|
* We don't have a mouse cursor so there's no need to implement this. */
|
|
#define os_csr_busy(a)
|
|
|
|
/* Update progress display.
|
|
*
|
|
* We don't provide any kind of "compilation progress display", so we
|
|
* just define this as an empty macro.
|
|
*/
|
|
#define os_progress(fname,linenum)
|
|
|
|
/* ============= File Access ================ */
|
|
|
|
/*
|
|
* Test access to a file - i.e., determine if the file exists. Returns
|
|
* zero if the file exists, non-zero if not. (The semantics may seem
|
|
* backwards, but this is consistent with the conventions used by most of
|
|
* the other osfxxx calls: zero indicates success, non-zero indicates an
|
|
* error. If the file exists, "accessing" it was successful, so osfacc
|
|
* returns zero; if the file doesn't exist, accessing it gets an error,
|
|
* hence a non-zero return code.)
|
|
*/
|
|
int osfacc(const char *fname);
|
|
|
|
|
|
/*
|
|
* Open text file for reading. This opens the file with read-only access;
|
|
* we're not allowed to write to the file using this handle. Returns NULL
|
|
* on error.
|
|
*
|
|
* A text file differs from a binary file in that some systems perform
|
|
* translations to map between C conventions and local file system
|
|
* conventions; for example, on DOS, the stdio library maps the DOS CR-LF
|
|
* newline convention to the C-style '\n' newline format. On many systems
|
|
* (Unix, for example), there is no distinction between text and binary
|
|
* files.
|
|
*
|
|
* On systems that support file sharing and locking, this should open the
|
|
* file in "shared read" mode - this means that other processes are allowed
|
|
* to simultaneously read from the file, but no other processs should be
|
|
* allowed to write to the file as long as we have it open. If another
|
|
* process already has the file open with write access, this routine should
|
|
* return failure, since we can't take away the write privileges the other
|
|
* process already has and thus we can't guarantee that other processes
|
|
* won't write to the file while we have it open.
|
|
*/
|
|
osfildef *osfoprt(const char *fname, os_filetype_t typ);
|
|
|
|
/*
|
|
* Open a text file for "volatile" reading: we open the file with read-only
|
|
* access, and we explicitly accept instability in the file's contents due
|
|
* to other processes simultaneously writing to the file. On systems that
|
|
* support file sharing and locking, the file should be opened in "deny
|
|
* none" mode, meaning that other processes can simultaneously open the
|
|
* file for reading and/or writing even while have the file open.
|
|
*/
|
|
osfildef *osfoprtv(const char *fname, os_filetype_t typ);
|
|
|
|
/*
|
|
* Open text file for writing; returns NULL on error. If the file already
|
|
* exists, this truncates the file to zero length, deleting any existing
|
|
* contents.
|
|
*/
|
|
osfildef *osfopwt(const char *fname, os_filetype_t typ);
|
|
|
|
/*
|
|
* Open text file for reading and writing, keeping the file's existing
|
|
* contents if the file already exists or creating a new file if no such
|
|
* file exists. Returns NULL on error.
|
|
*/
|
|
osfildef *osfoprwt(const char *fname, os_filetype_t typ);
|
|
|
|
/*
|
|
* Open text file for reading/writing. If the file already exists,
|
|
* truncate the existing contents to zero length. Create a new file if it
|
|
* doesn't already exist. Return null on error.
|
|
*/
|
|
osfildef *osfoprwtt(const char *fname, os_filetype_t typ);
|
|
|
|
/*
|
|
* Open binary file for writing; returns NULL on error. If the file
|
|
* exists, this truncates the existing contents to zero length.
|
|
*/
|
|
osfildef *osfopwb(const char *fname, os_filetype_t typ);
|
|
|
|
/*
|
|
* Open source file for reading - use the appropriate text or binary
|
|
* mode.
|
|
*/
|
|
osfildef *osfoprs(const char *fname, os_filetype_t typ);
|
|
|
|
/*
|
|
* Open binary file for reading; returns NULL on error.
|
|
*/
|
|
osfildef *osfoprb(const char *fname, os_filetype_t typ);
|
|
|
|
/*
|
|
* Open binary file for 'volatile' reading; returns NULL on error.
|
|
* ("Volatile" means that we'll accept writes from other processes while
|
|
* reading, so the file should be opened in "deny none" mode or the
|
|
* equivalent, to the extent that the local system supports file sharing
|
|
* modes.)
|
|
*/
|
|
osfildef *osfoprbv(const char *fname, os_filetype_t typ);
|
|
|
|
/*
|
|
* Open binary file for random-access reading/writing. If the file already
|
|
* exists, keep the existing contents; if the file doesn't already exist,
|
|
* create a new empty file.
|
|
*
|
|
* The caller is allowed to perform any mixture of read and write
|
|
* operations on the returned file handle, and can seek around in the file
|
|
* to read and write at random locations.
|
|
*
|
|
* If the local file system supports file sharing or locking controls, this
|
|
* should generally open the file in something equivalent to "exclusive
|
|
* write, shared read" mode ("deny write" in DENY terms), so that other
|
|
* processes can't modify the file at the same time we're modifying it (but
|
|
* it doesn't bother us to have other processes reading from the file while
|
|
* we're working on it, as long as they don't mind that we could change
|
|
* things on the fly). It's not absolutely necessary to assert these
|
|
* locking semantics, but if there's an option to do so this is preferred.
|
|
* Stricter semantics (such as "exclusive" or "deny all" mode) are better
|
|
* than less strict semantics. Less strict semantics are dicey, because in
|
|
* that case the caller has no way of knowing that another process could be
|
|
* modifying the file at the same time, and no way (through osifc) of
|
|
* coordinating that activity. If less strict semantics are implemented,
|
|
* the caller will basically be relying on luck to avoid corruptions due to
|
|
* writing by other processes.
|
|
*
|
|
* Return null on error.
|
|
*/
|
|
osfildef *osfoprwb(const char *fname, os_filetype_t typ);
|
|
|
|
/*
|
|
* Open binary file for random-access reading/writing. If the file already
|
|
* exists, truncate the existing contents (i.e., delete the contents of the
|
|
* file, resetting it to a zero-length file). Create a new file if it
|
|
* doesn't already exist. The caller is allowed to perform any mixture of
|
|
* read and write operations on the returned handle, and can seek around in
|
|
* the file to read and write at random locations.
|
|
*
|
|
* The same comments regarding sharing/locking modes for osfoprwb() apply
|
|
* here as well.
|
|
*
|
|
* Return null on error.
|
|
*/
|
|
osfildef *osfoprwtb(const char *fname, os_filetype_t typ);
|
|
|
|
/*
|
|
* Duplicate a file handle. Returns a new osfildef* handle that accesses
|
|
* the same open file as an existing osfildef* handle. The new handle is
|
|
* independent of the original handle, with its own seek position,
|
|
* buffering, etc. The new handle and the original handle must each be
|
|
* closed separately when the caller is done with them (closing one doesn't
|
|
* close the other). The effect should be roughly the same as the Unix
|
|
* dup() function.
|
|
*
|
|
* On success, returns a new, non-null osfildef* handle duplicating the
|
|
* original handle. Returns null on failure.
|
|
*
|
|
* 'mode' is a simplified stdio fopen() mode string. The first
|
|
* character(s) indicate the access type: "r" for read access, "w" for
|
|
* write access, or "r+" for read/write access. Note that "w+" mode is
|
|
* specifically not defined, since the fopen() handling of "w+" is to
|
|
* truncate any existing file contents, which is not desirable when
|
|
* duplicating a handle. The access type can optionally be followed by "t"
|
|
* for text mode, "s" for source file mode, or "b" for binary mode, with
|
|
* the same meanings as for the various osfop*() functions. The default is
|
|
* 't' for text mode if none of these are specified.
|
|
*
|
|
* If the osfop*() functions are implemented in terms of stdio FILE*
|
|
* objects, this can be implemented as fdopen(dup(fileno(orig)), mode), or
|
|
* using equivalents if the local stdio library uses different names for
|
|
* these functions. Note that "s" (source file format) isn't a stdio mode,
|
|
* so implementations must translate it to the appropriate "t" or "b" mode.
|
|
* (For that matter, "t" and "b" modes aren't universally supported either,
|
|
* so some implementations may have to translate these, or more likely
|
|
* simply remove them, as most platforms don't distinguish text and binary
|
|
* modes anyway.)
|
|
*/
|
|
osfildef *osfdup(osfildef *orig, const char *mode);
|
|
|
|
/*
|
|
* Set a file's type information. This is primarily for implementations on
|
|
* Mac OS 9 and earlier, where the file system keeps file-type metadata
|
|
* separate from the filename. On such systems, this can be used to set
|
|
* the type metadata after a file is created. The system should map the
|
|
* os_filetype_t values to the actual metadata values on the local system.
|
|
* On most systems, there's no such thing as file-type metadata, in which
|
|
* case this function should simply be stubbed out with an empty function.
|
|
*/
|
|
void os_settype(const char *f, os_filetype_t typ);
|
|
|
|
/*
|
|
* Get a line of text from a text file. Uses fgets semantics.
|
|
*/
|
|
char *osfgets(char *buf, size_t len, osfildef *fp);
|
|
|
|
/*
|
|
* Write a line of text to a text file. Uses fputs semantics.
|
|
*/
|
|
int osfputs(const char *buf, osfildef *fp);
|
|
|
|
/*
|
|
* Write to a text file. os_fprintz() takes a null-terminated string,
|
|
* while os_fprint() takes an explicit separate length argument that might
|
|
* not end with a null terminator.
|
|
*/
|
|
void os_fprintz(osfildef *fp, const char *str);
|
|
void os_fprint(osfildef *fp, const char *str, size_t len);
|
|
|
|
/*
|
|
* Write bytes to file. Return 0 on success, non-zero on error.
|
|
*/
|
|
int osfwb(osfildef *fp, const void *buf, size_t bufl);
|
|
|
|
/*
|
|
* Flush buffered writes to a file. This ensures that any bytes written to
|
|
* the file (with osfwb(), os_fprint(), etc) are actually sent out to the
|
|
* operating system, rather than being buffered in application memory for
|
|
* later writing.
|
|
*
|
|
* Note that this routine only guarantees that we write through to the
|
|
* operating system. This does *not* guarantee that the data will actually
|
|
* be committed to the underlying physical storage device. Such a
|
|
* guarantee is hard to come by in general, since most modern systems use
|
|
* multiple levels of software and hardware buffering - the OS might buffer
|
|
* some data in system memory, and the physical disk drive might itself
|
|
* buffer data in its own internal cache. This routine thus isn't good
|
|
* enough, for example, to protect transactional data that needs to survive
|
|
* a power failure or a serious system crash. What this routine *does*
|
|
* ensure is that buffered data are written through to the OS; in
|
|
* particular, this ensures that another process that's reading from the
|
|
* same file will see all updates we've made up to this point.
|
|
*
|
|
* Returns 0 on success, non-zero on error. Errors can occur for any
|
|
* reason that they'd occur on an ordinary write - a full disk, a hardware
|
|
* failure, etc.
|
|
*/
|
|
int osfflush(osfildef *fp);
|
|
|
|
/*
|
|
* Read a character from a file. Provides the same semantics as fgetc().
|
|
*/
|
|
int osfgetc(osfildef *fp);
|
|
|
|
/*
|
|
* Read bytes from file. Return 0 on success, non-zero on error.
|
|
*/
|
|
int osfrb(osfildef *fp, void *buf, size_t bufl);
|
|
|
|
/*
|
|
* Read bytes from file and return the number of bytes read. 0
|
|
* indicates that no bytes could be read.
|
|
*/
|
|
size_t osfrbc(osfildef *fp, void *buf, size_t bufl);
|
|
|
|
/*
|
|
* Get the current seek location in the file. The first byte of the
|
|
* file has seek position 0.
|
|
*/
|
|
long osfpos(osfildef *fp);
|
|
|
|
/*
|
|
* Seek to a location in the file. The first byte of the file has seek
|
|
* position 0. Returns zero on success, non-zero on error.
|
|
*
|
|
* The following constants must be defined in your OS-specific header;
|
|
* these values are used for the "mode" parameter to indicate where to
|
|
* seek in the file:
|
|
*
|
|
* OSFSK_SET - set position relative to the start of the file
|
|
*. OSFSK_CUR - set position relative to the current file position
|
|
*. OSFSK_END - set position relative to the end of the file
|
|
*/
|
|
int osfseek(osfildef *fp, long pos, int mode);
|
|
|
|
/*
|
|
* Close a file.
|
|
*
|
|
* If the OS implementation uses buffered writes, this routine guarantees
|
|
* that any buffered data are flushed to the underlying file. So, it's not
|
|
* necessary to call osfflush() before calling this routine. However,
|
|
* since this function doesn't return any error indication, a caller could
|
|
* use osfflush() first to check for errors on any final buffered writes.
|
|
*/
|
|
void osfcls(osfildef *fp);
|
|
|
|
/*
|
|
* Delete a file. Returns zero on success, non-zero on error.
|
|
*/
|
|
int osfdel(const char *fname);
|
|
|
|
/*
|
|
* Rename/move a file. This should apply the usual C rename() behavior.
|
|
* Renames the old file to the new name, which may be in a new directory
|
|
* location if supported on the local system; moves across devices,
|
|
* volumes, file systems, etc may or may not be supported according to the
|
|
* local system's rules. If the new file already exists, results are
|
|
* undefined. Returns true on success, false on failure.
|
|
*/
|
|
int os_rename_file(const char *oldname, const char *newname);
|
|
|
|
/* ------------------------------------------------------------------------ */
|
|
/*
|
|
* Look for a file in the "standard locations": current directory, program
|
|
* directory, PATH-like environment variables, etc. The actual standard
|
|
* locations are specific to each platform; the implementation is free to
|
|
* use whatever conventions are appropriate to the local system. On
|
|
* systems that have something like Unix environment variables, it might be
|
|
* desirable to define a TADS-specific variable (TADSPATH, for example)
|
|
* that provides a list of directories to search for TADS-related files.
|
|
*
|
|
* On return, fill in 'buf' with the full filename of the located copy of
|
|
* the file (if a copy was indeed found), in a format suitable for use with
|
|
* the osfopxxx() functions; in other words, after this function returns,
|
|
* the caller should be able to pass the contents of 'buf' to an osfopxxx()
|
|
* function to open the located file.
|
|
*
|
|
* Returns true (non-zero) if a copy of the file was located, false (zero)
|
|
* if the file could not be found in any of the standard locations.
|
|
*/
|
|
bool os_locate(const char *fname, int flen, const char *arg0,
|
|
char *buf, size_t bufsiz);
|
|
|
|
|
|
/* ------------------------------------------------------------------------ */
|
|
/*
|
|
* Create and open a temporary file. The file must be opened to allow
|
|
* both reading and writing, and must be in "binary" mode rather than
|
|
* "text" mode, if the system makes such a distinction. Returns null on
|
|
* failure.
|
|
*
|
|
* If 'fname' is non-null, then this routine should create and open a file
|
|
* with the given name. When 'fname' is non-null, this routine does NOT
|
|
* need to store anything in 'buf'. Note that the routine shouldn't try
|
|
* to put the file in a special directory or anything like that; just open
|
|
* the file with the name exactly as given.
|
|
*
|
|
* If 'fname' is null, this routine must choose a file name and fill in
|
|
* 'buf' with the chosen name; if possible, the file should be in the
|
|
* conventional location for temporary files on this system, and should be
|
|
* unique (i.e., it shouldn't be the same as any existing file). The
|
|
* filename stored in 'buf' is opaque to the caller, and cannot be used by
|
|
* the caller except to pass to osfdel_temp(). On some systems, it may
|
|
* not be possible to determine the actual filename of a temporary file;
|
|
* in such cases, the implementation may simply store an empty string in
|
|
* the buffer. (The only way the filename would be unavailable is if the
|
|
* implementation uses a system API that creates a temporary file, and
|
|
* that API doesn't return the name of the created temporary file. In
|
|
* such cases, we don't need the name; the only reason we need the name is
|
|
* so we can pass it to osfdel_temp() later, but since the system is going
|
|
* to delete the file automatically, osfdel_temp() doesn't need to do
|
|
* anything and thus doesn't need the name.)
|
|
*
|
|
* After the caller is done with the file, it should close the file (using
|
|
* osfcls() as normal), then the caller MUST call osfdel_temp() to delete
|
|
* the temporary file.
|
|
*
|
|
* This interface is intended to take advantage of systems that have
|
|
* automatic support for temporary files, while allowing implementation on
|
|
* systems that don't have any special temp file support. On systems that
|
|
* do have automatic delete-on-close support, this routine should use that
|
|
* system-level support, because it helps ensure that temp files will be
|
|
* deleted even if the caller fails to call osfdel_temp() due to a
|
|
* programming error or due to a process or system crash. On systems that
|
|
* don't have any automatic delete-on-close support, this routine can
|
|
* simply use the same underlying system API that osfoprwbt() normally
|
|
* uses (although this routine must also generate a name for the temp file
|
|
* when the caller doesn't supply one).
|
|
*
|
|
* This routine can be implemented using ANSI library functions as
|
|
* follows: if 'fname' is non-null, return fopen(fname,"w+b"); otherwise,
|
|
* set buf[0] to '\0' and return tmpfile().
|
|
*/
|
|
osfildef *os_create_tempfile(const char *fname, char *buf);
|
|
|
|
/*
|
|
* Delete a temporary file - this is used to delete a file created with
|
|
* os_create_tempfile(). For most platforms, this can simply be defined
|
|
* the same way as osfdel(). For platforms where the operating system or
|
|
* file manager will automatically delete a file opened as a temporary
|
|
* file, this routine should do nothing at all, since the system will take
|
|
* care of deleting the temp file.
|
|
*
|
|
* Callers are REQUIRED to call this routine after closing a file opened
|
|
* with os_create_tempfile(). When os_create_tempfile() is called with a
|
|
* non-null 'fname' argument, the same value should be passed as 'fname' to
|
|
* this function. When os_create_tempfile() is called with a null 'fname'
|
|
* argument, then the buffer passed in the 'buf' argument to
|
|
* os_create_tempfile() must be passed as the 'fname' argument here. In
|
|
* other words, if the caller explicitly names the temporary file to be
|
|
* opened in os_create_tempfile(), then that same filename must be passed
|
|
* here to delete the named file; if the caller lets os_create_tempfile()
|
|
* generate a filename, then the generated filename must be passed to this
|
|
* routine.
|
|
*
|
|
* If os_create_tempfile() is implemented using ANSI library functions as
|
|
* described above, then this routine can also be implemented with ANSI
|
|
* library calls as follows: if 'fname' is non-null and fname[0] != '\0',
|
|
* then call remove(fname); otherwise do nothing.
|
|
*/
|
|
int osfdel_temp(const char *fname);
|
|
|
|
/*
|
|
* Get the temporary file path. This should fill in the buffer with a
|
|
* path prefix (suitable for strcat'ing a filename onto) for a good
|
|
* directory for a temporary file, such as the swap file.
|
|
*/
|
|
void os_get_tmp_path(char *buf);
|
|
|
|
/*
|
|
* Generate a name for a temporary file. This constructs a random file
|
|
* path in the system temp directory that isn't already used by an existing
|
|
* file.
|
|
*
|
|
* On systems with long filenames, this can be implemented by selecting a
|
|
* GUID-strength random name (such as 32 random hex digits) with a decent
|
|
* random number generator. That's long enough that the odds of a
|
|
* collision are essentially zero. On systems that only support short
|
|
* filenames, the odds of a collision are non-zero, so the routine should
|
|
* actually check that the chosen filename doesn't exist.
|
|
*
|
|
* Optionally, before returning, this routine *may* create (and close) an
|
|
* empty placeholder file to "reserve" the chosen filename. This isn't
|
|
* required, and on systems with long filenames it's usually not necessary
|
|
* because of the negligible chance of a collision. On systems with short
|
|
* filenames, a placeholder can be useful to prevent a subsequent call to
|
|
* this routine, or a separate process, from using the same filename before
|
|
* the caller has had a chance to use the returned name to create the
|
|
* actual temp file.
|
|
*
|
|
* Returns true on success, false on failure. This can fail if there's no
|
|
* system temporary directory defined, or the temp directory is so full of
|
|
* other files that we can't find an unused filename.
|
|
*/
|
|
int os_gen_temp_filename(char *buf, size_t buflen);
|
|
|
|
|
|
/* ------------------------------------------------------------------------ */
|
|
/*
|
|
* Basic directory/folder management routines
|
|
*/
|
|
|
|
/*
|
|
* Switch to a new working directory.
|
|
*
|
|
* This is meant to behave similarly to the Unix concept of a working
|
|
* directory, in that it sets the base directory assumed for subsequent
|
|
* file operations (e.g., the osfopxx() functions, osfdel(), etc - anything
|
|
* that takes a filename or directory name as an argument). The working
|
|
* directory applies to filenames specified with relative paths in the
|
|
* local system notation. File operations on filenames specified with
|
|
* absolute paths, of course, ignore the working directory.
|
|
*/
|
|
void os_set_pwd(const char *dir);
|
|
|
|
/*
|
|
* Switch the working directory to the directory containing the given
|
|
* file. Generally, this routine should only need to parse the filename
|
|
* enough to determine the part that's the directory path, then use
|
|
* os_set_pwd() to switch to that directory.
|
|
*/
|
|
void os_set_pwd_file(const char *filename);
|
|
|
|
/*
|
|
* Create a directory. This creates a new directory/folder with the given
|
|
* name, which may be given as a relative or absolute path. Returns true
|
|
* on success, false on failure.
|
|
*
|
|
* If 'create_parents' is true, and the directory has mulitiple path
|
|
* elements, this routine should create each enclosing parent that doesn't
|
|
* already exist. For example, if the path is specified as "a/b/c", and
|
|
* there exists a folder "a" in the working directory, but "a" is empty,
|
|
* this should first create "b" and then create "c". If an error occurs
|
|
* creating any parent, the routine should simply stop and return failure.
|
|
* (Optionally, the routine may attempt to behave atomically by undoing any
|
|
* parent folder creations it accomplished before failing on a nested
|
|
* folder, but this isn't required. To reduce the chances of a failure
|
|
* midway through the operation, the routine might want to scan the
|
|
* filename before starting to ensure that it contains only valid
|
|
* characters, since an invalid character is the most likely reason for a
|
|
* failure part of the way through.)
|
|
*
|
|
* We recommend making the routine flexible in terms of the notation it
|
|
* accepts; e.g., on Unix, "/dir/sub/folder" and "/dir/sub/folder/" should
|
|
* be considered equivalent.
|
|
*/
|
|
bool os_mkdir(const char *dir, int create_parents);
|
|
|
|
/*
|
|
* Remove a directory. Returns true on success, false on failure.
|
|
*
|
|
* If the directory isn't already empty, this routine fails. That is, the
|
|
* routine does NOT recursively delete the contents of a non-empty
|
|
* directory. It's up to the caller to delete any contents before removing
|
|
* the directory, if that's the caller's intention. (Note to implementors:
|
|
* most native OS APIs to remove directories fail by default if the
|
|
* directory isn't empty, so it's usually safe to implement this simply by
|
|
* calling the native API. However, if your system's version of this API
|
|
* can remove a non-empty directory, you MUST add an extra test before
|
|
* removing the directory to ensure it's empty, and return failure if it's
|
|
* not. For the purposes of this test, "empty" should of course ignore any
|
|
* special objects that are automatically or implicitly present in all
|
|
* directories, such as the Unix "." and ".." relative links.)
|
|
*/
|
|
bool os_rmdir(const char *dir);
|
|
|
|
|
|
/* ------------------------------------------------------------------------ */
|
|
/*
|
|
* Filename manipulation routines
|
|
*/
|
|
|
|
/* apply a default extension to a filename, if it doesn't already have one */
|
|
void os_defext(char *fname, const char *ext);
|
|
|
|
/* unconditionally add an extension to a filename */
|
|
void os_addext(char *fname, const char *ext);
|
|
|
|
/* remove the extension from a filename */
|
|
void os_remext(char *fname);
|
|
|
|
/*
|
|
* Compare two file names/paths for syntactic equivalence. Returns true if
|
|
* the names are equivalent names according to the local file system's
|
|
* syntax conventions, false if not. This does a syntax-only comparison of
|
|
* the paths, without looking anything up in the file system. This means
|
|
* that a false return doesn't guarantee that the paths don't point to the
|
|
* same file.
|
|
*
|
|
* This routine DOES make the following equivalences:
|
|
*
|
|
* - if the local file system is insensitive to case, the names are
|
|
* compared ignoring case
|
|
*
|
|
* - meaningless path separator difference are ignored: on Unix, "a/b" ==
|
|
* "a//b" == "a/b/"; on Windows, "a/b" == "a\\b"
|
|
*
|
|
* - relative links that are strictly structural or syntactic are applied;
|
|
* for example, on Unix or Windows, "a/./b" == "a/b" = "a/b/c/..". This
|
|
* only applies for special relative links that can be resolved without
|
|
* looking anything up in the file system.
|
|
*
|
|
* This DOES NOT do the following:
|
|
*
|
|
* - it doesn't apply working directories/volums to relative paths
|
|
*
|
|
* - it doesn't follow symbolic links in the file system
|
|
*/
|
|
bool os_file_names_equal(const char *a, const char *b);
|
|
|
|
/*
|
|
* Get a pointer to the root name portion of a filename. This is the part
|
|
* of the filename after any path or directory prefix. For example, on
|
|
* Unix, given the string "/home/mjr/deep.gam", this function should return
|
|
* a pointer to the 'd' in "deep.gam". If the filename doesn't appear to
|
|
* have a path prefix, it should simply return the argument unchanged.
|
|
*
|
|
* IMPORTANT: the returned pointer MUST point into the original 'buf'
|
|
* string, and the contents of that buffer must NOT be modified. The
|
|
* return value must point into the same buffer because there are no
|
|
* allowances for the alternatives. In particular, (a) you can't return a
|
|
* pointer to newly allocated memory, because callers won't free it, so
|
|
* doing so would cause a memory leak; and (b) you can't return a pointer
|
|
* to an internal static buffer, because callers might call this function
|
|
* more than once and still rely on a value returned on an older call,
|
|
* which would be invalid if a static buffer could be overwritten on each
|
|
* call. For these reasons, it's required that the return value point to a
|
|
* position within the original string passed in 'buf'.
|
|
*/
|
|
const char *os_get_root_name(const char *buf);
|
|
|
|
/*
|
|
* Determine whether a filename specifies an absolute or relative path.
|
|
* This is used to analyze filenames provided by the user (for example,
|
|
* in a #include directive, or on a command line) to determine if the
|
|
* filename can be considered relative or absolute. This can be used,
|
|
* for example, to determine whether to search a directory path for a
|
|
* file; if a given filename is absolute, a path search makes no sense.
|
|
* A filename that doesn't specify an absolute path can be combined with
|
|
* a path using os_build_full_path().
|
|
*
|
|
* Returns true if the filename specifies an absolute path, false if
|
|
* not.
|
|
*/
|
|
bool os_is_file_absolute(const char *fname);
|
|
|
|
/*
|
|
* Extract the path from a filename. Fills in pathbuf with the path
|
|
* portion of the filename. If the filename has no path, the pathbuf
|
|
* should be set appropriately for the current directory (on Unix or DOS,
|
|
* for example, it can be set to an empty string).
|
|
*
|
|
* The result can end with a path separator character or not, depending on
|
|
* local OS conventions. Paths extracted with this function can only be
|
|
* used with os_build_full_path(), so the conventions should match that
|
|
* function's.
|
|
*
|
|
* Unix examples:
|
|
*
|
|
*. /home/mjr/deep.gam -> /home/mjr
|
|
*. games/deep.gam -> games
|
|
*. deep.gam -> [empty string]
|
|
*
|
|
* Mac examples:
|
|
*
|
|
* :home:mjr:deep.gam -> :home:mjr
|
|
*. Hard Disk:games:deep.gam -> Hard Disk:games
|
|
*. Hard Disk:deep.gam -> Hard Disk:
|
|
*. deep.gam -> [empty string]
|
|
*
|
|
* VMS examples:
|
|
*
|
|
*. SYS$DISK:[mjr.games]deep.gam -> SYS$DISK:[mjr.games]
|
|
*. SYS$DISK:[mjr.games] -> SYS$DISK:[mjr]
|
|
*. deep.gam -> [empty string]
|
|
*
|
|
* Note in the last example that we've retained the trailing colon in the
|
|
* path, whereas we didn't in the others; although the others could also
|
|
* retain the trailing colon, it's required only for the last case. The
|
|
* last case requires the colon because it would otherwise be impossible to
|
|
* determine whether "Hard Disk" was a local subdirectory or a volume name.
|
|
*
|
|
*/
|
|
void os_get_path_name(char *pathbuf, size_t pathbuflen, const char *fname);
|
|
|
|
/*
|
|
* Build a full path name, given a path and a filename. The path may have
|
|
* been specified by the user, or may have been extracted from another file
|
|
* via os_get_path_name(). This routine must take care to add path
|
|
* separators as needed, but also must take care not to add too many path
|
|
* separators.
|
|
*
|
|
* This routine should reformat the path into canonical format to the
|
|
* extent possible purely through syntactic analysis. For example, special
|
|
* relative links, such as Unix "." and "..", should be resolved; for
|
|
* example, combining "a/./b/c" with ".." on Unix should yield "a/b".
|
|
* However, symbolic links that require looking up names in the file system
|
|
* should NOT be resolved. We don't want to perform any actual file system
|
|
* lookups because might want to construct hypothetical paths that don't
|
|
* necessarily relate to files on the local system.
|
|
*
|
|
* Note that relative path names may require special care on some
|
|
* platforms. In particular, if the source path is relative, the result
|
|
* should also be relative. For example, on the Macintosh, a path of
|
|
* "games" and a filename "deep.gam" should yield ":games:deep.gam" - note
|
|
* the addition of the leading colon to make the result path relative.
|
|
*
|
|
* Note also that the 'filename' argument is not only allowed to be an
|
|
* ordinary file, possibly qualified with a relative path, but is also
|
|
* allowed to be a subdirectory. The result in this case should be a path
|
|
* that can be used as the 'path' argument to a subsequent call to
|
|
* os_build_full_path; this allows a path to be built in multiple steps by
|
|
* descending into subdirectories one at a time.
|
|
*
|
|
* Unix examples:
|
|
*
|
|
*. /home/mjr + deep.gam -> /home/mjr/deep.gam"
|
|
*. /home/mjr + .. -> /home
|
|
*. /home/mjr + ../deep.gam -> /home/deep.gam
|
|
*. /home/mjr/ + deep.gam -> /home/mjr/deep.gam"
|
|
*. games + deep.gam -> games/deep.gam"
|
|
*. games/ + deep.gam -> games/deep.gam"
|
|
*. /home/mjr + games/deep.gam -> /home/mjr/games/deep.gam"
|
|
*. games + scifi/deep.gam -> games/scifi/deep.gam"
|
|
*. /home/mjr + games -> /home/mjr/games"
|
|
*
|
|
* Mac examples:
|
|
*
|
|
*. Hard Disk: + deep.gam -> Hard Disk:deep.gam
|
|
*. :games: + deep.gam -> :games:deep.gam
|
|
*. :games:deep + ::test.gam -> :games:test.gam
|
|
*. games + deep.gam -> :games:deep.gam
|
|
*. Hard Disk: + :games:deep.gam -> Hard Disk:games:deep.gam
|
|
*. games + :scifi:deep.gam -> :games:scifi:deep.gam
|
|
*. Hard Disk: + games -> Hard Disk:games
|
|
*. Hard Disk:games + scifi -> Hard Disk:games:scifi
|
|
*. Hard Disk:games:scifi + deep.gam -> Hard Disk:games:scifi:deep.gam
|
|
*. Hard Disk:games + :scifi:deep.gam -> Hard Disk:games:scifi:deep.gam
|
|
*
|
|
* VMS examples:
|
|
*
|
|
*. [home.mjr] + deep.gam -> [home.mjr]deep.gam
|
|
*. [home.mjr] + [-]deep.gam -> [home]deep.gam
|
|
*. mjr.dir + deep.gam -> [.mjr]deep.gam
|
|
*. [home]mjr.dir + deep.gam -> [home.mjr]deep.gam
|
|
*. [home] + [.mjr]deep.gam -> [home.mjr]deep.gam
|
|
*/
|
|
void os_build_full_path(char *fullpathbuf, size_t fullpathbuflen,
|
|
const char *path, const char *filename);
|
|
|
|
/*
|
|
* Combine a path and a filename to form a full path to the file. This is
|
|
* *almost* the same as os_build_full_path(), but if the 'filename' element
|
|
* is a special relative link, such as Unix '.' or '..', this preserves
|
|
* that special link in the final name.
|
|
*
|
|
* Unix examples:
|
|
*
|
|
*. /home/mjr + deep.gam -> /home/mjr/deep.gam
|
|
*. /home/mjr + . -> /home/mjr/.
|
|
*. /home/mjr + .. -> /home/mjr/..
|
|
*
|
|
* Mac examples:
|
|
*
|
|
*. Hard Disk:games + deep.gam -> HardDisk:games:deep.gam
|
|
*. Hard Disk:games + :: -> HardDisk:games::
|
|
*
|
|
* VMS exmaples:
|
|
*
|
|
*. [home.mjr] + deep.gam -> [home.mjr]deep.gam
|
|
*. [home.mjr] + [-] -> [home.mjr.-]
|
|
*/
|
|
void os_combine_paths(char *fullpathbuf, size_t pathbuflen,
|
|
const char *path, const char *filename);
|
|
|
|
|
|
/*
|
|
* Get the absolute, fully qualified filename for a file. This fills in
|
|
* 'result_buf' with the absolute path to the given file, taking into
|
|
* account the current working directory and any other implied environment
|
|
* information that affects the way the file system would resolve the given
|
|
* file name to a specific file on disk if we opened the file now using
|
|
* this name.
|
|
*
|
|
* The returned path should be in absolute path form, meaning that it's
|
|
* independent of the current working directory or any other environment
|
|
* settings. That is, this path should still refer to the same file even
|
|
* if the working directory changes.
|
|
*
|
|
* Note that it's valid to get the absolute path for a file that doesn't
|
|
* exist, or for a path with directory components that don't exist. For
|
|
* example, a caller might generate the absolute path for a file that it's
|
|
* about to create, or a hypothetical filename for path comparison
|
|
* purposes. The function should succeed even if the file or any path
|
|
* components don't exist. If the file is in relative format, and any path
|
|
* elements don't exist but are syntactically well-formed, the result
|
|
* should be the path obtained from syntactically combining the working
|
|
* directory with the relative path.
|
|
*
|
|
* On many systems, a given file might be reachable through more than one
|
|
* absolute path. For example, on Unix it might be possible to reach a
|
|
* file through symbolic links to the file itself or to parent directories,
|
|
* or hard links to the file. It's up to the implementation to determine
|
|
* which path to use in such cases.
|
|
*
|
|
* On success, returns true. If it's not possible to resolve the file name
|
|
* to an absolute path, the routine copies the original filename to the
|
|
* result buffer exactly as given, and returns false.
|
|
*/
|
|
bool os_get_abs_filename(char *result_buf, size_t result_buf_size,
|
|
const char *filename);
|
|
|
|
/*
|
|
* Get the relative version of the given filename path 'filename', relative
|
|
* to the given base directory 'basepath'. Both paths must be given in
|
|
* absolute format.
|
|
*
|
|
* Returns true on success, false if it's not possible to rewrite the path
|
|
* in relative terms. For example, on Windows, it's not possible to
|
|
* express a path on the "D:" drive as relative to a base path on the "C:"
|
|
* drive, since each drive letter has an independent root folder; there's
|
|
* no namespace entity enclosing a drive letter's root folder. On
|
|
* Unix-like systems where the entire namespace has a single hierarchical
|
|
* root, it should always be possible to express any path relative to any
|
|
* other.
|
|
*
|
|
* The result should be a relative path that can be combined with
|
|
* 'basepath' using os_build_full_path() to reconstruct a path that
|
|
* identifies the same file as the original 'filename' (it's not important
|
|
* that this procedure would result in the identical string - it just has
|
|
* to point to the same file). If it's not possible to express the
|
|
* filename relative to the base path, fill in 'result_buf' with the
|
|
* original filename and return false.
|
|
*
|
|
* Windows examples:
|
|
*
|
|
*. c:\mjr\games | c:\mjr\games\deep.gam -> deep.gam
|
|
*. c:\mjr\games | c:\mjr\games\tads\deep.gam -> tads\deep.gam
|
|
*. c:\mjr\games | c:\mjr\tads\deep.gam -> ..\tads\deep.gam
|
|
*. c:\mjr\games | d:\deep.gam -> d:\deep.gam (and return false)
|
|
*
|
|
* Mac OS examples:
|
|
*
|
|
*. Mac HD:mjr:games | Mac HD:mjr:games:deep.gam -> deep.gam
|
|
*. Mac HD:mjr:games | Mac HD:mjr:games:tads:deep.gam -> :tads:deep.gam
|
|
*. Mac HD:mjr:games | Ext Disk:deep.gam -> Ext Disk:deep.gam (return false)
|
|
*
|
|
* VMS examples:
|
|
*
|
|
*. SYS$:[mjr.games] | SYS$:[mjr.games]deep.gam -> deep.gam
|
|
*. SYS$:[mjr.games] | SYS$:[mjr.games.tads]deep.gam -> [.tads]deep.gam
|
|
*. SYS$:[mjr.games] | SYS$:[mjr.tads]deep.gam -> [-.tads]deep.gam
|
|
*. SYS$:[mjr.games] | DISK$:[mjr]deep.gam -> DISK$[mjr]deep.gam (ret false)
|
|
*/
|
|
bool os_get_rel_path(char *result_buf, size_t result_buf_size,
|
|
const char *basepath, const char *filename);
|
|
|
|
/*
|
|
* Determine if the given file is in the given directory. Returns true if
|
|
* so, false if not. 'filename' is a relative or absolute file name;
|
|
* 'path' is a relative or absolute directory path, such as one returned
|
|
* from os_get_path_name().
|
|
*
|
|
* If 'include_subdirs' is true, the function returns true if the file is
|
|
* either directly in the directory 'path', OR it's in any subdirectory of
|
|
* 'path'. If 'include_subdirs' is false, the function returns true only
|
|
* if the file is directly in the given directory.
|
|
*
|
|
* If 'match_self' is true, the function returns true if 'filename' and
|
|
* 'path' are the same directory; otherwise it returns false in this case.
|
|
*
|
|
* This routine is allowed to return "false negatives" - that is, it can
|
|
* claim that the file isn't in the given directory even when it actually
|
|
* is. The reason is that it's not always possible to determine for sure
|
|
* that there's not some way for a given file path to end up in the given
|
|
* directory. In contrast, a positive return must be reliable.
|
|
*
|
|
* If possible, this routine should fully resolve the names through the
|
|
* file system to determine the path relationship, rather than merely
|
|
* analyzing the text superficially. This can be important because many
|
|
* systems have multiple ways to reach a given file, such as via symbolic
|
|
* links on Unix; analyzing the syntax alone wouldn't reveal these multiple
|
|
* pathways.
|
|
*
|
|
* SECURITY NOTE: If possible, implementations should fully resolve all
|
|
* symbolic links, relative paths (e.g., Unix ".."), etc, before rendering
|
|
* judgment. One important application for this routine is to determine if
|
|
* a file is in a sandbox directory, to enforce security restrictions that
|
|
* prevent a program from accessing files outside of a designated folder.
|
|
* If the implementation fails to resolve symbolic links or relative paths,
|
|
* a malicious program or user could bypass the security restriction by,
|
|
* for example, creating a symbolic link within the sandbox directory that
|
|
* points to the root folder. Implementations can avoid this loophole by
|
|
* converting the file and directory names to absolute paths and resolving
|
|
* all symbolic links and relative notation before comparing the paths.
|
|
*/
|
|
bool os_is_file_in_dir(const char *filename, const char *path,
|
|
bool include_subdirs, bool match_self);
|
|
|
|
|
|
|
|
/* ------------------------------------------------------------------------ */
|
|
/*
|
|
* Convert an OS filename path to URL-style format. This isn't a true URL
|
|
* conversion; rather, it simply expresses a filename in Unix-style
|
|
* notation, as a series of path elements separated by '/' characters.
|
|
* Unlike true URLs, we don't use % encoding or a scheme prefix (file://,
|
|
* etc).
|
|
*
|
|
* The result path never ends in a trailing '/', unless the entire result
|
|
* path is "/". This is for consistency; even if the source path ends with
|
|
* a local path separator, the result doesn't.
|
|
*
|
|
* If the local file system syntax uses '/' characters as ordinary filename
|
|
* characters, these must be replaced with some other suitable character in
|
|
* the result, since otherwise they'd be taken as path separators when the
|
|
* URL is parsed. If possible, the substitution should be reversible with
|
|
* respect to os_cvt_dir_url(), so that the same URL read back in on this
|
|
* same platform will produce the same original filename. One particular
|
|
* suggestion is that if the local system uses '/' to delimit what would be
|
|
* a filename extension on other platforms, replace '/' with '.', since
|
|
* this will provide reversibility as well as a good mapping if the URL is
|
|
* read back in on another platform.
|
|
*
|
|
* The local equivalents of "." and "..", if they exist, are converted to
|
|
* "." and ".." in the URL notation.
|
|
*
|
|
* Examples:
|
|
*
|
|
*. Windows: images\rooms\startroom.jpg -> images/rooms/startroom.jpg
|
|
*. Windows: ..\startroom.jpg -> ../startroom.jpg
|
|
*. Mac: :images:rooms:startroom.jpg -> images/rooms/startroom.jpg
|
|
*. Mac: ::startroom.jpg -> ../startroom.jpg
|
|
*. VMS: [.images.rooms]startroom.jpg -> images/rooms/startroom.jpg
|
|
*. VMS: [-.images]startroom.jpg -> ../images/startroom.jpg
|
|
*. Unix: images/rooms/startroom.jpg -> images/rooms/startroom.jpg
|
|
*. Unix: ../images/startroom.jpg -> ../images/startroom.jpg
|
|
*
|
|
* If the local name is an absolute path in the local file system (e.g.,
|
|
* Unix /file, Windows C:\file), translate as follows. If the local
|
|
* operating system uses a volume or device designator (Windows C:, VMS
|
|
* SYS$DISK:, etc), make the first element of the path the exact local
|
|
* syntax for the device designator: /C:/ on Windows, /SYS$DISK:/ on VMS,
|
|
* etc. Include the local syntax for the device prefix. For a system like
|
|
* Unix with a unified file system root ("/"), simply start with the root
|
|
* directory. Examples:
|
|
*
|
|
*. Windows: C:\games\deep.gam -> /C:/games/deep.gam
|
|
*. Windows: C:games\deep.gam -> /C:./games/deep.gam
|
|
*. Windows: \\SERVER\DISK\games\deep.gam -> /\\SERVER/DISK/games/deep.gam
|
|
*. Mac OS 9: Hard Disk:games:deep.gam -> /Hard Disk:/games/deep.gam
|
|
*. VMS: SYS$DISK:[games]deep.gam -> /SYS$DISK:/games/deep.gam
|
|
*. Unix: /games/deep.gam -> /games/deep.gam
|
|
*
|
|
* Rationale: it's effectively impossible to create a truly portable
|
|
* representation of an absolute path. Operating systems are too different
|
|
* in the way they represent root paths, and even if that were solvable, a
|
|
* root path is essentially unusable across machines anyway because it
|
|
* creates a dependency on the contents of a particular machine's disk. So
|
|
* if we're called upon to translate an absolute path, we can forget about
|
|
* trying to be truly portable and instead focus on round-trip fidelity -
|
|
* i.e., making sure that applying os_cvt_url_dir() to our result recovers
|
|
* the exact original path, assuming it's done on the same operating
|
|
* system. The approach outlined above should achieve round-trip fidelity
|
|
* when a local path is converted to a URL and back on the same machine,
|
|
* since the local URL-to-path converter should recognize its own special
|
|
* type of local absolute path prefix. It also produces reasonable results
|
|
* on other platforms - see the os_cvt_url_dir() comments below for
|
|
* examples of the decoding results for absolute paths moved to new
|
|
* platforms. The result when a device-rooted absolute path is encoded on
|
|
* one machine and then decoded on another will generally be a local path
|
|
* with a root on the default device/volume and an outermost directory with
|
|
* a name based on the original machine's device/volume name. This
|
|
* obviously won't reproduce the exact original path, but since that's
|
|
* impossible anyway, this is probably as good an approximation as we can
|
|
* create.
|
|
*
|
|
* Character sets: the input could be in local or UTF-8 character sets.
|
|
* The implementation shouldn't care, though - just treat bytes in the
|
|
* range 0-127 as plain ASCII, and everything else as opaque. I.e., do not
|
|
* quote or otherwise modify characters outside the 0-127 range.
|
|
*/
|
|
void os_cvt_dir_url(char *result_buf, size_t result_buf_size,
|
|
const char *src_path);
|
|
|
|
/*
|
|
* Convert a URL-style path into a filename path expressed in the local
|
|
* file system's syntax. Fills in result_buf with a file path, constructed
|
|
* using the local file system syntax, that corresponds to the path in
|
|
* src_url expressed in URL-style syntax. Examples:
|
|
*
|
|
* images/rooms/startroom.jpg ->
|
|
*. Windows -> images\rooms\startroom.jpg
|
|
*. Mac OS 9 -> :images:rooms:startroom.jpg
|
|
*. VMS -> [.images.rooms]startroom.jpg
|
|
*
|
|
* The source format isn't a true URL; it's simply a series of path
|
|
* elements separated by '/' characters. Unlike true URLs, our input
|
|
* format doesn't use % encoding and doesn't have a scheme (file://, etc).
|
|
* (Any % in the source is treated as an ordinary character and left as-is,
|
|
* even if it looks like a %XX sequence. Anything that looks like a scheme
|
|
* prefix is left as-is, with any // treated as path separators.
|
|
*
|
|
* images/file%20name.jpg ->
|
|
*. Windows -> images\file%20name.jpg
|
|
*
|
|
* file://images/file.jpg ->
|
|
*. Windows -> file_\\images\file.jpg
|
|
*
|
|
* Any characters in the path that are invalid in the local file system
|
|
* naming rules are converted to "_", unless "_" is itself invalid, in
|
|
* which case they're converted to "X". One exception is that if '/' is a
|
|
* valid local filename character (rather than a path separator as it is on
|
|
* Unix and Windows), it can be used as the replacement for the character
|
|
* that os_cvt_dir_url uses as its replacement for '/', so that this
|
|
* substitution is reversible when a URL is generated and then read back in
|
|
* on this same platform.
|
|
*
|
|
* images/file:name.jpg ->
|
|
*. Windows -> images\file_name.jpg
|
|
*. Mac OS 9 -> :images:file_name.jpg
|
|
*. Unix -> images/file:name.jpg
|
|
*
|
|
* The path elements "." and ".." are specifically defined as having their
|
|
* Unix meanings: "." is an alias for the preceding path element, or the
|
|
* working directory if it's the first element, and ".." is an alias for
|
|
* the parent of the preceding element. When these appear as path
|
|
* elements, this routine translates them to the appropriate local
|
|
* conventions. "." may be translated simply by removing it from the path,
|
|
* since it reiterates the previous path element. ".." may be translated
|
|
* by removing the previous element - HOWEVER, if ".." appears as the first
|
|
* element, it has to be retained and translated to the equivalent local
|
|
* notation, since it will have to be applied later, when the result_buf
|
|
* path is actually used to open a file, at which point it will combined
|
|
* with the working directory or another base path.
|
|
*
|
|
*. /images/../file.jpg -> [Windows] file.jpg
|
|
*. ../images/file.jpg ->
|
|
*. Windows -> ..\images\file.jpg
|
|
*. Mac OS 9 -> ::images:file.jpg
|
|
*. VMS -> [-.images]file.jpg
|
|
*
|
|
* If the URL path is absolute (starts with a '/'), the routine inspects
|
|
* the path to see if it was created by the same OS, according to the local
|
|
* rules for converting absolute paths in os_cvt_dir_url() (see). If so,
|
|
* we reverse the encoding done there. If it doesn't appear that the name
|
|
* was created by the same operating system - that is, if reversing the
|
|
* encoding doesn't produce a valid local filename - then we create a local
|
|
* absolute path as follows. If the local system uses device/volume
|
|
* designators, we start with the current working device/volume or some
|
|
* other suitable default volume. We then add the first element of the
|
|
* path, if any, as the root directory name, applying the usual "_" or "X"
|
|
* substitution for any characters that aren't allowed in local names. The
|
|
* rest of the path is handled in the usual fashion.
|
|
*
|
|
*. /images/file.jpg ->
|
|
*. Windows -> \images\file.jpg
|
|
*. Unix -> /images/file.jpg
|
|
*
|
|
*. /c:/images/file.jpg ->
|
|
*. Windows -> c:\images\file.jpg
|
|
*. Unix -> /c:/images/file.jpg
|
|
*. VMS -> SYS$DISK:[c__.images]file.jpg
|
|
*
|
|
*. /Hard Disk:/images/file.jpg ->
|
|
*. Windows -> \Hard Disk_\images\file.jpg
|
|
*. Unix -> SYS$DISK:[Hard_Disk_.images]file.jpg
|
|
*
|
|
* Note how the device/volume prefix becomes the top-level directory when
|
|
* moving a path across machines. It's simply not possible to reconstruct
|
|
* the exact original path in such cases, since device/volume syntax rules
|
|
* have little in common across systems. But this seems like a good
|
|
* approximation in that (a) it produces a valid local path, and (b) it
|
|
* gives the user a reasonable basis for creating a set of folders to mimic
|
|
* the original source system, if they want to use that approach to port
|
|
* the data rather than just changing the paths internally in the source
|
|
* material.
|
|
*
|
|
* Character sets: use the same rules as for os_cvt_dir_url().
|
|
*/
|
|
void os_cvt_url_dir(char *result_buf, size_t result_buf_size,
|
|
const char *src_url);
|
|
|
|
|
|
} // End of namespace TADS
|
|
} // End of namespace Glk
|
|
|
|
#endif
|