diff --git a/config.ld b/config.ld new file mode 100644 index 0000000..bbbb391 --- /dev/null +++ b/config.ld @@ -0,0 +1 @@ +readme = "README.md" diff --git a/osx.c b/osx.c index 25ff3c8..c3c24de 100644 --- a/osx.c +++ b/osx.c @@ -6,6 +6,15 @@ * Author: Lorenzo Cogotti */ +/** + * Operating System eXtension module. + * + * @license LGPL-3.0-or-later + * @copyright 2022, The DoubleFourteen Code Forge + * @author Lorenzo Cogotti + * @module osx + */ + #include "xconf.h" #include "osx.h" #include "osx_dir.h" @@ -264,19 +273,13 @@ static bool os_dir_filter(lua_State *L, const char *name) return result; } -static int os_dir_open(lua_State *L) +static int os_dir_close(lua_State *L) { - const char *path = luaL_checkstring(L, 1); + df_os_dir *dir = todir(L); - df_os_dir *dir = lua_newuserdata(L, sizeof(*dir)); - - dir->hn = os_opendir(path); - dir->closeflag = false; - if (!dir->hn) - return os_pushfail(L, path); - - luaL_setmetatable(L, DF_LUA_DIRHANDLE); - return 1; + os_closedir(dir->hn); + dir->closeflag = true; + return 0; } static int os_dir_tostring(lua_State *L) @@ -299,68 +302,6 @@ static int os_dir_tostring(lua_State *L) return 1; } -static int os_dir_close(lua_State *L) -{ - df_os_dir *dir = todir(L); - - os_closedir(dir->hn); - dir->closeflag = true; - return 0; -} - -static int os_dir_gc(lua_State *L) -{ - df_os_dir *dir = luaL_checkudata(L, 1, DF_LUA_DIRHANDLE); - - if (!dir->closeflag) - os_dir_close(L); - - return 0; -} - -static int os_dir_iter(lua_State *L); - -static int os_dir(lua_State *L) -{ - lua_pushcfunction(L, os_dir_open); - lua_pushvalue(L, 1); - lua_call(L, 1, 2); - if (lua_isnil(L, -2)) - lua_error(L); - - lua_pop(L, 1); - - lua_pushcclosure(L, os_dir_iter, 2); - return 1; -} - -static int os_dir_iter(lua_State *L) -{ - const char *path = lua_tostring(L, lua_upvalueindex(1)); - df_os_dir *dir = lua_touserdata(L, lua_upvalueindex(2)); - const char *filename; - - errno = 0; - -nextfilename: - filename = os_readdir(dir->hn); - if (!filename) { - // Error or end of directory - if (errno != 0) - luaL_error(L, lua_pushfstring(L, "%s: %s", path, strerror(errno))); - - return 0; - } - - // Skip dot and dotdot - if (strcmp(filename, ".") == 0 || strcmp(filename, "..") == 0) - goto nextfilename; - - // New directory entry - lua_pushstring(L, filename); - return 1; -} - static int os_dir_readdir(lua_State *L) { df_os_dir *dir = todir(L); @@ -417,6 +358,106 @@ static int os_dir_listfiles(lua_State *L) return os_dir_dolistfiles(L, false, NULL); } +static int os_dir_gc(lua_State *L) +{ + df_os_dir *dir = luaL_checkudata(L, 1, DF_LUA_DIRHANDLE); + + if (!dir->closeflag) + os_dir_close(L); + + return 0; +} + +/** + * Open a directory. + * + * @string path path to the directory to be opened + * @treturn[1] Dir handle to the newly opened directory + * @error[2] error message + * @treturn[2] int system error code + * @function opendir + */ +static int os_dir_open(lua_State *L) +{ + const char *path = luaL_checkstring(L, 1); + + df_os_dir *dir = lua_newuserdata(L, sizeof(*dir)); + + dir->hn = os_opendir(path); + dir->closeflag = false; + if (!dir->hn) + return os_pushfail(L, path); + + luaL_setmetatable(L, DF_LUA_DIRHANDLE); + return 1; +} + +static int os_dir_iter(lua_State *L); + +/** + * Iterate directory. + * + * Enumerates directory contents one filename at a time, '.' and '..' are + * filtered out. Function is intended to control a for loop. + * + * @string path path to directory to be traversed + * @treturn string next file in directory + * @raise error if the directory could not be opened, or an error occurs during its traversal. + * @function dir + */ +static int os_dir(lua_State *L) +{ + lua_pushcfunction(L, os_dir_open); + lua_pushvalue(L, 1); + lua_call(L, 1, 2); + if (lua_isnil(L, -2)) + lua_error(L); + + lua_pop(L, 1); + + lua_pushcclosure(L, os_dir_iter, 2); + return 1; +} + +static int os_dir_iter(lua_State *L) +{ + const char *path = lua_tostring(L, lua_upvalueindex(1)); + df_os_dir *dir = lua_touserdata(L, lua_upvalueindex(2)); + const char *filename; + + errno = 0; + +nextfilename: + filename = os_readdir(dir->hn); + if (!filename) { + // Error or end of directory + if (errno != 0) + luaL_error(L, lua_pushfstring(L, "%s: %s", path, strerror(errno))); + + return 0; + } + + // Skip dot and dotdot + if (strcmp(filename, ".") == 0 || strcmp(filename, "..") == 0) + goto nextfilename; + + // New directory entry + lua_pushstring(L, filename); + return 1; +} + +/** + * List directory contents. + * + * @string path path to directory to be listed + * @tparam[opt] function filter a function taking a filename string and returning + * a boolean to accept/reject each entry + * @treturn[1] {string,...} array of filenames within the directory, + * excluding '.' and '..' + * @error[2] error message + * @treturn[2] int system error code + * @function listfiles + */ static int os_listfiles(lua_State *L) { const char *path = luaL_checkstring(L, 1); @@ -435,6 +476,26 @@ static int os_listfiles(lua_State *L) return os_dir_dolistfiles(L, true, path); } +/** + * File info returned on success by @{stat}. + * + * @string type file type, either of 'regular', 'directory', or 'other', for + * regular files, directories and any other file respectively + * @int mtime last modified time in seconds since Unix Epoch, only available for + * 'regular' and 'directory' + * @int size file size in bytes, only available for 'regular' files + * @table FileStat + */ + +/** + * Get file info. + * + * @tparam ?string|File f path or opened file handle + * @treturn[1] FileStat on success + * @error[2] error message + * @treturn[2] int system error code + * @function stat + */ static int os_stat(lua_State *L) { const char *path = NULL; @@ -471,6 +532,16 @@ static int os_stat(lua_State *L) return 1; } +/** + * Create a new directory. + * + * @string path path to the directory to be created + * @treturn[1] bool true on success and a new directory is created at path + * @treturn[2] bool false on failure (note: directory may already exist) + * @treturn[2] string error message + * @treturn[2] int system error code + * @function mkdir + */ static int os_mkdir(lua_State *L) { const char *path = luaL_checkstring(L, 1); @@ -479,6 +550,14 @@ static int os_mkdir(lua_State *L) return os_result(L, ec, path); } +/** + * Get process current working directory. + * + * @treturn[1] string path to current working directory on success + * @error[2] error message + * @treturn[2] int system error code + * @function getcwd + */ static int os_getcwd(lua_State *L) { #if LUA_VERSION_NUM >= 502 @@ -542,6 +621,16 @@ fail: #endif } +/** + * Change process current working directory. + * + * @string path path to the new working directory + * @treturn[1] boolean true on success + * @treturn[2] boolean false on failure + * @treturn[2] string error message + * @treturn[2] int system error code + * @function chdir + */ static int os_chdir(lua_State *L) { const char *path = luaL_checkstring(L, 1); @@ -550,6 +639,15 @@ static int os_chdir(lua_State *L) return os_result(L, ec, path); } +/** + * Get the active drive letter. + * + * @treturn[1] string the current drive letter on systems supporting multiple drives + * (e.g. Windows), an empty string otherwise + * @error[2] error message + * @treturn[2] int system error code + * @function getdrive + */ static int os_getdrive(lua_State *L) { errno = 0; @@ -569,6 +667,19 @@ static int os_getdrive(lua_State *L) return 1; } +/** + * Change active drive. + * + * May only succeed on systems with multiple drives (e.g. Windows). + * On any other system this function always fail with an appropriate error. + * + * @string drive new drive letter (e.g. 'A', 'C', 'D') + * @treturn[1] bool true on success + * @treturn[2] bool false on failure + * @treturn[2] string error message + * @treturn[2] int system error code + * @function chdrive + */ static int os_chdrive(lua_State *L) { size_t len; @@ -584,6 +695,22 @@ static int os_chdrive(lua_State *L) return os_result(L, ec, NULL); } +/** + * Set file mode. + * + * May only succeed on systems where multiple file mode exist (e.g. Windows), + * will always fail anywhere else (e.g. Unix). + * Typically such error can be ignored. + * + * @tparam File f a file opened for read + * @string mode new mode for file, either of 'binary', 'text', 'u8text', + * 'u16text' or 'wtext' + * @treturn[1] bool true on success + * @treturn[2] bool false on failure + * @treturn[2] string error message + * @treturn[2] int system error code + * @function setmode + */ static int os_setmode(lua_State *L) { static const char *modes[] = { @@ -609,6 +736,17 @@ static int os_setmode(lua_State *L) return os_result(L, ec, NULL); } +/** + * Change file size. + * + * @tparam ?string|File f path or file opened for write + * @int size new file size in bytes + * @treturn[1] bool true on success + * @treturn[2] bool false on failure + * @treturn[2] string error string + * @treturn[2] int system error code + * @function chsize + */ static int os_chsize(lua_State *L) { lua_Integer size = luaL_checkinteger(L, 2); @@ -629,6 +767,17 @@ static int os_chsize(lua_State *L) return os_result(L, ec, path); } +/** + * Advise kernel on file access pattern. + * + * @tparam File f a file opened for read + * @string advise read access pattern (either of: 'normal', 'sequential', 'random', 'noreuse') + * @treturn[1] boolean true on success + * @treturn[2] boolean false on failure + * @treturn[2] string error message + * @treturn[2] int system error code + * @function fadvise + */ static int os_fadvise(lua_State *L) { static const char *hints[] = { @@ -652,6 +801,16 @@ static int os_fadvise(lua_State *L) return os_result(L, ec, NULL); } +/** + * Commit any pending write to disk. + * + * @tparam File f a file opened for write + * @treturn[1] boolean true on success + * @treturn[2] boolean false on failure + * @treturn[2] string error message + * @treturn[2] int system error code + * @function commit + */ static int os_commit(lua_State *L) { luaL_Stream *stream = luaL_checkudata(L, 1, LUA_FILEHANDLE); @@ -660,6 +819,20 @@ static int os_commit(lua_State *L) return os_result(L, ec, NULL); } +/** + * Place or remove a read (shared) or write (exclusive) lock to a file. + * + * @tparam File f a file opened with appropriate mode for the requested lock + * @string mode lock mode, any of 'r' (read), 'w' (write), 'u' (release lock) + * @int[opt=0] start initial offset for the requested section + * @int[optchain=0] len length of the requested section, 0 has a special meaning, + * indicating the whole file starting from the initial offset + * @treturn[1] boolean true on success + * @treturn[2] boolean false on failure + * @treturn[2] string error message + * @treturn[2] int system error code + * @function locking + */ static int os_locking(lua_State *L) { static const char *modes[] = { "r", "w", "u", NULL }; @@ -674,6 +847,53 @@ static int os_locking(lua_State *L) return os_result(L, ec, NULL); } +/** + * preferred path separator char: '\\' on Windows, '/' anywhere else + * + * @string sep + */ + +/** + * Directory handle. + * + * Allows convenient file traversal on directories. + * Entries traversal order is system-specific, and may not be ordered at all. + * Provides metamethods: __gc to close directories on garbage collect, + * __close (on Lua 5.4) to close directories automatically, and __tostring for + * adequate formatting. + * + * @type Dir + */ + +/** + * Read next directory entry. + * + * @treturn[1] string next filename in directory, '.' and '..' are also returned + * @error[2] error message on failure + * @treturn[2] int system error code + * @raise error message when attempting to read from a closed directory + * @function readdir + */ + +/** + * Read and filter subsequent directory entries. + * + * @tparam[opt] function filter a function taking a filename string and returning + * a boolean to accept/reject each entry + * @treturn[1] {string,...} subsequent filtered contents, '.' and '..' are always filtered out. + * @error[2] error message on failure + * @treturn[2] int system error code + * @raise error message when attempting to read from a closed directory + * @function listfiles + */ + +/** + * Close opened directory. + * + * @raise error message when closing a directory twice + * @function close + */ + static void createmeta(lua_State *L) { static const luaL_Reg metameth[] = { @@ -703,9 +923,9 @@ static void createmeta(lua_State *L) DF_OSXMOD_API int luaopen_osx(lua_State *L) { static const luaL_Reg funcs[] = { - { "listfiles", os_listfiles }, { "opendir", os_dir_open }, { "dir", os_dir }, + { "listfiles", os_listfiles }, { "stat", os_stat }, { "mkdir", os_mkdir }, { "getcwd", os_getcwd },