povray/vfe/unix/vfeplatform.cpp
2013-11-06 13:07:19 -05:00

429 lines
16 KiB
C++

/*******************************************************************************
* 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 <http://www.gnu.org/licenses/>.
* ---------------------------------------------------------------------------
* 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 <pthread.h>
#include <stdlib.h>
#include <string.h>
#ifdef HAVE_TIME_H
# include <time.h>
#endif
#ifdef HAVE_SYS_TIME_H
# include <sys/time.h>
#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<UnixOptionsProcessor>(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<UCS2String> pc = path.GetAllFolders();
vector<UCS2String> 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;
}
}