1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
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
})
|