diff --git a/data/core/doc/init.lua b/data/core/doc/init.lua index 73342ca1..ec9d46bc 100644 --- a/data/core/doc/init.lua +++ b/data/core/doc/init.lua @@ -103,7 +103,23 @@ function Doc:save(filename, abs_filename) else assert(self.filename or abs_filename, "calling save on unnamed doc without absolute path") end - local fp = assert(io.open(filename, "wb")) + + local fp + if PLATFORM == "Windows" then + -- On Windows, opening a hidden file with wb fails with a permission error. + -- To get around this, we must open the file as r+b and truncate. + -- Since r+b fails if file doesn't exist, fall back to wb. + fp = io.open(filename, "r+b") + if fp then + system.ftruncate(fp) + else + -- file probably doesn't exist, create one + fp = assert ( io.open(filename, "wb") ) + end + else + fp = assert ( io.open(filename, "wb") ) + end + for _, line in ipairs(self.lines) do if self.crlf then line = line:gsub("\n", "\r\n") end fp:write(line) diff --git a/docs/api/system.lua b/docs/api/system.lua index 55f79163..66af600f 100644 --- a/docs/api/system.lua +++ b/docs/api/system.lua @@ -190,6 +190,15 @@ function system.rmdir(path) end ---@param path string function system.chdir(path) end +--- +---Truncates a file to a set length. +--- +---@param file file* A file handle returned by io.open(). +---@param length integer? Number of bytes to truncate to. Defaults to 0. +---@return boolean success True if the operation suceeded, false otherwise +---@return string? message An error message if the operation failed. +function system.ftruncate(file, length) end + --- ---Create a new directory, note that this function doesn't recursively ---creates the directories on the given path. diff --git a/src/api/system.c b/src/api/system.c index 7baf3eab..ace17337 100644 --- a/src/api/system.c +++ b/src/api/system.c @@ -27,6 +27,9 @@ #if !defined(S_ISDIR) && defined(S_IFMT) && defined(S_IFDIR) #define S_ISDIR(m) (((m) & S_IFMT) == S_IFDIR) #endif + + #define fileno _fileno + #define ftruncate _chsize #else #include @@ -847,6 +850,26 @@ static int f_get_fs_type(lua_State *L) { } +static int f_ftruncate(lua_State *L) { +#if LUA_VERSION_NUM < 503 + // note: it is possible to support pre 5.3 and JIT + // since file handles are just FILE* wrapped in a userdata; + // but it is not standardized. YMMV. + #error luaL_Stream is not supported in this version of Lua. +#endif + luaL_Stream *stream = luaL_checkudata(L, 1, LUA_FILEHANDLE); + lua_Integer len = luaL_optinteger(L, 2, 0); + if (ftruncate(fileno(stream->f), len) != 0) { + lua_pushboolean(L, 0); + lua_pushfstring(L, "ftruncate(): %s", strerror(errno)); + return 2; + } + + lua_pushboolean(L, 1); + return 1; +} + + static int f_mkdir(lua_State *L) { const char *path = luaL_checkstring(L, 1); @@ -1285,6 +1308,7 @@ static const luaL_Reg lib[] = { { "get_fs_type", f_get_fs_type }, { "text_input", f_text_input }, { "setenv", f_setenv }, + { "ftruncate", f_ftruncate }, { NULL, NULL } };