diff --git a/src/ioslaves/file/ConfigureChecks.cmake b/src/ioslaves/file/ConfigureChecks.cmake --- a/src/ioslaves/file/ConfigureChecks.cmake +++ b/src/ioslaves/file/ConfigureChecks.cmake @@ -1,8 +1,10 @@ include(CheckFunctionExists) include(CheckLibraryExists) +include(CheckSymbolExists) include(CheckIncludeFile) include(CheckIncludeFiles) include(CheckStructHasMember) +include(CheckCXXSourceCompiles) check_include_files(sys/time.h HAVE_SYS_TIME_H) check_include_files(string.h HAVE_STRING_H) @@ -14,3 +16,19 @@ check_library_exists(volmgt volmgt_running "" HAVE_VOLMGT) check_struct_has_member("struct dirent" d_type dirent.h HAVE_DIRENT_D_TYPE LANGUAGE CXX) + +check_symbol_exists("__GLIBC__" "stdlib.h" LIBC_IS_GLIBC) +if (LIBC_IS_GLIBC) + check_cxx_source_compiles(" + #include + #include + + int main() { + struct statx buf; + statx(AT_FDCWD, \"/foo\", AT_EMPTY_PATH, STATX_BASIC_STATS, &buf); + return 0; + } + " HAVE_STATX) +else() + set(HAVE_STATX 0) +endif() diff --git a/src/ioslaves/file/config-kioslave-file.h.cmake b/src/ioslaves/file/config-kioslave-file.h.cmake --- a/src/ioslaves/file/config-kioslave-file.h.cmake +++ b/src/ioslaves/file/config-kioslave-file.h.cmake @@ -15,3 +15,6 @@ /* Defined if system has the sendfile function. */ #cmakedefine01 HAVE_SENDFILE + +/* Defined if system has the statx function, meaning glibc >= 2.28 */ +#cmakedefine01 HAVE_STATX 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 @@ -65,6 +65,10 @@ #include #include +#if HAVE_STATX +#include +#endif + #if HAVE_VOLMGT #include #include @@ -838,47 +842,111 @@ return *it; } +#if HAVE_STATX +// statx syscall is available +inline int STAT(const char* path, struct statx * buff) { + return statx(AT_FDCWD, path, AT_SYMLINK_NOFOLLOW, STATX_BASIC_STATS | STATX_BTIME, 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 uint64_t stat_mtime(struct statx &buf) { return buf.stx_mtime.tv_sec; } +#else +// regular stat struct +inline int STAT(const char* path, QT_STATBUF * buff) { + return QT_LSTAT(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 + bool FileProtocol::createUDSEntry(const QString &filename, const QByteArray &path, UDSEntry &entry, short int details) { assert(entry.count() == 0); // by contract :-) - entry.reserve(8); - + switch (details) { + case 0: + // filename, access, type, size, linkdest + entry.reserve(5); + break; + case 1: + // uid, gid, atime, mtime, btime + entry.reserve(10); + break; + case 2: + // acl data + entry.reserve(13); + break; + default: // case details > 2 + // dev, inode + entry.reserve(15); + break; + } entry.fastInsert(KIO::UDSEntry::UDS_NAME, filename); mode_t type; mode_t access; bool isBrokenSymLink = false; - long long size = 0LL; + 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 (QT_LSTAT(path.data(), &buff) == 0) { + if (STAT(path.data(), &buff) == 0) { if (details > 2) { - entry.fastInsert(KIO::UDSEntry::UDS_DEVICE_ID, buff.st_dev); - entry.fastInsert(KIO::UDSEntry::UDS_INODE, buff.st_ino); + entry.fastInsert(KIO::UDSEntry::UDS_DEVICE_ID, stat_dev(buff)); + entry.fastInsert(KIO::UDSEntry::UDS_INODE, stat_ino(buff)); } - if ((buff.st_mode & QT_STAT_MASK) == QT_STAT_LNK) { + if ((stat_mode(buff) & QT_STAT_MASK) == QT_STAT_LNK) { #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) - const off_t lowerLimit = 1; - const off_t upperLimit = 1024; - size_t bufferSize = qBound(lowerLimit, buff.st_size, upperLimit); + #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 = (size_t) s; + #else + off_t lowerBound = 256; + off_t higherBound = 1024; + off_t size = stat_size(buff); + #endif + auto bufferSize = qBound(lowerBound, size +1, higherBound); QByteArray linkTargetBuffer; 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; } @@ -891,7 +959,7 @@ // A symlink -> follow it only if details>1 if (details > 1) { - if (QT_STAT(path.constData(), &buff) == -1) { + if (STAT(path.constData(), &buff) == -1) { isBrokenSymLink = true; } else { #if HAVE_POSIX_ACL @@ -912,9 +980,9 @@ access = S_IRWXU | S_IRWXG | S_IRWXO; size = 0LL; } else { - type = buff.st_mode & S_IFMT; // extract file type - access = buff.st_mode & 07777; // extract permissions - size = buff.st_size; + 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); @@ -931,14 +999,15 @@ #endif if (details > 0) { - entry.fastInsert(KIO::UDSEntry::UDS_MODIFICATION_TIME, buff.st_mtime); + entry.fastInsert(KIO::UDSEntry::UDS_MODIFICATION_TIME, stat_mtime(buff)); + entry.fastInsert(KIO::UDSEntry::UDS_ACCESS_TIME, stat_atime(buff)); #ifndef Q_OS_WIN - entry.fastInsert(KIO::UDSEntry::UDS_USER, getUserName(KUserId(buff.st_uid))); - entry.fastInsert(KIO::UDSEntry::UDS_GROUP, getGroupName(KGroupId(buff.st_gid))); + 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 - entry.fastInsert(KIO::UDSEntry::UDS_ACCESS_TIME, buff.st_atime); + #ifdef st_birthtime /* For example FreeBSD's and NetBSD's stat contains a field for * the inode birth time: st_birthtime @@ -953,15 +1022,14 @@ 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 } - // Note: buff.st_ctime isn't the creation time ! - // We made that mistake for KDE 2.0, but it's in fact the - // "file status" change time, which we don't care about. - // For FreeBSD and NetBSD, use st_birthtime. For OpenBSD, - // use __st_birthtime. - return true; } diff --git a/tests/kioslavetest.cpp b/tests/kioslavetest.cpp --- a/tests/kioslavetest.cpp +++ b/tests/kioslavetest.cpp @@ -347,6 +347,8 @@ // It's rather rare to iterate that way, usually you'd use numberValue/stringValue directly. // This is just to print out all that we got + QDateTime timestamp; + const QVector keys = entry.fields(); QVector::const_iterator it = keys.begin(); for (; it != keys.end(); ++it) { @@ -357,6 +359,9 @@ if ((mode & QT_STAT_MASK) == QT_STAT_DIR) { qDebug() << "is a dir"; } + if ((mode & QT_STAT_MASK) == QT_STAT_LNK) { + qDebug() << "is a link"; + } } break; case KIO::UDSEntry::UDS_ACCESS: @@ -384,6 +389,18 @@ case KIO::UDSEntry::UDS_SIZE: qDebug() << "Size: " << KIO::convertSize(entry.numberValue(*it)); break; + case KIO::UDSEntry::UDS_CREATION_TIME: + timestamp = QDateTime::fromSecsSinceEpoch(entry.numberValue(*it)); + qDebug() << "CreationTime: " << timestamp.toString(Qt::SystemLocaleShortDate); + break; + case KIO::UDSEntry::UDS_MODIFICATION_TIME: + timestamp = QDateTime::fromSecsSinceEpoch(entry.numberValue(*it)); + qDebug() << "ModificationTime: " << timestamp.toString(Qt::SystemLocaleShortDate); + break; + case KIO::UDSEntry::UDS_ACCESS_TIME: + timestamp = QDateTime::fromSecsSinceEpoch(entry.numberValue(*it)); + qDebug() << "AccessTime: " << timestamp.toString(Qt::SystemLocaleShortDate); + break; } } }