diff --git a/src/ioslaves/file/file.h b/src/ioslaves/file/file.h --- a/src/ioslaves/file/file.h +++ b/src/ioslaves/file/file.h @@ -94,8 +94,6 @@ void virtual_hook(int id, void *data) override; private: - bool createUDSEntry(const QString &filename, const QByteArray &path, KIO::UDSEntry &entry, - KIO::StatDetails details); int setACL(const char *path, mode_t perm, bool _directoryDefault); QString getUserName(KUserId uid) const; QString getGroupName(KGroupId gid) const; @@ -118,8 +116,6 @@ void closeWithoutFinish(); private: - mutable QHash mUsercache; - mutable QHash mGroupcache; QFile *mFile; bool testMode = false; diff --git a/src/ioslaves/file/file.cpp b/src/ioslaves/file/file.cpp --- a/src/ioslaves/file/file.cpp +++ b/src/ioslaves/file/file.cpp @@ -67,10 +67,6 @@ #include #include -#if HAVE_STATX -#include -#endif - #if HAVE_VOLMGT #include #include @@ -829,290 +825,6 @@ finished(); } -QString FileProtocol::getUserName(KUserId uid) const -{ - if (Q_UNLIKELY(!uid.isValid())) { - return QString(); - } - auto it = mUsercache.find(uid); - if (it == mUsercache.end()) { - KUser user(uid); - QString name = user.loginName(); - if (name.isEmpty()) { - name = uid.toString(); - } - it = mUsercache.insert(uid, name); - } - return *it; -} - -QString FileProtocol::getGroupName(KGroupId gid) const -{ - if (Q_UNLIKELY(!gid.isValid())) { - return QString(); - } - auto it = mGroupcache.find(gid); - if (it == mGroupcache.end()) { - KUserGroup group(gid); - QString name = group.name(); - if (name.isEmpty()) { - name = gid.toString(); - } - it = mGroupcache.insert(gid, name); - } - return *it; -} - -#if HAVE_STATX -// statx syscall is available -inline int LSTAT(const char* path, struct statx * buff, KIO::StatDetails details) { - uint32_t mask = 0; - if (details & KIO::Basic) { - // filename, access, type, size, linkdest - mask |= STATX_SIZE | STATX_TYPE; - } - if (details & KIO::User) { - // uid, gid - mask |= STATX_UID | STATX_GID; - } - if (details & KIO::Time) { - // atime, mtime, btime - mask |= STATX_ATIME | STATX_MTIME | STATX_BTIME; - } - if (details & KIO::Inode) { - // dev, inode - mask |= STATX_INO; - } - return statx(AT_FDCWD, path, AT_SYMLINK_NOFOLLOW, mask, buff); -} -inline int STAT(const char* path, struct statx * buff, KIO::StatDetails details) { - uint32_t mask = 0; - if (details & KIO::Basic) { - // filename, access, type, size, linkdest - mask |= STATX_SIZE | STATX_TYPE; - } - if (details & KIO::User) { - // uid, gid - mask |= STATX_UID | STATX_GID; - } - if (details & KIO::Time) { - // atime, mtime, btime - mask |= STATX_ATIME | STATX_MTIME | STATX_BTIME; - } - // KIO::Inode is ignored as when STAT is called, the entry inode field has already been filled - return statx(AT_FDCWD, path, AT_STATX_SYNC_AS_STAT, mask, buff); -} -inline static uint16_t stat_mode(struct statx &buf) { return buf.stx_mode; } -inline static uint32_t stat_dev(struct statx &buf) { return buf.stx_dev_major; } -inline static uint64_t stat_ino(struct statx &buf) { return buf.stx_ino; } -inline static uint64_t stat_size(struct statx &buf) { return buf.stx_size; } -inline static uint32_t stat_uid(struct statx &buf) { return buf.stx_uid; } -inline static uint32_t stat_gid(struct statx &buf) { return buf.stx_gid; } -inline static int64_t stat_atime(struct statx &buf) { return buf.stx_atime.tv_sec; } -inline static int64_t stat_mtime(struct statx &buf) { return buf.stx_mtime.tv_sec; } -#else -// regular stat struct -inline int LSTAT(const char* path, QT_STATBUF * buff, KIO::StatDetails details) { - Q_UNUSED(details) - return QT_LSTAT(path, buff); -} -inline int STAT(const char* path, QT_STATBUF * buff, KIO::StatDetails details) { - Q_UNUSED(details) - return QT_STAT(path, buff); -} -inline static mode_t stat_mode(QT_STATBUF &buf) { return buf.st_mode; } -inline static dev_t stat_dev(QT_STATBUF &buf) { return buf.st_dev; } -inline static ino_t stat_ino(QT_STATBUF &buf) { return buf.st_ino; } -inline static off_t stat_size(QT_STATBUF &buf) { return buf.st_size; } -#ifndef Q_OS_WIN -inline static uid_t stat_uid(QT_STATBUF &buf) { return buf.st_uid; } -inline static gid_t stat_gid(QT_STATBUF &buf) { return buf.st_gid; } -#endif -inline static time_t stat_atime(QT_STATBUF &buf) { return buf.st_atime; } -inline static time_t stat_mtime(QT_STATBUF &buf) { return buf.st_mtime; } -#endif - -bool FileProtocol::createUDSEntry(const QString &filename, const QByteArray &path, UDSEntry &entry, - KIO::StatDetails details) -{ - assert(entry.count() == 0); // by contract :-) - int entries = 0; - if (details & KIO::Basic) { - // filename, access, type, size, linkdest - entries += 5; - } - if (details & KIO::User) { - // uid, gid - entries += 2; - } - if (details & KIO::Time) { - // atime, mtime, btime - entries += 3; - } - if (details & KIO::Acl) { - // acl data - entries += 3; - } - if (details & KIO::Inode) { - // dev, inode - entries += 2; - } - entry.reserve(entries); - - if (details & KIO::Basic) { - entry.fastInsert(KIO::UDSEntry::UDS_NAME, filename); - } - - mode_t type; - mode_t access; - bool isBrokenSymLink = false; - signed long long size = 0LL; -#if HAVE_POSIX_ACL - QByteArray targetPath = path; -#endif - -#if HAVE_STATX - // statx syscall is available - struct statx buff; -#else - QT_STATBUF buff; -#endif - - if (LSTAT(path.data(), &buff, details) == 0) { - - if (details & KIO::Inode) { - entry.fastInsert(KIO::UDSEntry::UDS_DEVICE_ID, stat_dev(buff)); - entry.fastInsert(KIO::UDSEntry::UDS_INODE, stat_ino(buff)); - } - - if ((stat_mode(buff) & QT_STAT_MASK) == QT_STAT_LNK) { - - QByteArray linkTargetBuffer; - if (details & (KIO::Basic|KIO::ResolveSymlink)) { -#ifdef Q_OS_WIN - const QString linkTarget = QFile::symLinkTarget(QFile::decodeName(path)); -#else - // Use readlink on Unix because symLinkTarget turns relative targets into absolute (#352927) - #if HAVE_STATX - size_t lowerBound = 256; - size_t higherBound = 1024; - uint64_t s = stat_size(buff); - if (s > SIZE_MAX) { - qCWarning(KIO_FILE) << "file size bigger than SIZE_MAX, too big for readlink use!" << path; - return false; - } - size_t size = static_cast(s); - using SizeType = size_t; - #else - off_t lowerBound = 256; - off_t higherBound = 1024; - off_t size = stat_size(buff); - using SizeType = off_t; - #endif - SizeType bufferSize = qBound(lowerBound, size +1, higherBound); - linkTargetBuffer.resize(bufferSize); - while (true) { - ssize_t n = readlink(path.constData(), linkTargetBuffer.data(), bufferSize); - if (n < 0 && errno != ERANGE) { - qCWarning(KIO_FILE) << "readlink failed!" << path; - return false; - } else if (n > 0 && static_cast(n) != bufferSize) { - // the buffer was not filled in the last iteration - // we are finished reading, break the loop - linkTargetBuffer.truncate(n); - break; - } - bufferSize *= 2; - linkTargetBuffer.resize(bufferSize); - } - const QString linkTarget = QFile::decodeName(linkTargetBuffer); -#endif - entry.fastInsert(KIO::UDSEntry::UDS_LINK_DEST, linkTarget); - } - - // A symlink - if (details & KIO::ResolveSymlink) { - if (STAT(path.constData(), &buff, details) == -1) { - isBrokenSymLink = true; - } else { -#if HAVE_POSIX_ACL - if (details & KIO::Acl) { - // valid symlink, will get the ACLs of the destination - targetPath = linkTargetBuffer; - } -#endif - } - } - } - } else { - // qCWarning(KIO_FILE) << "lstat didn't work on " << path.data(); - return false; - } - - if (details & KIO::Basic) { - if (isBrokenSymLink) { - // It is a link pointing to nowhere - type = S_IFMT - 1; - access = S_IRWXU | S_IRWXG | S_IRWXO; - size = 0LL; - } else { - type = stat_mode(buff) & S_IFMT; // extract file type - access = stat_mode(buff) & 07777; // extract permissions - size = stat_size(buff); - } - - entry.fastInsert(KIO::UDSEntry::UDS_FILE_TYPE, type); - entry.fastInsert(KIO::UDSEntry::UDS_ACCESS, access); - entry.fastInsert(KIO::UDSEntry::UDS_SIZE, size); - } - -#if HAVE_POSIX_ACL - if (details & KIO::Acl) { - /* Append an atom indicating whether the file has extended acl information - * and if withACL is specified also one with the acl itself. If it's a directory - * and it has a default ACL, also append that. */ - appendACLAtoms(targetPath, entry, type); - } -#endif - - if (details & KIO::User) { -#ifndef Q_OS_WIN - entry.fastInsert(KIO::UDSEntry::UDS_USER, getUserName(KUserId(stat_uid(buff)))); - entry.fastInsert(KIO::UDSEntry::UDS_GROUP, getGroupName(KGroupId(stat_gid(buff)))); -#else -#pragma message("TODO: st_uid and st_gid are always zero, use GetSecurityInfo to find the owner") -#endif - } - - if (details & KIO::Time) { - entry.fastInsert(KIO::UDSEntry::UDS_MODIFICATION_TIME, stat_mtime(buff)); - entry.fastInsert(KIO::UDSEntry::UDS_ACCESS_TIME, stat_atime(buff)); - -#ifdef st_birthtime - /* For example FreeBSD's and NetBSD's stat contains a field for - * the inode birth time: st_birthtime - * This however only works on UFS and ZFS, and not, on say, NFS. - * Instead of setting a bogus fallback like st_mtime, only use - * it if it is greater than 0. */ - if (buff.st_birthtime > 0) { - entry.fastInsert(KIO::UDSEntry::UDS_CREATION_TIME, buff.st_birthtime); - } -#elif defined __st_birthtime - /* As above, but OpenBSD calls it slightly differently. */ - if (buff.__st_birthtime > 0) { - entry.fastInsert(KIO::UDSEntry::UDS_CREATION_TIME, buff.__st_birthtime); - } -#elif HAVE_STATX - /* And linux version using statx syscall */ - if (buff.stx_mask & STATX_BTIME) { - entry.fastInsert(KIO::UDSEntry::UDS_CREATION_TIME, buff.stx_btime.tv_sec); - } -#endif - } - - return true; -} - void FileProtocol::special(const QByteArray &data) { int tmp; diff --git a/src/ioslaves/file/file_unix.cpp b/src/ioslaves/file/file_unix.cpp --- a/src/ioslaves/file/file_unix.cpp +++ b/src/ioslaves/file/file_unix.cpp @@ -50,6 +50,10 @@ #include "fdreceiver.h" #include "statjob.h" +#if HAVE_STATX +#include +#endif + //sendfile has different semantics in different platforms #if HAVE_SENDFILE && defined Q_OS_LINUX #define USE_SENDFILE 1 @@ -136,6 +140,285 @@ && (requestPrivilegeOperation(QStringLiteral("Test Call")) == KIO::OperationAllowed); } + +static QHash staticUserCache; +static QHash staticGroupCache; + +static QString getUserName(KUserId uid) +{ + if (Q_UNLIKELY(!uid.isValid())) { + return QString(); + } + auto it = staticUserCache.find(uid); + if (it == staticUserCache.end()) { + KUser user(uid); + QString name = user.loginName(); + if (name.isEmpty()) { + name = uid.toString(); + } + it = staticUserCache.insert(uid, name); + } + return *it; +} + +static QString getGroupName(KGroupId gid) +{ + if (Q_UNLIKELY(!gid.isValid())) { + return QString(); + } + auto it = staticGroupCache.find(gid); + if (it == staticGroupCache.end()) { + KUserGroup group(gid); + QString name = group.name(); + if (name.isEmpty()) { + name = gid.toString(); + } + it = staticGroupCache.insert(gid, name); + } + return *it; +} + +#if HAVE_STATX +// statx syscall is available +inline int LSTAT(const char* path, struct statx * buff, KIO::StatDetails details) { + uint32_t mask = 0; + if (details & KIO::Basic) { + // filename, access, type, size, linkdest + mask |= STATX_SIZE | STATX_TYPE; + } + if (details & KIO::User) { + // uid, gid + mask |= STATX_UID | STATX_GID; + } + if (details & KIO::Time) { + // atime, mtime, btime + mask |= STATX_ATIME | STATX_MTIME | STATX_BTIME; + } + if (details & KIO::Inode) { + // dev, inode + mask |= STATX_INO; + } + return statx(AT_FDCWD, path, AT_SYMLINK_NOFOLLOW, mask, buff); +} +inline int STAT(const char* path, struct statx * buff, KIO::StatDetails details) { + uint32_t mask = 0; + if (details & KIO::Basic) { + // filename, access, type, size, linkdest + mask |= STATX_SIZE | STATX_TYPE; + } + if (details & KIO::User) { + // uid, gid + mask |= STATX_UID | STATX_GID; + } + if (details & KIO::Time) { + // atime, mtime, btime + mask |= STATX_ATIME | STATX_MTIME | STATX_BTIME; + } + // KIO::Inode is ignored as when STAT is called, the entry inode field has already been filled + return statx(AT_FDCWD, path, AT_STATX_SYNC_AS_STAT, mask, buff); +} +inline static uint16_t stat_mode(struct statx &buf) { return buf.stx_mode; } +inline static uint32_t stat_dev(struct statx &buf) { return buf.stx_dev_major; } +inline static uint64_t stat_ino(struct statx &buf) { return buf.stx_ino; } +inline static uint64_t stat_size(struct statx &buf) { return buf.stx_size; } +inline static uint32_t stat_uid(struct statx &buf) { return buf.stx_uid; } +inline static uint32_t stat_gid(struct statx &buf) { return buf.stx_gid; } +inline static int64_t stat_atime(struct statx &buf) { return buf.stx_atime.tv_sec; } +inline static int64_t stat_mtime(struct statx &buf) { return buf.stx_mtime.tv_sec; } +#else +// regular stat struct +inline int LSTAT(const char* path, QT_STATBUF * buff, KIO::StatDetails details) { + Q_UNUSED(details) + return QT_LSTAT(path, buff); +} +inline int STAT(const char* path, QT_STATBUF * buff, KIO::StatDetails details) { + Q_UNUSED(details) + return QT_STAT(path, buff); +} +inline static mode_t stat_mode(QT_STATBUF &buf) { return buf.st_mode; } +inline static dev_t stat_dev(QT_STATBUF &buf) { return buf.st_dev; } +inline static ino_t stat_ino(QT_STATBUF &buf) { return buf.st_ino; } +inline static off_t stat_size(QT_STATBUF &buf) { return buf.st_size; } +inline static uid_t stat_uid(QT_STATBUF &buf) { return buf.st_uid; } +inline static gid_t stat_gid(QT_STATBUF &buf) { return buf.st_gid; } +inline static time_t stat_atime(QT_STATBUF &buf) { return buf.st_atime; } +inline static time_t stat_mtime(QT_STATBUF &buf) { return buf.st_mtime; } +#endif + +static bool createUDSEntry(const QString &filename, const QByteArray &path, UDSEntry &entry, + KIO::StatDetails details) +{ + assert(entry.count() == 0); // by contract :-) + int entries = 0; + if (details & KIO::Basic) { + // filename, access, type, size, linkdest + entries += 5; + } + if (details & KIO::User) { + // uid, gid + entries += 2; + } + if (details & KIO::Time) { + // atime, mtime, btime + entries += 3; + } + if (details & KIO::Acl) { + // acl data + entries += 3; + } + if (details & KIO::Inode) { + // dev, inode + entries += 2; + } + entry.reserve(entries); + + if (details & KIO::Basic) { + entry.fastInsert(KIO::UDSEntry::UDS_NAME, filename); + } + + mode_t type; + mode_t access; + bool isBrokenSymLink = false; + signed long long size = 0LL; +#if HAVE_POSIX_ACL + QByteArray targetPath = path; +#endif + +#if HAVE_STATX + // statx syscall is available + struct statx buff; +#else + QT_STATBUF buff; +#endif + + if (LSTAT(path.data(), &buff, details) == 0) { + + if (details & KIO::Inode) { + entry.fastInsert(KIO::UDSEntry::UDS_DEVICE_ID, stat_dev(buff)); + entry.fastInsert(KIO::UDSEntry::UDS_INODE, stat_ino(buff)); + } + + if ((stat_mode(buff) & QT_STAT_MASK) == QT_STAT_LNK) { + + QByteArray linkTargetBuffer; + if (details & (KIO::Basic|KIO::ResolveSymlink)) { + + // Use readlink on Unix because symLinkTarget turns relative targets into absolute (#352927) + #if HAVE_STATX + size_t lowerBound = 256; + size_t higherBound = 1024; + uint64_t s = stat_size(buff); + if (s > SIZE_MAX) { + qCWarning(KIO_FILE) << "file size bigger than SIZE_MAX, too big for readlink use!" << path; + return false; + } + size_t size = static_cast(s); + using SizeType = size_t; + #else + off_t lowerBound = 256; + off_t higherBound = 1024; + off_t size = stat_size(buff); + using SizeType = off_t; + #endif + SizeType bufferSize = qBound(lowerBound, size +1, higherBound); + linkTargetBuffer.resize(bufferSize); + while (true) { + ssize_t n = readlink(path.constData(), linkTargetBuffer.data(), bufferSize); + if (n < 0 && errno != ERANGE) { + qCWarning(KIO_FILE) << "readlink failed!" << path; + return false; + } else if (n > 0 && static_cast(n) != bufferSize) { + // the buffer was not filled in the last iteration + // we are finished reading, break the loop + linkTargetBuffer.truncate(n); + break; + } + bufferSize *= 2; + linkTargetBuffer.resize(bufferSize); + } + const QString linkTarget = QFile::decodeName(linkTargetBuffer); + entry.fastInsert(KIO::UDSEntry::UDS_LINK_DEST, linkTarget); + } + + // A symlink + if (details & KIO::ResolveSymlink) { + if (STAT(path.constData(), &buff, details) == -1) { + isBrokenSymLink = true; + } else { +#if HAVE_POSIX_ACL + if (details & KIO::Acl) { + // valid symlink, will get the ACLs of the destination + targetPath = linkTargetBuffer; + } +#endif + } + } + } + } else { + // qCWarning(KIO_FILE) << "lstat didn't work on " << path.data(); + return false; + } + + if (details & KIO::Basic) { + if (isBrokenSymLink) { + // It is a link pointing to nowhere + type = S_IFMT - 1; + access = S_IRWXU | S_IRWXG | S_IRWXO; + size = 0LL; + } else { + type = stat_mode(buff) & S_IFMT; // extract file type + access = stat_mode(buff) & 07777; // extract permissions + size = stat_size(buff); + } + + entry.fastInsert(KIO::UDSEntry::UDS_FILE_TYPE, type); + entry.fastInsert(KIO::UDSEntry::UDS_ACCESS, access); + entry.fastInsert(KIO::UDSEntry::UDS_SIZE, size); + } + +#if HAVE_POSIX_ACL + if (details & KIO::Acl) { + /* Append an atom indicating whether the file has extended acl information + * and if withACL is specified also one with the acl itself. If it's a directory + * and it has a default ACL, also append that. */ + appendACLAtoms(targetPath, entry, type); + } +#endif + + if (details & KIO::User) { + entry.fastInsert(KIO::UDSEntry::UDS_USER, getUserName(KUserId(stat_uid(buff)))); + entry.fastInsert(KIO::UDSEntry::UDS_GROUP, getGroupName(KGroupId(stat_gid(buff)))); + } + + if (details & KIO::Time) { + entry.fastInsert(KIO::UDSEntry::UDS_MODIFICATION_TIME, stat_mtime(buff)); + entry.fastInsert(KIO::UDSEntry::UDS_ACCESS_TIME, stat_atime(buff)); + +#ifdef st_birthtime + /* For example FreeBSD's and NetBSD's stat contains a field for + * the inode birth time: st_birthtime + * This however only works on UFS and ZFS, and not, on say, NFS. + * Instead of setting a bogus fallback like st_mtime, only use + * it if it is greater than 0. */ + if (buff.st_birthtime > 0) { + entry.fastInsert(KIO::UDSEntry::UDS_CREATION_TIME, buff.st_birthtime); + } +#elif defined __st_birthtime + /* As above, but OpenBSD calls it slightly differently. */ + if (buff.__st_birthtime > 0) { + entry.fastInsert(KIO::UDSEntry::UDS_CREATION_TIME, buff.__st_birthtime); + } +#elif HAVE_STATX + /* And linux version using statx syscall */ + if (buff.stx_mask & STATX_BTIME) { + entry.fastInsert(KIO::UDSEntry::UDS_CREATION_TIME, buff.stx_btime.tv_sec); + } +#endif + } + + return true; +} + PrivilegeOperationReturnValue FileProtocol::tryOpen(QFile &f, const QByteArray &path, int flags, int mode, int errcode) { const QString sockPath = socketPath(); @@ -1009,3 +1292,5 @@ return PrivilegeOperationReturnValue::failure(KIO::ERR_ACCESS_DENIED); } + +