File indexing completed on 2025-03-16 06:50:39
0001 /* 0002 This file is part of the KDE Baloo Project 0003 SPDX-FileCopyrightText: 2014 Raphael Kubo da Costa <rakuco@FreeBSD.org> 0004 0005 SPDX-License-Identifier: LGPL-2.1-or-later 0006 */ 0007 0008 #ifndef KFILEMETADATA_XATTR_P_H 0009 #define KFILEMETADATA_XATTR_P_H 0010 0011 #include <QByteArray> 0012 #include <QFile> 0013 #include <QString> 0014 #include <QDebug> 0015 #include <QFileInfo> 0016 0017 #if defined(Q_OS_LINUX) || defined(__GLIBC__) 0018 #include <sys/types.h> 0019 #include <sys/xattr.h> 0020 0021 #if defined(Q_OS_ANDROID) || defined(Q_OS_LINUX) 0022 // attr/xattr.h is not available in the Android NDK so we are defining ENOATTR ourself 0023 #ifndef ENOATTR 0024 # define ENOATTR ENODATA /* No such attribute */ 0025 #endif 0026 #endif 0027 0028 #include <errno.h> 0029 #elif defined(Q_OS_MAC) 0030 #include <sys/types.h> 0031 #include <sys/xattr.h> 0032 #include <errno.h> 0033 #elif defined(Q_OS_FREEBSD) || defined(Q_OS_NETBSD) 0034 #include <sys/types.h> 0035 #include <sys/extattr.h> 0036 #include <errno.h> 0037 #elif defined(Q_OS_OPENBSD) 0038 #include <errno.h> 0039 #elif defined(Q_OS_WIN) 0040 #include <windows.h> 0041 #define ssize_t SSIZE_T 0042 #endif 0043 0044 #if defined(Q_OS_LINUX) || defined(Q_OS_MAC) || defined(Q_OS_FREEBSD) || defined(Q_OS_NETBSD) 0045 inline ssize_t k_getxattr(const QString& path, const QString& name, QString* value) 0046 { 0047 const QByteArray p = QFile::encodeName(path); 0048 const char* encodedPath = p.constData(); 0049 0050 #if defined(Q_OS_FREEBSD) || defined(Q_OS_NETBSD) 0051 const QByteArray n = name.toUtf8(); 0052 #else 0053 const QByteArray n = QByteArrayView("user.") + name.toUtf8(); 0054 #endif 0055 const char* attributeName = n.constData(); 0056 0057 // First get the size of the data we are going to get to reserve the right amount of space. 0058 #if defined(Q_OS_LINUX) || (defined(__GLIBC__) && !defined(__stub_getxattr)) 0059 const ssize_t size = getxattr(encodedPath, attributeName, nullptr, 0); 0060 #elif defined(Q_OS_MAC) 0061 const ssize_t size = getxattr(encodedPath, attributeName, NULL, 0, 0, 0); 0062 #elif defined(Q_OS_FREEBSD) || defined(Q_OS_NETBSD) 0063 const ssize_t size = extattr_get_file(encodedPath, EXTATTR_NAMESPACE_USER, attributeName, NULL, 0); 0064 #endif 0065 0066 if (!value) { 0067 return size; 0068 } 0069 0070 if (size <= 0) { 0071 value->clear(); 0072 return size; 0073 } 0074 0075 QByteArray data(size, Qt::Uninitialized); 0076 0077 while (true) { 0078 #if defined(Q_OS_LINUX) || (defined(__GLIBC__) && !defined(__stub_getxattr)) 0079 const ssize_t r = getxattr(encodedPath, attributeName, data.data(), data.size()); 0080 #elif defined(Q_OS_MAC) 0081 const ssize_t r = getxattr(encodedPath, attributeName, data.data(), data.size(), 0, 0); 0082 #elif defined(Q_OS_FREEBSD) || defined(Q_OS_NETBSD) 0083 const ssize_t r = extattr_get_file(encodedPath, EXTATTR_NAMESPACE_USER, attributeName, data.data(), data.size()); 0084 #endif 0085 0086 if (r < 0 && errno != ERANGE) { 0087 value->clear(); 0088 return r; 0089 } 0090 0091 if (r >= 0) { 0092 data.resize(r); 0093 *value = QString::fromUtf8(data); 0094 return size; 0095 } else { 0096 // ERANGE 0097 data.resize(data.size() * 2); 0098 } 0099 } 0100 } 0101 0102 inline int k_setxattr(const QString& path, const QString& name, const QString& value) 0103 { 0104 const QByteArray p = QFile::encodeName(path); 0105 const char* encodedPath = p.constData(); 0106 0107 0108 #if defined(Q_OS_FREEBSD) || defined(Q_OS_NETBSD) 0109 const QByteArray n = name.toUtf8(); 0110 #else 0111 const QByteArray n = QByteArrayView("user.") + name.toUtf8(); 0112 #endif 0113 const char* attributeName = n.constData(); 0114 0115 const QByteArray v = value.toUtf8(); 0116 const void* attributeValue = v.constData(); 0117 0118 const size_t valueSize = v.size(); 0119 0120 #if defined(Q_OS_LINUX) || (defined(__GLIBC__) && !defined(__stub_setxattr)) 0121 return setxattr(encodedPath, attributeName, attributeValue, valueSize, 0); 0122 #elif defined(Q_OS_MAC) 0123 return setxattr(encodedPath, attributeName, attributeValue, valueSize, 0, 0); 0124 #elif defined(Q_OS_FREEBSD) || defined(Q_OS_NETBSD) 0125 const ssize_t count = extattr_set_file(encodedPath, EXTATTR_NAMESPACE_USER, attributeName, attributeValue, valueSize); 0126 return count == -1 ? -1 : 0; 0127 #endif 0128 } 0129 0130 0131 inline int k_removexattr(const QString& path, const QString& name) 0132 { 0133 const QByteArray p = QFile::encodeName(path); 0134 const char* encodedPath = p.constData(); 0135 0136 #if defined(Q_OS_FREEBSD) || defined(Q_OS_NETBSD) 0137 const QByteArray n = name.toUtf8(); 0138 #else 0139 const QByteArray n = QByteArrayView("user.") + name.toUtf8(); 0140 #endif 0141 const char* attributeName = n.constData(); 0142 0143 #if defined(Q_OS_LINUX) || (defined(__GLIBC__) && !defined(__stub_removexattr)) 0144 return removexattr(encodedPath, attributeName); 0145 #elif defined(Q_OS_MAC) 0146 return removexattr(encodedPath, attributeName, XATTR_NOFOLLOW ); 0147 #elif defined(Q_OS_FREEBSD) || defined(Q_OS_NETBSD) 0148 return extattr_delete_file (encodedPath, EXTATTR_NAMESPACE_USER, attributeName); 0149 #endif 0150 } 0151 0152 inline bool k_hasAttribute(const QString& path, const QString& name) 0153 { 0154 auto ret = k_getxattr(path, name, nullptr); 0155 return (ret >= 0); 0156 } 0157 0158 inline bool k_isSupported(const QString& path) 0159 { 0160 auto ret = k_getxattr(path, QStringLiteral("test"), nullptr); 0161 return (ret >= 0) || (errno != ENOTSUP); 0162 } 0163 0164 0165 static KFileMetaData::UserMetaData::Attribute _mapAttribute(QByteArrayView key) 0166 { 0167 using KFileMetaData::UserMetaData; 0168 if (key == "xdg.tags") { 0169 return UserMetaData::Attribute::Tags; 0170 } 0171 if (key == "baloo.rating") { 0172 return UserMetaData::Attribute::Rating; 0173 } 0174 if (key == "xdg.comment") { 0175 return UserMetaData::Attribute::Comment; 0176 } 0177 if (key == "xdg.origin.url") { 0178 return UserMetaData::Attribute::OriginUrl; 0179 } 0180 if (key == "xdg.origin.email.subject") { 0181 return UserMetaData::Attribute::OriginEmailSubject; 0182 } 0183 if (key == "xdg.origin.email.sender") { 0184 return UserMetaData::Attribute::OriginEmailSender; 0185 } 0186 if (key == "xdg.origin.email.message-id") { 0187 return UserMetaData::Attribute::OriginEmailMessageId; 0188 } 0189 return UserMetaData::Attribute::Other; 0190 } 0191 0192 #if defined(Q_OS_FREEBSD) || defined(Q_OS_NETBSD) 0193 static QList<QByteArray> _split_length_value(QByteArray data) 0194 { 0195 int pos = 0; 0196 QList<QByteArray> entries; 0197 0198 while (pos < data.size()) { 0199 unsigned char len = data[pos]; 0200 if (pos + 1 + len <= data.size()) { 0201 auto value = data.mid(pos + 1, len); 0202 entries.append(value); 0203 } 0204 pos += 1 + len; 0205 } 0206 return entries; 0207 } 0208 #endif 0209 0210 KFileMetaData::UserMetaData::Attributes k_queryAttributes(const QString& path, 0211 KFileMetaData::UserMetaData::Attributes attributes) 0212 { 0213 using KFileMetaData::UserMetaData; 0214 0215 const QByteArray p = QFile::encodeName(path); 0216 const char* encodedPath = p.constData(); 0217 0218 #if defined(Q_OS_LINUX) 0219 const ssize_t size = listxattr(encodedPath, nullptr, 0); 0220 #elif defined(Q_OS_MAC) 0221 const ssize_t size = listxattr(encodedPath, nullptr, 0, 0); 0222 #elif defined(Q_OS_FREEBSD) || defined(Q_OS_NETBSD) 0223 const ssize_t size = extattr_list_file(encodedPath, EXTATTR_NAMESPACE_USER, nullptr, 0); 0224 #endif 0225 0226 if (size == 0) { 0227 return UserMetaData::Attribute::None; 0228 } 0229 0230 if (size < 0) { 0231 if (errno == E2BIG) { 0232 return UserMetaData::Attribute::All; 0233 } 0234 0235 return UserMetaData::Attribute::None; 0236 } 0237 0238 if (attributes == UserMetaData::Attribute::Any) { 0239 return UserMetaData::Attribute::All; 0240 } 0241 0242 QByteArray data(size, Qt::Uninitialized); 0243 0244 while (true) { 0245 #if defined(Q_OS_LINUX) 0246 const ssize_t r = listxattr(encodedPath, data.data(), data.size()); 0247 #elif defined(Q_OS_MAC) 0248 const ssize_t r = listxattr(encodedPath, data.data(), data.size(), 0); 0249 #elif defined(Q_OS_FREEBSD) || defined(Q_OS_NETBSD) 0250 const ssize_t r = extattr_list_file(encodedPath, EXTATTR_NAMESPACE_USER, data.data(), data.size()); 0251 #endif 0252 0253 if (r == 0) { 0254 return UserMetaData::Attribute::None; 0255 } 0256 0257 if (r < 0 && errno != ERANGE) { 0258 return UserMetaData::Attribute::None; 0259 } 0260 0261 if (r > 0) { 0262 data.resize(r); 0263 break; 0264 } else { 0265 data.resize(data.size() * 2); 0266 } 0267 } 0268 0269 #if defined(Q_OS_FREEBSD) || defined(Q_OS_NETBSD) 0270 const QByteArrayView prefix; 0271 const auto entries = _split_length_value(data); 0272 #else 0273 const QByteArrayView prefix("user."); 0274 const auto entries = data.split('\0'); 0275 #endif 0276 0277 UserMetaData::Attributes fileAttributes = UserMetaData::Attribute::None; 0278 for (const auto &entry : entries) { 0279 if (!entry.startsWith(prefix)) { 0280 continue; 0281 } 0282 fileAttributes |= _mapAttribute(QByteArrayView(entry).sliced(prefix.size())); 0283 fileAttributes &= attributes; 0284 if (fileAttributes == attributes) { 0285 break; 0286 } 0287 } 0288 return fileAttributes; 0289 } 0290 0291 #elif defined(Q_OS_WIN) 0292 0293 inline ssize_t k_getxattr(const QString& path, const QString& name, QString* value) 0294 { 0295 const QString fullADSName = path + QLatin1String(":user.") + name; 0296 HANDLE hFile = ::CreateFileW(reinterpret_cast<const WCHAR*>(fullADSName.utf16()), GENERIC_READ, FILE_SHARE_READ, NULL, 0297 OPEN_EXISTING, FILE_FLAG_SEQUENTIAL_SCAN, NULL); 0298 0299 if(!hFile) return 0; 0300 0301 LARGE_INTEGER lsize; 0302 BOOL ret = GetFileSizeEx(hFile, &lsize); 0303 0304 if (ret || lsize.QuadPart > 0x7fffffff || lsize.QuadPart == 0) { 0305 CloseHandle(hFile); 0306 value->clear(); 0307 return lsize.QuadPart == 0 ? 0 : -1; 0308 } 0309 0310 DWORD r = 0; 0311 QByteArray data(lsize.QuadPart, Qt::Uninitialized); 0312 // should we care about attributes longer than 2GiB? - unix xattr are restricted to much lower values 0313 ret = ::ReadFile(hFile, data.data(), data.size(), &r, NULL); 0314 CloseHandle(hFile); 0315 0316 if (ret || r == 0) { 0317 value->clear(); 0318 return r == 0 ? 0 : -1; 0319 } 0320 0321 data.resize(r); 0322 0323 *value = QString::fromUtf8(data); 0324 return r; 0325 } 0326 0327 inline int k_setxattr(const QString& path, const QString& name, const QString& value) 0328 { 0329 const QByteArray v = value.toUtf8(); 0330 0331 const QString fullADSName = path + QLatin1String(":user.") + name; 0332 HANDLE hFile = ::CreateFileW(reinterpret_cast<const WCHAR*>(fullADSName.utf16()), GENERIC_WRITE, FILE_SHARE_READ, NULL, 0333 CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL | FILE_FLAG_SEQUENTIAL_SCAN, NULL); 0334 0335 if(!hFile) return -1; 0336 0337 DWORD count = 0; 0338 0339 if(!::WriteFile(hFile, v.constData(), v.size(), &count, NULL)) { 0340 DWORD dw = GetLastError(); 0341 TCHAR msg[1024]; 0342 FormatMessage( 0343 FORMAT_MESSAGE_ALLOCATE_BUFFER | 0344 FORMAT_MESSAGE_FROM_SYSTEM | 0345 FORMAT_MESSAGE_IGNORE_INSERTS, 0346 NULL, 0347 dw, 0348 MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), 0349 (LPTSTR) &msg, 0350 0, NULL ); 0351 qWarning() << "failed to write to ADS:" << msg; 0352 CloseHandle(hFile); 0353 return -1; 0354 } 0355 0356 CloseHandle(hFile); 0357 return count; 0358 } 0359 0360 inline bool k_hasAttribute(const QString& path, const QString& name) 0361 { 0362 // enumerate all streams: 0363 const QString streamName = QLatin1String(":user.") + name + QStringLiteral(":$DATA"); 0364 HANDLE hFile = ::CreateFileW(reinterpret_cast<const WCHAR*>(path.utf16()), GENERIC_READ, FILE_SHARE_READ, NULL, 0365 OPEN_EXISTING, FILE_FLAG_SEQUENTIAL_SCAN, NULL); 0366 0367 if(!hFile) { 0368 return false; 0369 } 0370 FILE_STREAM_INFO* fi = new FILE_STREAM_INFO[256]; 0371 if(GetFileInformationByHandleEx(hFile, FileStreamInfo, fi, 256 * sizeof(FILE_STREAM_INFO))) { 0372 if(QString::fromUtf16((char16_t*)fi->StreamName, fi->StreamNameLength / sizeof(char16_t)) == streamName) { 0373 delete[] fi; 0374 CloseHandle(hFile); 0375 return true; 0376 } 0377 FILE_STREAM_INFO* p = fi; 0378 do { 0379 p = (FILE_STREAM_INFO*) ((char*)p + p->NextEntryOffset); 0380 if(QString::fromUtf16((char16_t*)p->StreamName, p->StreamNameLength / sizeof(char16_t)) == streamName) { 0381 delete[] fi; 0382 CloseHandle(hFile); 0383 return true; 0384 } 0385 } while(p->NextEntryOffset != NULL); 0386 } 0387 delete[] fi; 0388 CloseHandle(hFile); 0389 return false; 0390 } 0391 0392 inline int k_removexattr(const QString& path, const QString& name) 0393 { 0394 const QString fullADSName = path + QLatin1String(":user.") + name; 0395 int ret = (DeleteFileW(reinterpret_cast<const WCHAR*>(fullADSName.utf16()))) ? 0 : -1; 0396 return ret; 0397 } 0398 0399 inline bool k_isSupported(const QString& path) 0400 { 0401 QFileInfo f(path); 0402 const QString drive = QString(f.absolutePath().left(2)) + QStringLiteral("\\"); 0403 WCHAR szFSName[MAX_PATH]; 0404 DWORD dwVolFlags; 0405 ::GetVolumeInformationW(reinterpret_cast<const WCHAR*>(drive.utf16()), NULL, 0, NULL, NULL, &dwVolFlags, szFSName, MAX_PATH); 0406 return ((dwVolFlags & FILE_NAMED_STREAMS) && _wcsicmp(szFSName, L"NTFS") == 0); 0407 } 0408 0409 KFileMetaData::UserMetaData::Attributes k_queryAttributes(const QString& path, 0410 KFileMetaData::UserMetaData::Attributes attributes) 0411 { 0412 using KFileMetaData::UserMetaData; 0413 0414 if (!k_isSupported(path)) { 0415 return UserMetaData::Attribute::None; 0416 } 0417 0418 // TODO - this is mostly a stub, streams should be enumerated, see k_hasAttribute above 0419 if (attributes == UserMetaData::Attribute::Any) { 0420 return UserMetaData::Attribute::All; 0421 } 0422 return attributes; 0423 } 0424 0425 #else 0426 inline ssize_t k_getxattr(const QString&, const QString&, QString*) 0427 { 0428 return 0; 0429 } 0430 0431 inline int k_setxattr(const QString&, const QString&, const QString&) 0432 { 0433 return -1; 0434 } 0435 0436 inline int k_removexattr(const QString&, const QString&) 0437 { 0438 return -1; 0439 } 0440 0441 inline bool k_hasAttribute(const QString&, const QString&) 0442 { 0443 return false; 0444 } 0445 0446 inline bool k_isSupported(const QString&) 0447 { 0448 return false; 0449 } 0450 0451 KFileMetaData::UserMetaData::Attributes k_queryAttributes(const QString&, 0452 KFileMetaData::UserMetaData::Attributes attributes) 0453 { 0454 return KFileMetaData::UserMetaData::Attribute::None; 0455 } 0456 0457 #endif 0458 0459 #endif // KFILEMETADATA_XATTR_P_H