/******************************************************************************* * vfeplatform.cpp * * This module contains *nix platform-specific support code for the VFE. * * Based on vfe/win/vfeplatform.cpp by Christopher J. Cason * * --------------------------------------------------------------------------- * Persistence of Vision Ray Tracer ('POV-Ray') version 3.7. * Copyright 1991-2013 Persistence of Vision Raytracer Pty. Ltd. * * POV-Ray is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version. * * POV-Ray is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . * --------------------------------------------------------------------------- * POV-Ray is based on the popular DKB raytracer version 2.12. * DKBTrace was originally written by David K. Buck. * DKBTrace Ver 2.0-2.12 were written by David K. Buck & Aaron A. Collins. * --------------------------------------------------------------------------- * $File: //depot/public/povray/3.x/vfe/unix/vfeplatform.cpp $ * $Revision: #1 $ * $Change: 6069 $ * $DateTime: 2013/11/06 11:59:40 $ * $Author: chrisc $ *******************************************************************************/ // must come first #include "syspovconfig.h" #include #include #include #ifdef HAVE_TIME_H # include #endif #ifdef HAVE_SYS_TIME_H # include #endif #include "vfe.h" #include "unixoptions.h" namespace vfePlatform { using namespace vfe; bool gShelloutsPermittedFixThis = false; ///////////////////////////////////////////////////////////////////////// // return a number that uniquely identifies the calling thread amongst // all other running threads in the process (and preferably in the OS). POVMS_Sys_Thread_Type GetThreadId () { return (POVMS_Sys_Thread_Type) pthread_self(); } ////////////////////////////////////////////////////////////// // User-interface functions ////////////////////////////////////////////////////////////// vfeUnixSession::vfeUnixSession(int id) : m_LastTimestamp(0), m_TimestampOffset(0), vfeSession(id) { m_OptionsProc = boost::shared_ptr(new UnixOptionsProcessor(this)); } ///////////////////////////////////////////////////////////////////////// // this method will get called when a render completes and the image writing // code is unable to write the requested output file for some reason (e.g. // disk full, existing output file is read-only, or whatever). the return // value can determine any one of several possible actions the code will take. // it's up to you what you do here, but a typical example would be to display // an error dialog showing the reason (which will be a short string) and the old // path, and allow the user to browse for a new path (you may want to auto- // suggest one). it is allowable to perform tests yourself on the path to // obtain some OS-specific information as to why it failed in order to better // determine what to suggest as the new path (e.g. the the output path is // not writable due to insufficient privileges, you may want to default the // new path to the user's home directory). // // any new path that is to be returned should be placed in NewPath. the parameter // CallCount will initially be 0, and represents the number of times the // method has been called for a given rendering; this allows you for example // to auto-default on the first call and prompt the user on subsequent ones. // (note that if you do this and the auto-default succeeds, it's up to you to // notify the user that the output file is not what they originally asked for). // // you may want to place a timeout on this dialog if the session is running // an animation and return in the case of a timeout a default value. // // the return values can be any of the following: // // 0 : don't try again, leave the render without an output file but preserve // the state file (so a render with +c will reload the image data). // 1 : reserved for later support // 2 : don't try again and delete the state file (rendered data can't be // recovered). // 3 : try again with the same file (NewPath is ignored). // 4 : try again with the new path returned in NewPath. // // note that if you choose to specify any of the "don't try again" options, // and the session happens to be an animation, and it's likely the same // error will occur again (e.g. invalid output path or something), then // you may want to call the render cancel API so the user isn't bombarded // with an error message for each frame of the render. int vfeUnixSession::RequestNewOutputPath(int CallCount, const string& Reason, const UCS2String& OldPath, UCS2String& NewPath) { // TODO: print warning and cancel? return 0; } ////////////////////////////////////////////////////////////// // File-system related support functions ////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////// // return an absolute path including trailing path separator. // *nix platforms might want to just return "/tmp/" here. UCS2String vfeUnixSession::GetTemporaryPath(void) const { return ASCIItoUCS2String (m_OptionsProc->GetTemporaryPath().c_str()); } ///////////////////////////////////////////////////////////////////////// // might be better called 'CreateTemporaryFileName()' since pov // doesn't actually want the file opened; just the full path and // name to one that it can use. UCS2String vfeUnixSession::CreateTemporaryFile(void) const { char str [FILE_NAME_LENGTH] = ""; snprintf(str, FILE_NAME_LENGTH, "%spov%d", m_OptionsProc->GetTemporaryPath().c_str(), getpid ()); DELETE_FILE (str); return ASCIItoUCS2String (str); } ///////////////////////////////////////////////////////////////////////// // you could check that the path given lies within the paths that // your platform gives out for temporary files if you want; this // example doesn't do that but it's not a bad idea to add. void vfeUnixSession::DeleteTemporaryFile(const UCS2String& filename) const { DELETE_FILE (UCS2toASCIIString (filename).c_str()); } ////////////////////////////////////////////////////////////// // vfe or POVMS related support functions ////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////// // This method will get called on POVMS critical errors (e.g. cannot // allocate memory, amongst other things). Note that you should not // assume that you can e.g. allocate memory when processing this call. // // Before calling this function vfe sets a flag that prevents the worker // thread from processing any more messages; it will wait for you to // call Shutdown(). Your UI thread can find out if this method has been // called by checking for the presence of the stCriticalError status bit // returned from vfeSession::GetStatus() (note that this bit is not // necessarily already set at the time the method is called though). // // If you are running a genuine virtual frontend (e.g. stateless HTTP // interface), we may want to set a flag to display the message later // rather than pop up a messagebox on the local windowstation. Otherwise // you would probably display the message immediately. void vfeUnixSession::NotifyCriticalError (const char *message, const char *filename, int line) { fprintf (stderr, "POV-Ray Critical Error: %s", message); } //////////////////////////////////////////////////////////////////////// // Return a timestamp to be used internally for queue sorting etc. The // value returned must be 64-bit and in milliseconds; the origin of the // count is not important (e.g. milliseconds since 1/1/1970, or whatever // it doesn't matter), as long as it is consistent value (milliseconds // since system boot is NOT a valid value since it will change each boot). // Also please don't call time() and multiply by 1000 since vfe wants at // least 100ms precision (so it can do sub-one-second event timing). // // It's also important that the count not go backwards during the life // of a vfeSession instance; this means you should attempt to detect wall // clock changes by caching the last value your implementation returns // and adding an appropriate offset if you calculate a lower value later // in the session. POV_LONG vfeUnixSession::GetTimestamp(void) const { POV_LONG timestamp = 0; // in milliseconds #ifdef HAVE_CLOCK_GETTIME struct timespec ts; if (clock_gettime(CLOCK_REALTIME, &ts) == 0) timestamp = (POV_LONG) (1000)*ts.tv_sec + ts.tv_nsec/1000000; else #endif #ifdef HAVE_GETTIMEOFDAY { struct timeval tv; // seconds + microseconds since the Epoch (1970-01-01) if (gettimeofday(&tv, NULL) == 0) timestamp = (POV_LONG) (1000)*tv.tv_sec + tv.tv_usec/1000; } #endif // FIXME: add fallback using boost::xtime() timestamp += m_TimestampOffset; if (timestamp < m_LastTimestamp) { // perhaps the system clock has been adjusted? m_TimestampOffset += m_LastTimestamp - timestamp; timestamp = m_LastTimestamp; } else m_LastTimestamp = timestamp; return timestamp; } ///////////////////////////////////////////////////////////////////////// // called when the worker thread starts - you could if you like set the // thread priority here. void vfeUnixSession::WorkerThreadStartup() { } ///////////////////////////////////////////////////////////////////////// // called just before the worker thread exits. void vfeUnixSession::WorkerThreadShutdown() { } ///////////////////////////////////////////////////////////////////////// // The following methods support the I/O permissions feature ///////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////// // case-sensitive string comparison bool vfeUnixSession::StrCompare (const UCS2String& lhs, const UCS2String& rhs) const { return lhs.compare(rhs); } ///////////////////////////////////////////////////////////////////////// // return true if the path component of file is equal to the path component // of path. will also return true if recursive is true and path is a parent // of file. does not support relative paths, and will perform case-sensitive // comparisons. bool vfeUnixSession::TestPath (const Path& path, const Path& file, bool recursive) const { // we don't support relative paths if (path.HasVolume() == false || file.HasVolume() == false) return (false); if (StrCompare(path.GetVolume(), file.GetVolume()) == false) return (false); vector pc = path.GetAllFolders(); vector fc = file.GetAllFolders(); if (fc.size() < pc.size()) return (false) ; for (int i = 0 ; i < pc.size(); i++) if (StrCompare(fc[i], pc[i]) == false) return (false) ; return (fc.size() == pc.size() ? true : recursive); } ///////////////////////////////////////////////////////////////////////// // returns true if opening the given file in the specified mode is to // be permitted. it is allowed to ask the user, so this method could // take some time to return (especially if user is not at workstation ...) // // TODO: modify this to work fully with UCS2 strings (no conversion) bool vfeUnixSession::TestAccessAllowed (const Path& file, bool isWrite) const { if (!m_OptionsProc->isIORestrictionsEnabled(isWrite)) return true; string FullFnm = m_OptionsProc->CanonicalizePath(UCS2toASCIIString(file())); if(FullFnm.length() == 0) return false; Path fullPath(FullFnm); if (isWrite) { // we do special-case hard-coded exclusion test(s) here for (IOPathVector::const_iterator it = GetExcludedPaths().begin(); it != GetExcludedPaths().end(); it++) { if (TestPath(it->GetPath(), fullPath, it->IsRecursive())) { m_OptionsProc->IORestrictionsError(UCS2toASCIIString(file()), isWrite, false); return (false) ; } } } else { // it's a read for (IOPathVector::const_iterator it = GetReadPaths().begin(); it != GetReadPaths().end(); it++) if (TestPath(it->GetPath(), fullPath, it->IsRecursive())) return (true) ; // access for write implies access for read (this is by design). // so we check the write specs below instead of giving an error here. } for (IOPathVector::const_iterator it = GetWritePaths().begin(); it != GetWritePaths().end(); it++) if (TestPath(it->GetPath(), fullPath, it->IsRecursive())) return (true) ; m_OptionsProc->IORestrictionsError(UCS2toASCIIString(file()), isWrite, true); return (false); } ///////////////////////////////////////////////////////////////////////////// // Shellout support class (UnixShelloutProcessing) ///////////////////////////////////////////////////////////////////////////// // See the comments in source/frontend/shelloutprocessing.h for documentation // on the requirements for these methods. ///////////////////////////////////////////////////////////////////////////// UnixShelloutProcessing::UnixShelloutProcessing(POVMS_Object& opts, const string& scene, uint width, uint height): ShelloutProcessing(opts, scene, width, height) { m_ProcessRunning = false; m_ProcessId = m_LastError = m_ExitCode = 0; } UnixShelloutProcessing::~UnixShelloutProcessing() { if (UnixShelloutProcessing::CommandRunning()) KillShellouts(1, true); CollectCommand(); } bool UnixShelloutProcessing::ExecuteCommand(const string& cmd, const string& params) { #if 0 if (UnixShelloutProcessing::CommandRunning()) throw POV_EXCEPTION(kNotNowErr, "Cannot create new process as previous shellout has not terminated"); CollectCommand(); m_Command = cmd; m_Params = params; m_ProcessId = m_LastError = m_ExitCode = 0; // TODO: IMPLEMENT (see vfe/win/vfeplatform.cpp for example) #else // until we get a UNIX guy to assist, we just use system to do as simple an implementation as possible. // no background support or output piping is done. m_Command = cmd; m_Params = params; m_ProcessId = m_LastError = m_ExitCode = 0; string command = cmd + " " + params; boost::trim(command); if (command.empty()) throw POV_EXCEPTION(kParamErr, "Empty shellout command"); if (*command.rbegin() == '&') // background processing not supported throw POV_EXCEPTION(kParamErr, "Background execution of shellout commands not currently supported"); m_ProcessRunning = true; int result = system(command.c_str()); m_ProcessRunning = false; if (result == -1) { m_LastError = errno; m_ExitCode = -1; } else { m_ExitCode = WEXITSTATUS(result); } #endif return false; } bool UnixShelloutProcessing::CommandRunning(void) { // TODO: IMPLEMENT (see vfe/win/vfeplatform.cpp for example) return m_ProcessRunning; } int UnixShelloutProcessing::ProcessID(void) { if (!UnixShelloutProcessing::CommandRunning()) return 0; return m_ProcessId; } bool UnixShelloutProcessing::KillCommand(int timeout, bool force) { if (!UnixShelloutProcessing::CommandRunning()) return true; // TODO: IMPLEMENT (see vfe/win/vfeplatform.cpp for example) throw POV_EXCEPTION(kCannotHandleRequestErr, "Kill for shellout commands not currently supported"); } int UnixShelloutProcessing::CollectCommand(void) { if (m_ProcessRunning == false) return -2; if (UnixShelloutProcessing::CommandRunning()) return -1; // TODO: IMPLEMENT (see vfe/win/vfeplatform.cpp for example) return m_ExitCode; } int UnixShelloutProcessing::CollectCommand(string& output) { // TODO: IMPLEMENT IF OUTPUT COLLECTION TO BE SUPPORTED return CollectCommand(); } bool UnixShelloutProcessing::CommandPermitted(const string& command, const string& parameters) { // until we get a unix support guy, this is just a hack: use a global string cmd = command + " " + parameters; boost::trim(cmd); if (command.empty() || *command.rbegin() == '&') return false; return gShelloutsPermittedFixThis; } }