#include "paths.hpp" #include #include #include #include #include #include #include #include #include #include #include #include #include "instanceinfo.hpp" Q_LOGGING_CATEGORY(logPaths, "quickshell.paths", QtWarningMsg); QsPaths* QsPaths::instance() { static auto* instance = new QsPaths(); // NOLINT return instance; } void QsPaths::init(QString shellId, QString pathId) { auto* instance = QsPaths::instance(); instance->shellId = std::move(shellId); instance->pathId = std::move(pathId); } QDir QsPaths::crashDir(const QString& id) { auto dir = QDir(QStandardPaths::writableLocation(QStandardPaths::CacheLocation)); dir = QDir(dir.filePath("crashes")); dir = QDir(dir.filePath(id)); return dir; } QString QsPaths::basePath(const QString& id) { auto path = QsPaths::instance()->baseRunDir()->filePath("by-id"); path = QDir(path).filePath(id); return path; } QString QsPaths::ipcPath(const QString& id) { return QDir(QsPaths::basePath(id)).filePath("ipc.sock"); } QDir* QsPaths::cacheDir() { if (this->cacheState == DirState::Unknown) { auto dir = QDir(QStandardPaths::writableLocation(QStandardPaths::CacheLocation)); dir = QDir(dir.filePath(this->shellId)); this->mCacheDir = dir; qCDebug(logPaths) << "Initialized cache path:" << dir.path(); if (!dir.mkpath(".")) { qCCritical(logPaths) << "Could not create cache directory at" << dir.path(); this->cacheState = DirState::Failed; } else { this->cacheState = DirState::Ready; } } if (this->cacheState == DirState::Failed) return nullptr; else return &this->mCacheDir; } QDir* QsPaths::baseRunDir() { if (this->baseRunState == DirState::Unknown) { auto runtimeDir = qEnvironmentVariable("XDG_RUNTIME_DIR"); if (runtimeDir.isEmpty()) { runtimeDir = QString("/run/user/$1").arg(getuid()); qCInfo(logPaths) << "XDG_RUNTIME_DIR was not set, defaulting to" << runtimeDir; } this->mBaseRunDir = QDir(runtimeDir); this->mBaseRunDir = QDir(this->mBaseRunDir.filePath("quickshell")); qCDebug(logPaths) << "Initialized base runtime path:" << this->mBaseRunDir.path(); if (!this->mBaseRunDir.mkpath(".")) { qCCritical(logPaths) << "Could not create base runtime directory at" << this->mBaseRunDir.path(); this->baseRunState = DirState::Failed; } else { this->baseRunState = DirState::Ready; } } if (this->baseRunState == DirState::Failed) return nullptr; else return &this->mBaseRunDir; } QDir* QsPaths::shellRunDir() { if (this->shellRunState == DirState::Unknown) { if (auto* baseRunDir = this->baseRunDir()) { this->mShellRunDir = QDir(baseRunDir->filePath("by-shell")); this->mShellRunDir = QDir(this->mShellRunDir.filePath(this->shellId)); qCDebug(logPaths) << "Initialized runtime path:" << this->mShellRunDir.path(); if (!this->mShellRunDir.mkpath(".")) { qCCritical(logPaths) << "Could not create runtime directory at" << this->mShellRunDir.path(); this->shellRunState = DirState::Failed; } else { this->shellRunState = DirState::Ready; } } else { qCCritical(logPaths) << "Could not create shell runtime path as it was not possible to " "create the base runtime path."; this->shellRunState = DirState::Failed; } } if (this->shellRunState == DirState::Failed) return nullptr; else return &this->mShellRunDir; } QDir* QsPaths::instanceRunDir() { if (this->instanceRunState == DirState::Unknown) { auto* runDir = this->baseRunDir(); if (!runDir) { qCCritical(logPaths) << "Cannot create instance runtime directory as main runtim directory " "could not be created."; this->instanceRunState = DirState::Failed; } else { auto byIdDir = QDir(runDir->filePath("by-id")); this->mInstanceRunDir = byIdDir.filePath(InstanceInfo::CURRENT.instanceId); qCDebug(logPaths) << "Initialized instance runtime path:" << this->mInstanceRunDir.path(); if (!this->mInstanceRunDir.mkpath(".")) { qCCritical(logPaths) << "Could not create instance runtime directory at" << this->mInstanceRunDir.path(); this->instanceRunState = DirState::Failed; } else { this->instanceRunState = DirState::Ready; } } } if (this->shellRunState == DirState::Failed) return nullptr; else return &this->mInstanceRunDir; } void QsPaths::linkRunDir() { if (auto* runDir = this->instanceRunDir()) { auto pidDir = QDir(this->baseRunDir()->filePath("by-pid")); auto* shellDir = this->shellRunDir(); if (!shellDir) { qCCritical(logPaths ) << "Could not create by-id symlink as the shell runtime path could not be created."; } else { auto shellPath = shellDir->filePath(runDir->dirName()); QFile::remove(shellPath); auto r = symlinkat(runDir->filesystemCanonicalPath().c_str(), 0, shellPath.toStdString().c_str()); if (r != 0) { qCCritical(logPaths).nospace() << "Could not create id symlink to " << runDir->path() << " at " << shellPath << " with error code " << errno << ": " << qt_error_string(); } else { qCDebug(logPaths) << "Created shellid symlink" << shellPath << "to instance runtime path" << runDir->path(); } } if (!pidDir.mkpath(".")) { qCCritical(logPaths) << "Could not create PID symlink directory."; } else { auto pidPath = pidDir.filePath(QString::number(getpid())); QFile::remove(pidPath); auto r = symlinkat(runDir->filesystemCanonicalPath().c_str(), 0, pidPath.toStdString().c_str()); if (r != 0) { qCCritical(logPaths).nospace() << "Could not create PID symlink to " << runDir->path() << " at " << pidPath << " with error code " << errno << ": " << qt_error_string(); } else { qCDebug(logPaths) << "Created PID symlink" << pidPath << "to instance runtime path" << runDir->path(); } } } else { qCCritical(logPaths) << "Could not create PID symlink to runtime directory, as the runtime " "directory could not be created."; } } void QsPaths::linkPathDir() { if (auto* runDir = this->shellRunDir()) { auto pathDir = QDir(this->baseRunDir()->filePath("by-path")); if (!pathDir.mkpath(".")) { qCCritical(logPaths) << "Could not create path symlink directory."; return; } auto linkPath = pathDir.filePath(this->pathId); QFile::remove(linkPath); auto r = symlinkat(runDir->filesystemCanonicalPath().c_str(), 0, linkPath.toStdString().c_str()); if (r != 0) { qCCritical(logPaths).nospace() << "Could not create path symlink to " << runDir->path() << " at " << linkPath << " with error code " << errno << ": " << qt_error_string(); } else { qCDebug(logPaths) << "Created path symlink" << linkPath << "to shell runtime path" << runDir->path(); } } else { qCCritical(logPaths) << "Could not create path symlink to shell runtime directory, as the " "shell runtime directory could not be created."; } } void QsPaths::createLock() { if (auto* runDir = this->instanceRunDir()) { auto path = runDir->filePath("instance.lock"); auto* file = new QFile(path); // leaked if (!file->open(QFile::ReadWrite | QFile::Truncate)) { qCCritical(logPaths) << "Could not create instance lock at" << path; return; } auto lock = flock { .l_type = F_WRLCK, .l_whence = SEEK_SET, .l_start = 0, .l_len = 0, .l_pid = 0, }; if (fcntl(file->handle(), F_SETLK, &lock) != 0) { // NOLINT qCCritical(logPaths).nospace() << "Could not lock instance lock at " << path << " with error code " << errno << ": " << qt_error_string(); } else { auto stream = QDataStream(file); stream << InstanceInfo::CURRENT; file->flush(); qCDebug(logPaths) << "Created instance lock at" << path; } } else { qCCritical(logPaths ) << "Could not create instance lock, as the instance runtime directory could not be created."; } } bool QsPaths::checkLock(const QString& path, InstanceLockInfo* info) { auto file = QFile(QDir(path).filePath("instance.lock")); if (!file.open(QFile::ReadOnly)) return false; auto lock = flock { .l_type = F_WRLCK, .l_whence = SEEK_SET, .l_start = 0, .l_len = 0, .l_pid = 0, }; fcntl(file.handle(), F_GETLK, &lock); // NOLINT if (lock.l_type == F_UNLCK) return false; if (info) { info->pid = lock.l_pid; auto stream = QDataStream(&file); stream >> info->instance; } return true; } QVector QsPaths::collectInstances(const QString& path) { qCDebug(logPaths) << "Collecting instances from" << path; auto instances = QVector(); auto dir = QDir(path); InstanceLockInfo info; for (auto& entry: dir.entryList(QDir::Dirs | QDir::NoDotAndDotDot)) { auto path = dir.filePath(entry); if (QsPaths::checkLock(path, &info)) { qCDebug(logPaths).nospace() << "Found live instance " << info.instance.instanceId << " (pid " << info.pid << ") at " << path; instances.push_back(info); } else { qCDebug(logPaths) << "Skipped dead instance at" << path; } } return instances; }