diff options
-rw-r--r-- | manifest.json | 8 | ||||
-rw-r--r-- | plugins/su_save.lua | 108 |
2 files changed, 116 insertions, 0 deletions
diff --git a/manifest.json b/manifest.json index 81e06c9..4397258 100644 --- a/manifest.json +++ b/manifest.json @@ -1277,6 +1277,14 @@ "mod_version": "3" }, { + "description": "Save files that require root permissions. Needs `pkexec`.", + "version": "1.0", + "path": "plugins/su_save.lua", + "id": "su_save", + "name": "Save as Super User", + "mod_version": "3" + }, + { "description": "Takes an SVG screenshot. Only browsers seem to support the generated SVG properly.", "version": "0.1", "path": "plugins/svg_screenshot.lua", diff --git a/plugins/su_save.lua b/plugins/su_save.lua new file mode 100644 index 0000000..4091f4a --- /dev/null +++ b/plugins/su_save.lua @@ -0,0 +1,108 @@ +-- mod-version:3 priority:99 +local core = require "core" +local common = require "core.common" +local command = require "core.command" +local config = require "core.config" +local Doc = require "core.doc" + + +-- When pkexec version >= 121 is more widespread, +-- change to "pkexec --keep-cwd cp '%s' '%s'" +local default_command = "pkexec sh -c \"cd $PWD; cp '%s' '%s'\"" +config.plugins.su_save = common.merge({ + enabled = true, + save_command = default_command, + config_spec = { + name = "Super User Save", + { + label = "Enabled", + description = "Disable or enable the automatic save as super user.", + path = "enabled", + type = "toggle", + default = true + }, + { + label = "Save command", + description = "Command used to save the temporary file (first '%s') over the original file (second '%s').", + path = "save_command", + type = "string", + default = default_command + }, + } +}, config.plugins.su_save) + + +local doc_save = Doc.save +local function su_save(doc, filename, abs_filename, ...) + if not config.plugins.su_save then + return error("Bad su_save plugin configuration") + end + + local old_clean_change_id = doc.clean_change_id + + -- Override io.open to check for permission errors + local io_open = io.open + local temp_filename, save_location + local io_open_valid = true + io.open = function(...) + -- Only override the first io.open call. Hopefully this works well enough. + io.open = io_open + + -- In case Doc.save crashes before even getting to the first io.open, + -- we need to use the original one. + if not io_open_valid then return io_open(...) end + + local fp, error_msg, error_code = io_open(...) + -- If there was an access issue with open, save to a temporary file + if error_code == 13 then -- 13 seems to be EACCES, to verify use `errno -l` + save_location = select(1, ...) + temp_filename = core.temp_filename() + core.log_quiet('Trying to save "%s" as super user using "%s" as temporary file', save_location, temp_filename) + return io_open(temp_filename, select(2, ...)) + end + + return fp, error_msg, error_code + end + + -- Call original Doc:save, now with custom io.open + local ok, result = pcall(doc_save, doc, filename, abs_filename, ...) + io_open_valid = false + + if temp_filename then + if ok then + -- This is using the blocking os.execute to simplify error management + local success, exit_type, exit_code = os.execute(string.format(config.plugins.su_save.save_command, temp_filename, save_location)) + if not success then + -- Restore change_id because save failed + doc.clean_change_id = old_clean_change_id + -- 126 means "dismissed" for pkexec. TODO: Should this be configurable? + if exit_type == "exit" and exit_code == 126 then + return error(string.format('Unable to save "%s" with super user permissions (dismissed)', save_location)) + end + return error(string.format('Unable to save "%s" with super user permissions (%s code %d)', save_location, exit_type, exit_code)) + end + end + os.remove(temp_filename) + end + + if not ok then + -- Propagate error + return error(result) + end + + return result +end + +function Doc:save(...) + if not (config.plugins.su_save and config.plugins.su_save.enabled) then + return doc_save(self, ...) + end + return su_save(self, ...) +end + +command.add("core.docview!", { + ["su-save:save-as-super-user"] = function(dv) + su_save(dv.doc) + core.log('Saved "%s"', dv.doc.filename) + end +}) |