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.
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
mTowerDataLoaded = true;
NavDataCache* cache = NavDataCache::instance();
+ if (cache->isReadOnly()) {
+ return;
+ }
+
SGPath path;
if (!XMLLoader::findAirportData(ident(), "twr", path)) {
return; // no XML tower data
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
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());
# include <config.h>
#endif
+#include <simgear/compiler.h>
+
#include <stdio.h>
#include <stdlib.h>
#include <string.h> // strcmp()
-#ifdef _WIN32
+#if defined(SG_WINDOWS)
# include <io.h> // isatty()
+# include <process.h> // _getpid()
+# include <Windows.h>
# define isatty _isatty
#endif
-#include <simgear/compiler.h>
-
#include <string>
#include <boost/algorithm/string/compare.hpp>
#include <boost/algorithm/string/predicate.hpp>
}
#endif
-void fgInitHome()
+bool fgInitHome()
{
SGPath dataPath = SGPath::fromEnv("FG_HOME", platformDefaultDataPath());
globals->set_fg_home(dataPath.c_str());
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)
// 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 );
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()
{
}
} 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");
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
{"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 },
#include <simgear/threads/SGGuard.hxx>
#include <Main/globals.hxx>
+#include <Main/fg_props.hxx>
#include <Main/options.hxx>
#include "markerbeacon.hxx"
#include "navrecord.hxx"
outer(o),
db(NULL),
path(p),
+ readOnly(false),
cacheHits(0),
cacheMisses(0),
transactionLevel(0),
{
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'");
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;
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
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
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;
d->execUpdate(q);
}
+bool NavDataCache::isReadOnly() const
+{
+ return d->readOnly;
+}
+
/////////////////////////////////////////////////////////////////////////////////////////
// Transaction RAII object
NavDataCache* _instance;
bool _committed;
};
+
+ bool isReadOnly() const;
private:
NavDataCache();