1/*
2 * Copyright (C) 2010 Tobias Koenig <tokoe@kde.org>
3 * Copyright (C) 2014 Daniel Vrátil <dvratil@redhat.com>
4 *
5 * This library is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU Library General Public
7 * License as published by the Free Software Foundation; either
8 * version 2 of the License, or (at your option) any later version.
9 *
10 * This library is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 * Library General Public License for more details.
14 *
15 * You should have received a copy of the GNU Library General Public License
16 * along with this library; see the file COPYING.LIB. If not, write to
17 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
18 * Boston, MA 02110-1301, USA.
19 *
20 */
21
22#include "utils.h"
23
24#include <akdebug.h>
25#include <akstandarddirs.h>
26#include <libs/xdgbasedirs_p.h>
27
28#include <QtCore/QDebug>
29#include <QtCore/QDir>
30#include <QtCore/QFileInfo>
31#include <QtCore/QSettings>
32#include <QtNetwork/QHostInfo>
33
34#if !defined(Q_OS_WIN)
35#include <cstdlib>
36#include <sys/types.h>
37#include <cerrno>
38#include <pwd.h>
39#include <unistd.h>
40
41static QString akonadiSocketDirectory();
42static bool checkSocketDirectory(const QString &path);
43static bool createSocketDirectory(const QString &link, const QString &tmpl);
44#endif
45
46#ifdef Q_OS_LINUX
47#include <stdio.h>
48#include <mntent.h>
49#include <sys/ioctl.h>
50#include <fcntl.h>
51#endif
52
53using namespace Akonadi;
54using namespace Akonadi::Server;
55
56QString Utils::preferredSocketDirectory(const QString &defaultDirectory)
57{
58 const QString serverConfigFile = AkStandardDirs::serverConfigFile(XdgBaseDirs::ReadWrite);
59 const QSettings serverSettings(serverConfigFile, QSettings::IniFormat);
60
61#if defined(Q_OS_WIN)
62 const QString socketDir = serverSettings.value(QLatin1String("Connection/SocketDirectory"), defaultDirectory).toString();
63#else
64 QString socketDir = defaultDirectory;
65 if (!serverSettings.contains(QLatin1String("Connection/SocketDirectory"))) {
66 // if no socket directory is defined, use the symlinked from /tmp
67 socketDir = akonadiSocketDirectory();
68
69 if (socketDir.isEmpty()) { // if that does not work, fall back on default
70 socketDir = defaultDirectory;
71 }
72 } else {
73 socketDir = serverSettings.value(QLatin1String("Connection/SocketDirectory"), defaultDirectory).toString();
74 }
75
76 const QString userName = QString::fromLocal8Bit(qgetenv("USER"));
77 if (socketDir.contains(QLatin1String("$USER")) && !userName.isEmpty()) {
78 socketDir.replace(QLatin1String("$USER"), userName);
79 }
80
81 if (socketDir[0] != QLatin1Char('/')) {
82 QDir::home().mkdir(socketDir);
83 socketDir = QDir::homePath() + QLatin1Char('/') + socketDir;
84 }
85
86 QFileInfo dirInfo(socketDir);
87 if (!dirInfo.exists()) {
88 QDir::home().mkpath(dirInfo.absoluteFilePath());
89 }
90#endif
91 return socketDir;
92}
93
94#if !defined(Q_OS_WIN)
95QString akonadiSocketDirectory()
96{
97 const QString hostname = QHostInfo::localHostName();
98
99 if (hostname.isEmpty()) {
100 qCritical() << "QHostInfo::localHostName() failed";
101 return QString();
102 }
103
104 const uid_t uid = getuid();
105 const struct passwd *pw_ent = getpwuid(uid);
106 if (!pw_ent) {
107 qCritical() << "Could not get passwd entry for user id" << uid;
108 return QString();
109 }
110
111 const QString link = AkStandardDirs::saveDir("data") + QLatin1Char('/') + QLatin1String("socket-") + hostname;
112 const QString tmpl = QLatin1String("akonadi-") + QLatin1String(pw_ent->pw_name) + QLatin1String(".XXXXXX");
113
114 if (checkSocketDirectory(link)) {
115 return QFileInfo(link).symLinkTarget();
116 }
117
118 if (createSocketDirectory(link, tmpl)) {
119 return QFileInfo(link).symLinkTarget();
120 }
121
122 qCritical() << "Could not create socket directory for Akonadi.";
123 return QString();
124}
125
126static bool checkSocketDirectory(const QString &path)
127{
128 QFileInfo info(path);
129
130 if (!info.exists()) {
131 return false;
132 }
133
134 if (info.isSymLink()) {
135 info = QFileInfo(info.symLinkTarget());
136 }
137
138 if (!info.isDir()) {
139 return false;
140 }
141
142 if (info.ownerId() != getuid()) {
143 return false;
144 }
145
146 return true;
147}
148
149static bool createSocketDirectory(const QString &link, const QString &tmpl)
150{
151 QString directory = QString::fromLatin1("%1%2%3").arg(QDir::tempPath()).arg(QDir::separator()).arg(tmpl);
152
153 QByteArray directoryString = directory.toLocal8Bit().data();
154
155 if (!mkdtemp(directoryString.data())) {
156 qCritical() << "Creating socket directory with template" << directoryString << "failed:" << strerror(errno);
157 return false;
158 }
159
160 directory = QString::fromLocal8Bit(directoryString);
161
162 QFile::remove(link);
163
164 if (!QFile::link(directory, link)) {
165 qCritical() << "Creating symlink from" << directory << "to" << link << "failed";
166 return false;
167 }
168
169 return true;
170}
171#endif
172
173QString Utils::getDirectoryFileSystem(const QString &directory)
174{
175#ifndef Q_OS_LINUX
176 return QString();
177#else
178 QString bestMatchPath;
179 QString bestMatchFS;
180
181 FILE *mtab = setmntent("/etc/mtab", "r");
182 while (mntent *mnt = getmntent(mtab)) {
183 if (qstrcmp(mnt->mnt_type, MNTTYPE_IGNORE) == 0) {
184 continue;
185 }
186
187 const QString dir = QString::fromLocal8Bit(mnt->mnt_dir);
188 if (!directory.startsWith(dir) || dir.length() < bestMatchPath.length()) {
189 continue;
190 }
191
192 bestMatchPath = dir;
193 bestMatchFS = QString::fromLocal8Bit(mnt->mnt_type);
194 }
195
196 endmntent(mtab);
197
198 return bestMatchFS;
199#endif
200}
201
202void Utils::disableCoW(const QString &path)
203{
204#ifndef Q_OS_LINUX
205 Q_UNUSED(path);
206#else
207 qDebug() << "Detected Btrfs, disabling copy-on-write on database files";
208
209 // from linux/fs.h, so that Akonadi does not depend on Linux header files
210#ifndef FS_IOC_GETFLAGS
211#define FS_IOC_GETFLAGS _IOR('f', 1, long)
212#endif
213#ifndef FS_IOC_SETFLAGS
214#define FS_IOC_SETFLAGS _IOW('f', 2, long)
215#endif
216
217 // Disable COW on file
218#ifndef FS_NOCOW_FL
219#define FS_NOCOW_FL 0x00800000
220#endif
221
222 ulong flags = 0;
223 const int fd = open(qPrintable(path), O_RDONLY);
224 if (fd == -1) {
225 qWarning() << "Failed to open" << path << "to modify flags (" << errno << ")";
226 return;
227 }
228
229 if (ioctl(fd, FS_IOC_GETFLAGS, &flags) == -1) {
230 qWarning() << "ioctl error: failed to get file flags (" << errno << ")";
231 close(fd);
232 return;
233 }
234 if (!(flags & FS_NOCOW_FL)) {
235 flags |= FS_NOCOW_FL;
236 if (ioctl(fd, FS_IOC_SETFLAGS, &flags) == -1) {
237 qWarning() << "ioctl error: failed to set file flags (" << errno << ")";
238 close(fd);
239 return;
240 }
241 }
242 close(fd);
243#endif
244}
245