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.
This commit is contained in:
parent
13d5fd4c25
commit
0cda3cbfb2
8 changed files with 153 additions and 47 deletions
|
@ -556,6 +556,10 @@ void FGAirport::loadProcedures() const
|
||||||
void FGAirport::loadSceneryDefinitions() const
|
void FGAirport::loadSceneryDefinitions() const
|
||||||
{
|
{
|
||||||
NavDataCache* cache = NavDataCache::instance();
|
NavDataCache* cache = NavDataCache::instance();
|
||||||
|
if (cache->isReadOnly()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
SGPath path;
|
SGPath path;
|
||||||
if (!XMLLoader::findAirportData(ident(), "threshold", path)) {
|
if (!XMLLoader::findAirportData(ident(), "threshold", path)) {
|
||||||
return; // no XML threshold data
|
return; // no XML threshold data
|
||||||
|
@ -636,6 +640,10 @@ void FGAirport::validateTowerData() const
|
||||||
|
|
||||||
mTowerDataLoaded = true;
|
mTowerDataLoaded = true;
|
||||||
NavDataCache* cache = NavDataCache::instance();
|
NavDataCache* cache = NavDataCache::instance();
|
||||||
|
if (cache->isReadOnly()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
SGPath path;
|
SGPath path;
|
||||||
if (!XMLLoader::findAirportData(ident(), "twr", path)) {
|
if (!XMLLoader::findAirportData(ident(), "twr", path)) {
|
||||||
return; // no XML tower data
|
return; // no XML tower data
|
||||||
|
@ -684,6 +692,10 @@ bool FGAirport::validateILSData()
|
||||||
|
|
||||||
mILSDataLoaded = true;
|
mILSDataLoaded = true;
|
||||||
NavDataCache* cache = NavDataCache::instance();
|
NavDataCache* cache = NavDataCache::instance();
|
||||||
|
if (cache->isReadOnly()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
SGPath path;
|
SGPath path;
|
||||||
if (!XMLLoader::findAirportData(ident(), "ils", path)) {
|
if (!XMLLoader::findAirportData(ident(), "ils", path)) {
|
||||||
return false; // no XML tower data
|
return false; // no XML tower data
|
||||||
|
|
|
@ -1057,7 +1057,9 @@ FGPUIDialog::makeObject (SGPropertyNode *props, int parentWidth, int parentHeigh
|
||||||
string logClass = props->getStringValue("logclass");
|
string logClass = props->getStringValue("logclass");
|
||||||
if (logClass == "terrasync") {
|
if (logClass == "terrasync") {
|
||||||
simgear::SGTerraSync* tsync = (simgear::SGTerraSync*) globals->get_subsystem("terrasync");
|
simgear::SGTerraSync* tsync = (simgear::SGTerraSync*) globals->get_subsystem("terrasync");
|
||||||
|
if (tsync) {
|
||||||
obj->setBuffer(tsync->log());
|
obj->setBuffer(tsync->log());
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
FGNasalSys* nasal = (FGNasalSys*) globals->get_subsystem("nasal");
|
FGNasalSys* nasal = (FGNasalSys*) globals->get_subsystem("nasal");
|
||||||
obj->setBuffer(nasal->log());
|
obj->setBuffer(nasal->log());
|
||||||
|
|
|
@ -25,17 +25,19 @@
|
||||||
# include <config.h>
|
# include <config.h>
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#include <simgear/compiler.h>
|
||||||
|
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <string.h> // strcmp()
|
#include <string.h> // strcmp()
|
||||||
|
|
||||||
#ifdef _WIN32
|
#if defined(SG_WINDOWS)
|
||||||
# include <io.h> // isatty()
|
# include <io.h> // isatty()
|
||||||
|
# include <process.h> // _getpid()
|
||||||
|
# include <Windows.h>
|
||||||
# define isatty _isatty
|
# define isatty _isatty
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#include <simgear/compiler.h>
|
|
||||||
|
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <boost/algorithm/string/compare.hpp>
|
#include <boost/algorithm/string/compare.hpp>
|
||||||
#include <boost/algorithm/string/predicate.hpp>
|
#include <boost/algorithm/string/predicate.hpp>
|
||||||
|
@ -407,7 +409,7 @@ static SGPath platformDefaultDataPath()
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
void fgInitHome()
|
bool fgInitHome()
|
||||||
{
|
{
|
||||||
SGPath dataPath = SGPath::fromEnv("FG_HOME", platformDefaultDataPath());
|
SGPath dataPath = SGPath::fromEnv("FG_HOME", platformDefaultDataPath());
|
||||||
globals->set_fg_home(dataPath.c_str());
|
globals->set_fg_home(dataPath.c_str());
|
||||||
|
@ -416,6 +418,66 @@ void fgInitHome()
|
||||||
if (!fgHome.exists()) {
|
if (!fgHome.exists()) {
|
||||||
fgHome.create(0755);
|
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)
|
// Read in configuration (file and command line)
|
||||||
|
|
|
@ -34,7 +34,7 @@ class SGPropertyNode;
|
||||||
// Return the current base package version
|
// Return the current base package version
|
||||||
std::string fgBasePackageVersion();
|
std::string fgBasePackageVersion();
|
||||||
|
|
||||||
void fgInitHome();
|
bool fgInitHome();
|
||||||
|
|
||||||
// Read in configuration (file and command line)
|
// Read in configuration (file and command line)
|
||||||
int fgInitConfig ( int argc, char **argv );
|
int fgInitConfig ( int argc, char **argv );
|
||||||
|
|
|
@ -96,6 +96,39 @@ static void fgMainLoop( void )
|
||||||
simgear::AtomicChangeListener::fireChangeListeners();
|
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()
|
static void registerMainLoop()
|
||||||
{
|
{
|
||||||
|
@ -127,35 +160,7 @@ static void fgIdleFunction ( void ) {
|
||||||
|
|
||||||
} else if ( idle_state == 2 ) {
|
} else if ( idle_state == 2 ) {
|
||||||
|
|
||||||
// start TerraSync up now, so it can be synchronizing shared models
|
initTerrasync();
|
||||||
// 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);
|
|
||||||
|
|
||||||
idle_state++;
|
idle_state++;
|
||||||
fgSplashProgress("loading-nav-dat");
|
fgSplashProgress("loading-nav-dat");
|
||||||
|
|
||||||
|
@ -325,10 +330,14 @@ int fgMainInit( int argc, char **argv ) {
|
||||||
sglog().setLogLevels( SG_ALL, SG_ALERT );
|
sglog().setLogLevels( SG_ALL, SG_ALERT );
|
||||||
|
|
||||||
globals = new FGGlobals;
|
globals = new FGGlobals;
|
||||||
fgInitHome();
|
if (!fgInitHome()) {
|
||||||
|
return EXIT_FAILURE;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!fgGetBool("/sim/fghome-readonly")) {
|
||||||
// now home is initialised, we can log to a file inside it
|
// now home is initialised, we can log to a file inside it
|
||||||
logToFile();
|
logToFile();
|
||||||
|
}
|
||||||
|
|
||||||
std::string version;
|
std::string version;
|
||||||
#ifdef FLIGHTGEAR_VERSION
|
#ifdef FLIGHTGEAR_VERSION
|
||||||
|
|
|
@ -1409,6 +1409,7 @@ struct OptionDesc {
|
||||||
{"enable-fullscreen", false, OPTION_BOOL, "/sim/startup/fullscreen", true, "", 0 },
|
{"enable-fullscreen", false, OPTION_BOOL, "/sim/startup/fullscreen", true, "", 0 },
|
||||||
{"disable-save-on-exit", false, OPTION_BOOL, "/sim/startup/save-on-exit", false, "", 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 },
|
{"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 },
|
{"ignore-autosave", false, OPTION_FUNC, "", false, "", fgOptIgnoreAutosave },
|
||||||
{"restore-defaults", false, OPTION_BOOL, "/sim/startup/restore-defaults", true, "", 0 },
|
{"restore-defaults", false, OPTION_BOOL, "/sim/startup/restore-defaults", true, "", 0 },
|
||||||
{"shading-flat", false, OPTION_BOOL, "/sim/rendering/shading", false, "", 0 },
|
{"shading-flat", false, OPTION_BOOL, "/sim/rendering/shading", false, "", 0 },
|
||||||
|
|
|
@ -58,6 +58,7 @@
|
||||||
#include <simgear/threads/SGGuard.hxx>
|
#include <simgear/threads/SGGuard.hxx>
|
||||||
|
|
||||||
#include <Main/globals.hxx>
|
#include <Main/globals.hxx>
|
||||||
|
#include <Main/fg_props.hxx>
|
||||||
#include <Main/options.hxx>
|
#include <Main/options.hxx>
|
||||||
#include "markerbeacon.hxx"
|
#include "markerbeacon.hxx"
|
||||||
#include "navrecord.hxx"
|
#include "navrecord.hxx"
|
||||||
|
@ -209,6 +210,7 @@ public:
|
||||||
outer(o),
|
outer(o),
|
||||||
db(NULL),
|
db(NULL),
|
||||||
path(p),
|
path(p),
|
||||||
|
readOnly(false),
|
||||||
cacheHits(0),
|
cacheHits(0),
|
||||||
cacheMisses(0),
|
cacheMisses(0),
|
||||||
transactionLevel(0),
|
transactionLevel(0),
|
||||||
|
@ -225,12 +227,14 @@ public:
|
||||||
{
|
{
|
||||||
SG_LOG(SG_NAVCACHE, SG_INFO, "NavCache at:" << path);
|
SG_LOG(SG_NAVCACHE, SG_INFO, "NavCache at:" << path);
|
||||||
|
|
||||||
// see http://code.google.com/p/flightgear-bugs/issues/detail?id=1055
|
readOnly = fgGetBool("/sim/fghome-readonly", false);
|
||||||
// for the logic here. Sigh.
|
|
||||||
std::string pathUtf8 = simgear::strutils::convertWindowsLocal8BitToUtf8(path.str());
|
|
||||||
sqlite3_open_v2(pathUtf8.c_str(), &db,
|
|
||||||
SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE, NULL);
|
|
||||||
|
|
||||||
|
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, openFlags, NULL);
|
||||||
|
|
||||||
sqlite3_stmt_ptr checkTables =
|
sqlite3_stmt_ptr checkTables =
|
||||||
prepare("SELECT count(*) FROM sqlite_master WHERE name='properties'");
|
prepare("SELECT count(*) FROM sqlite_master WHERE name='properties'");
|
||||||
|
@ -240,7 +244,7 @@ public:
|
||||||
|
|
||||||
execSelect(checkTables);
|
execSelect(checkTables);
|
||||||
bool didCreate = false;
|
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");
|
SG_LOG(SG_NAVCACHE, SG_INFO, "will create tables");
|
||||||
initTables();
|
initTables();
|
||||||
didCreate = true;
|
didCreate = true;
|
||||||
|
@ -858,6 +862,7 @@ public:
|
||||||
NavDataCache* outer;
|
NavDataCache* outer;
|
||||||
sqlite3* db;
|
sqlite3* db;
|
||||||
SGPath path;
|
SGPath path;
|
||||||
|
bool readOnly;
|
||||||
|
|
||||||
/// the actual cache of ID -> instances. This holds an owning reference,
|
/// the actual cache of ID -> instances. This holds an owning reference,
|
||||||
/// so once items are in the cache they will never be deleted until
|
/// so once items are in the cache they will never be deleted until
|
||||||
|
@ -1048,8 +1053,12 @@ NavDataCache::NavDataCache()
|
||||||
SG_LOG(SG_NAVCACHE, t == 0 ? SG_WARN : SG_ALERT, "NavCache: init failed:" << e.what()
|
SG_LOG(SG_NAVCACHE, t == 0 ? SG_WARN : SG_ALERT, "NavCache: init failed:" << e.what()
|
||||||
<< " (attempt " << t << ")");
|
<< " (attempt " << t << ")");
|
||||||
d.reset();
|
d.reset();
|
||||||
|
|
||||||
|
// only wipe the existing if not readonly
|
||||||
|
if (!fgGetBool("/sim/fghome-readonly", false)) {
|
||||||
homePath.remove();
|
homePath.remove();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
} // of retry loop
|
} // of retry loop
|
||||||
|
|
||||||
double RADIUS_EARTH_M = 7000 * 1000.0; // 7000km is plenty
|
double RADIUS_EARTH_M = 7000 * 1000.0; // 7000km is plenty
|
||||||
|
@ -1097,6 +1106,10 @@ NavDataCache* NavDataCache::instance()
|
||||||
|
|
||||||
bool NavDataCache::isRebuildRequired()
|
bool NavDataCache::isRebuildRequired()
|
||||||
{
|
{
|
||||||
|
if (d->readOnly) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
if (flightgear::Options::sharedInstance()->isOptionSet("restore-defaults")) {
|
if (flightgear::Options::sharedInstance()->isOptionSet("restore-defaults")) {
|
||||||
SG_LOG(SG_NAVCACHE, SG_INFO, "NavCache: restore-defaults requested, will rebuild cache");
|
SG_LOG(SG_NAVCACHE, SG_INFO, "NavCache: restore-defaults requested, will rebuild cache");
|
||||||
return true;
|
return true;
|
||||||
|
@ -2148,6 +2161,11 @@ void NavDataCache::dropGroundnetFor(PositionedID aAirport)
|
||||||
d->execUpdate(q);
|
d->execUpdate(q);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool NavDataCache::isReadOnly() const
|
||||||
|
{
|
||||||
|
return d->readOnly;
|
||||||
|
}
|
||||||
|
|
||||||
/////////////////////////////////////////////////////////////////////////////////////////
|
/////////////////////////////////////////////////////////////////////////////////////////
|
||||||
// Transaction RAII object
|
// Transaction RAII object
|
||||||
|
|
||||||
|
|
|
@ -267,6 +267,8 @@ public:
|
||||||
NavDataCache* _instance;
|
NavDataCache* _instance;
|
||||||
bool _committed;
|
bool _committed;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
bool isReadOnly() const;
|
||||||
private:
|
private:
|
||||||
NavDataCache();
|
NavDataCache();
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue