/* 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 2 * 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, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * */ /* 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 extention 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