/** * @file sandbox.cpp * @copyright 2013 Jolla Ltd. * @author Martin Kampas * @date 2013 */ #include "sandbox_p.h" #include #include #include #include #include #include #include "libssu/ssucoreconfig_p.h" #include "constants.h" /** * @class Sandbox * @brief Helps to redirect file operations into sandbox directory * * The term world files is used to reffer files outside sandbox. * * To write a sandbox aware code, simply use the map() function to process paths * before accessing them. * * @code * QFile data(Sandbox::map("/usr/share/my_app/data")); * data.open(); * ... * @endcode * * Its effect is controlled by activate() and deactivate() calls. Only one * Sandbox instance can be active at any time. Active sandbox is automatically * deactivated upon destruction. * * When constructed without arguments, path to sandbox directory is get from * @c SSU_SANDBOX_DIR environment variable. * * @attention When constructed without arguments, it is activated automatically * and failure to do so is reported with @c qFatal(), i.e., application will be * abort()ed. * * When constructed with @a usage UseAsSkeleton, it will first make temporary * copy of @a sandboxPath to work on and files in the original directory will * stay untouched. Also see addWorldFiles(). * * The argument @scopes allows to control if the sandbox will be used by this * process, its children processes (@c SSU_SANDBOX_DIR environment variable * will be exported), or both. */ Sandbox *Sandbox::s_activeInstance = 0; Sandbox::Sandbox() : m_defaultConstructed(true), m_usage(UseDirectly), m_scopes(ThisProcess), m_sandboxPath(QProcessEnvironment::systemEnvironment().value("SSU_SANDBOX_DIR")), m_prepared(false) { if (!activate()) { qFatal("%s: Failed to activate", Q_FUNC_INFO); } } Sandbox::Sandbox(const QString &sandboxPath, Usage usage, Scopes scopes) : m_defaultConstructed(false), m_usage(usage), m_scopes(scopes), m_sandboxPath(sandboxPath), m_prepared(false) { Q_ASSERT(!sandboxPath.isEmpty()); } Sandbox::~Sandbox() { if (isActive()) { deactivate(); } if (!m_tempDir.isEmpty() && QFileInfo(m_tempDir).exists()) { if (!QDir(m_tempDir).removeRecursively()) { qWarning("%s: Failed to remove temporary directory", Q_FUNC_INFO); } } } bool Sandbox::isActive() const { return s_activeInstance == this; } bool Sandbox::activate() { Q_ASSERT_X(s_activeInstance == 0, Q_FUNC_INFO, "Only one instance can be active!"); if (!prepare()) { return false; } if (m_scopes & ChildProcesses) { setenv("SSU_SANDBOX_DIR", qPrintable(m_workingSandboxDir.path()), 1); } s_activeInstance = this; return true; } void Sandbox::deactivate() { Q_ASSERT(isActive()); if (m_scopes & ChildProcesses) { unsetenv("SSU_SANDBOX_DIR"); } s_activeInstance = 0; } QDir Sandbox::effectiveRootDir() { return s_activeInstance != 0 && s_activeInstance->m_scopes & ThisProcess ? s_activeInstance->m_workingSandboxDir : QDir::root(); } QString Sandbox::map(const QString &fileName) { return effectiveRootDir().filePath( QDir::root().relativeFilePath( QFileInfo(fileName).absoluteFilePath())); } QString Sandbox::map(const QString &pathName, const QString &fileName) { return effectiveRootDir().filePath( QDir::root().relativeFilePath( QFileInfo(pathName + fileName).absoluteFilePath())); } /** * Copies selected files into sandbox. Existing files in sandbox are not overwriten. * * @c QDir::NoDotAndDotDot is always added into @a filters. */ bool Sandbox::addWorldFiles(const QString &directory, QDir::Filters filters, const QStringList &filterNames, bool recurse) { Q_ASSERT(!isActive()); Q_ASSERT(!directory.isEmpty()); if (!prepare()) { return false; } const QString sandboxedDirectory = m_workingSandboxDir.filePath( QDir::root().relativeFilePath( QFileInfo(directory).absoluteFilePath())); if (!QFileInfo(directory).exists()) { // Accept missing world directory - allow to create directories inside sandbox qDebug("%s: Directory does not exist - an empty one will be created instead of copied: '%s'", Q_FUNC_INFO, qPrintable(directory)); } else if (!QFileInfo(directory).isDir()) { qWarning("%s: Is not a directory: '%s'", Q_FUNC_INFO, qPrintable(directory)); return false; } if (!QFileInfo(sandboxedDirectory).exists()) { if (!QDir().mkpath(sandboxedDirectory)) { qWarning("%s: Failed to create sandbox directory '%s'", Q_FUNC_INFO, qPrintable(sandboxedDirectory)); return false; } } else if (!QFileInfo(sandboxedDirectory).isDir()) { qWarning("%s: Failed to create sandbox directory '%s': Is not a directory", Q_FUNC_INFO, qPrintable(sandboxedDirectory)); return false; } if (filters == QDir::NoFilter) { filters = QDir::AllEntries; } filters |= QDir::NoDotAndDotDot; foreach (const QFileInfo &worldEntryInfo, QDir(directory).entryInfoList(filterNames, filters)) { const QFileInfo sandboxEntryInfo(QDir(sandboxedDirectory).filePath(worldEntryInfo.fileName())); if (worldEntryInfo.isDir()) { if (!sandboxEntryInfo.exists()) { if (!QDir(sandboxedDirectory).mkdir(worldEntryInfo.fileName())) { qWarning("%s: Failed to create overlay directory '%s/%s'", Q_FUNC_INFO, qPrintable(sandboxedDirectory), qPrintable(worldEntryInfo.fileName())); return false; } } else if (!sandboxEntryInfo.isDir()) { qWarning("%s: Failed to create sandboxed copy '%s': Is not a directory", Q_FUNC_INFO, qPrintable(sandboxEntryInfo.filePath())); return false; } if (recurse) { if (!addWorldFiles(worldEntryInfo.absoluteFilePath(), filters, filterNames, true)) { return false; } } } else { if (!sandboxEntryInfo.exists()) { if (!QFile(worldEntryInfo.filePath()).copy(sandboxEntryInfo.filePath())) { qWarning("%s: Failed to copy file into sandbox '%s'", Q_FUNC_INFO, qPrintable(worldEntryInfo.filePath())); return false; } } else if (sandboxEntryInfo.isDir()) { qWarning("%s: Failed to create sandboxed copy '%s': Is a directory", Q_FUNC_INFO, qPrintable(sandboxEntryInfo.filePath())); return false; } } } return true; } bool Sandbox::addWorldFile(const QString &file) { return addWorldFiles(QFileInfo(file).path(), QDir::NoFilter, QStringList() << QFileInfo(file).fileName()); } bool Sandbox::prepare() { Q_ASSERT(m_defaultConstructed || !m_sandboxPath.isEmpty()); if (m_prepared) { return true; } if (m_sandboxPath.isEmpty()) { return true; } if (!QFileInfo(m_sandboxPath).exists()) { qWarning("%s: Invalid sandboxPath: No such file or directory", qPrintable(m_sandboxPath)); return false; } if (!QFileInfo(m_sandboxPath).isDir()) { qWarning("%s: Invalid sandboxPath: Not a directory", qPrintable(m_sandboxPath)); return false; } if (m_usage == UseAsSkeleton) { if (m_tempDir = createTmpDir("ssu-sandbox.%1"), m_tempDir.isEmpty()) { qWarning("%s: Failed to create sandbox directory", Q_FUNC_INFO); return false; } const QString sandboxCopyPath = QDir(m_tempDir).filePath("configroot"); if (!copyDir(m_sandboxPath, sandboxCopyPath)) { qWarning("%s: Failed to copy sandbox directory", Q_FUNC_INFO); return false; } m_workingSandboxDir = QDir(sandboxCopyPath); } else { m_workingSandboxDir = QDir(m_sandboxPath); } m_prepared = true; return true; } QString Sandbox::createTmpDir(const QString &nameTemplate) { const int REASONABLE_REPEAT_COUNT = 10; for (int i = 0; i < REASONABLE_REPEAT_COUNT; ++i) { QString path; int suffix = 0; do { path = QDir::temp().filePath(nameTemplate.arg(++suffix)); } while (QFileInfo(path).exists()); if (QDir().mkpath(path)) { return path; } } qWarning("%s: Failed to create temporary directory", Q_FUNC_INFO); return QString(); } bool Sandbox::copyDir(const QString &directory, const QString &newName) { const QDir sourceRoot(directory); const QDir destinationRoot(newName); QDirIterator it(directory, QDir::AllEntries | QDir::NoDotAndDotDot, QDirIterator::Subdirectories); while (it.hasNext()) { it.next(); const QFileInfo destination = QFileInfo(destinationRoot.filePath( sourceRoot.relativeFilePath(it.filePath()))); if (it.fileInfo().isDir()) { if (!QDir().mkpath(destination.absoluteFilePath())) { qWarning("%s: Failed to mkpath '%s'", Q_FUNC_INFO, qPrintable(destination.absolutePath())); return false; } } else if (it.fileInfo().isFile()) { if (!QDir().mkpath(destination.absolutePath())) { qWarning("%s: Failed to mkpath '%s'", Q_FUNC_INFO, qPrintable(destination.absolutePath())); return false; } if (!QFile::copy(it.fileInfo().absoluteFilePath(), destination.absoluteFilePath())) { qWarning("%s: Failed to copy file '%s'", Q_FUNC_INFO, qPrintable(it.filePath())); return false; } } else { qWarning("%s: Cannot copy other than regular files: '%s'", Q_FUNC_INFO, qPrintable(it.filePath())); return false; } } return true; }