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 | |
41 | static QString akonadiSocketDirectory(); |
42 | static bool checkSocketDirectory(const QString &path); |
43 | static 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 | |
53 | using namespace Akonadi; |
54 | using namespace Akonadi::Server; |
55 | |
56 | QString 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) |
95 | QString 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 | |
126 | static 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 | |
149 | static 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 | |
173 | QString 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 | |
202 | void 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 | |