File indexing completed on 2025-01-05 04:47:05

0001 /*
0002  * SPDX-FileCopyrightText: 2010 Tobias Koenig <tokoe@kde.org>
0003  * SPDX-FileCopyrightText: 2014 Daniel Vrátil <dvratil@redhat.com>
0004  *
0005  * SPDX-License-Identifier: LGPL-2.0-or-later
0006  *
0007  */
0008 
0009 #include "utils.h"
0010 #include "akonadiserver_debug.h"
0011 #include "instance_p.h"
0012 
0013 #include "private/standarddirs_p.h"
0014 
0015 #include <QDir>
0016 #include <QFileInfo>
0017 #include <QHostInfo>
0018 #include <QSettings>
0019 
0020 #if !defined(Q_OS_WIN)
0021 #include <cerrno>
0022 #include <cstdlib>
0023 #include <sys/types.h>
0024 #include <sys/un.h>
0025 #include <unistd.h>
0026 
0027 static QString akonadiSocketDirectory();
0028 static bool checkSocketDirectory(const QString &path);
0029 static bool createSocketDirectory(const QString &link);
0030 #endif
0031 
0032 #ifdef Q_OS_LINUX
0033 #include <fcntl.h>
0034 #include <mntent.h>
0035 #include <stdio.h>
0036 #include <sys/ioctl.h>
0037 #endif
0038 
0039 using namespace Akonadi;
0040 using namespace Akonadi::Server;
0041 
0042 QString Utils::preferredSocketDirectory(const QString &defaultDirectory, int fnLengthHint)
0043 {
0044     const QString serverConfigFile = StandardDirs::serverConfigFile(StandardDirs::ReadWrite);
0045     const QSettings serverSettings(serverConfigFile, QSettings::IniFormat);
0046 
0047 #if defined(Q_OS_WIN)
0048     const QString socketDir = serverSettings.value(QLatin1StringView("Connection/SocketDirectory"), defaultDirectory).toString();
0049 #else
0050     QString socketDir = defaultDirectory;
0051     if (!serverSettings.contains(QStringLiteral("Connection/SocketDirectory"))) {
0052         // if no socket directory is defined, use the symlinked from /tmp
0053         socketDir = akonadiSocketDirectory();
0054 
0055         if (socketDir.isEmpty()) { // if that does not work, fall back on default
0056             socketDir = defaultDirectory;
0057         }
0058     } else {
0059         socketDir = serverSettings.value(QStringLiteral("Connection/SocketDirectory"), defaultDirectory).toString();
0060     }
0061 
0062     if (socketDir.contains(QLatin1StringView("$USER"))) {
0063         const QString userName = QString::fromLocal8Bit(qgetenv("USER"));
0064         if (!userName.isEmpty()) {
0065             socketDir.replace(QLatin1StringView("$USER"), userName);
0066         }
0067     }
0068 
0069     if (socketDir[0] != QLatin1Char('/')) {
0070         QDir::home().mkdir(socketDir);
0071         socketDir = QDir::homePath() + QLatin1Char('/') + socketDir;
0072     }
0073 
0074     QFileInfo dirInfo(socketDir);
0075     if (!dirInfo.exists()) {
0076         QDir::home().mkpath(dirInfo.absoluteFilePath());
0077     }
0078 
0079     const std::size_t totalLength = socketDir.length() + 1 + fnLengthHint;
0080     const std::size_t maxLen = sizeof(sockaddr_un::sun_path);
0081     if (totalLength >= maxLen) {
0082         qCCritical(AKONADISERVER_LOG) << "akonadiSocketDirectory() length of" << totalLength << "is longer than the system limit" << maxLen;
0083     }
0084 #endif
0085     return socketDir;
0086 }
0087 
0088 #if !defined(Q_OS_WIN)
0089 QString akonadiSocketDirectory()
0090 {
0091     const QString hostname = QHostInfo::localHostName();
0092 
0093     if (hostname.isEmpty()) {
0094         qCCritical(AKONADISERVER_LOG) << "QHostInfo::localHostName() failed";
0095         return QString();
0096     }
0097 
0098     const QString identifier = Instance::hasIdentifier() ? Instance::identifier() : QStringLiteral("default");
0099     const QString link = StandardDirs::saveDir("data") + QStringLiteral("/socket-%1-%2").arg(hostname, identifier);
0100 
0101     if (checkSocketDirectory(link)) {
0102         return QFileInfo(link).symLinkTarget();
0103     }
0104 
0105     if (createSocketDirectory(link)) {
0106         return QFileInfo(link).symLinkTarget();
0107     }
0108 
0109     qCCritical(AKONADISERVER_LOG) << "Could not create socket directory for Akonadi.";
0110     return QString();
0111 }
0112 
0113 static bool checkSocketDirectory(const QString &path)
0114 {
0115     QFileInfo info(path);
0116 
0117     if (!info.exists()) {
0118         return false;
0119     }
0120 
0121     if (info.isSymLink()) {
0122         info = QFileInfo(info.symLinkTarget());
0123     }
0124 
0125     if (!info.isDir()) {
0126         return false;
0127     }
0128 
0129     if (info.ownerId() != getuid()) {
0130         return false;
0131     }
0132 
0133     return true;
0134 }
0135 
0136 static bool createSocketDirectory(const QString &link)
0137 {
0138     const QString directory = StandardDirs::saveDir("runtime");
0139 
0140     if (!QDir().mkpath(directory)) {
0141         qCCritical(AKONADISERVER_LOG) << "Creating socket directory with name" << directory << "failed:" << strerror(errno);
0142         return false;
0143     }
0144 
0145     QFile::remove(link);
0146 
0147     if (!QFile::link(directory, link)) {
0148         qCCritical(AKONADISERVER_LOG) << "Creating symlink from" << directory << "to" << link << "failed";
0149         return false;
0150     }
0151 
0152     return true;
0153 }
0154 #endif
0155 
0156 QString Utils::getDirectoryFileSystem(const QString &directory)
0157 {
0158 #ifndef Q_OS_LINUX
0159     Q_UNUSED(directory)
0160     return QString();
0161 #else
0162     QString bestMatchPath;
0163     QString bestMatchFS;
0164 
0165     FILE *mtab = setmntent("/etc/mtab", "r");
0166     if (!mtab) {
0167         return QString();
0168     }
0169     while (mntent *mnt = getmntent(mtab)) {
0170         if (qstrcmp(mnt->mnt_type, MNTTYPE_IGNORE) == 0) {
0171             continue;
0172         }
0173 
0174         const QString dir = QString::fromLocal8Bit(mnt->mnt_dir);
0175         if (!directory.startsWith(dir) || dir.length() < bestMatchPath.length()) {
0176             continue;
0177         }
0178 
0179         bestMatchPath = dir;
0180         bestMatchFS = QString::fromLocal8Bit(mnt->mnt_type);
0181     }
0182 
0183     endmntent(mtab);
0184 
0185     return bestMatchFS;
0186 #endif
0187 }
0188 
0189 void Utils::disableCoW(const QString &path)
0190 {
0191 #ifndef Q_OS_LINUX
0192     Q_UNUSED(path)
0193 #else
0194     qCDebug(AKONADISERVER_LOG) << "Detected Btrfs, disabling copy-on-write on database files";
0195 
0196     // from linux/fs.h, so that Akonadi does not depend on Linux header files
0197 #ifndef FS_IOC_GETFLAGS
0198 #define FS_IOC_GETFLAGS _IOR('f', 1, long)
0199 #endif
0200 #ifndef FS_IOC_SETFLAGS
0201 #define FS_IOC_SETFLAGS _IOW('f', 2, long)
0202 #endif
0203 
0204     // Disable COW on file
0205 #ifndef FS_NOCOW_FL
0206 #define FS_NOCOW_FL 0x00800000
0207 #endif
0208 
0209     ulong flags = 0;
0210     const int fd = open(qPrintable(path), O_RDONLY);
0211     if (fd == -1) {
0212         qCWarning(AKONADISERVER_LOG) << "Failed to open" << path << "to modify flags (" << errno << ")";
0213         return;
0214     }
0215 
0216     if (ioctl(fd, FS_IOC_GETFLAGS, &flags) == -1) {
0217         qCWarning(AKONADISERVER_LOG) << "ioctl error: failed to get file flags (" << errno << ")";
0218         close(fd);
0219         return;
0220     }
0221     if (!(flags & FS_NOCOW_FL)) {
0222         flags |= FS_NOCOW_FL;
0223         if (ioctl(fd, FS_IOC_SETFLAGS, &flags) == -1) {
0224             qCWarning(AKONADISERVER_LOG) << "ioctl error: failed to set file flags (" << errno << ")";
0225             close(fd);
0226             return;
0227         }
0228     }
0229     close(fd);
0230 #endif
0231 }