cont-run.lua /size: 8165 b    last modification: 2021-10-28 13:50
1if not modules then modules = { } end modules ['cont-run'] = {
2    version   = 1.001,
3    comment   = "companion to cont-yes.mkiv",
4    author    = "Hans Hagen, PRAGMA-ADE, Hasselt NL",
5    copyright = "PRAGMA ADE / ConTeXt Development Team",
6    license   = "see context related readme files"
7}
8
9-- When a style is loaded there is a good change that we never enter
10-- this code.
11
12
13local type, tostring = type, tostring
14
15local report_sandbox = logs.reporter("sandbox","call")
16local report_system  = logs.reporter("system")
17local fastserialize  = table.fastserialize
18local quoted         = string.quoted
19local possiblepath   = sandbox.possiblepath
20
21local context        = context
22local implement      = interfaces.implement
23
24local qualified      = { }
25local writeable      = { }
26local readable       = { }
27local blocked        = { }
28local trace_files    = false
29local trace_calls    = false
30local nofcalls       = 0
31local nofrejected    = 0
32local logfilename    = "sandbox.log"
33
34local function registerstats()
35    statistics.register("sandboxing", function()
36        if trace_files then
37            return string.format("%i calls, %i rejected, logdata in '%s'",nofcalls,nofrejected,logfilename)
38        else
39            return string.format("%i calls, %i rejected",nofcalls,nofrejected)
40        end
41    end)
42    registerstats = false
43end
44
45local function logsandbox(details)
46    local comment   = details.comment
47    local result    = details.result
48    local arguments = details.arguments
49    for i=1,#arguments do
50        local argument = arguments[i]
51        local t = type(argument)
52        if t == "string" then
53            arguments[i] = quoted(argument)
54            if trace_files and possiblepath(argument) then
55                local q = qualified[argument]
56                if q then
57                    local c = q[comment]
58                    if c then
59                        local r = c[result]
60                        if r then
61                            c[result] = r + 1
62                        else
63                            c[result] = r
64                        end
65                    else
66                        q[comment] = {
67                            [result] = 1
68                        }
69                    end
70                else
71                    qualified[argument] = {
72                        [comment] = {
73                            [result] = 1
74                        }
75                    }
76                end
77            end
78        elseif t == "table" then
79            arguments[i] = fastserialize(argument)
80        else
81            arguments[i] = tostring(argument)
82        end
83    end
84    if trace_calls then
85        report_sandbox("%s(%,t) => %l",details.comment,arguments,result)
86    end
87    nofcalls = nofcalls + 1
88    if not result then
89        nofrejected = nofrejected + 1
90    end
91end
92
93local ioopen = sandbox.original(io.open) -- dummy call
94
95local function logsandboxfiles(name,what,asked,okay)
96    -- we're only interested in permitted access
97    if not okay then
98        blocked  [asked] = blocked  [asked] or 0 + 1
99    elseif what == "*" or what == "w" then
100        writeable[asked] = writeable[asked] or 0 + 1
101    else
102        readable [asked] = readable [asked] or 0 + 1
103    end
104end
105
106function sandbox.logcalls()
107    if not trace_calls then
108        trace_calls = true
109        sandbox.setlogger(logsandbox)
110        if registerstats then
111            registerstats()
112        end
113    end
114end
115
116function sandbox.logfiles()
117    if not trace_files then
118        trace_files = true
119        sandbox.setlogger(logsandbox)
120        sandbox.setfilenamelogger(logsandboxfiles)
121        luatex.registerstopactions(function()
122            table.save(logfilename,{
123                calls = {
124                    nofcalls    = nofcalls,
125                    nofrejected = nofrejected,
126                    filenames   = qualified,
127                },
128                checkednames = {
129                    readable  = readable,
130                    writeable = writeable,
131                    blocked   = blocked,
132                },
133            })
134        end)
135        if registerstats then
136            registerstats()
137        end
138    end
139end
140
141trackers.register("sandbox.tracecalls",sandbox.logcalls)
142trackers.register("sandbox.tracefiles",sandbox.logfiles)
143
144local sandboxing = environment.arguments.sandbox
145local debugging  = environment.arguments.debug
146
147if sandboxing then
148
149    report_system("enabling sandbox")
150
151    sandbox.enable()
152
153    if type(sandboxing) == "string" then
154        sandboxing = utilities.parsers.settings_to_hash(sandboxing)
155        if sandboxing.calls then
156            sandbox.logcalls()
157        end
158        if sandboxing.files then
159            sandbox.logfiles()
160        end
161    end
162
163    -- Nicer would be if we could just disable write 18 and keep os.execute
164    -- which in fact we can do by defining write18 as macro instead of
165    -- primitive ... todo ... well, it has been done now.
166
167    -- We block some potential escapes from protection.
168
169    context [[\let\primitive\relax\let\normalprimitive\relax]]
170
171    debug = {
172        traceback = debug.traceback,
173    }
174
175    package.loaded.debug = debug
176
177elseif debugging then
178
179    -- we keep debug
180
181else
182
183    debug = {
184        traceback = debug.traceback,
185        getinfo   = debug.getinfo,
186        sethook   = debug.sethook,
187    }
188
189    package.loaded.debug = debug
190
191end
192
193local function processjob()
194
195    tokens.setters.macro("processjob","") -- make a
196
197    environment.initializefilenames() -- todo: check if we really need to pre-prep the filename
198
199    local arguments = environment.arguments
200    local suffix    = environment.suffix
201    local filename  = environment.filename -- hm, not inputfilename !
202
203    environment.lmtxmode = false
204
205    if arguments.nosynctex then
206        luatex.synctex.setup {
207            state  = interfaces.variables.never,
208        }
209    elseif arguments.synctex then
210        luatex.synctex.setup {
211            state  = interfaces.variables.start,
212            method = interfaces.variables.max,
213        }
214    end
215
216    if not filename or filename == "" then
217        -- skip
218    elseif suffix == "xml" or arguments.forcexml then
219
220        -- Maybe we should move the preamble parsing here as it
221        -- can be part of (any) loaded (sub) file. The \starttext
222        -- wrapping might go away.
223
224        report_system("processing as xml: %s",filename)
225
226        context.starttext()
227            context.xmlprocess("main",filename,"")
228        context.stoptext()
229
230    elseif suffix == "cld" or arguments.forcecld then
231
232        report_system("processing as cld: %s",filename)
233
234        context.runfile(filename)
235
236    elseif suffix == "lua" or arguments.forcelua then
237
238        -- The wrapping might go away. Why is is it there in the
239        -- first place.
240
241        report_system("processing as lua: %s",filename)
242
243        context.starttext()
244            context.ctxlua(string.format('dofile("%s")',filename))
245        context.stoptext()
246
247    elseif suffix == "mp" or arguments.forcemp then
248
249        report_system("processing as metapost: %s",filename)
250
251        context.starttext()
252            context.processMPfigurefile(filename)
253        context.stoptext()
254
255    -- elseif suffix == "prep" then
256    --
257    --     -- Why do we wrap here. Because it can be xml? Let's get rid
258    --     -- of prepping in general.
259    --
260    --     context.starttext()
261    --     context.input(filename)
262    --     context.stoptext()
263
264    elseif suffix == "mps" or arguments.forcemps then
265
266        report_system("processing metapost output: %s",filename)
267
268        context.starttext()
269            context.startTEXpage()
270                context.externalfigure { filename }
271            context.stopTEXpage()
272        context.stoptext()
273
274    else
275
276     -- \writestatus{system}{processing as tex}
277        -- We have a regular tex file so no \starttext yet as we can
278        -- load fonts.
279     -- context.enabletrackers { "resolvers.*" }
280        context.input(filename)
281     -- context.disabletrackers { "resolvers.*" }
282
283    end
284
285    context.finishjob()
286
287end
288
289implement {
290    name     = "processjob",
291    public   = true,
292    onlyonce = true,
293    actions  = processjob,
294}
295