Allow writing to hidden files on Windows (#1653)

* feat(system): add system.ftruncate

* fix: EPERM writing to hidden files on Windows

* chore(doc): fix capitalization

* refactor(system): make ftruncate length optional

* refactor(doc): don't specify length
This commit is contained in:
Takase 2024-10-23 00:14:30 +08:00 committed by GitHub
parent 08fd994423
commit d925da47fa
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 50 additions and 1 deletions

View File

@ -103,7 +103,23 @@ function Doc:save(filename, abs_filename)
else else
assert(self.filename or abs_filename, "calling save on unnamed doc without absolute path") assert(self.filename or abs_filename, "calling save on unnamed doc without absolute path")
end 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 for _, line in ipairs(self.lines) do
if self.crlf then line = line:gsub("\n", "\r\n") end if self.crlf then line = line:gsub("\n", "\r\n") end
fp:write(line) fp:write(line)

View File

@ -190,6 +190,15 @@ function system.rmdir(path) end
---@param path string ---@param path string
function system.chdir(path) end 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 ---Create a new directory, note that this function doesn't recursively
---creates the directories on the given path. ---creates the directories on the given path.

View File

@ -27,6 +27,9 @@
#if !defined(S_ISDIR) && defined(S_IFMT) && defined(S_IFDIR) #if !defined(S_ISDIR) && defined(S_IFMT) && defined(S_IFDIR)
#define S_ISDIR(m) (((m) & S_IFMT) == S_IFDIR) #define S_ISDIR(m) (((m) & S_IFMT) == S_IFDIR)
#endif #endif
#define fileno _fileno
#define ftruncate _chsize
#else #else
#include <dirent.h> #include <dirent.h>
@ -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) { static int f_mkdir(lua_State *L) {
const char *path = luaL_checkstring(L, 1); const char *path = luaL_checkstring(L, 1);
@ -1285,6 +1308,7 @@ static const luaL_Reg lib[] = {
{ "get_fs_type", f_get_fs_type }, { "get_fs_type", f_get_fs_type },
{ "text_input", f_text_input }, { "text_input", f_text_input },
{ "setenv", f_setenv }, { "setenv", f_setenv },
{ "ftruncate", f_ftruncate },
{ NULL, NULL } { NULL, NULL }
}; };