diff --git a/src/common/impl/io_unix.c b/src/common/impl/io_unix.c index 840df3be6..83ef3e8d0 100644 --- a/src/common/impl/io_unix.c +++ b/src/common/impl/io_unix.c @@ -11,6 +11,7 @@ #include #else #include + #define st_mtim st_mtimespec // `struct stat` spells it `st_mtimespec` on Apple #endif #if FF_HAVE_WORDEXP @@ -331,3 +332,18 @@ FFNativeFD ffGetNullFD(void) { bool ffRemoveFile(const char* fileName) { return unlink(fileName) == 0; } + +uint64_t ffPathGetMtime(const char* path) { + struct stat st; + if (stat(path, &st) != 0) { + return 0; + } + + // An mtime at or before the Unix epoch means the filesystem did not fill it in. Reporting it + // as-is would hand the caller a value indistinguishable from "unknown", so treat it as such. + if (st.st_mtim.tv_sec <= 0) { + return 0; + } + + return (uint64_t) st.st_mtim.tv_sec * 1000ull + (uint64_t) st.st_mtim.tv_nsec / 1000000ull; +} diff --git a/src/common/impl/io_windows.c b/src/common/impl/io_windows.c index b9691eb39..65115a34d 100644 --- a/src/common/impl/io_windows.c +++ b/src/common/impl/io_windows.c @@ -1,6 +1,7 @@ #include "fastfetch.h" #include "common/io.h" #include "common/strutil.h" +#include "common/time.h" #include "common/windows/nt.h" #include "common/windows/unicode.h" @@ -499,3 +500,19 @@ FFNativeFD ffGetNullFD(void) { bool ffRemoveFile(const char* fileName) { return DeleteFileA(fileName) != FALSE; } + +uint64_t ffPathGetMtime(const char* path) { + FF_AUTO_CLOSE_FD HANDLE handle = CreateFileA(path, GENERIC_READ, FILE_SHARE_READ, nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr); + + if (handle == INVALID_HANDLE_VALUE) { // file doesn't exist or isn't accessible + return 0; + } + + FILE_BASIC_INFORMATION fileInfo; + IO_STATUS_BLOCK iosb; + if (!NT_SUCCESS(NtQueryInformationFile(handle, &iosb, &fileInfo, sizeof(fileInfo), FileBasicInformation))) { + return 0; + } + + return ffFileTimeToUnixMs((uint64_t) fileInfo.LastWriteTime.QuadPart); +} diff --git a/src/common/io.h b/src/common/io.h index 9086bea43..7a8c7e7b0 100644 --- a/src/common/io.h +++ b/src/common/io.h @@ -291,3 +291,7 @@ static inline void wrapClosedir(HANDLE* pdir) { FFNativeFD ffGetNullFD(void); bool ffRemoveFile(const char* fileName); +// Modification time of a file, in milliseconds since the Unix epoch, or 0 if it can not be read. +// The representation is uniform across platforms so that a value derived from it means the +// same thing everywhere, which matters for callers that store it as a cache key. +[[gnu::nonnull(1)]] uint64_t ffPathGetMtime(const char* path); diff --git a/src/detection/packages/packages.c b/src/detection/packages/packages.c index c72ea8757..a508342d8 100644 --- a/src/detection/packages/packages.c +++ b/src/detection/packages/packages.c @@ -1,14 +1,9 @@ #include "packages.h" #include "common/io.h" -#include "common/time.h" #include #include -#ifdef __APPLE__ - #define st_mtim st_mtimespec -#endif - void ffDetectPackagesImpl(FFPackagesResult* result, FFPackagesOptions* options); const char* ffDetectPackages(FFPackagesResult* result, FFPackagesOptions* options) { @@ -22,38 +17,20 @@ const char* ffDetectPackages(FFPackagesResult* result, FFPackagesOptions* option } bool ffPackagesReadCache(FFstrbuf* cacheDir, FFstrbuf* cacheContent, const char* filePath, const char* packageId, uint32_t* result) { -#ifndef _WIN32 - struct stat st; - if (stat(filePath, &st) < 0) // file doesn't exist or isn't accessible - { + const uint64_t mtime_current = ffPathGetMtime(filePath); + if (__builtin_expect(mtime_current == 0, false)) { + // A missing database legitimately means "no packages installed", and must not be cached. + // A database whose modification time could not be read must not be cached either, and + // reporting 0 for it would be a lie. ffPathGetMtime() reports 0 for both, so tell them + // apart before deciding which of the two applies. + if (ffPathExists(filePath, FF_PATHTYPE_FILE)) { + return false; + } + *result = 0; return true; } - if (__builtin_expect(st.st_mtim.tv_sec <= 0, false)) { - return false; - } - - uint64_t mtime_current = (uint64_t) st.st_mtim.tv_sec * 1000ull + (uint64_t) st.st_mtim.tv_nsec / 1000000ull; -#else - FF_AUTO_CLOSE_FD HANDLE handle = CreateFileA(filePath, GENERIC_READ, FILE_SHARE_READ, nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr); - - if (handle == INVALID_HANDLE_VALUE) // file doesn't exist or isn't accessible - { - *result = 0; - return true; - } - - uint64_t mtime_current; - FILE_BASIC_INFORMATION fileInfo; - IO_STATUS_BLOCK iosb; - if (!NT_SUCCESS(NtQueryInformationFile(handle, &iosb, &fileInfo, sizeof(fileInfo), FileBasicInformation))) { - return false; - } - - mtime_current = ffFileTimeToUnixMs((uint64_t) fileInfo.LastWriteTime.QuadPart); -#endif - ffStrbufSet(cacheDir, &instance.state.platform.cacheDir); ffStrbufEnsureEndsWithC(cacheDir, '/'); ffStrbufAppendF(cacheDir, "fastfetch/packages/%s.txt", packageId);