File indexing completed on 2024-05-05 16:09:04

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