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 }