/** * PANDA 3D SOFTWARE * Copyright (c) Carnegie Mellon University. All rights reserved. * * All use of this software is subject to the terms of the revised BSD * license. You should have received a copy of this license along * with this source code in a file named "LICENSE." * * @file p3dInstanceManager.cxx * @author drose * @date 2009-05-29 */ #include "p3dInstanceManager.h" #include "p3dInstance.h" #include "p3dSession.h" #include "p3dAuthSession.h" #include "p3dHost.h" #include "p3d_plugin_config.h" #include "p3dWinSplashWindow.h" #include "p3dUndefinedObject.h" #include "p3dNoneObject.h" #include "p3dBoolObject.h" #include "p3dPackage.h" #include "find_root_dir.h" #include "fileSpec.h" #include "get_tinyxml.h" #include "binaryXml.h" #include "mkdir_complete.h" #include "wstring_encode.h" // We can include this header file to get the DTOOL_PLATFORM definition, even // though we don't link with dtool. #include "dtool_platform.h" #ifdef _WIN32 #include #include // chmod() #include // rmdir() #include // GetModuleHandle() etc. #else #include #include #include #endif #ifdef __APPLE__ #include #endif #include static ofstream logfile; ostream *nout_stream = &logfile; P3DInstanceManager *P3DInstanceManager::_global_ptr; /** * */ P3DInstanceManager:: P3DInstanceManager() { init_xml(); _is_initialized = false; _created_runtime_environment = false; _api_version = 0; _next_temp_filename_counter = 1; _unique_id = 0; _trusted_environment = false; _console_environment = false; _plugin_major_version = 0; _plugin_minor_version = 0; _plugin_sequence_version = 0; _plugin_official_version = false; _coreapi_timestamp = 0; _notify_thread_continue = false; _started_notify_thread = false; INIT_THREAD(_notify_thread); // Initialize the singleton objects. _undefined_object = new P3DUndefinedObject(); _none_object = new P3DNoneObject(); _true_object = new P3DBoolObject(true); _false_object = new P3DBoolObject(false); _auth_session = NULL; // Seed the lame random number generator in rand(); we use it to select a // mirror for downloading. srand((unsigned int)time(NULL)); #ifdef _WIN32 // Ensure the appropriate Windows common controls are available to this // application. INITCOMMONCONTROLSEX icc; icc.dwSize = sizeof(icc); icc.dwICC = ICC_PROGRESS_CLASS; InitCommonControlsEx(&icc); #endif #ifndef _WIN32 // On Mac or Linux, we'd better ignore SIGPIPE, or this signal will shut // down the browser if the plugin exits unexpectedly. struct sigaction ignore; memset(&ignore, 0, sizeof(ignore)); ignore.sa_handler = SIG_IGN; sigaction(SIGPIPE, &ignore, &_old_sigpipe); #endif // _WIN32 } /** * */ P3DInstanceManager:: ~P3DInstanceManager() { if (_started_notify_thread) { _notify_ready.acquire(); _notify_thread_continue = false; _notify_ready.notify(); _notify_ready.release(); JOIN_THREAD(_notify_thread); _started_notify_thread = false; } #ifndef _WIN32 // Restore the original SIGPIPE handler. sigaction(SIGPIPE, &_old_sigpipe, NULL); #endif // _WIN32 // force-finish any remaining instances. while (!_instances.empty()) { P3DInstance *inst = *(_instances.begin()); finish_instance(inst); } assert(_sessions.empty()); assert(_instances.empty()); if (_auth_session != NULL) { p3d_unref_delete(_auth_session); _auth_session = NULL; } Hosts::iterator hi; for (hi = _hosts.begin(); hi != _hosts.end(); ++hi) { delete (*hi).second; } _hosts.clear(); // Delete any remaining temporary files. TempFilenames::iterator ti; for (ti = _temp_filenames.begin(); ti != _temp_filenames.end(); ++ti) { const string &filename = (*ti); nout << "Removing delinquent temp file " << filename << "\n"; unlink(filename.c_str()); } _temp_filenames.clear(); nout << "counts: " << _undefined_object->_ref_count << " " << _none_object->_ref_count << " " << _true_object->_ref_count << " " << _false_object->_ref_count << "\n"; /* assert(_undefined_object->_ref_count == 1); assert(_none_object->_ref_count == 1); assert(_true_object->_ref_count == 1); assert(_false_object->_ref_count == 1); */ P3D_OBJECT_DECREF(_undefined_object); P3D_OBJECT_DECREF(_none_object); P3D_OBJECT_DECREF(_true_object); P3D_OBJECT_DECREF(_false_object); #ifdef _WIN32 P3DWinSplashWindow::unregister_window_class(); #endif } /** * Called by the plugin host at application startup. It returns true if the * DLL is successfully initialized, false if it should be immediately shut * down and redownloaded. */ bool P3DInstanceManager:: initialize(int api_version, const string &contents_filename, const string &host_url, P3D_verify_contents verify_contents, const string &platform, const string &log_directory, const string &log_basename, bool trusted_environment, bool console_environment, const string &root_dir, const string &host_dir, const string &start_dir) { _api_version = api_version; _host_url = host_url; _verify_contents = verify_contents; _platform = platform; _log_directory = log_directory; _log_basename = log_basename; _trusted_environment = trusted_environment; _console_environment = console_environment; if (_host_url.empty()) { _host_url = PANDA_PACKAGE_HOST_URL; } if (_platform.empty()) { // If the platform is compiled in (as opposed to passed in by the caller), // we might in fact support multiple platforms. _platform = DTOOL_PLATFORM; #ifdef _WIN32 if (_platform == "win_amd64") { _supported_platforms.push_back("win_amd64"); _supported_platforms.push_back("win_i386"); _supported_platforms.push_back("win32"); } else if (_platform == "win_i386" || _platform == "win32") { // This is a WIN32 process, but determine if the underlying OS actually // supports WIN64. if (supports_win64()) { _supported_platforms.push_back("win_amd64"); } _supported_platforms.push_back("win_i386"); _supported_platforms.push_back("win32"); } #elif defined(__APPLE__) if (_platform == "osx_amd64") { _supported_platforms.push_back("osx_amd64"); _supported_platforms.push_back("osx_i386"); } else if (_platform == "osx_i386") { // This is a 32-bit process, but determine if the underlying OS supports // 64-bit. int mib[2] = { CTL_HW, HW_MACHINE }; char machine[512]; size_t len = 511; if (sysctl(mib, 2, (void *)machine, &len, NULL, 0) == 0) { if (strcmp(machine, "x86_64") == 0) { _supported_platforms.push_back("osx_amd64"); } } _supported_platforms.push_back("osx_i386"); } #endif // _WIN32 // TODO: Linux multiplatform support. Just add the appropriate platform // strings to _supported_platforms. } else { nout << "Platform string was set by plugin to " << _platform << "\n"; } if (_supported_platforms.empty()) { // Hack for older plug-ins, which should still remain compatible with // newer versions of the runtime distribution. if (_platform == "win32") { _supported_platforms.push_back("win_i386"); } // We always support at least the specific platform on which we're // running. _supported_platforms.push_back(_platform); } #ifdef P3D_PLUGIN_LOG_DIRECTORY if (_log_directory.empty()) { _log_directory = P3D_PLUGIN_LOG_DIRECTORY; } #endif #ifdef P3D_PLUGIN_LOG_BASENAME2 if (_log_basename.empty()) { _log_basename = P3D_PLUGIN_LOG_BASENAME2; } #endif if (_log_basename.empty()) { _log_basename = "p3dcore"; } if (root_dir.empty()) { _root_dir = find_root_dir(); if (_root_dir.empty()) { cerr << "Could not find root directory.\n"; return false; } } else { _root_dir = root_dir; } _host_dir = host_dir; if (start_dir.empty()) { _start_dir = _root_dir + "/start"; } else { _start_dir = start_dir; } // Allow the caller (e.g. panda3d.exe) to specify a log directory. Or, // allow the developer to compile one in. Failing that, we write logfiles // to Panda3Dlog. if (_log_directory.empty()) { _log_directory = _root_dir + "/log"; } // Ensure that the log directory ends with a slash. if (!_log_directory.empty() && _log_directory[_log_directory.size() - 1] != '/') { #ifdef _WIN32 if (_log_directory[_log_directory.size() - 1] != '\\') #endif _log_directory += "/"; } // Construct the logfile pathname. _log_pathname = _log_directory; _log_pathname += _log_basename; _log_pathname += ".log"; create_runtime_environment(); _is_initialized = true; if (!host_url.empty() && !contents_filename.empty()) { // Attempt to pre-read the supplied contents.xml file, to avoid an // unnecessary download later. P3DHost *host = get_host(host_url); if (!host->read_contents_file(contents_filename, false)) { nout << "Couldn't read " << contents_filename << "\n"; } } nout << "Supported platforms:"; for (size_t pi = 0; pi < _supported_platforms.size(); ++pi) { nout << " " << _supported_platforms[pi]; } nout << "\n"; return true; } /** * Specifies the version of the calling plugin, for reporting to JavaScript * and the like. */ void P3DInstanceManager:: set_plugin_version(int major, int minor, int sequence, bool official, const string &distributor, const string &coreapi_host_url, time_t coreapi_timestamp, const string &coreapi_set_ver) { reconsider_runtime_environment(); _plugin_major_version = major; _plugin_minor_version = minor; _plugin_sequence_version = sequence; _plugin_official_version = official; _plugin_distributor = distributor; // The Core API "host URL" is both compiled in, and comes in externally; we // trust the external source in the case of a conflict. string internal_host_url = PANDA_PACKAGE_HOST_URL; if (coreapi_host_url != internal_host_url) { nout << "Warning! Downloaded Core API from " << coreapi_host_url << ", but its internal URL was " << internal_host_url << "\n"; } _coreapi_host_url = coreapi_host_url; if (_coreapi_host_url.empty()) { _coreapi_host_url = internal_host_url; } // The Core API timestamp is only available externally. _coreapi_timestamp = coreapi_timestamp; // The Core API "set ver", or version, is both compiled in and comes in // externally; for this one we trust the internal version in the case of a // conflict. string internal_set_ver = P3D_COREAPI_VERSION_STR; if (coreapi_set_ver != internal_set_ver && !coreapi_set_ver.empty() && !internal_set_ver.empty()) { nout << "Warning! contents.xml reports Core API version number " << coreapi_set_ver << ", but its actual version number is " << internal_set_ver << "\n"; } _coreapi_set_ver = internal_set_ver; if (_coreapi_set_ver.empty()) { _coreapi_set_ver = coreapi_set_ver; } nout << "Plugin version: " << _plugin_major_version << "." << _plugin_minor_version << "." << _plugin_sequence_version; if (!_plugin_official_version) { nout << "c"; } nout << "\n"; nout << "Plugin distributor: " << _plugin_distributor << "\n"; nout << "Core API host URL: " << _coreapi_host_url << "\n"; nout << "Core API version: " << _coreapi_set_ver << "\n"; const char *timestamp_string = ctime(&_coreapi_timestamp); if (timestamp_string == NULL) { timestamp_string = ""; } nout << "Core API date: " << timestamp_string << "\n"; } /** * Specifies the "super mirror" URL. See p3d_plugin.h. */ void P3DInstanceManager:: set_super_mirror(const string &super_mirror_url) { reconsider_runtime_environment(); if (!super_mirror_url.empty()) { nout << "super_mirror = " << super_mirror_url << "\n"; } _super_mirror_url = super_mirror_url; // Make sure it ends with a slash. if (!_super_mirror_url.empty() && _super_mirror_url[_super_mirror_url.size() - 1] != '/') { _super_mirror_url += '/'; } } /** * Returns a newly-allocated P3DInstance with the indicated startup * information. */ P3DInstance *P3DInstanceManager:: create_instance(P3D_request_ready_func *func, const P3D_token tokens[], size_t num_tokens, int argc, const char *argv[], void *user_data) { reconsider_runtime_environment(); P3DInstance *inst = new P3DInstance(func, tokens, num_tokens, argc, argv, user_data); inst->ref(); _instances.insert(inst); return inst; } /** * Sets the p3d_filename (or p3d_url) on a particular instance. */ bool P3DInstanceManager:: set_p3d_filename(P3DInstance *inst, bool is_local, const string &p3d_filename, const int &p3d_offset) { if (inst->is_started()) { nout << "Instance started twice: " << inst << "\n"; return false; } if (is_local) { inst->set_p3d_filename(p3d_filename, p3d_offset); } else { inst->set_p3d_url(p3d_filename); } return true; } /** * Indicates an intention to transmit the p3d data as a stream. Should return * a new unique stream ID to receive it. */ int P3DInstanceManager:: make_p3d_stream(P3DInstance *inst, const string &p3d_url) { if (inst->is_started()) { nout << "Instance started twice: " << inst << "\n"; return -1; } return inst->make_p3d_stream(p3d_url); } /** * Actually starts the instance running on a particular session. This is * called by the P3DInstance when it successfully loads its instance file. */ bool P3DInstanceManager:: start_instance(P3DInstance *inst) { if (inst->is_failed()) { // Don't bother trying again. return false; } if (inst->is_started()) { // Already started. return true; } P3DSession *session; Sessions::iterator si = _sessions.find(inst->get_session_key()); if (si == _sessions.end()) { session = new P3DSession(inst); session->ref(); bool inserted = _sessions.insert(Sessions::value_type(session->get_session_key(), session)).second; assert(inserted); } else { session = (*si).second; } session->start_instance(inst); return inst->is_started(); } /** * Terminates and removes a previously-returned instance. */ void P3DInstanceManager:: finish_instance(P3DInstance *inst) { nout << "finish_instance: " << inst << "\n"; Instances::iterator ii; ii = _instances.find(inst); if (ii != _instances.end()) { _instances.erase(ii); } Sessions::iterator si = _sessions.find(inst->get_session_key()); if (si != _sessions.end()) { P3DSession *session = (*si).second; session->terminate_instance(inst); // If that was the last instance in this session, terminate the session. if (session->get_num_instances() == 0) { _sessions.erase(session->get_session_key()); session->shutdown(); p3d_unref_delete(session); } } inst->cleanup(); p3d_unref_delete(inst); } /** * Creates a new P3DAuthSession object, to pop up a window for the user to * authorize the certificate on this instance. Automatically terminates any * previously-created P3DAuthSession. */ P3DAuthSession *P3DInstanceManager:: authorize_instance(P3DInstance *inst) { if (_auth_session != NULL) { // We only want one auth_session window open at a time, to minimize user // confusion, so close any previous window. _auth_session->shutdown(true); p3d_unref_delete(_auth_session); _auth_session = NULL; } _auth_session = new P3DAuthSession(inst); _auth_session->ref(); return _auth_session; } /** * Returns the P3DInstance pointer corresponding to the indicated P3D_instance * if it is valid, or NULL if it is not. */ P3DInstance *P3DInstanceManager:: validate_instance(P3D_instance *instance) { Instances::iterator ii; ii = _instances.find((P3DInstance *)instance); if (ii != _instances.end()) { return (*ii); } return NULL; } /** * If a request is currently pending on any instance, returns its pointer. * Otherwise, returns NULL. */ P3DInstance *P3DInstanceManager:: check_request() { Instances::iterator ii; for (ii = _instances.begin(); ii != _instances.end(); ++ii) { P3DInstance *inst = (*ii); if (inst->has_request()) { return inst; } } return NULL; } /** * Does not return until a request is pending on some instance, or until no * instances remain, or until the indicated time in seconds has elapsed. Use * check_request to retrieve the pending request. Due to the possibility of * race conditions, it is possible for this function to return when there is * in fact no request pending (another thread may have extracted the request * first). */ void P3DInstanceManager:: wait_request(double timeout) { #ifdef _WIN32 int stop_tick = int(GetTickCount() + timeout * 1000.0); #else struct timeval stop_time; gettimeofday(&stop_time, NULL); int seconds = (int)floor(timeout); stop_time.tv_sec += seconds; stop_time.tv_usec += (int)((timeout - seconds) * 1000.0); if (stop_time.tv_usec > 1000) { stop_time.tv_usec -= 1000; ++stop_time.tv_sec; } #endif _request_ready.acquire(); if (check_request() != (P3DInstance *)NULL) { _request_ready.release(); return; } if (_instances.empty()) { _request_ready.release(); return; } // No pending requests; go to sleep. _request_ready.wait(timeout); while (true) { #ifdef _WIN32 int remaining_ticks = stop_tick - GetTickCount(); if (remaining_ticks <= 0) { break; } timeout = remaining_ticks * 0.001; #else struct timeval now; gettimeofday(&now, NULL); struct timeval remaining; remaining.tv_sec = stop_time.tv_sec - now.tv_sec; remaining.tv_usec = stop_time.tv_usec - now.tv_usec; if (remaining.tv_usec < 0) { remaining.tv_usec += 1000; --remaining.tv_sec; } if (remaining.tv_sec < 0) { break; } timeout = remaining.tv_sec + remaining.tv_usec * 0.001; #endif if (check_request() != (P3DInstance *)NULL) { _request_ready.release(); return; } if (_instances.empty()) { _request_ready.release(); return; } // No pending requests; go to sleep. _request_ready.wait(timeout); } _request_ready.release(); } /** * Returns a (possibly shared) pointer to the indicated download host. */ P3DHost *P3DInstanceManager:: get_host(const string &host_url) { Hosts::iterator pi = _hosts.find(host_url); if (pi != _hosts.end()) { return (*pi).second; } P3DHost *host = new P3DHost(host_url, _host_dir); bool inserted = _hosts.insert(Hosts::value_type(host_url, host)).second; assert(inserted); return host; } /** * Removes the indicated host from the cache. */ void P3DInstanceManager:: forget_host(P3DHost *host) { const string &host_url = host->get_host_url(); nout << "Forgetting host " << host_url << "\n"; // Hmm, this is a memory leak. But we allow it to remain, since it's an // unusual circumstance (uninstalling), and it's safer to leak than to risk // a floating pointer. _hosts.erase(host_url); } /** * Returns a number used to uniquify different instances. This number is * guaranteed to be different at each call, at least until the int space rolls * over. */ int P3DInstanceManager:: get_unique_id() { ++_unique_id; return _unique_id; } /** * May be called in any thread to indicate that a new P3D_request is available * in the indicated instance. */ void P3DInstanceManager:: signal_request_ready(P3DInstance *inst) { if (inst->get_request_ready_func() != NULL) { // This instance requires asynchronous notifications of requests. Thus, // we should tell the notify thread to wake up and make the callback. _notify_ready.acquire(); _notify_instances.push_back(inst); _notify_ready.notify(); _notify_ready.release(); // Oh, and we should spawn the thread if we haven't already. if (!_started_notify_thread) { _notify_thread_continue = true; SPAWN_THREAD(_notify_thread, nt_thread_run, this); _started_notify_thread = true; } } // Then, wake up the main thread, in case it's sleeping on wait_request(). _request_ready.acquire(); _request_ready.notify(); _request_ready.release(); } /** * */ P3D_class_definition *P3DInstanceManager:: make_class_definition() const { P3D_class_definition *new_class = new P3D_class_definition(P3DObject::_generic_class); // TODO: save this pointer so we can delete it on destruction. return new_class; } /** * Constructs a new, unique temporary filename with the indicated extension. * You should use the P3DTemporaryFilename interface instead of calling this * method directly. */ string P3DInstanceManager:: make_temp_filename(const string &extension) { string result; bool exists; do { int tid; #ifdef _WIN32 tid = GetCurrentProcessId(); #else tid = getpid(); #endif if (tid == 0) { tid = 1; } int hash = ((clock() + _next_temp_filename_counter) * ((time(NULL) * tid) >> 8)) & 0xffffff; ++_next_temp_filename_counter; char hex_code[10]; sprintf(hex_code, "%06x", hash); result = _temp_directory; result += "p3d_"; result += hex_code; result += extension; exists = false; if (_temp_filenames.find(result) != _temp_filenames.end()) { // We've previously allocated this file. exists = true; } else { // Check if the file exists on disk. #ifdef _WIN32 DWORD results = GetFileAttributes(result.c_str()); if (results != -1) { exists = true; } #else // _WIN32 struct stat this_buf; if (stat(result.c_str(), &this_buf) == 0) { exists = true; } #endif } } while (exists); nout << "make_temp_filename: " << result << "\n"; return result; } /** * Releases a temporary filename assigned earlier via make_temp_filename(). * If the file exists, it will be removed. You should use the * P3DTemporaryFilename interface instead of calling this method directly. */ void P3DInstanceManager:: release_temp_filename(const string &filename) { nout << "release_temp_filename: " << filename << "\n"; _temp_filenames.erase(filename); unlink(filename.c_str()); } /** * Looks for the particular certificate in the cache of recognized * certificates. Returns true if it is found, false if not. */ bool P3DInstanceManager:: find_cert(X509 *cert) { // First, we need the DER representation. string der = cert_to_der(cert); // If we've previously found this certificate, we don't have to hit disk // again. ApprovedCerts::iterator ci = _approved_certs.find(der); if (ci != _approved_certs.end()) { return true; } // Well, we haven't found it already. Look for it on disk. For this, we // hash the cert into a hex string. This is similar to OpenSSL's // get_by_subject() approach, except we hash the whole cert, not just the // subject. (Since we also store self-signed certs in this list, we can't // trust the subject name alone.) string this_cert_dir = get_cert_dir(cert); nout << "looking in " << this_cert_dir << "\n"; vector contents; scan_directory(this_cert_dir, contents); // Now look at each of the files in this directory and see if any of them // matches the certificate. vector::iterator si; for (si = contents.begin(); si != contents.end(); ++si) { string filename = this_cert_dir + "/" + (*si); X509 *x509 = NULL; FILE *fp = NULL; #ifdef _WIN32 wstring filename_w; if (string_to_wstring(filename_w, filename)) { fp = _wfopen(filename_w.c_str(), L"r"); } #else // _WIN32 fp = fopen(filename.c_str(), "r"); #endif // _WIN32 if (fp != NULL) { x509 = PEM_read_X509(fp, NULL, NULL, (void *)""); fclose(fp); } if (x509 != NULL) { string der2 = cert_to_der(x509); // We might as well save this cert in the table for next time, even if // it's not the one we're looking for right now. _approved_certs.insert(der2); if (der == der2) { return true; } } } // Nothing matched. return false; } /** * Reads the pre-approved certificates in the certlist package and adds them * to the in-memory cache. */ void P3DInstanceManager:: read_certlist(P3DPackage *package) { nout << "reading certlist in " << package->get_package_dir() << "\n"; vector contents; scan_directory(package->get_package_dir(), contents); vector::iterator si; for (si = contents.begin(); si != contents.end(); ++si) { const string &basename = (*si); if (basename.length() > 4) { string suffix = basename.substr(basename.length() - 4); if (suffix == ".pem" || suffix == ".crt") { string filename = package->get_package_dir() + "/" + basename; X509 *x509 = NULL; FILE *fp = NULL; #ifdef _WIN32 wstring filename_w; if (string_to_wstring(filename_w, filename)) { fp = _wfopen(filename_w.c_str(), L"r"); } #else // _WIN32 fp = fopen(filename.c_str(), "r"); #endif // _WIN32 if (fp != NULL) { x509 = PEM_read_X509(fp, NULL, NULL, (void *)""); fclose(fp); } if (x509 != NULL) { string der2 = cert_to_der(x509); _approved_certs.insert(der2); } } } } } /** * Returns the directory searched for this particular certificate. */ string P3DInstanceManager:: get_cert_dir(X509 *cert) { string der = cert_to_der(cert); static const size_t hash_size = 16; unsigned char md[hash_size]; MD5_CTX ctx; MD5_Init(&ctx); MD5_Update(&ctx, der.data(), der.size()); MD5_Final(md, &ctx); string basename; static const size_t keep_hash = 6; for (size_t i = 0; i < keep_hash; ++i) { int high = (md[i] >> 4) & 0xf; int low = md[i] & 0xf; basename += P3DInstanceManager::encode_hexdigit(high); basename += P3DInstanceManager::encode_hexdigit(low); } return _certs_dir + "/" + basename; } /** * Converts the indicated certificate to its binary DER representation. */ string P3DInstanceManager:: cert_to_der(X509 *cert) { int buffer_size = i2d_X509(cert, NULL); unsigned char *buffer = new unsigned char[buffer_size]; unsigned char *p = buffer; i2d_X509(cert, &p); string result((char *)buffer, buffer_size); delete[] buffer; return result; } /** * Stops all active instances and removes *all* downloaded files from all * hosts, and empties the current user's Panda3D directory as much as * possible. * * This cannot remove the coreapi dll or directory on Windows. */ void P3DInstanceManager:: uninstall_all() { Instances::iterator ii; for (ii = _instances.begin(); ii != _instances.end(); ++ii) { P3DInstance *inst = (*ii); inst->uninstall_host(); } Hosts::iterator hi; for (hi = _hosts.begin(); hi != _hosts.end(); ++hi) { P3DHost *host = (*hi).second; host->uninstall(); } // Close the logfile so we can remove that too. logfile.close(); if (!_root_dir.empty()) { // This won't be able to delete the coreapi directory on Windows, because // we're running that DLL right now. But it will delete everything else. delete_directory_recursively(_root_dir); } _created_runtime_environment = false; } /** * */ P3DInstanceManager *P3DInstanceManager:: get_global_ptr() { if (_global_ptr == NULL) { _global_ptr = new P3DInstanceManager; } return _global_ptr; } /** * This is called only at plugin shutdown time; it deletes the global instance * manager pointer and clears it to NULL. */ void P3DInstanceManager:: delete_global_ptr() { if (_global_ptr != NULL) { delete _global_ptr; _global_ptr = NULL; } } /** * Attempts to open the named filename as if it were a directory and looks for * the non-hidden files within the directory. Fills the given vector up with * the sorted list of filenames that are local to this directory. * * It is the user's responsibility to ensure that the contents vector is empty * before making this call; otherwise, the new files will be appended to it. * * Returns true on success, false if the directory could not be read for some * reason. */ bool P3DInstanceManager:: scan_directory(const string &dirname, vector &contents) { #ifdef _WIN32 // Use Windows' FindFirstFile() FindNextFile() to walk through the list of // files in a directory. size_t orig_size = contents.size(); string match = dirname + "\\*.*"; WIN32_FIND_DATAW find_data; wstring match_w; string_to_wstring(match_w, match); HANDLE handle = FindFirstFileW(match_w.c_str(), &find_data); if (handle == INVALID_HANDLE_VALUE) { if (GetLastError() == ERROR_NO_MORE_FILES) { // No matching files is not an error. return true; } return false; } do { string filename; wstring_to_string(filename, find_data.cFileName); if (filename != "." && filename != "..") { contents.push_back(filename); } } while (FindNextFileW(handle, &find_data)); bool scan_ok = (GetLastError() == ERROR_NO_MORE_FILES); FindClose(handle); sort(contents.begin() + orig_size, contents.end()); return scan_ok; #else // _WIN32 // Use Posix's opendir() readdir() to walk through the list of files in a // directory. size_t orig_size = contents.size(); DIR *root = opendir(dirname.c_str()); if (root == (DIR *)NULL) { return false; } struct dirent *d; d = readdir(root); while (d != (struct dirent *)NULL) { if (d->d_name[0] != '.') { contents.push_back(d->d_name); } d = readdir(root); } closedir(root); sort(contents.begin() + orig_size, contents.end()); return true; #endif // _WIN32 } /** * Fills up filename_contents with the list of all files (but not * directories), and dirname_contents with the list of all directories, rooted * at the indicated dirname and below. The filenames generated are relative * to the root of the dirname, with slashes (not backslashes) as the directory * separator character. * * Returns true on success, false if the original dirname wasn't a directory * or something like that. */ bool P3DInstanceManager:: scan_directory_recursively(const string &dirname, vector &filename_contents, vector &dirname_contents, const string &prefix) { vector dir_contents; if (!scan_directory(dirname, dir_contents)) { // Apparently dirname wasn't a directory. return false; } // Walk through the contents of dirname. vector::const_iterator si; for (si = dir_contents.begin(); si != dir_contents.end(); ++si) { // Here's a particular file within dirname. Is it another directory, or // is it a regular file? string pathname = dirname + "/" + (*si); string rel_filename = prefix + (*si); if (scan_directory_recursively(pathname, filename_contents, dirname_contents, rel_filename + "/")) { // It's a directory, and it's just added its results to the contents. dirname_contents.push_back(rel_filename); } else { // It's not a directory, so assume it's an ordinary file, and add it to // the contents. filename_contents.push_back(rel_filename); } } return true; } /** * Deletes all of the files and directories in the named directory and below, * like rm -rf. Use with extreme caution. */ void P3DInstanceManager:: delete_directory_recursively(const string &root_dir) { vector contents, dirname_contents; if (!scan_directory_recursively(root_dir, contents, dirname_contents)) { // Maybe it's just a single file, not a directory. Delete it. #ifdef _WIN32 wstring root_dir_w; string_to_wstring(root_dir_w, root_dir); // Windows can't delete a file if it's read-only. _wchmod(root_dir_w.c_str(), 0644); int result = _wunlink(root_dir_w.c_str()); #else // _WIN32 int result = unlink(root_dir.c_str()); #endif // _WIN32 if (result == 0) { nout << "Deleted " << root_dir << "\n"; } else { #ifdef _WIN32 result = _waccess(root_dir_w.c_str(), 0); #else // _WIN32 result = access(root_dir.c_str(), 0); #endif // _WIN32 if (result == 0) { nout << "Could not delete " << root_dir << "\n"; } } return; } vector::iterator ci; for (ci = contents.begin(); ci != contents.end(); ++ci) { string filename = (*ci); string pathname = root_dir + "/" + filename; #ifdef _WIN32 wstring pathname_w; string_to_wstring(pathname_w, pathname); // Windows can't delete a file if it's read-only. _wchmod(pathname_w.c_str(), 0644); int result = _wunlink(pathname_w.c_str()); #else // _WIN32 int result = unlink(pathname.c_str()); #endif // _WIN32 if (result == 0) { nout << " Deleted " << filename << "\n"; } else { nout << " Could not delete " << filename << "\n"; } } // Now delete all of the directories too. They're already in reverse order, // so we remove deeper directories first. for (ci = dirname_contents.begin(); ci != dirname_contents.end(); ++ci) { string filename = (*ci); string pathname = root_dir + "/" + filename; #ifdef _WIN32 wstring pathname_w; string_to_wstring(pathname_w, pathname); _wchmod(pathname_w.c_str(), 0755); int result = _wrmdir(pathname_w.c_str()); #else // _WIN32 int result = rmdir(pathname.c_str()); #endif // _WIN32 if (result == 0) { nout << " Removed directory " << filename << "\n"; } else { nout << " Could not remove directory " << filename << "\n"; } } // Finally, delete the root directory itself. string pathname = root_dir; #ifdef _WIN32 wstring pathname_w; string_to_wstring(pathname_w, pathname); _wchmod(pathname_w.c_str(), 0755); int result = _wrmdir(pathname_w.c_str()); #else // _WIN32 int result = rmdir(pathname.c_str()); #endif // _WIN32 if (result == 0) { nout << "Removed directory " << root_dir << "\n"; } else { #ifdef _WIN32 result = _waccess(pathname_w.c_str(), 0); #else // _WIN32 result = access(pathname.c_str(), 0); #endif // _WIN32 if (result == 0) { nout << "Could not remove directory " << root_dir << "\n"; } } } /** * Removes the first instance of the indicated file from the given list. * Returns true if removed, false if it was not found. * * On Windows, the directory separator characters are changed from backslash * to forward slash before searching in the list; so it is assumed that the * list contains filenames with a forward slash used as a separator. */ bool P3DInstanceManager:: remove_file_from_list(vector &contents, const string &filename) { #ifdef _WIN32 // Convert backslashes to slashes. string clean_filename; for (string::const_iterator pi = filename.begin(); pi != filename.end(); ++pi) { if ((*pi) == '\\') { clean_filename += '/'; } else { clean_filename += (*pi); } } #else const string &clean_filename = filename; #endif // _WIN32 vector::iterator ci; for (ci = contents.begin(); ci != contents.end(); ++ci) { if ((*ci) == clean_filename) { contents.erase(ci); return true; } } return false; } /** * Appends the indicated basename to the root directory name, which is * modified in-place. The basename is allowed to contain nested slashes, but * no directory component of the basename may begin with a ".", thus * precluding ".." and hidden files. */ void P3DInstanceManager:: append_safe_dir(string &root, const string &basename) { if (basename.empty()) { return; } size_t p = 0; while (p < basename.length()) { size_t q = basename.find('/', p); if (q == string::npos) { if (q != p) { append_safe_dir_component(root, basename.substr(p)); } return; } if (q != p) { append_safe_dir_component(root, basename.substr(p, q - p)); } p = q + 1; } } /** * Called during initialize, or after a previous call to uninstall_all(), to * make sure all needed directories exist and the logfile is open. */ void P3DInstanceManager:: create_runtime_environment() { mkdir_complete(_log_directory, cerr); logfile.close(); logfile.clear(); #ifdef _WIN32 wstring log_pathname_w; string_to_wstring(log_pathname_w, _log_pathname); logfile.open(log_pathname_w.c_str(), ios::out | ios::trunc); #else logfile.open(_log_pathname.c_str(), ios::out | ios::trunc); #endif // _WIN32 if (logfile) { logfile.setf(ios::unitbuf); nout_stream = &logfile; } // Determine the temporary directory. #ifdef _WIN32 wchar_t buffer_1[MAX_PATH]; wstring temp_directory_w; // Figuring out the correct path for temporary files is a real mess on // Windows. We should be able to use GetTempPath(), but that relies on $TMP // or $TEMP being defined, and it appears that Mozilla clears these // environment variables for the plugin, which forces GetTempPath() into // $USERPROFILE instead. This is really an inappropriate place for // temporary files, so, GetTempPath() isn't a great choice. // We could use SHGetSpecialFolderPath() instead to get us the path to // "Temporary Internet Files", which is acceptable. The trouble is, if we // happen to be running in "Protected Mode" on Vista, this folder isn't // actually writable by us! On Vista, we're supposed to use // IEGetWriteableFolderPath() instead, but *this* function doesn't exist on // XP and below. Good Lord. // We could go through a bunch of LoadLibrary() calls to try to find the // right path, like we do in find_root_dir(), but I'm just tired of doing // all that nonsense. We'll use a two-stage trick instead. We'll check for // $TEMP or $TMP being defined specifically, and if they are, we'll use // GetTempPath(); otherwise, we'll fall back to SHGetSpecialFolderPath(). if (getenv("TEMP") != NULL || getenv("TMP") != NULL) { if (GetTempPathW(MAX_PATH, buffer_1) != 0) { temp_directory_w = buffer_1; } } if (temp_directory_w.empty()) { if (SHGetSpecialFolderPathW(NULL, buffer_1, CSIDL_INTERNET_CACHE, true)) { temp_directory_w = buffer_1; // That just *might* return a non-writable folder, if we're in Protected // Mode. We'll test this with GetTempFileName(). wchar_t temp_buffer[MAX_PATH]; if (!GetTempFileNameW(temp_directory_w.c_str(), L"p3d", 0, temp_buffer)) { nout << "GetTempFileName failed on " << temp_directory_w << ", switching to GetTempPath\n"; temp_directory_w.clear(); } else { DeleteFileW(temp_buffer); } } } // If both of the above failed, we'll fall back to GetTempPath() once again // as a last resort, which is supposed to return *something* that works, // even if $TEMP and $TMP are undefined. if (temp_directory_w.empty()) { if (GetTempPathW(MAX_PATH, buffer_1) != 0) { temp_directory_w = buffer_1; } } // Also insist that the temp directory is fully specified. size_t needs_size_2 = GetFullPathNameW(temp_directory_w.c_str(), 0, NULL, NULL); wchar_t *buffer_2 = new wchar_t[needs_size_2]; if (GetFullPathNameW(temp_directory_w.c_str(), needs_size_2, buffer_2, NULL) != 0) { temp_directory_w = buffer_2; } delete[] buffer_2; // And make sure the directory actually exists. mkdir_complete_w(temp_directory_w, nout); wstring_to_string(_temp_directory, temp_directory_w); #else _temp_directory = "/tmp/"; #endif // _WIN32 // Ensure that the temp directory ends with a slash. if (!_temp_directory.empty() && _temp_directory[_temp_directory.size() - 1] != '/') { #ifdef _WIN32 if (_temp_directory[_temp_directory.size() - 1] != '\\') #endif _temp_directory += "/"; } nout << "\n_root_dir = " << _root_dir << ", _temp_directory = " << _temp_directory << ", platform = " << _platform << ", host_url = " << _host_url << ", verify_contents = " << _verify_contents << "\n"; if (!_host_dir.empty()) { nout << "_host_dir = " << _host_dir << "\n"; } nout << "api_version = " << _api_version << "\n"; // Make the certificate directory. _certs_dir = _root_dir + "/certs"; if (!get_trusted_environment()) { if (!mkdir_complete(_certs_dir, nout)) { nout << "Couldn't mkdir " << _certs_dir << "\n"; } } #ifndef _WIN32 // If we're running from the console, make sure that terminating the parent // process will cause the child process to terminate as well. if (_console_environment) { struct sigaction ignore; memset(&ignore, 0, sizeof(ignore)); ignore.sa_handler = SIG_IGN; sigaction(SIGINT, &ignore, NULL); } #endif _created_runtime_environment = true; } /** * Appends a single directory component, implementing append_safe_dir(), * above. */ void P3DInstanceManager:: append_safe_dir_component(string &root, const string &component) { if (component.empty()) { return; } root += '/'; if (component[0] == '.') { root += 'x'; } root += component; } /** * The main function for the notify thread. */ void P3DInstanceManager:: nt_thread_run() { /* * The notify thread exists because we need to be able to send asynchronous * notifications of request events. These request events were detected in the * various read threads associated with each session, but we can't call back * into the plugin host space from the read thread, since if the host * immediately responds to a callback by calling back into the p3d_plugin * space, we will have our read thread doing stuff in here that's not related * to the read thread. Even worse, some of the things it might need to do * might require a separate read thread to be running! */ _notify_ready.acquire(); while (_notify_thread_continue) { NotifyInstances instances; while (!_notify_instances.empty()) { instances.clear(); instances.swap(_notify_instances); // Go ahead and drop the lock while we make the callback, to reduce the // risk of deadlock. We don't want to be holding any locks when we call // into client code. _notify_ready.release(); NotifyInstances::iterator ni; for (ni = instances.begin(); ni != instances.end(); ++ni) { // TODO: a race condition here when instances are deleted. P3DInstance *inst = (*ni); assert(inst != NULL); P3D_request_ready_func *func = inst->get_request_ready_func(); if (func != NULL) { (*func)(inst); } } _notify_ready.acquire(); } _notify_ready.wait(); } _notify_ready.release(); } #ifdef _WIN32 bool P3DInstanceManager:: supports_win64() { BOOL is_win64 = false; typedef BOOL (WINAPI *LPFN_ISWOW64PROCESS) (HANDLE, PBOOL); LPFN_ISWOW64PROCESS _IsWow64Process; _IsWow64Process = (LPFN_ISWOW64PROCESS)GetProcAddress(GetModuleHandle("kernel32"), "IsWow64Process"); if (_IsWow64Process != NULL) { if (!_IsWow64Process(GetCurrentProcess(), &is_win64)) { is_win64 = false; } } return (is_win64 != 0); } #endif // _WIN32