diff --git a/CMakeLists.txt b/CMakeLists.txt index 7e30560ec..75dda1e63 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -506,6 +506,7 @@ set(LIBFASTFETCH_SRC src/common/impl/FFPlatform.c src/common/impl/smbios.c src/common/impl/strutil.c + src/common/impl/zlib.c src/detection/bluetoothradio/bluetoothradio.c src/detection/bootmgr/bootmgr.c src/detection/chassis/chassis.c diff --git a/src/common/android/dex.c b/src/common/android/dex.c index d828806a7..199ea8f48 100644 --- a/src/common/android/dex.c +++ b/src/common/android/dex.c @@ -9,8 +9,7 @@ #include #ifdef FF_HAVE_ZLIB - #include "common/library.h" - #include + #include "common/zlib.h" #endif // Offsets into the dex header, from https://source.android.com/docs/core/runtime/dex-format. @@ -543,10 +542,11 @@ static const uint8_t* findDexMagic(const uint8_t* jar, size_t jarSize, size_t* d #ifdef FF_HAVE_ZLIB static const char* inflateDex(const uint8_t* data, size_t dataSize, uint32_t uncompressedSize, uint8_t** out) { - FF_LIBRARY_LOAD(zlib, "dlopen(libz) failed", "libz" FF_LIBRARY_EXTENSION, 2) - FF_LIBRARY_LOAD_SYMBOL(zlib, inflateInit2_, "dlsym(inflateInit2_) failed") - FF_LIBRARY_LOAD_SYMBOL(zlib, inflate, "dlsym(inflate) failed") - FF_LIBRARY_LOAD_SYMBOL(zlib, inflateEnd, "dlsym(inflateEnd) failed") + const char* error = ffZlibLoad(FF_ZLIB_INFLATE); + if (error != nullptr) { + FF_DEBUG("The dex cannot be inflated: %s", error); + return error; + } // Released by the cleanup on every path out below; ownership moves to the caller by clearing // the pointer, which is also why no failure path has to free it by hand. @@ -565,13 +565,13 @@ static const char* inflateDex(const uint8_t* data, size_t dataSize, uint32_t unc stream.next_out = buffer; stream.avail_out = (uInt) uncompressedSize; - const int initStatus = ffinflateInit2_(&stream, -MAX_WBITS, ZLIB_VERSION, (int) sizeof(z_stream)); + const int initStatus = inflateInit2(&stream, -MAX_WBITS); if (initStatus != Z_OK) { FF_DEBUG("inflateInit2() failed: zlib status %d", initStatus); return "inflateInit2 failed"; } - const int status = ffinflate(&stream, Z_FINISH); - ffinflateEnd(&stream); + const int status = inflate(&stream, Z_FINISH); + inflateEnd(&stream); if (status != Z_STREAM_END || stream.total_out != (uLong) uncompressedSize) { FF_DEBUG("inflate() returned %d, produced %lu of %u bytes", status, stream.total_out, uncompressedSize); diff --git a/src/common/impl/zlib.c b/src/common/impl/zlib.c new file mode 100644 index 000000000..77ac015b8 --- /dev/null +++ b/src/common/impl/zlib.c @@ -0,0 +1,81 @@ +#include "common/zlib.h" +#include "common/debug.h" + +#if FF_HAVE_ZLIB && !FF_DISABLE_DLOPEN + +struct FFZlibData zlibData; + +// Opens the library and leaves the handle in zlibData, so the groups that follow resolve against the +// same mapping instead of opening their own. +static const char* openZlib(void) { + #ifdef _WIN32 + FF_LIBRARY_LOAD_MESSAGE(zlib, "zlib1" FF_LIBRARY_EXTENSION, 2) + #else + FF_LIBRARY_LOAD_MESSAGE(zlib, "libz" FF_LIBRARY_EXTENSION, 2) + #endif + + zlibData.handle = zlib; + zlib = nullptr; // the handle is kept in zlibData, so the cleanup must not close it + return nullptr; +} + +const char* ffZlibLoad(FFZlibGroup group) { + FF_DEBUG("Loading zlib group: %x", group); + + // Opened once, by whichever group is asked for first; the later ones reuse that mapping. A + // failed open is retried by the next group rather than cached, so a system without libz pays at + // most one attempt per group -- not one per call. + if (zlibData.handle == nullptr) { + if (zlibData.attempted == UINT8_MAX) { + return "Previous attempt to open zlib failed"; + } + + const char* error = openZlib(); + if (error != nullptr) { + static_assert(sizeof(zlibData.attempted) == 1, "zlibData.attempted must be 1 byte"); + zlibData.attempted = UINT8_MAX; + return error; + } + } + + if (group & FF_ZLIB_COMPRESS) { + if (zlibData.attempted & FF_ZLIB_COMPRESS) { + if (!zlibData.ffcompress2) { + return "Previous attempt to load compress failed"; + } + } else { + zlibData.attempted |= FF_ZLIB_COMPRESS; + FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(zlibData.handle, zlibData, compressBound) + FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(zlibData.handle, zlibData, compress2) + } + } + if (group & FF_ZLIB_INFLATE) { + if (zlibData.attempted & FF_ZLIB_INFLATE) { + if (!zlibData.ffinflateEnd) { + return "Previous attempt to load inflate failed"; + } + } else { + zlibData.attempted |= FF_ZLIB_INFLATE; + FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(zlibData.handle, zlibData, inflateInit2_) + FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(zlibData.handle, zlibData, inflate) + FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(zlibData.handle, zlibData, inflateEnd) + } + } + + if (group & FF_ZLIB_GZFILE) { + if (zlibData.attempted & FF_ZLIB_GZFILE) { + if (!zlibData.ffgzclose) { + return "Previous attempt to load gzfile failed"; + } + } else { + zlibData.attempted |= FF_ZLIB_GZFILE; + FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(zlibData.handle, zlibData, gzopen) + FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(zlibData.handle, zlibData, gzread) + FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(zlibData.handle, zlibData, gzclose) + } + } + + return nullptr; +} + +#endif diff --git a/src/common/zlib.h b/src/common/zlib.h new file mode 100644 index 000000000..749fbd57a --- /dev/null +++ b/src/common/zlib.h @@ -0,0 +1,106 @@ +#pragma once + +#include "fastfetch.h" + +#if FF_HAVE_ZLIB + +// Which set of entry points a caller needs. Resolving is per group rather than all at once so that a +// symbol nobody uses cannot make a caller fail: libz is not one library across the platforms +// fastfetch builds on, and the gz family in particular is not guaranteed to be present. +typedef enum FFZlibGroup : uint8_t { + FF_ZLIB_COMPRESS = 1 << 0, // compressBound, compress2 + FF_ZLIB_INFLATE = 1 << 1, // inflateInit2_, inflate, inflateEnd + FF_ZLIB_GZFILE = 1 << 2, // gzopen, gzread, gzclose +} FFZlibGroup; + + #if FF_DISABLE_DLOPEN + + #define Z_LARGE64 1 + #include + +const char* ffZlibLoad([[maybe_unused]] FFZlibGroup group) { + // the library is linked in: nothing to open, nothing to resolve, nothing to fail + return nullptr; +} + + #else + // Hack. declares everything with an extern linkage macro, which prevents us from + // implementing the functions ourselves. zlib spells it ZEXTERN/ZEXPORT and zlib-ng's + // zlib-compat header spells it Z_EXTERN/Z_EXPORT; both set theirs under `#ifndef`, so + // defining all four before the include is what makes the definitions at the bottom of this + // header legal -- they inherit the static inline linkage from the declarations zlib.h emits + // here. This header must therefore be the first thing in a translation unit to pull in + // ; if it is not, the definitions below stop being static and clang reports + // `static declaration follows non-static declaration` right there. + #undef ZEXTERN + #undef ZEXPORT + #undef Z_EXTERN + #undef Z_EXPORT + #define ZEXTERN static inline + #define ZEXPORT + #define Z_EXTERN static inline + #define Z_EXPORT + + #pragma GCC diagnostic ignored "-Wunused-function" + + #include + + #include "common/library.h" + +extern struct FFZlibData { + // Kept open rather than closed once opened: every group resolves against this one mapping, and + // re-opening per group would pay the cold load again. + void* handle; + + FF_LIBRARY_SYMBOL(compressBound) + FF_LIBRARY_SYMBOL(compress2) + FF_LIBRARY_SYMBOL(inflateInit2_) + FF_LIBRARY_SYMBOL(inflate) + FF_LIBRARY_SYMBOL(inflateEnd) + FF_LIBRARY_SYMBOL(gzopen) + FF_LIBRARY_SYMBOL(gzread) + FF_LIBRARY_SYMBOL(gzclose) + + FFZlibGroup attempted; +} zlibData; + +[[gnu::always_inline]] static inline uLong(compressBound)(uLong sourceLen) { + return zlibData.ffcompressBound(sourceLen); +} + +[[gnu::always_inline]] static inline int(compress2)(Bytef* dest, uLongf* destLen, const Bytef* source, uLong sourceLen, int level) { + return zlibData.ffcompress2(dest, destLen, source, sourceLen, level); +} + +[[gnu::always_inline]] static inline int(inflateInit2_)(z_streamp strm, int windowBits, const char* version, int stream_size) { + return zlibData.ffinflateInit2_(strm, windowBits, version, stream_size); +} + +[[gnu::always_inline]] static inline int(inflate)(z_streamp strm, int flush) { + return zlibData.ffinflate(strm, flush); +} + +[[gnu::always_inline]] static inline int(inflateEnd)(z_streamp strm) { + return zlibData.ffinflateEnd(strm); +} + +[[gnu::always_inline]] static inline gzFile(gzopen)(const char* path, const char* mode) { + return zlibData.ffgzopen(path, mode); +} + +[[gnu::always_inline]] static inline int(gzread)(gzFile file, voidp buf, unsigned len) { + return zlibData.ffgzread(file, buf, len); +} + +[[gnu::always_inline]] static inline int(gzclose)(gzFile file) { + return zlibData.ffgzclose(file); +} + +// Resolves the entry points of `group` and returns nullptr, or an error message. Call it before using +// that group's functions: until it has succeeded, the forwarders above are null pointers. The library +// itself is opened once, by whichever group is asked for first, and stays open. +const char* ffZlibLoad(FFZlibGroup group); + + #endif + +#endif diff --git a/src/detection/lm/lm_linux.c b/src/detection/lm/lm_linux.c index 5a706ca0a..e9eb016b5 100644 --- a/src/detection/lm/lm_linux.c +++ b/src/detection/lm/lm_linux.c @@ -55,35 +55,32 @@ static const char* getSshdVersion(FFstrbuf* version) { } #ifdef FF_HAVE_ZLIB - #include "common/library.h" #include "common/path.h" + #include "common/zlib.h" #include - #include static const char* getSddmVersion(FFstrbuf* version) { - FF_LIBRARY_LOAD_MESSAGE(zlib, "libz" FF_LIBRARY_EXTENSION, 2) - FF_LIBRARY_LOAD_SYMBOL_MESSAGE(zlib, gzopen) - FF_LIBRARY_LOAD_SYMBOL_MESSAGE(zlib, gzread) - FF_LIBRARY_LOAD_SYMBOL_MESSAGE(zlib, gzerror) - FF_LIBRARY_LOAD_SYMBOL_MESSAGE(zlib, gztell) - FF_LIBRARY_LOAD_SYMBOL_MESSAGE(zlib, gzrewind) - FF_LIBRARY_LOAD_SYMBOL_MESSAGE(zlib, gzclose) + const char* error = ffZlibLoad(FF_ZLIB_GZFILE); + if (error != nullptr) { + FF_DEBUG("The sddm version cannot be read: %s", error); + return error; + } - gzFile file = ffgzopen(FF_PATH_PKG_BASE "/share/man/man1/sddm.1.gz", "rb"); + gzFile file = gzopen(FF_PATH_PKG_BASE "/share/man/man1/sddm.1.gz", "rb"); if (file == Z_NULL) { - FF_DEBUG("ffgzopen(\"/usr/share/man/man1/sddm.1.gz\", \"rb\") failed: %s", strerror(errno)); - return "ffgzopen(\"/usr/share/man/man1/sddm.1.gz\", \"rb\") failed"; + FF_DEBUG("gzopen(\"/usr/share/man/man1/sddm.1.gz\", \"rb\") failed: %s", strerror(errno)); + return "gzopen(\"/usr/share/man/man1/sddm.1.gz\", \"rb\") failed"; } ffStrbufEnsureFree(version, 2047); memset(version->chars, 0, version->allocated); - int size = ffgzread(file, version->chars, version->allocated - 1); - ffgzclose(file); + int size = gzread(file, version->chars, version->allocated - 1); + gzclose(file); if (size <= 0) { - FF_DEBUG("ffgzread(file, version->chars, version->length) failed"); - return "ffgzread(file, version->chars, version->length) failed"; + FF_DEBUG("gzread(file, version->chars, version->length) failed"); + return "gzread(file, version->chars, version->length) failed"; } version->length = (uint32_t) size; diff --git a/src/logo/image/image.c b/src/logo/image/image.c index 9380c2aad..c96e8db39 100644 --- a/src/logo/image/image.c +++ b/src/logo/image/image.c @@ -489,6 +489,7 @@ static bool printImageKittyDirect(bool printError) { #include #include "common/time.h" + #include "common/debug.h" #ifndef _WIN32 #include @@ -498,19 +499,14 @@ static bool printImageKittyDirect(bool printError) { #endif #ifdef FF_HAVE_ZLIB - #include + #include "common/zlib.h" static bool compressBlob(void** blob, size_t* length) { - FF_LIBRARY_LOAD(zlib, false, - #ifdef _WIN32 - "zlib1" - #else - "libz" - #endif - FF_LIBRARY_EXTENSION, - 2) - FF_LIBRARY_LOAD_SYMBOL(zlib, compressBound, false) - FF_LIBRARY_LOAD_SYMBOL(zlib, compress2, false) + const char* error = ffZlibLoad(FF_ZLIB_COMPRESS); + if (error != nullptr) { + FF_DEBUG("The logo stays uncompressed: %s", error); + return false; + } #if _WIN32 // zlib's uLong is 32-bit on Windows (LLP64), so a >4 GiB source can't be @@ -520,7 +516,7 @@ static bool compressBlob(void** blob, size_t* length) { } #endif - uLong compressedLength = ffcompressBound((uLong) *length); + uLong compressedLength = compressBound((uLong) *length); void* compressed = malloc(compressedLength); if (compressed == nullptr) { return false; @@ -534,7 +530,7 @@ static bool compressBlob(void** blob, size_t* length) { // The gap is far wider on Windows, where the same frames cost 37.7 ms each at level 9 against // 6.4 at level 6: raising this back to 9 now that the DLL name above actually loads is a 4.2x // slowdown there (398 ms -> 1692 ms). Re-measure both platforms before touching it. - if (ffcompress2(compressed, &compressedLength, *blob, (uLong) *length, Z_DEFAULT_COMPRESSION) != Z_OK) { + if (compress2(compressed, &compressedLength, *blob, (uLong) *length, Z_DEFAULT_COMPRESSION) != Z_OK) { free(compressed); return false; }