1 //******************************************************************************
2 ///
3 /// @file frontend/shelloutprocessing.h
4 ///
5 /// @todo   What's in here?
6 ///
7 /// @copyright
8 /// @parblock
9 ///
10 /// Persistence of Vision Ray Tracer ('POV-Ray') version 3.8.
11 /// Copyright 1991-2017 Persistence of Vision Raytracer Pty. Ltd.
12 ///
13 /// POV-Ray is free software: you can redistribute it and/or modify
14 /// it under the terms of the GNU Affero General Public License as
15 /// published by the Free Software Foundation, either version 3 of the
16 /// License, or (at your option) any later version.
17 ///
18 /// POV-Ray is distributed in the hope that it will be useful,
19 /// but WITHOUT ANY WARRANTY; without even the implied warranty of
20 /// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
21 /// GNU Affero General Public License for more details.
22 ///
23 /// You should have received a copy of the GNU Affero General Public License
24 /// along with this program.  If not, see <http://www.gnu.org/licenses/>.
25 ///
26 /// ----------------------------------------------------------------------------
27 ///
28 /// POV-Ray is based on the popular DKB raytracer version 2.12.
29 /// DKBTrace was originally written by David K. Buck.
30 /// DKBTrace Ver 2.0-2.12 were written by David K. Buck & Aaron A. Collins.
31 ///
32 /// @endparblock
33 ///
34 //******************************************************************************
35 
36 #ifndef POVRAY_FRONTEND_SHELLOUTPROCESSING_H
37 #define POVRAY_FRONTEND_SHELLOUTPROCESSING_H
38 
39 // Module config header file must be the first file included within POV-Ray unit header files
40 #include "frontend/configfrontend.h"
41 
42 #include <boost/format.hpp>
43 
44 #include "povms/povmscpp.h"
45 
46 namespace pov_frontend
47 {
48 
49 class ShelloutProcessing;
50 
51 class ShelloutAction
52 {
53 public:
54     typedef enum
55     {
56         ignore = 'i',
57         skipOne = 's',
58         skipAll = 'a',
59         quit = 'q',
60         abort = 'u',
61         fatal ='f'
62     } Action;
63 
64     ShelloutAction(const ShelloutProcessing *sp, unsigned int attribID, POVMS_Object& opts);
~ShelloutAction()65     ~ShelloutAction() {}
66 
ReturnAction(void)67     Action ReturnAction(void) const { return returnAction; }
IsSet(void)68     bool IsSet(void) const { return isSet; }
RawCommand(void)69     const string& RawCommand(void) const { return rawCommand; }
Command(void)70     const string& Command(void) const { return command; }
RawParameters(void)71     const string& RawParameters(void) const { return rawParameters; }
Parameters(void)72     const string& Parameters(void) const { return parameters; }
ReturnNegate(void)73     bool ReturnNegate(void) const { return returnNegate; }
74     void ExpandParameters(const string& scene, const string& ofn, unsigned int w, unsigned int h, float clock, unsigned int frame);
75 
76 private:
77     bool          isSet;
78     bool          returnNegate;
79     string        command;
80     string        rawCommand;
81     string        parameters;
82     string        rawParameters;
83     Action        returnAction;
84 
85     ShelloutAction();
86 };
87 
88 class ShelloutProcessing
89 {
90     friend class ShelloutAction;
91 
92 public:
93     typedef shared_ptr<ShelloutAction> ShelloutPtr;
94 
95     typedef enum
96     {
97         preScene,
98         postScene,
99         preFrame,
100         postFrame,
101         userAbort,
102         fatalError,
103         lastShelloutEvent
104     } shelloutEvent;
105 
106     // we use strings rather than UCS2Strings for the scene name and parameters since the passed
107     // parameters (via POVMS) are also strings.
108     ShelloutProcessing(POVMS_Object& opts, const string& scene, unsigned int width, unsigned int height);
109 
110     // you should reap any processes here as needed, and forcefully terminate ones still running.
111     virtual ~ShelloutProcessing();
112 
113     // true if a shellout command was specified for the given phase
IsSet(shelloutEvent which)114     bool IsSet(shelloutEvent which) const { return shellouts[which]->IsSet(); }
115 
116     // retrieve details for each type of shellout.
ReturnAction(shelloutEvent which)117     ShelloutAction::Action ReturnAction(shelloutEvent which) const { return shellouts[which]->ReturnAction(); }
118 
119     // return the command string as passed from the option parser; i.e. complete with parameters
RawCommand(shelloutEvent which)120     string RawCommand(shelloutEvent which) const { return shellouts[which]->RawCommand(); }
121 
122     // the command itself, separated from its parameters. quotes around the command will have been removed.
Command(shelloutEvent which)123     string Command(shelloutEvent which) const { return shellouts[which]->Command(); }
124 
125     // the raw parameters after separation from the command. any quotes will remain in place.
RawParameters(shelloutEvent which)126     string RawParameters(shelloutEvent which) const { return shellouts[which]->RawParameters(); }
127 
128     // the parameters after expansion of terms; e.g. %s to scene name. SetOutputFile() and
129     // SetFrameClock() (if relevant) must be called prior to calling this method.
Parameters(shelloutEvent which)130     string Parameters(shelloutEvent which) const { return shellouts[which]->Parameters(); }
131 
132     // returns true if all frames should be skipped. if so, any subsequent calls for
133     // pre-frame and post-frame actions will be ignored (and preferebly should not be
134     // made). the post-scene action should always be called; internal logic will determine
135     // if it will do anything.
SkipAllFrames(void)136     bool SkipAllFrames(void) const { return skipAllFrames; }
137 
138     // returns true if next frame should be skipped. if so, pre-frame and post-frame actions
139     // will be ignored. the internal skip frame flag is only reset when the frame number is
140     // updated. NB skip all frames does not imply skip next frame since they are handled differently.
SkipNextFrame(void)141     bool SkipNextFrame(void) const { return skipNextFrame; }
142 
143     // returns the exit code that POV-Ray should return at the end of the render.
144     // 0 means normal exit, 1 means fatal error, and 2 means user abort.
ExitCode(void)145     int ExitCode(void) const { return exitCode; }
146 
147     // returns a string representation of the exit code; e.g. 'user abort'
ExitDesc(void)148     string ExitDesc(void) const { return exitCode == 0 ? "SUCCESS" : exitCode == 2 ? "USER ABORT" : "FATAL ERROR"; }
149 
150     // returns true if the render should be halted.
RenderCancelled(void)151     bool RenderCancelled(void) const { return cancelRender; }
152 
153     // return true if the pre-scene event has been seen
HadPreScene(void)154     bool HadPreScene(void) const { return hadPreScene; }
155 
156     // return true if the post-scene event has been seen
HadPostScene(void)157     bool HadPostScene(void) const { return hadPostScene; }
158 
159     // if there is no output file, it is not required to call SetOutputFile().
160     // if there is an output file, this method must be called prior to the pre-scene
161     // action with the value of the first output file, and prior to pre-frame for each
162     // subsequent output file if the render is an animation.
SetOutputFile(const string & filename)163     void SetOutputFile(const string& filename) { outputFile = filename; }
164 
165     // if the render is not an animation, there is no need to call SetFrameClock().
166     // if it is an animation, it must be called prior to the pre-scene action, and
167     // then prior to pre-frame for each frame of the animation. note that if an action
168     // returns the 'skip next frame' option, the SkipNextFrame() method will continue
169     // to return true until the frame number supplied via this method has changed.
SetFrameClock(unsigned int frame,float clock)170     void SetFrameClock(unsigned int frame, float clock) { if (frameNo != frame) skipNextFrame = false; frameNo = frame; clockVal = clock; }
171 
172     // shutdown any currently-running shellouts. if force is true, force them to exit.
173     // in either case, don't wait more than timeout seconds. return true if there are
174     // no more processes running afterwards.
175     bool KillShellouts(int timeout, bool force = false);
176 
177     // the message is constructed as per the documentation for the boost::format class.
178     // the positional parameters are as follows:
179     //   1: the event causing the cancel (as a string), e.g. "pre-scene"
180     //   2: the POV-Ray return code (as an integer)
181     //   3: the return code (as an upper-case string), e.g. "USER ABORT"
182     //   4: the return code (as a lower-case string).
183     //   5: the reason for the cancel (as a string), e.g. "generate a user abort"
184     //   6: the command name that generated the cancel
185     //   7: the command parameters (CAUTION: may contain escape codes)
186     //   8: the command return code (as an integer)
187     //   9: output text from the command, as returned by LastShelloutResult()
188     virtual string GetCancelMessage(void);
SetCancelMessage(const string & format)189     virtual void SetCancelMessage(const string& format) { cancelFormat.parse(format); }
190 
191     // the positional parameters are as follows:
192     //   1: the event causing the skip (as a string), e.g. "pre-scene"
193     //   2: the type of the skip (as a string); e.g. "skip frame 11" or "skip all remaining frames"
194     //   3: the command name that generated the skip
195     //   4: the command parameters (CAUTION: may contain escape codes)
196     //   5: the command return code (as an integer)
197     //   6: output text from the command, as returned by LastShelloutResult()
198     virtual string GetSkipMessage(void);
SetSkipMessage(const string & format)199     virtual void SetSkipMessage(const string& format) { skipFormat.parse(format); }
200 
201     // advise the code that a particular event should be handled now; e.g. pre-scene, post-scene
202     // and so forth. this method should be called even if the platform indicates it does not
203     // support shellouts; if a render defines one, the code will thrown a kCannotOpenFileErr
204     // exception at the time (which should be handled by the caller). this method does not block
205     // the caller during processing of shellouts as they are run in the background. the return
206     // value indicates whether or not rendering should be cancelled as a result of a shellout:
207     // generally this won't be known at the time of return, however it could be set early if
208     // a shellout could not be started and the INI file indicated that render should be halted
209     // on failure.
ProcessEvent(shelloutEvent event)210     virtual bool ProcessEvent(shelloutEvent event) { return HandleProcessEvent(event, false); }
211 
212     // returns true if a shellout is currently running. if this method is being called in an
213     // event loop to wait until a shellout has completed, it is the responsibility of the event
214     // loop to perform appropriate sleeps to avoid wasting CPU time. platforms that implement
215     // shellout support MUST override this method to return an appropriate value by actually
216     // checking the process each time it's called.
217     virtual bool ShelloutRunning(void);
218 
219     // return the name of the currently running shellout (without parameters)
220     // if no shellout is running, an empty string should be returned.
ProcessName(void)221     virtual string ProcessName(void) { return ShelloutRunning() ? runningProcessName : string(); }
222 
223     // return the PID of the currently running shellout (or equivalent thereof).
224     // returns 0 if no process is running, and -1 of the platform has no PID equivalent
225     // or this method is not implemented.
ProcessID(void)226     virtual int ProcessID(void) { return -1; }
227 
228     // return a descriptive string detailing the result of the last shellout command
229     // in a form suitable for display on the console or UI message log - preferably
230     // no more than a single line (width unimportant). if not implemented, an empty
231     // string should be returned.
LastShelloutResult(void)232     virtual string LastShelloutResult(void) { return string(); }
233 
234     // return true if this platform supports shellouts.
ShelloutsSupported(void)235     virtual bool ShelloutsSupported(void) { return false; }
236 
237 protected:
238     int exitCode;
239     int cancelReturn;
240     int skipReturn;
241     bool skipAllFrames;
242     bool skipNextFrame;
243     bool cancelRender;
244     bool skipCallouts;
245     bool killRequested;
246     bool hadPreScene;
247     bool hadPostScene;
248     bool hadUserAbort;
249     bool hadFatalError;
250     bool commandProhibited;
251     unsigned int frameNo;
252     unsigned int imageWidth;
253     unsigned int imageHeight;
254     float clockVal;
255     string sceneName;
256     string outputFile;
257     string runningProcessName;
258     string cancelPhase;
259     string cancelReason;
260     string cancelCommand;
261     string cancelParameters;
262     string cancelOutput;
263     string skipPhase;
264     string skipReason;
265     string skipCommand;
266     string skipParameters;
267     string skipOutput;
268     boost::format cancelFormat;
269     boost::format skipFormat;
270     ShelloutPtr shellouts[lastShelloutEvent];
271 
272     // helper method
273     string GetPhaseName(shelloutEvent event);
274 
275     // execute the given command with the supplied parameters, which have already
276     // been expanded as per the docs, and immediately return true without waiting
277     // for completion of the process. if the command can't be run other than for
278     // one of the reasons documented below, return false (in which case CollectCommand
279     // should return -2 if called later on).
280     //
281     // if shellouts are not supported, or access to the executable is prohibited by
282     // POV-Ray internal rules (not the OS), throw a kCannotOpenFile exception with an
283     // appropriate message. you should also throw a (different) exception if a process
284     // is still running. any exception thrown will cancel a render (including remaining
285     // frames of an animation) and the fatal error shellout (if defined) will not be
286     // called.
287     //
288     // you should reap any processes in your destructor in case CollectCommand doesn't
289     // get called.
290     //
291     // if the platform implemeting a subclass of this method has the equivalent of a
292     // system log (e.g. syslog on unix, event log on windows), the implementation should
293     // consider providing a user-controllable option to log any commands using such.
294     virtual bool ExecuteCommand(const string& cmd, const string& params);
295 
296     // shutdown any currently-running shellouts. if force is true, force them to exit.
297     // in either case, don't wait more than timeout milliseconds. return true if there
298     // are no more processes running afterwards.
299     virtual bool KillCommand(int timeout, bool force = false) { return true; }
300 
301     // returns true if a shellout is currently running. if this method is being called in an
302     // event loop to wait until a shellout has completed, it is the responsibility of the event
303     // loop to perform appropriate sleeps to avoid wasting CPU time. platforms that implement
304     // shellout support MUST override this method to return an appropriate value by actually
305     // checking the process each time it's called.
CommandRunning(void)306     virtual bool CommandRunning(void) { return false; }
307 
308     // if no process is running or has already been reaped, return -2. if a process
309     // is still running, return -1. if the process is complete, place the output into
310     // output then return the process's exit code. if the process failed, it would help
311     // if the output string included (or was only) stderr, but this is not a requirement.
312     //
313     // if the platform does not support capturing output of processes (or the
314     // processes are GUI-based), there is no requirement to return any output.
CollectCommand(string & output)315     virtual int CollectCommand(string& output) { return -2; }
CollectCommand(void)316     virtual int CollectCommand(void) { return -2; }
317 
318     // return true if the requested shellout command is permitted. this method is
319     // called just before a shellout runs. if it fails, an exception will generally
320     // be thrown by the caller (the method itself should not throw an exception).
CommandPermitted(const string & command,const string & parameters)321     virtual bool CommandPermitted(const string& command, const string& parameters) { return true; }
322 
323     // called by the internal parser during construction to separate commands from parameters.
324     // given a raw string in the form returned from the POV INI file, extract the command and any parameters.
325     // the default version of this method should suffice for most implementations, however some platforms
326     // may need different treatment of quotes or escapes. for example, the windows platform may wish to
327     // provide a special-case for strings that look like windows paths, and exempt them from escaping of
328     // the backslash.
329     //
330     // the default method will trim the source and then search it for the first whitespace character
331     // that is both outside of a quoted string and not escaped. this forms the boundary between the
332     // command and the parameters (if not found, the entire source is considered the command). the code
333     // accepts single and double quotes as acceptable delimiters. if quotes are found around the command,
334     // they are removed. no other quotes (including any within the parameters) will be altered.
335     //
336     // when parsing the command portion of the string, any backslashes found are considered to be escapes
337     // which remove the special meaning of the following character. the escape is removed and the next
338     // character will not be subject to special interpretation (this affects single quote, double quote,
339     // backslashes, and any whitespace characters. any escapes in the text after the point where the
340     // parameters start will not be removed.
341     //
342     // this method should return true if the command is non-empty upon completion.
343     virtual bool ExtractCommand(const string& src, string& command, string& parameters) const;
344 
345 private:
346     bool processStartRequested;
347     shelloutEvent postProcessEvent;
348 
349     bool HandleProcessEvent(shelloutEvent which, bool internalCall);
350     bool PostProcessEvent(void);
351 };
352 
353 }
354 
355 #endif // POVRAY_FRONTEND_SHELLOUTPROCESSING_H
356