From 0cda3cbfb2754aa77aa2699720fd0c57cfb9b9d4 Mon Sep 17 00:00:00 2001 From: James Turner Date: Thu, 14 Nov 2013 16:48:14 +0000 Subject: [PATCH] Multiple-instance support. Write PID file to FG_HOME, use this to detect multiple launches. When this situation is detected, set a marker property and place various objects into read-only mode, such as the NavCache and TerraSync. PID file is created using open+unlink semantics on POSIX, and DELETE_ON_CLOSE on Windows, so it will be removed when fgfs exits, even if killed or crashes. --- src/Airports/airport.cxx | 12 ++++++ src/GUI/FGPUIDialog.cxx | 4 +- src/Main/fg_init.cxx | 70 +++++++++++++++++++++++++++++++-- src/Main/fg_init.hxx | 2 +- src/Main/main.cxx | 75 ++++++++++++++++++++---------------- src/Main/options.cxx | 1 + src/Navaids/NavDataCache.cxx | 34 ++++++++++++---- src/Navaids/NavDataCache.hxx | 2 + 8 files changed, 153 insertions(+), 47 deletions(-) diff --git a/src/Airports/airport.cxx b/src/Airports/airport.cxx index 42d76f5ba..2b6020d9f 100644 --- a/src/Airports/airport.cxx +++ b/src/Airports/airport.cxx @@ -556,6 +556,10 @@ void FGAirport::loadProcedures() const void FGAirport::loadSceneryDefinitions() const { NavDataCache* cache = NavDataCache::instance(); + if (cache->isReadOnly()) { + return; + } + SGPath path; if (!XMLLoader::findAirportData(ident(), "threshold", path)) { return; // no XML threshold data @@ -636,6 +640,10 @@ void FGAirport::validateTowerData() const mTowerDataLoaded = true; NavDataCache* cache = NavDataCache::instance(); + if (cache->isReadOnly()) { + return; + } + SGPath path; if (!XMLLoader::findAirportData(ident(), "twr", path)) { return; // no XML tower data @@ -684,6 +692,10 @@ bool FGAirport::validateILSData() mILSDataLoaded = true; NavDataCache* cache = NavDataCache::instance(); + if (cache->isReadOnly()) { + return false; + } + SGPath path; if (!XMLLoader::findAirportData(ident(), "ils", path)) { return false; // no XML tower data diff --git a/src/GUI/FGPUIDialog.cxx b/src/GUI/FGPUIDialog.cxx index 5620ae0fc..a57aeba5e 100644 --- a/src/GUI/FGPUIDialog.cxx +++ b/src/GUI/FGPUIDialog.cxx @@ -1057,7 +1057,9 @@ FGPUIDialog::makeObject (SGPropertyNode *props, int parentWidth, int parentHeigh string logClass = props->getStringValue("logclass"); if (logClass == "terrasync") { simgear::SGTerraSync* tsync = (simgear::SGTerraSync*) globals->get_subsystem("terrasync"); - obj->setBuffer(tsync->log()); + if (tsync) { + obj->setBuffer(tsync->log()); + } } else { FGNasalSys* nasal = (FGNasalSys*) globals->get_subsystem("nasal"); obj->setBuffer(nasal->log()); diff --git a/src/Main/fg_init.cxx b/src/Main/fg_init.cxx index 8ad9803f9..f478098b5 100644 --- a/src/Main/fg_init.cxx +++ b/src/Main/fg_init.cxx @@ -25,17 +25,19 @@ # include #endif +#include + #include #include #include // strcmp() -#ifdef _WIN32 +#if defined(SG_WINDOWS) # include // isatty() +# include // _getpid() +# include # define isatty _isatty #endif -#include - #include #include #include @@ -407,7 +409,7 @@ static SGPath platformDefaultDataPath() } #endif -void fgInitHome() +bool fgInitHome() { SGPath dataPath = SGPath::fromEnv("FG_HOME", platformDefaultDataPath()); globals->set_fg_home(dataPath.c_str()); @@ -416,6 +418,66 @@ void fgInitHome() if (!fgHome.exists()) { fgHome.create(0755); } + + if (!fgHome.exists()) { + flightgear::fatalMessageBox("Problem setting up user data", + "Unable to create the user-data storage folder at: '" + + dataPath.str() + "'"); + return false; + } + + if (fgGetBool("/sim/fghome-readonly", false)) { + // user / config forced us into readonly mode, fine + SG_LOG(SG_GENERAL, SG_INFO, "Running with FG_HOME readonly"); + return true; + } + +// write our PID, and check writeability + SGPath pidPath(dataPath, "fgfs.pid"); + if (pidPath.exists()) { + SG_LOG(SG_GENERAL, SG_INFO, "flightgear instance already running, switching to FG_HOME read-only."); + // set a marker property so terrasync/navcache don't try to write + // from secondary instances + fgSetBool("/sim/fghome-readonly", true); + return true; + } + + char buf[16]; + bool result = false; +#if defined(SG_WINDOWS) + size_t len = snprintf(buf, 16, "%d", _getpid()); + + HANDLE f = CreateFileA(pidPath.c_str(), GENERIC_READ | GENERIC_WRITE, + FILE_SHARE_READ, /* sharing */ + NULL, /* security attributes */ + CREATE_NEW, /* error if already exists */ + FILE_FLAG_DELETE_ON_CLOSE, + NULL /* template */); + + result = (f != INVALID_HANDLE_VALUE); + if (result) { + DWORD written; + WriteFile(f, buf, len, &written, NULL /* overlapped */); + } +#else + // POSIX, do open+unlink trick to the file is deleted on exit, even if we + // crash or exit(-1) + size_t len = snprintf(buf, 16, "%d", getpid()); + int fd = ::open(pidPath.c_str(), O_WRONLY | O_CREAT | O_TRUNC | O_EXCL, 0644); + if (fd >= 0) { + ::write(fd, buf, len); + ::unlink(pidPath.c_str()); // delete file when app quits + result = true; + } + + fgSetBool("/sim/fghome-readonly", false); +#endif + if (!result) { + flightgear::fatalMessageBox("File permissions problem", + "Can't write to user-data storage folder, check file permissions and FG_HOME.", + "User-data at:" + dataPath.str()); + } + return result; } // Read in configuration (file and command line) diff --git a/src/Main/fg_init.hxx b/src/Main/fg_init.hxx index 5329b0381..ffadf6d22 100644 --- a/src/Main/fg_init.hxx +++ b/src/Main/fg_init.hxx @@ -34,7 +34,7 @@ class SGPropertyNode; // Return the current base package version std::string fgBasePackageVersion(); -void fgInitHome(); +bool fgInitHome(); // Read in configuration (file and command line) int fgInitConfig ( int argc, char **argv ); diff --git a/src/Main/main.cxx b/src/Main/main.cxx index 3f82429bf..84f03a6fd 100644 --- a/src/Main/main.cxx +++ b/src/Main/main.cxx @@ -96,6 +96,39 @@ static void fgMainLoop( void ) simgear::AtomicChangeListener::fireChangeListeners(); } +static void initTerrasync() +{ + if (fgGetBool("/sim/fghome-readonly", false)) { + return; + } + + // start TerraSync up now, so it can be synchronizing shared models + // and airports data in parallel with a nav-cache rebuild. + SGPath tsyncCache(globals->get_fg_home()); + tsyncCache.append("terrasync-cache.xml"); + + // wipe the cache file if requested + if (flightgear::Options::sharedInstance()->isOptionSet("restore-defaults")) { + SG_LOG(SG_GENERAL, SG_INFO, "restore-defaults requested, wiping terrasync update cache at " << + tsyncCache); + if (tsyncCache.exists()) { + tsyncCache.remove(); + } + } + + fgSetString("/sim/terrasync/cache-path", tsyncCache.c_str()); + + simgear::SGTerraSync* terra_sync = new simgear::SGTerraSync(); + terra_sync->setRoot(globals->get_props()); + globals->add_subsystem("terrasync", terra_sync); + + terra_sync->bind(); + terra_sync->init(); + + // add the terrasync root as a data path so data can be retrieved from it + std::string terraSyncDir(fgGetString("/sim/terrasync/scenery-dir")); + globals->append_data_path(terraSyncDir); +} static void registerMainLoop() { @@ -126,36 +159,8 @@ static void fgIdleFunction ( void ) { } } else if ( idle_state == 2 ) { - - // start TerraSync up now, so it can be synchronizing shared models - // and airports data in parallel with a nav-cache rebuild. - SGPath tsyncCache(globals->get_fg_home()); - tsyncCache.append("terrasync-cache.xml"); - - // wipe the cache file if requested - if (flightgear::Options::sharedInstance()->isOptionSet("restore-defaults")) { - SG_LOG(SG_GENERAL, SG_INFO, "restore-defaults requested, wiping terrasync update cache at " << - tsyncCache); - if (tsyncCache.exists()) { - tsyncCache.remove(); - } - } - - fgSetString("/sim/terrasync/cache-path", tsyncCache.c_str()); - - simgear::SGTerraSync* terra_sync = new simgear::SGTerraSync(); - terra_sync->setRoot(globals->get_props()); - globals->add_subsystem("terrasync", terra_sync); - - - - terra_sync->bind(); - terra_sync->init(); - - // add the terrasync root as a data path so data can be retrieved from it - std::string terraSyncDir(fgGetString("/sim/terrasync/scenery-dir")); - globals->append_data_path(terraSyncDir); - + + initTerrasync(); idle_state++; fgSplashProgress("loading-nav-dat"); @@ -325,10 +330,14 @@ int fgMainInit( int argc, char **argv ) { sglog().setLogLevels( SG_ALL, SG_ALERT ); globals = new FGGlobals; - fgInitHome(); + if (!fgInitHome()) { + return EXIT_FAILURE; + } - // now home is initialised, we can log to a file inside it - logToFile(); + if (!fgGetBool("/sim/fghome-readonly")) { + // now home is initialised, we can log to a file inside it + logToFile(); + } std::string version; #ifdef FLIGHTGEAR_VERSION diff --git a/src/Main/options.cxx b/src/Main/options.cxx index f0906b966..eecb6e668 100644 --- a/src/Main/options.cxx +++ b/src/Main/options.cxx @@ -1409,6 +1409,7 @@ struct OptionDesc { {"enable-fullscreen", false, OPTION_BOOL, "/sim/startup/fullscreen", true, "", 0 }, {"disable-save-on-exit", false, OPTION_BOOL, "/sim/startup/save-on-exit", false, "", 0 }, {"enable-save-on-exit", false, OPTION_BOOL, "/sim/startup/save-on-exit", true, "", 0 }, + {"read-only", false, OPTION_BOOL, "/sim/fghome-readonly", true, "", 0 }, {"ignore-autosave", false, OPTION_FUNC, "", false, "", fgOptIgnoreAutosave }, {"restore-defaults", false, OPTION_BOOL, "/sim/startup/restore-defaults", true, "", 0 }, {"shading-flat", false, OPTION_BOOL, "/sim/rendering/shading", false, "", 0 }, diff --git a/src/Navaids/NavDataCache.cxx b/src/Navaids/NavDataCache.cxx index 28b8a636b..80422483d 100644 --- a/src/Navaids/NavDataCache.cxx +++ b/src/Navaids/NavDataCache.cxx @@ -58,6 +58,7 @@ #include #include
+#include
#include
#include "markerbeacon.hxx" #include "navrecord.hxx" @@ -209,6 +210,7 @@ public: outer(o), db(NULL), path(p), + readOnly(false), cacheHits(0), cacheMisses(0), transactionLevel(0), @@ -225,12 +227,14 @@ public: { SG_LOG(SG_NAVCACHE, SG_INFO, "NavCache at:" << path); - // see http://code.google.com/p/flightgear-bugs/issues/detail?id=1055 - // for the logic here. Sigh. + readOnly = fgGetBool("/sim/fghome-readonly", false); + + int openFlags = readOnly ? SQLITE_OPEN_READONLY : + SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE; + // see http://code.google.com/p/flightgear-bugs/issues/detail?id=1055 + // for the UTF8 / path logic here std::string pathUtf8 = simgear::strutils::convertWindowsLocal8BitToUtf8(path.str()); - sqlite3_open_v2(pathUtf8.c_str(), &db, - SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE, NULL); - + sqlite3_open_v2(pathUtf8.c_str(), &db, openFlags, NULL); sqlite3_stmt_ptr checkTables = prepare("SELECT count(*) FROM sqlite_master WHERE name='properties'"); @@ -240,7 +244,7 @@ public: execSelect(checkTables); bool didCreate = false; - if (sqlite3_column_int(checkTables, 0) == 0) { + if (!readOnly && (sqlite3_column_int(checkTables, 0) == 0)) { SG_LOG(SG_NAVCACHE, SG_INFO, "will create tables"); initTables(); didCreate = true; @@ -858,7 +862,8 @@ public: NavDataCache* outer; sqlite3* db; SGPath path; - + bool readOnly; + /// the actual cache of ID -> instances. This holds an owning reference, /// so once items are in the cache they will never be deleted until /// the cache drops its reference @@ -1048,7 +1053,11 @@ NavDataCache::NavDataCache() SG_LOG(SG_NAVCACHE, t == 0 ? SG_WARN : SG_ALERT, "NavCache: init failed:" << e.what() << " (attempt " << t << ")"); d.reset(); - homePath.remove(); + + // only wipe the existing if not readonly + if (!fgGetBool("/sim/fghome-readonly", false)) { + homePath.remove(); + } } } // of retry loop @@ -1097,6 +1106,10 @@ NavDataCache* NavDataCache::instance() bool NavDataCache::isRebuildRequired() { + if (d->readOnly) { + return false; + } + if (flightgear::Options::sharedInstance()->isOptionSet("restore-defaults")) { SG_LOG(SG_NAVCACHE, SG_INFO, "NavCache: restore-defaults requested, will rebuild cache"); return true; @@ -2148,6 +2161,11 @@ void NavDataCache::dropGroundnetFor(PositionedID aAirport) d->execUpdate(q); } +bool NavDataCache::isReadOnly() const +{ + return d->readOnly; +} + ///////////////////////////////////////////////////////////////////////////////////////// // Transaction RAII object diff --git a/src/Navaids/NavDataCache.hxx b/src/Navaids/NavDataCache.hxx index ed6579249..4975b0954 100644 --- a/src/Navaids/NavDataCache.hxx +++ b/src/Navaids/NavDataCache.hxx @@ -267,6 +267,8 @@ public: NavDataCache* _instance; bool _committed; }; + + bool isReadOnly() const; private: NavDataCache();