diff --git a/src/Add-ons/Addon.cxx b/src/Add-ons/Addon.cxx index 895ed2138..7d7be7a22 100644 --- a/src/Add-ons/Addon.cxx +++ b/src/Add-ons/Addon.cxx @@ -24,6 +24,7 @@ #include #include +#include #include #include #include @@ -34,6 +35,7 @@ #include
#include
+#include
#include #include "addon_fwd.hxx" @@ -93,6 +95,7 @@ Addon::Addon(std::string id, AddonVersion version, SGPath basePath, _version( shared_ptr_traits::makeStrongRef(std::move(version))), _basePath(std::move(basePath)), + _storagePath(globals->get_fg_home() / ("Export/Addons/" + _id)), _minFGVersionRequired(std::move(minFGVersionRequired)), _maxFGVersionRequired(std::move(maxFGVersionRequired)), _addonNode(addonNode) @@ -187,6 +190,48 @@ SGPath Addon::getBasePath() const void Addon::setBasePath(const SGPath& addonBasePath) { _basePath = addonBasePath; } +SGPath Addon::getStoragePath() const +{ return _storagePath; } + +SGPath Addon::createStorageDir() const +{ + if (_storagePath.exists()) { + if (!_storagePath.isDir()) { + string msg = + "Unable to create add-on storage directory because the entry already " + "exists, but is not a directory: '" + _storagePath.utf8Str() + "'"; + // Log + throw, because if called from Nasal, only throwing would cause + // the exception message to 1) be truncated and 2) only appear in the + // log, not stopping the sim. Then users would have to figure out why + // their add-on doesn't work... + SG_LOG(SG_GENERAL, SG_POPUP, msg); + throw errors::unable_to_create_addon_storage_dir(msg); + } + } else { + SGPath authorizedPath = fgValidatePath(_storagePath, true /* write */); + + if (authorizedPath.isNull()) { + string msg = + "Unable to create add-on storage directory because of the FlightGear " + "security policy (refused by fgValidatePath()): '" + + _storagePath.utf8Str() + "'"; + SG_LOG(SG_GENERAL, SG_POPUP, msg); + throw errors::unable_to_create_addon_storage_dir(msg); + } else { + simgear::Dir(authorizedPath).create(0777); + } + } + + // The sensitive operation (creating the directory) is behind us; return + // _storagePath instead of authorizedPath for consistency with the + // getStoragePath() method (_storagePath and authorizedPath could be + // different in case the former contains symlink components). Further + // sensitive operations beneath _storagePath must use fgValidatePath() again + // every time, of course (otherwise attackers could use symlinks in + // _storagePath to bypass the security policy). + return _storagePath; +} + std::string Addon::resourcePath(const std::string& relativePath) const { if (strutils::starts_with(relativePath, "/")) { @@ -448,6 +493,8 @@ void Addon::setupGhost(nasal::Hash& addonsModule) .member("licenseUrl", &Addon::getLicenseUrl) .member("tags", &Addon::getTags) .member("basePath", &Addon::getBasePath) + .member("storagePath", &Addon::getStoragePath) + .method("createStorageDir", &Addon::createStorageDir) .method("resourcePath", &Addon::resourcePath) .member("minFGVersionRequired", &Addon::getMinFGVersionRequired) .member("maxFGVersionRequired", &Addon::getMaxFGVersionRequired) diff --git a/src/Add-ons/Addon.hxx b/src/Add-ons/Addon.hxx index 062216d43..20bf2a483 100644 --- a/src/Add-ons/Addon.hxx +++ b/src/Add-ons/Addon.hxx @@ -127,6 +127,13 @@ public: SGPath getBasePath() const; void setBasePath(const SGPath& addonBasePath); + // Return $FG_HOME/Export/Addons/ADDON_ID as an SGPath instance. + SGPath getStoragePath() const; + // Create directory $FG_HOME/Export/Addons/ADDON_ID, including any parent, + // if it doesn't already exist. Throw an exception in case of problems. + // Return an SGPath instance for the directory (same as getStoragePath()). + SGPath createStorageDir() const; + // Return a resource path suitable for use with the simgear::ResourceManager. // 'relativePath' is relative to the add-on base path, and should not start // with a '/'. @@ -218,6 +225,8 @@ private: std::vector _tags; SGPath _basePath; + // $FG_HOME/Export/Addons/ADDON_ID + const SGPath _storagePath; // To be used with simgear::strutils::compare_versions() std::string _minFGVersionRequired; diff --git a/src/Add-ons/addon_fwd.hxx b/src/Add-ons/addon_fwd.hxx index cb5ef9889..efa4e06cf 100644 --- a/src/Add-ons/addon_fwd.hxx +++ b/src/Add-ons/addon_fwd.hxx @@ -61,6 +61,7 @@ class duplicate_registration_attempt; class fg_version_too_old; class fg_version_too_recent; class invalid_resource_path; +class unable_to_create_addon_storage_dir; } // of namespace errors diff --git a/src/Add-ons/exceptions.hxx b/src/Add-ons/exceptions.hxx index 0fb162cf1..6539606b0 100644 --- a/src/Add-ons/exceptions.hxx +++ b/src/Add-ons/exceptions.hxx @@ -65,6 +65,9 @@ class fg_version_too_recent : public error class invalid_resource_path : public error { using error::error; }; +class unable_to_create_addon_storage_dir : public error +{ using error::error; }; + } // of namespace errors } // of namespace addons diff --git a/src/Main/fg_init.cxx b/src/Main/fg_init.cxx index aed942bc0..53a9a96a9 100644 --- a/src/Main/fg_init.cxx +++ b/src/Main/fg_init.cxx @@ -488,6 +488,21 @@ bool fgInitHome() return result; } +static void createBaseStorageDirForAddons(const SGPath& exportDir) +{ + SGPath addonStorageBasePath = exportDir / "Addons"; + if (addonStorageBasePath.exists()) { + if (!addonStorageBasePath.isDir()) { + throw sg_error( + "Unable to create add-on storage base directory, because the entry " + "already exists but is not a directory: '" + + addonStorageBasePath.utf8Str() + "'"); + } + } else { + simgear::Dir(addonStorageBasePath).create(0777); // respect user's umask + } +} + // Read in configuration (file and command line) int fgInitConfig ( int argc, char **argv, bool reinit ) { @@ -497,7 +512,11 @@ int fgInitConfig ( int argc, char **argv, bool reinit ) if (!exportDir.exists()) { exportDir.create(0755); } - + + // Reserve a directory where add-ons can write. There will be a subdir for + // each add-on, see Addon::createStorageDir() and Addon::getStoragePath(). + createBaseStorageDirForAddons(exportDir.path()); + // Set /sim/fg-home. Use FG_HOME if necessary. // deliberately not a tied property, for fgValidatePath security // write-protect to avoid accidents