File indexing completed on 2024-05-05 03:54:41

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