aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--manifest.json8
-rw-r--r--plugins/su_save.lua108
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
+})