Compare commits

..

2 Commits

Author SHA1 Message Date
Carter Li 70d2de4aee Update build-windows-hosts.yml 2026-10-02 23:18:25 +08:00
dependabot[bot] 790b21cda4 CI: Bump the github-actions group with 5 updates
Bumps the github-actions group with 5 updates:

| Package | From | To |
| --- | --- | --- |
| [benchmark-action/github-action-benchmark](https://github.com/benchmark-action/github-action-benchmark) | `1.22.1` | `1.22.2` |
| [github/codeql-action/init](https://github.com/github/codeql-action) | `4.38.0` | `4.38.1` |
| [github/codeql-action/analyze](https://github.com/github/codeql-action) | `4.38.0` | `4.38.1` |
| [docker/setup-qemu-action](https://github.com/docker/setup-qemu-action) | `4.3.0` | `4.4.0` |
| [signpath/github-action-submit-signing-request](https://github.com/signpath/github-action-submit-signing-request) | `2.3` | `3.0` |


Updates `benchmark-action/github-action-benchmark` from 1.22.1 to 1.22.2
- [Release notes](https://github.com/benchmark-action/github-action-benchmark/releases)
- [Changelog](https://github.com/benchmark-action/github-action-benchmark/blob/master/CHANGELOG.md)
- [Commits](https://github.com/benchmark-action/github-action-benchmark/compare/52576c92bccf6ac60c8223ec7eb2565637cae9ba...4322e5726e6334590d251fc4f92bec0efafc45dc)

Updates `github/codeql-action/init` from 4.38.0 to 4.38.1
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/github/codeql-action/compare/b96794f015dfd88f77b49b1c93e0fa7110f94c63...1c5b675653bb5c22dbe9b12b556ec555138e09fd)

Updates `github/codeql-action/analyze` from 4.38.0 to 4.38.1
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/github/codeql-action/compare/b96794f015dfd88f77b49b1c93e0fa7110f94c63...1c5b675653bb5c22dbe9b12b556ec555138e09fd)

Updates `docker/setup-qemu-action` from 4.3.0 to 4.4.0
- [Release notes](https://github.com/docker/setup-qemu-action/releases)
- [Commits](https://github.com/docker/setup-qemu-action/compare/1f40c72289eff860ee54a304f1438e3cff362e0a...99012661954931238ded8c8b007157a8430204e1)

Updates `signpath/github-action-submit-signing-request` from 2.3 to 3.0
- [Release notes](https://github.com/signpath/github-action-submit-signing-request/releases)
- [Commits](https://github.com/signpath/github-action-submit-signing-request/compare/c92b958760219087e01f8d67a1669ed57afe2627...f6d04783b4569d051e0c80105fe66e82819d0092)

---
updated-dependencies:
- dependency-name: benchmark-action/github-action-benchmark
  dependency-version: 1.22.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: github-actions
- dependency-name: github/codeql-action/init
  dependency-version: 4.38.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: github-actions
- dependency-name: github/codeql-action/analyze
  dependency-version: 4.38.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: github-actions
- dependency-name: docker/setup-qemu-action
  dependency-version: 4.4.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: github-actions
- dependency-name: signpath/github-action-submit-signing-request
  dependency-version: '3.0'
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: github-actions
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-09-27 18:07:11 +00:00
331 changed files with 1856 additions and 5633 deletions
+5
View File
@@ -0,0 +1,5 @@
[codespell]
check-filenames =
builtin = clear,rare,usage,informal
skip = */.git,*/cmake-build-*,*/.idea,*/completions,*/presets,*/screenshots,*/tests,*/3rdparty,*/logo/ascii,./src/detection/gpu/asahi_drm.h,./src/detection/cpu/cpu_x86.c,./src/detection/gpu/gpu_bsddrm.c,./src/detection/displayserver/linux/wayland/*-protocol.*,./build,./build-*,./build_*,./src/common/impl/genconfig.c
ignore-words-list = iterm,compiletime,unknwn,pengwin,siduction,master,slave,sur,doas,conexant,ags,bu,renderD,SME,Carmel,kinda,symbole,connexion,comando,processus,lokale,adres,titel,som,environnement,Sistem,Lokal,Lokal,Sistem,Paket,Hava,statics,IIterator,whitelist
-13
View File
@@ -1,15 +1,2 @@
# Normalise text files to LF in the repository and check them out with LF, so a CRLF
# never enters an object and never shows up as a whole-file diff on Windows.
* text=auto eol=lf
# The vendored AMD Display Library SDK ships with CRLF endings of its own. Leaving it
# alone keeps `Dep (3rdparty): upgrades display-library` from carrying a 7000-line
# whitespace diff, and a re-vendor will not fight the rule above.
src/3rdparty/display-library/** -text
# The logo fixtures store the exact bytes fastfetch prints, ANSI escapes included.
# They are demo data rather than text, and are not read by any test.
*.fflogo -text
*.h linguist-language=c
*.c linguist-language=c
+1 -1
View File
@@ -36,7 +36,7 @@ jobs:
sudo pkg install -y llvm cmake git pkgconf binutils wayland vulkan-headers vulkan-loader libxcb libXrandr libX11 libdrm glib dconf dbus sqlite3-tcl egl opencl ocl-icd v4l_compat chafa libelf lua54 libva libvdpau
- name: CMake configuration
run: env CC=clang cmake -DSET_TWEAK=Off -DBUILD_TESTS=On -DENABLE_EMBEDDED_PCIIDS=On -DENABLE_EMBEDDED_AMDGPUIDS=On -DCMAKE_BUILD_TYPE=${{ vars.CMAKE_BUILD_TYPE || 'RelWithDebInfo' }} -DCMAKE_C_FLAGS="-fno-pie" -DCMAKE_EXE_LINKER_FLAGS="-no-pie" .
run: env CC=clang cmake -DSET_TWEAK=Off -DBUILD_TESTS=On -DENABLE_EMBEDDED_PCIIDS=On -DENABLE_EMBEDDED_AMDGPUIDS=On -DCMAKE_BUILD_TYPE=${{ vars.CMAKE_BUILD_TYPE || 'RelWithDebInfo' }} .
- name: CMake build
run: cmake --build . --target package --verbose -j4
+1 -1
View File
@@ -37,7 +37,7 @@ jobs:
sudo pkg install -y cmake git pkgconf binutils wayland vulkan-headers vulkan-loader libxcb libXrandr libX11 libdrm glib dconf dbus sqlite3 egl opencl ocl-icd v4l_compat chafa lua54 libva libvdpau
- name: CMake configuration
run: cmake -DSET_TWEAK=Off -DBUILD_TESTS=On -DENABLE_EMBEDDED_PCIIDS=On -DENABLE_EMBEDDED_AMDGPUIDS=On -DCMAKE_BUILD_TYPE=${{ vars.CMAKE_BUILD_TYPE || 'RelWithDebInfo' }} -DCMAKE_C_FLAGS="-fno-pie" -DCMAKE_EXE_LINKER_FLAGS="-no-pie" .
run: cmake -DSET_TWEAK=Off -DBUILD_TESTS=On -DENABLE_EMBEDDED_PCIIDS=On -DENABLE_EMBEDDED_AMDGPUIDS=On -DCMAKE_BUILD_TYPE=${{ vars.CMAKE_BUILD_TYPE || 'RelWithDebInfo' }} .
- name: CMake build
run: cmake --build . --target package --verbose -j4
+3 -3
View File
@@ -17,7 +17,7 @@ jobs:
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: run VM
uses: uraimo/run-on-arch-action@fa1f3e7de95534497266c7265950b5188355fbfb # v3.2.1
uses: uraimo/run-on-arch-action@460cb8e6d9f726a588fc9b5e681c8a6cab09ae41 # v3.2.0
id: runcmd
with:
arch: armv7
@@ -30,8 +30,8 @@ jobs:
curl -L https://apt.kitware.com/keys/kitware-archive-latest.asc | gpg --dearmor - | tee /usr/share/keyrings/kitware-archive-keyring.gpg >/dev/null
echo 'deb [signed-by=/usr/share/keyrings/kitware-archive-keyring.gpg] https://apt.kitware.com/ubuntu/ jammy main' | tee /etc/apt/sources.list.d/kitware.list >/dev/null
echo -e 'Acquire::https::Verify-Peer "false";\nAcquire::https::Verify-Host "false";' >> /etc/apt/apt.conf.d/99ignore-certificates
apt-get update && apt-get install -y cmake make gcc-13 libvulkan-dev libwayland-dev libxrandr-dev libxcb-randr0-dev libdconf-dev libdbus-1-dev libmagickcore-dev libsqlite3-dev librpm-dev libegl-dev libglx-dev ocl-icd-opencl-dev libdrm-dev libelf-dev libefl-all-dev liblua5.4-dev libvdpau-dev libva-dev rpm
CC=gcc-13 cmake -DSET_TWEAK=Off -DBUILD_TESTS=On -DCMAKE_INSTALL_PREFIX=/usr -DCMAKE_C_FLAGS="-fno-pie" -DCMAKE_EXE_LINKER_FLAGS="-no-pie" .
apt-get update && apt-get install -y cmake make gcc-13 libvulkan-dev libwayland-dev libxrandr-dev libxcb-randr0-dev libdconf-dev libdbus-1-dev libmagickcore-dev libsqlite3-dev librpm-dev libegl-dev libglx-dev ocl-icd-opencl-dev libpulse-dev libdrm-dev libelf-dev libefl-all-dev liblua5.4-dev libvdpau-dev libva-dev rpm
CC=gcc-13 cmake -DSET_TWEAK=Off -DBUILD_TESTS=On -DCMAKE_INSTALL_PREFIX=/usr .
cmake --build . --target package --verbose -j4
./fastfetch --list-features
time ./fastfetch -c presets/ci.jsonc --stat false
+4 -4
View File
@@ -43,7 +43,7 @@ jobs:
run: sudo add-apt-repository -y ppa:ubuntu-toolchain-r/test
- name: install required packages
run: sudo apt-get update && sudo apt-get install -y gcc-13 jq libvulkan-dev libwayland-dev libxrandr-dev libxcb-randr0-dev libdconf-dev libdbus-1-dev libmagickcore-dev libsqlite3-dev librpm-dev libegl-dev libglx-dev ocl-icd-opencl-dev libdrm-dev libelf-dev libefl-all-dev libunwind-dev liblua5.4-dev libvdpau-dev libva-dev rpm ninja-build
run: sudo apt-get update && sudo apt-get install -y gcc-13 jq libvulkan-dev libwayland-dev libxrandr-dev libxcb-randr0-dev libdconf-dev libdbus-1-dev libmagickcore-dev libsqlite3-dev librpm-dev libegl-dev libglx-dev ocl-icd-opencl-dev libpulse-dev libdrm-dev libelf-dev libddcutil-dev libefl-all-dev libunwind-dev liblua5.4-dev libvdpau-dev libva-dev rpm ninja-build
- name: install linuxbrew packages
run: |
@@ -52,19 +52,19 @@ jobs:
- name: Initialize CodeQL
if: inputs.arch == 'amd64'
uses: github/codeql-action/init@2892aa5e19bbd11bc0cff5427e3b750a04d9e3c2 # v4.38.2
uses: github/codeql-action/init@1c5b675653bb5c22dbe9b12b556ec555138e09fd # v4.38.1
with:
languages: c
- name: configure project
run: CC=gcc-13 CMAKE_PREFIX_PATH=/home/linuxbrew/.linuxbrew PKG_CONFIG_PATH=/home/linuxbrew/.linuxbrew/lib/pkgconfig:$PKG_CONFIG_PATH cmake -GNinja -DSET_TWEAK=Off -DBUILD_TESTS=On -DENABLE_EMBEDDED_PCIIDS=On -DENABLE_EMBEDDED_AMDGPUIDS=On -DCMAKE_INSTALL_PREFIX=/usr -DCMAKE_C_FLAGS="-fno-pie" -DCMAKE_EXE_LINKER_FLAGS="-no-pie" .
run: CC=gcc-13 CMAKE_PREFIX_PATH=/home/linuxbrew/.linuxbrew PKG_CONFIG_PATH=/home/linuxbrew/.linuxbrew/lib/pkgconfig:$PKG_CONFIG_PATH cmake -GNinja -DSET_TWEAK=Off -DBUILD_TESTS=On -DENABLE_EMBEDDED_PCIIDS=On -DENABLE_EMBEDDED_AMDGPUIDS=On -DCMAKE_INSTALL_PREFIX=/usr .
- name: build project
run: cmake --build . --target package --verbose -j4
- name: perform CodeQL analysis
if: inputs.arch == 'amd64'
uses: github/codeql-action/analyze@2892aa5e19bbd11bc0cff5427e3b750a04d9e3c2 # v4.38.2
uses: github/codeql-action/analyze@1c5b675653bb5c22dbe9b12b556ec555138e09fd # v4.38.1
- name: list features
run: ./fastfetch --list-features
+2 -2
View File
@@ -29,7 +29,7 @@ jobs:
run: sudo add-apt-repository -y ppa:ubuntu-toolchain-r/test
- name: install required packages
run: sudo apt-get update && sudo apt-get install -y gcc-13 gcc-13-multilib libvulkan-dev libwayland-dev libxrandr-dev libxcb-randr0-dev libdconf-dev libdbus-1-dev libmagickcore-dev libsqlite3-dev librpm-dev libegl-dev libglx-dev ocl-icd-opencl-dev libdrm-dev libelf-dev libefl-all-dev libunwind-dev liblua5.4-dev libvdpau-dev libva-dev rpm ninja-build
run: sudo apt-get update && sudo apt-get install -y gcc-13 gcc-13-multilib libvulkan-dev libwayland-dev libxrandr-dev libxcb-randr0-dev libdconf-dev libdbus-1-dev libmagickcore-dev libsqlite3-dev librpm-dev libegl-dev libglx-dev ocl-icd-opencl-dev libpulse-dev libdrm-dev libelf-dev libddcutil-dev libefl-all-dev libunwind-dev liblua5.4-dev libvdpau-dev libva-dev rpm ninja-build
- name: install linuxbrew packages
run: |
@@ -40,7 +40,7 @@ jobs:
run: cmake --version
- name: configure project
run: CC=gcc-13 CMAKE_PREFIX_PATH=/home/linuxbrew/.linuxbrew PKG_CONFIG_PATH=/home/linuxbrew/.linuxbrew/lib/pkgconfig:$PKG_CONFIG_PATH cmake -DCMAKE_C_FLAGS="-m32 -march=i686 -mtune=i686 -fno-pie" -DCMAKE_SYSTEM_NAME=Linux -DCMAKE_SYSTEM_VERSION=5.10.0 -DCMAKE_SYSTEM_PROCESSOR=i686 -DCPACK_DEBIAN_PACKAGE_ARCHITECTURE=i386 -GNinja -DSET_TWEAK=Off -DBUILD_TESTS=On -DENABLE_EMBEDDED_PCIIDS=On -DENABLE_EMBEDDED_AMDGPUIDS=On -DCMAKE_INSTALL_PREFIX=/usr -DCMAKE_EXE_LINKER_FLAGS="-no-pie" .
run: CC=gcc-13 CMAKE_PREFIX_PATH=/home/linuxbrew/.linuxbrew PKG_CONFIG_PATH=/home/linuxbrew/.linuxbrew/lib/pkgconfig:$PKG_CONFIG_PATH cmake -DCMAKE_C_FLAGS="-m32 -march=i686 -mtune=i686" -DCMAKE_SYSTEM_NAME=Linux -DCMAKE_SYSTEM_VERSION=5.10.0 -DCMAKE_SYSTEM_PROCESSOR=i686 -DCPACK_DEBIAN_PACKAGE_ARCHITECTURE=i386 -GNinja -DSET_TWEAK=Off -DBUILD_TESTS=On -DENABLE_EMBEDDED_PCIIDS=On -DENABLE_EMBEDDED_AMDGPUIDS=On -DCMAKE_INSTALL_PREFIX=/usr .
- name: build project
run: cmake --build . --target package --verbose -j4
+2 -2
View File
@@ -37,13 +37,13 @@ jobs:
libdconf-dev libdbus-1-dev libmagickcore-dev \
libsqlite3-dev librpm-dev \
libegl-dev libglx-dev ocl-icd-opencl-dev \
libdrm-dev \
libpulse-dev libdrm-dev \
libelf-dev libefl-all-dev \
liblua5.4-dev \
libvdpau-dev libva-dev \
rpm
cmake -DSET_TWEAK=Off -DBUILD_TESTS=On -DCMAKE_INSTALL_PREFIX=/usr -DCMAKE_C_FLAGS="-fno-pie" -DCMAKE_EXE_LINKER_FLAGS="-no-pie" .
cmake -DSET_TWEAK=Off -DBUILD_TESTS=On -DCMAKE_INSTALL_PREFIX=/usr .
cmake --build . --target package --verbose -j$(nproc)
./fastfetch --list-features
time ./fastfetch -c presets/ci.jsonc --stat false
+3 -3
View File
@@ -21,7 +21,7 @@ jobs:
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: run VM
uses: uraimo/run-on-arch-action@fa1f3e7de95534497266c7265950b5188355fbfb # v3.2.1
uses: uraimo/run-on-arch-action@460cb8e6d9f726a588fc9b5e681c8a6cab09ae41 # v3.2.0
id: runcmd
with:
arch: ${{ inputs.arch }}
@@ -31,8 +31,8 @@ jobs:
uname -a
apt-get update && apt-get install -y software-properties-common
add-apt-repository -y ppa:ubuntu-toolchain-r/test
apt-get update && apt-get install -y cmake make gcc-13 libvulkan-dev libwayland-dev libxrandr-dev libxcb-randr0-dev libdconf-dev libdbus-1-dev libmagickcore-dev libsqlite3-dev librpm-dev libegl-dev libglx-dev ocl-icd-opencl-dev libdrm-dev libchafa-dev libelf-dev libefl-all-dev liblua5.4-dev libvdpau-dev libva-dev rpm
CC=gcc-13 cmake -DSET_TWEAK=Off -DBUILD_TESTS=On -DCMAKE_INSTALL_PREFIX=/usr -DCMAKE_C_FLAGS="-fno-pie" -DCMAKE_EXE_LINKER_FLAGS="-no-pie" .
apt-get update && apt-get install -y cmake make gcc-13 libvulkan-dev libwayland-dev libxrandr-dev libxcb-randr0-dev libdconf-dev libdbus-1-dev libmagickcore-dev libsqlite3-dev librpm-dev libegl-dev libglx-dev ocl-icd-opencl-dev libpulse-dev libdrm-dev libchafa-dev libelf-dev libefl-all-dev liblua5.4-dev libvdpau-dev libva-dev rpm
CC=gcc-13 cmake -DSET_TWEAK=Off -DBUILD_TESTS=On -DCMAKE_INSTALL_PREFIX=/usr .
cmake --build . --target package --verbose -j4
./fastfetch --list-features
time ./fastfetch -c presets/ci.jsonc --stat false
+2 -2
View File
@@ -24,12 +24,12 @@ jobs:
run: |
cat /etc/alpine-release
uname -a
apk add cmake samurai vulkan-loader-dev libxcb-dev libxrandr-dev rpm-dev wayland-dev libdrm-dev dconf-dev imagemagick-dev chafa-dev zlib-dev dbus-dev mesa-dev opencl-dev sqlite-dev networkmanager-dev elfutils-dev lua5.4-dev quickjs-ng-dev libvdpau-dev libva-dev gcc g++
apk add cmake samurai vulkan-loader-dev libxcb-dev libxrandr-dev rpm-dev wayland-dev libdrm-dev dconf-dev imagemagick-dev chafa-dev zlib-dev dbus-dev mesa-dev opencl-dev sqlite-dev networkmanager-dev pulseaudio-dev ddcutil-dev elfutils-dev lua5.4-dev quickjs-ng-dev libvdpau-dev libva-dev gcc g++
shell: alpine.sh --root {0}
- name: build
run: |
cmake -DSET_TWEAK=Off -DBUILD_TESTS=On -DCMAKE_INSTALL_PREFIX=/usr -DIS_MUSL=ON -DENABLE_EMBEDDED_PCIIDS=On -DENABLE_EMBEDDED_AMDGPUIDS=On -DCMAKE_C_FLAGS="-fno-pie" -DCMAKE_EXE_LINKER_FLAGS="-no-pie" -GNinja .
cmake -DSET_TWEAK=Off -DBUILD_TESTS=On -DCMAKE_INSTALL_PREFIX=/usr -DIS_MUSL=ON -DENABLE_EMBEDDED_PCIIDS=On -DENABLE_EMBEDDED_AMDGPUIDS=On -GNinja .
cmake --build . --target package --verbose -j4
shell: alpine.sh {0}
+1 -1
View File
@@ -35,7 +35,7 @@ jobs:
sudo pkgin -y install clang cmake git pkgconf wayland vulkan-headers dconf dbus sqlite3 ImageMagick libva libvdpau
- name: CMake configuration
run: CC=clang cmake -DSET_TWEAK=Off -DBUILD_TESTS=On -DENABLE_EMBEDDED_PCIIDS=On -DENABLE_EMBEDDED_AMDGPUIDS=On -DCMAKE_BUILD_TYPE=${{ vars.CMAKE_BUILD_TYPE || 'RelWithDebInfo' }} -DCMAKE_C_FLAGS="-fno-pie" -DCMAKE_EXE_LINKER_FLAGS="-no-pie" .
run: CC=clang cmake -DSET_TWEAK=Off -DBUILD_TESTS=On -DENABLE_EMBEDDED_PCIIDS=On -DENABLE_EMBEDDED_AMDGPUIDS=On -DCMAKE_BUILD_TYPE=${{ vars.CMAKE_BUILD_TYPE || 'RelWithDebInfo' }} .
- name: CMake build
run: cmake --build . --target package --verbose -j4
+1 -1
View File
@@ -21,7 +21,7 @@ jobs:
run: uname -a
- name: configure project
run: cmake -DSET_TWEAK=Off -DBUILD_TESTS=On -DCMAKE_INSTALL_PREFIX=/usr -DCMAKE_C_FLAGS="-fno-pie" -DCMAKE_EXE_LINKER_FLAGS="-no-pie" . -DENABLE_VULKAN=OFF -DENABLE_WAYLAND=OFF -DENABLE_XCB_RANDR=OFF -DENABLE_XCB=OFF -DENABLE_XRANDR=OFF -DENABLE_X11=OFF -DENABLE_DRM=OFF -DENABLE_DRM_AMDGPU=OFF -DENABLE_GIO=OFF -DENABLE_DCONF=OFF -DENABLE_DBUS=OFF -DENABLE_SQLITE3=OFF -DENABLE_RPM=OFF -DENABLE_IMAGEMAGICK7=OFF -DENABLE_IMAGEMAGICK6=OFF -DENABLE_CHAFA=OFF -DENABLE_ZLIB=OFF -DENABLE_EGL=OFF -DENABLE_GLX=OFF -DENABLE_OPENCL=OFF -DENABLE_FREETYPE=OFF -DENABLE_ELF=OFF -DENABLE_EET=OFF -DENABLE_THREADS=OFF -DENABLE_IMAGE_LOGO=OFF
run: cmake -DSET_TWEAK=Off -DBUILD_TESTS=On -DCMAKE_INSTALL_PREFIX=/usr . -DENABLE_VULKAN=OFF -DENABLE_WAYLAND=OFF -DENABLE_XCB_RANDR=OFF -DENABLE_XCB=OFF -DENABLE_XRANDR=OFF -DENABLE_X11=OFF -DENABLE_DRM=OFF -DENABLE_DRM_AMDGPU=OFF -DENABLE_GIO=OFF -DENABLE_DCONF=OFF -DENABLE_DBUS=OFF -DENABLE_SQLITE3=OFF -DENABLE_RPM=OFF -DENABLE_IMAGEMAGICK7=OFF -DENABLE_IMAGEMAGICK6=OFF -DENABLE_CHAFA=OFF -DENABLE_ZLIB=OFF -DENABLE_EGL=OFF -DENABLE_GLX=OFF -DENABLE_OPENCL=OFF -DENABLE_FREETYPE=OFF -DENABLE_PULSE=OFF -DENABLE_DDCUTIL=OFF -DENABLE_ELF=OFF -DENABLE_EET=OFF -DENABLE_THREADS=OFF -DENABLE_IMAGE_LOGO=OFF
- name: build project
run: cmake --build . --target package --verbose -j4
+1 -1
View File
@@ -35,7 +35,7 @@ jobs:
sudo pkg install gcc14 cmake git pkg-config glib2 dbus sqlite-3 imagemagick ninja
- name: CMake configuration
run: cmake -DSET_TWEAK=Off -DBUILD_TESTS=On -GNinja -DENABLE_EMBEDDED_PCIIDS=On -DENABLE_EMBEDDED_AMDGPUIDS=On -DCMAKE_BUILD_TYPE=${{ vars.CMAKE_BUILD_TYPE || 'RelWithDebInfo' }} -DCMAKE_C_FLAGS="-fno-pie" -DCMAKE_EXE_LINKER_FLAGS="-no-pie" .
run: cmake -DSET_TWEAK=Off -DBUILD_TESTS=On -GNinja -DENABLE_EMBEDDED_PCIIDS=On -DENABLE_EMBEDDED_AMDGPUIDS=On -DCMAKE_BUILD_TYPE=${{ vars.CMAKE_BUILD_TYPE || 'RelWithDebInfo' }} .
- name: CMake build
run: cmake --build . --verbose -j4
+1 -1
View File
@@ -35,7 +35,7 @@ jobs:
sudo pkg_add -r llvm-21.1.8p4 cmake git wayland vulkan-headers vulkan-loader glib2 dconf dbus sqlite3 imagemagick chafa lua-5.4.8p0 # pkg-config is preinstalled
- name: CMake configuration
run: CC=clang-21 cmake -DSET_TWEAK=Off -DBUILD_TESTS=On -DENABLE_EMBEDDED_PCIIDS=On -DENABLE_EMBEDDED_AMDGPUIDS=On -DCMAKE_BUILD_TYPE=${{ vars.CMAKE_BUILD_TYPE || 'RelWithDebInfo' }} -DCMAKE_C_FLAGS="-fno-pie" -DCMAKE_EXE_LINKER_FLAGS="-no-pie" .
run: CC=clang-21 cmake -DSET_TWEAK=Off -DBUILD_TESTS=On -DENABLE_EMBEDDED_PCIIDS=On -DENABLE_EMBEDDED_AMDGPUIDS=On -DCMAKE_BUILD_TYPE=${{ vars.CMAKE_BUILD_TYPE || 'RelWithDebInfo' }} .
- name: CMake build
run: cmake --build . --target package --verbose -j4
+2 -2
View File
@@ -17,7 +17,7 @@ jobs:
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: run VM
uses: vmactions/solaris-vm@87d3c436511cef802cd1637f86f73b1a97715c8c # v1.4.1
uses: vmactions/solaris-vm@a11f407d3720a3717673d4194f19ee63b284977e # v1.4.0
with:
usesh: true
envs: 'CMAKE_BUILD_TYPE'
@@ -28,7 +28,7 @@ jobs:
run: |
export PKG_CONFIG_PATH=/usr/lib/64/pkgconfig
cmake -DSET_TWEAK=Off -DBUILD_TESTS=On -GNinja -DCMAKE_C_FLAGS="-fno-pie" -DCMAKE_EXE_LINKER_FLAGS="-no-pie" .
cmake -DSET_TWEAK=Off -DBUILD_TESTS=On -GNinja .
cmake --build . --verbose -j4
./fastfetch --list-features
time ./fastfetch -c presets/ci.jsonc --stat false
+24
View File
@@ -0,0 +1,24 @@
name: Reusable Spellcheck
on:
workflow_call:
permissions:
contents: read
jobs:
spellcheck:
runs-on: ubuntu-latest
steps:
- name: checkout repository
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Install codespell
shell: bash
run: |
pip3 install codespell
codespell --version
- name: Run Spellchecker
run: codespell
+3 -3
View File
@@ -45,11 +45,11 @@ jobs:
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: setup-msys2
uses: msys2/setup-msys2@ec48f7c5447b3140e2b088413ae3a55687bccb6e # v2.33.0
uses: msys2/setup-msys2@66cd2cce69caa17b53920067426061ca1de3a884 # v2.32.0
with:
msystem: ${{ inputs.msystem }}
update: true
install: git mingw-w64-clang-${{ inputs.msys-arch }}-7zip mingw-w64-clang-${{ inputs.msys-arch }}-cmake mingw-w64-clang-${{ inputs.msys-arch }}-clang mingw-w64-clang-${{ inputs.msys-arch }}-jq mingw-w64-clang-${{ inputs.msys-arch }}-vulkan-loader mingw-w64-clang-${{ inputs.msys-arch }}-vulkan-headers mingw-w64-clang-${{ inputs.msys-arch }}-opencl-icd mingw-w64-clang-${{ inputs.msys-arch }}-opencl-headers mingw-w64-clang-${{ inputs.msys-arch }}-cppwinrt mingw-w64-clang-${{ inputs.msys-arch }}-imagemagick mingw-w64-clang-${{ inputs.msys-arch }}-chafa mingw-w64-clang-${{ inputs.msys-arch }}-lua mingw-w64-clang-${{ inputs.msys-arch }}-directx-headers
install: git mingw-w64-clang-${{ inputs.msys-arch }}-7zip mingw-w64-clang-${{ inputs.msys-arch }}-cmake mingw-w64-clang-${{ inputs.msys-arch }}-clang mingw-w64-clang-${{ inputs.msys-arch }}-jq mingw-w64-clang-${{ inputs.msys-arch }}-vulkan-loader mingw-w64-clang-${{ inputs.msys-arch }}-vulkan-headers mingw-w64-clang-${{ inputs.msys-arch }}-opencl-icd mingw-w64-clang-${{ inputs.msys-arch }}-opencl-headers mingw-w64-clang-${{ inputs.msys-arch }}-cppwinrt mingw-w64-clang-${{ inputs.msys-arch }}-imagemagick mingw-w64-clang-${{ inputs.msys-arch }}-chafa mingw-w64-clang-${{ inputs.msys-arch }}-lua mingw-w64-clang-${{ inputs.msys-arch }}-quickjs-ng mingw-w64-clang-${{ inputs.msys-arch }}-directx-headers
- name: print msys version
run: uname -a
@@ -61,7 +61,7 @@ jobs:
run: cmake --build . --verbose -j4
- name: copy necessary dlls
run: cp /${{ inputs.msystem-lower }}/bin/lua55.dll .
run: cp /${{ inputs.msystem-lower }}/bin/{lua55,libqjs-0}.dll .
- name: list features
run: ./fastfetch --list-features
+6
View File
@@ -5,7 +5,13 @@ on:
- pull_request
jobs:
spellcheck:
permissions:
contents: read
uses: ./.github/workflows/build-spellcheck.yml
no-features-test:
needs: spellcheck
name: No-features-test
permissions:
contents: read
+4 -16
View File
@@ -1,27 +1,15 @@
# 2.70.0
# Unreleased
Changes:
* LocalIP flags on Windows now describe the interface itself — its operational status, its media connection state and its type, for example `ETHERNET_CSMACD`, `IEEE80211` or `SOFTWARE_LOOPBACK` — instead of the `IP_ADAPTER_*` bits.
* libpulse and libddcutil are no longer used on Linux. Package managers should remove them from their fastfetch package.
* QuickJS format scripting has been removed (deprecated in v2.69.0). Migrate scripts to Lua if necessary. (General)
Features:
* Added Sound detection on Android. (Sound, Android)
* Added battery temperature, remaining time and charger type (AC / USB / wireless) detection on Android. (Battery, Android)
* Performance improvements & internal cleanups
* Added Finnish Language Support
* Improved performance of LocalIP, NetIO and DNS on Windows
Bugfixes:
* Fixed `--config`/`-c` bash completion offering one candidate per word instead of one candidate per file for paths containing spaces. (Completion, Bash)
* Fixed external monitors detected via DDC/CI being randomly reported as built-in on Linux. (#2615, Brightness, Linux)
* Fixed the Vulkan driver name being concatenated with the name of a previously enumerated device on multi-GPU systems. (#2619, Vulkan)
* Fixed the foot terminal font size not being parsed when written as `Font Name-size`, e.g. `Berkeley Mono-12`. (#2624, TerminalFont, Linux)
* Fixed `/boot` being hidden by default on Haiku, where it is the system volume. (Disk, Haiku)
* Improved Snapdragon X1/X2 model name detection on Linux. (#2625, CPU, Linux)
Logos:
* Added filled Artix logo (#2631)
* Renamed PostmarketOS to nura (#2626)
* Fixed external monitors detected via DDC/CI being randomly reported as built-in, and libddcutil trace messages occasionally leaking into the output and breaking `--format json`. (#2615, Brightness / Linux)
* Fixed the Vulkan driver name being concatenated with the driver name of a previously enumerated device on systems with more than one GPU. (#2619, Vulkan)
# 2.69.0
+31 -21
View File
@@ -1,7 +1,7 @@
cmake_minimum_required(VERSION 3.21.0) # C_STANDARD C17 and C23
project(fastfetch
VERSION 2.70.0
VERSION 2.69.0
LANGUAGES C
DESCRIPTION "Fast neofetch-like system information tool"
HOMEPAGE_URL "https://github.com/fastfetch-cli/fastfetch"
@@ -92,6 +92,8 @@ cmake_dependent_option(ENABLE_EGL "Enable egl" ON "LINUX OR FreeBSD OR OpenBSD O
cmake_dependent_option(ENABLE_GLX "Enable glx" ON "LINUX OR FreeBSD OR OpenBSD OR NetBSD OR ANDROID OR SunOS OR GNU" OFF)
cmake_dependent_option(ENABLE_OPENCL "Enable opencl" ON "LINUX OR FreeBSD OR OpenBSD OR NetBSD OR WIN32 OR ANDROID OR SunOS OR Haiku OR GNU" OFF)
cmake_dependent_option(ENABLE_FREETYPE "Enable freetype" ON "ANDROID" OFF)
cmake_dependent_option(ENABLE_PULSE "Enable pulse" ON "LINUX OR ANDROID OR GNU" OFF)
cmake_dependent_option(ENABLE_DDCUTIL "Enable ddcutil" ON "LINUX" OFF)
cmake_dependent_option(ENABLE_ELF "Enable libelf" ON "LINUX OR ANDROID OR DragonFly OR Haiku OR GNU" OFF)
cmake_dependent_option(ENABLE_THREADS "Enable multithreading" ON "Threads_FOUND" OFF)
@@ -121,6 +123,7 @@ option(ENABLE_EMBEDDED_PCIIDS "Embed pci.ids into fastfetch, requires `python`"
option(ENABLE_EMBEDDED_AMDGPUIDS "Embed amdgpu.ids into fastfetch, requires `python`" OFF)
option(ENABLE_WORDEXP "Enable using of wordexp(3) if available, instead of glob(3)" ON)
option(ENABLE_LUA "Enable lua scripting for format strings" ON)
option(ENABLE_QUICKJS "Enable quickjs scripting for format strings" OFF)
option(ENABLE_LIBZFS "Enable libzfs" ON)
option(ENABLE_WCWIDTH "Detect wide-width characters with wcwidth when calculating logo and separator width. Disable for a smaller and slightly faster build, but logos containing CJK/emoji may be measured too narrow and render incorrectly. Such usecases are uncommon." ON)
set(DEFAULT_STRUCTURE "Title:Separator:OS:Host:Kernel:Uptime:Packages:Shell:Display:DE:WM:WMTheme:Theme:Icons:Font:Cursor:Terminal:TerminalFont:CPU:GPU:Memory:Swap:Disk:LocalIp:Battery:PowerAdapter:Locale:Break:Colors" CACHE STRING "Default module structure for detection")
@@ -280,22 +283,6 @@ if(ENABLE_LTO AND NOT CMAKE_BUILD_TYPE STREQUAL "Debug")
endif()
endif()
# Release and MinSizeRel drop the symbol table and the unwind metadata; Debug and RelWithDebInfo keep
# both, so those stay fully debuggable. CMake does not strip MinSizeRel on its own.
if(CMAKE_BUILD_TYPE STREQUAL "Release" OR CMAKE_BUILD_TYPE STREQUAL "MinSizeRel")
if(NOT APPLE) # ld: warning: -s is obsolete
# The symbol table is dead weight: 437 KB (16%) of a MinGW build, 91 KB (3%) of a GCC build.
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,-s")
endif()
# Drops .eh_frame / .eh_frame_hdr on ELF (74 KB) and .pdata / .xdata on PE (26 KB). Every C++
# translation unit here is built with -fno-exceptions -fno-rtti and no source file contains a
# single try / catch, so no exception can ever unwind through these frames. The only loss is
# backtrace() / gdb / perf / crash-dump stack walking in a release binary.
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fno-asynchronous-unwind-tables")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fno-asynchronous-unwind-tables")
endif()
#######################
# Target FS structure #
#######################
@@ -476,7 +463,6 @@ file(GENERATE OUTPUT logo_builtin.h CONTENT "${LOGO_BUILTIN_H}")
set(LIBFASTFETCH_SRC
src/common/impl/commandoption.c
src/common/impl/debug.c
src/common/impl/duration.c
src/common/impl/font.c
src/common/impl/format.c
@@ -721,7 +707,6 @@ elseif(ANDROID)
src/detection/packages/packages_nix.c
src/detection/poweradapter/poweradapter_nosupport.c
src/detection/processes/processes_linux.c
src/detection/sound/sound_android.c
src/detection/sound/sound_linux.c
src/detection/swap/swap_linux.c
src/detection/terminalfont/terminalfont_android.c
@@ -1109,10 +1094,10 @@ elseif(WIN32)
src/common/impl/processing_windows.c
src/common/impl/FFPlatform_windows.c
src/common/impl/binary_windows.c
src/common/impl/debug_windows.c
src/common/impl/kmod_windows.c
src/common/windows/getline.c
src/common/windows/com.c
src/common/windows/folders.c
src/common/windows/registry.c
src/common/windows/unicode.c
src/common/windows/variant.cpp
@@ -1860,10 +1845,23 @@ ff_lib_enable(FREETYPE
"freetype2"
"FreeType2"
)
ff_lib_enable(PULSE
"libpulse"
"Pulse"
)
ff_lib_enable(DDCUTIL
"ddcutil"
"Ddcutil"
)
ff_lib_enable(ELF
"libelf"
"libelf"
)
ff_lib_enable(QUICKJS
"qjs"
"qjs"
"qjs"
)
if(ENABLE_LUA)
# Lua is a special case since its CMake config does not follow the usual pattern
find_package(Lua QUIET)
@@ -1956,10 +1954,10 @@ elseif(APPLE)
PRIVATE "-framework SystemConfiguration"
PRIVATE "-framework VideoToolbox"
PRIVATE "-weak_framework CoreDisplay"
PRIVATE "-F /System/Library/PrivateFrameworks"
PRIVATE "-weak_framework DisplayServices"
PRIVATE "-weak_framework MediaRemote"
PRIVATE "-weak_framework SkyLight"
)
elseif(WIN32)
target_link_libraries(libfastfetch
@@ -2277,6 +2275,15 @@ if (BUILD_TESTS)
PRIVATE libfastfetch
)
if (CMAKE_SYSTEM_NAME STREQUAL "Linux")
add_executable(fastfetch-test-cpu
tests/cpu.c
)
target_link_libraries(fastfetch-test-cpu
PRIVATE libfastfetch
)
endif()
add_executable(fastfetch-test-strbuf
tests/strbuf.c
)
@@ -2390,6 +2397,9 @@ if (BUILD_TESTS)
)
enable_testing()
if (CMAKE_SYSTEM_NAME STREQUAL "Linux")
add_test(NAME test-cpu COMMAND fastfetch-test-cpu)
endif()
add_test(NAME test-cache COMMAND fastfetch-test-cache)
add_test(NAME test-logo COMMAND fastfetch-test-logo)
add_test(NAME test-strbuf COMMAND fastfetch-test-strbuf)
+6 -1
View File
@@ -96,7 +96,7 @@ cmake -DBUILD_TESTS=On -DENABLE_VULKAN=OFF -DENABLE_WAYLAND=OFF -DENABLE_X11=OFF
Required: CMake ≥ 3.21 and a C23 compiler (GCC, Clang or MSVC). Everything else is optional.
Optional dependencies are auto-detected: libpci, libdrm, vulkan, wayland, xcb, xrandr, dbus, sqlite3, rpm, imagemagick{6,7}, chafa, zlib, egl, glx, opencl, freetype, elf, libzfs, and more.
Optional dependencies are auto-detected: libpci, libdrm, vulkan, wayland, xcb, xrandr, dbus, sqlite3, rpm, imagemagick{6,7}, chafa, zlib, egl, glx, opencl, freetype, pulse, ddcutil, elf, libzfs, and more.
---
@@ -587,6 +587,10 @@ clang-format -i src/modules/foo/*.c src/modules/foo/*.h
| Module options | `FF<Name>Options` | `FFCPUOptions` |
| Detection results | `FF<Name>Result` | `FFCPUResult` |
### Spelling
CI runs codespell (`.codespellrc`). Known false positives are listed in `ignore-words-list` (`iterm`, `compiletime`, and various non-English distro words). Add new words there rather than changing the code.
### Compiler warnings
The build enables `-Wall -Wextra -Wconversion` plus several `-Werror`s:
@@ -703,6 +707,7 @@ Coverage focuses on the core data structures and the formatting engine in `commo
```sh
clang-format -i <changed files> # format
codespell # spelling
cmake -B build -DBUILD_TESTS=On && cmake --build build -j
cd build && ctest --output-on-failure # tests
./build/fastfetch --format json # verify JSON output is well-formed
+1 -1
View File
@@ -2,7 +2,7 @@ Source: fastfetch
Section: universe/utils
Priority: optional
Maintainer: Carter Li <zhangsongcui@live.cn>
Build-Depends: libelf-dev, libvulkan-dev, libwayland-dev, libxrandr-dev, libxcb-randr0-dev, libdconf-dev, libdbus-1-dev, libmagickcore-dev, libsqlite3-dev, librpm-dev, libegl-dev, libglx-dev, ocl-icd-opencl-dev, libdrm-dev, libchafa-dev, libefl-all-dev, pkgconf, cmake (>= 3.12), debhelper (>= 11.2), dh-cmake, dh-cmake-compat (= 1), dh-sequence-cmake, dh-sequence-ctest, ninja-build, python3-setuptools
Build-Depends: libelf-dev, libvulkan-dev, libwayland-dev, libxrandr-dev, libxcb-randr0-dev, libdconf-dev, libdbus-1-dev, libmagickcore-dev, libsqlite3-dev, librpm-dev, libegl-dev, libglx-dev, ocl-icd-opencl-dev, libpulse-dev, libdrm-dev, libddcutil-dev, libchafa-dev, libefl-all-dev, pkgconf, cmake (>= 3.12), debhelper (>= 11.2), dh-cmake, dh-cmake-compat (= 1), dh-sequence-cmake, dh-sequence-ctest, ninja-build, python3-setuptools
Standards-Version: 4.0.0
Homepage: https://github.com/fastfetch-cli/fastfetch
-1
View File
@@ -630,7 +630,6 @@
"cs": "Czech",
"de": "German",
"es": "Spanish",
"fi": "Finnish",
"fr": "French",
"gl": "Galician",
"he": "Hebrew",
+2 -7
View File
@@ -1123,11 +1123,6 @@
"const": "es",
"description": "Spanish"
},
{
"type": "string",
"const": "fi",
"description": "Finnish"
},
{
"type": "string",
"const": "fr",
@@ -1919,7 +1914,7 @@
"$ref": "#/$defs/percent"
},
"ddcciSleep": {
"description": "Sleep time in milliseconds between DDC/CI requests\nSet to null to skip DDC/CI detection",
"description": "Sleep time in milliseconds between DDC/CI requests\nSet to null to skip DDC/CI detection\nSee <https://www.ddcutil.com/performance_options/#option-sleep-multiplier> for details",
"oneOf": [
{
"type": "null",
@@ -4078,7 +4073,7 @@
"const": "publicip"
},
"url": {
"description": "URL of the public IP detection server to use. Only HTTP is supported\nIf empty, `http://ipinfo.io/json` is requested (`http://v6.ipinfo.io/json` when `ipv6` is set), with TCP Fast Open enabled. A non-empty URL disables both and makes `ipv6` have no effect",
"description": "URL of the public IP detection server to use. Only HTTP is supported\nIf empty, `http://ipinfo.io/json` is requested (`http://v6.ipinfo.io/json` when `ipv6` is set), with HTTP compression and TCP Fast Open enabled. A non-empty URL disables both and makes `ipv6` have no effect",
"type": "string",
"format": "url"
},
+5 -30
View File
@@ -1,8 +1,5 @@
#include "common/android/binder.h"
#include "common/debug.h"
#include "common/io.h"
#include <errno.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <unistd.h>
@@ -10,18 +7,14 @@
const char* ffBinderOpen(FFBinder* binder) {
*binder = (FFBinder) { .fd = -1, .shared = nullptr, .sharedSize = 0, .protocolVersion = 0 };
FF_AUTO_CLOSE_FD int fd = open(FF_BINDER_DEVICE, O_RDWR | O_CLOEXEC);
const int fd = open(FF_BINDER_DEVICE, O_RDWR | O_CLOEXEC);
if (fd < 0) {
FF_DEBUG("open(" FF_BINDER_DEVICE ") failed: %s", strerror(errno));
return "Failed to open " FF_BINDER_DEVICE;
}
struct binder_version version = { .protocol_version = 0 };
const int versionIoctl = ioctl(fd, BINDER_VERSION, &version);
if (versionIoctl != 0 || version.protocol_version != BINDER_CURRENT_PROTOCOL_VERSION) {
FF_DEBUG("BINDER_VERSION reported protocol %u, expected %u (%s)",
version.protocol_version, BINDER_CURRENT_PROTOCOL_VERSION,
versionIoctl != 0 ? strerror(errno) : "version mismatch");
if (ioctl(fd, BINDER_VERSION, &version) != 0 || version.protocol_version != BINDER_CURRENT_PROTOCOL_VERSION) {
close(fd);
return "Unsupported binder protocol version";
}
@@ -29,7 +22,7 @@ const char* ffBinderOpen(FFBinder* binder) {
// addresses in BR_REPLY, so it has to stay mapped for as long as the handle is used.
void* shared = mmap(nullptr, FF_BINDER_SHARED_SIZE, PROT_READ, MAP_PRIVATE | MAP_NORESERVE, fd, 0);
if (shared == MAP_FAILED) {
FF_DEBUG("mmap(" FF_BINDER_DEVICE ") failed: %s", strerror(errno));
close(fd);
return "Failed to mmap " FF_BINDER_DEVICE;
}
@@ -39,7 +32,6 @@ const char* ffBinderOpen(FFBinder* binder) {
.sharedSize = FF_BINDER_SHARED_SIZE,
.protocolVersion = version.protocol_version,
};
fd = -1; // ownership moved into *binder, which ffBinderClose() lets go
return nullptr;
}
@@ -63,8 +55,6 @@ const char* ffBinderTransact(FFBinder* binder, uint32_t handle, uint32_t code, u
reply->fdCount = 0;
if (parcel->truncated) {
FF_DEBUG("The parcel was truncated at %zu bytes, the caller buffer holds %zu",
parcel->size, parcel->capacity);
return "Binder parcel does not fit the caller buffer";
}
@@ -96,7 +86,6 @@ const char* ffBinderTransact(FFBinder* binder, uint32_t handle, uint32_t code, u
.read_buffer = (binder_uintptr_t) (uintptr_t) readBuffer,
};
if (ioctl(binder->fd, BINDER_WRITE_READ, &exchange) != 0) {
FF_DEBUG("BINDER_WRITE_READ(handle=%u, code=%u) failed: %s", handle, code, strerror(errno));
return "BINDER_WRITE_READ failed";
}
@@ -114,10 +103,8 @@ const char* ffBinderTransact(FFBinder* binder, uint32_t handle, uint32_t code, u
break;
case BR_FAILED_REPLY:
FF_DEBUG("BR_FAILED_REPLY for handle=%u, code=%u", handle, code);
return "Binder transaction failed";
case BR_DEAD_REPLY:
FF_DEBUG("BR_DEAD_REPLY: handle=%u is dead", handle);
return "Binder service is not running";
case BR_INCREFS:
@@ -136,8 +123,6 @@ const char* ffBinderTransact(FFBinder* binder, uint32_t handle, uint32_t code, u
case BR_REPLY: {
if ((size_t) (end - cursor) < sizeof(struct binder_transaction_data)) {
FF_DEBUG("BR_REPLY is truncated: %zu of %zu bytes left",
(size_t) (end - cursor), sizeof(struct binder_transaction_data));
return "Truncated binder reply";
}
struct binder_transaction_data data;
@@ -155,8 +140,6 @@ const char* ffBinderTransact(FFBinder* binder, uint32_t handle, uint32_t code, u
const char* error = nullptr;
if (payloadSize > 0) {
if (payloadSize > reply->capacity) {
FF_DEBUG("The reply carries %zu payload bytes, the caller buffer holds %zu",
payloadSize, reply->capacity);
error = "Binder reply does not fit the caller buffer";
} else {
memcpy(reply->data, payload, payloadSize);
@@ -228,7 +211,6 @@ const char* ffBinderTransact(FFBinder* binder, uint32_t handle, uint32_t code, u
// later transaction on them fails with BR_FAILED_REPLY, which says nothing about the
// real cause. Reported, but only when nothing more specific is already at hand.
if (ioctl(binder->fd, BINDER_WRITE_READ, &release) != 0 && error == nullptr) {
FF_DEBUG("BC_ACQUIRE/BC_FREE_BUFFER failed: %s", strerror(errno));
error = "Releasing the binder reply failed";
}
@@ -236,13 +218,10 @@ const char* ffBinderTransact(FFBinder* binder, uint32_t handle, uint32_t code, u
}
default:
FF_DEBUG("Unexpected binder command 0x%08x for handle=%u, code=%u", command, handle, code);
return "Unexpected binder command";
}
}
FF_DEBUG("The binder reply for handle=%u, code=%u held no BR_REPLY (%zu bytes read)",
handle, code, (size_t) exchange.read_consumed);
return "No reply from binder";
}
@@ -259,16 +238,12 @@ const char* ffBinderLookupService(FFBinder* binder, const char* name, uint32_t t
return error;
}
if (ffBinderReplyIsStatus(&reply)) {
FF_DEBUG("The service manager returned a status parcel for %s, code=%u", name, transactionCode);
return "Service manager rejected the request";
}
const int32_t exception = ffBinderReadI32(reply.data, reply.size, 0);
if (exception != 0) {
FF_DEBUG("The service manager raised exception %d for %s", exception, name);
if (ffBinderReadI32(reply.data, reply.size, 0) != 0) {
return "Service manager raised an exception";
}
if (reply.handleCount == 0) {
FF_DEBUG("The service manager returned no handle for %s", name);
return "Service is not registered";
}
+123 -431
View File
@@ -1,12 +1,9 @@
#include "common/android/dex.h"
#include "common/debug.h"
#include "common/io.h"
#include "common/mallocHelper.h"
#include <errno.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <unistd.h>
#ifdef FF_HAVE_ZLIB
#include "common/library.h"
@@ -35,48 +32,19 @@
#define FF_DEX_FIELD_ID_SIZE 8
#define FF_DEX_OFF_FIELD_ID_NAME 4
// The zip local file header, from APPNOTE.TXT 4.3.7. Only the two length fields are read from it:
// the payload starts right behind them, and the local extra field is allowed to differ from the
// central one, so the offset cannot be computed from the directory alone.
// The zip local file header, from APPNOTE.TXT 4.3.7. The central directory would carry the same
// fields, but the local header sits right in front of the data, so one walk over the file finds
// both the bounds and the payload.
#define FF_ZIP_LOCAL_HEADER_SIZE 30
#define FF_ZIP_OFF_METHOD 8
#define FF_ZIP_OFF_COMPRESSED_SIZE 18
#define FF_ZIP_OFF_UNCOMPRESSED_SIZE 22
#define FF_ZIP_OFF_NAME_LENGTH 26
#define FF_ZIP_OFF_EXTRA_LENGTH 28
// The central directory. This is what makes an entry reachable without walking everything in front
// of it, and that matters here: framework.jar is 52 MB and its classes2.dex starts 9.6 MB in, so
// looking for a local header by scanning the file costs a pass over everything before it -- once per
// lookup. Measured on the test device, where the sound module resolves seven codes, that was the
// difference between 30 ms and 2 seconds.
#define FF_ZIP_CENTRAL_MAGIC "PK\x01\x02"
#define FF_ZIP_CENTRAL_SIZE 46
#define FF_ZIP_OFF_CENTRAL_METHOD 10
#define FF_ZIP_OFF_CENTRAL_COMPRESSED_SIZE 20
#define FF_ZIP_OFF_CENTRAL_UNCOMPRESSED_SIZE 24
#define FF_ZIP_OFF_CENTRAL_NAME_LENGTH 28
#define FF_ZIP_OFF_CENTRAL_EXTRA_LENGTH 30
#define FF_ZIP_OFF_CENTRAL_COMMENT_LENGTH 32
#define FF_ZIP_OFF_CENTRAL_LOCAL_OFFSET 42
// The end of central directory record, which points at the directory. It is the last thing in the
// file, behind a comment of at most 64 KiB, so it is searched for backwards over that much.
#define FF_ZIP_EOCD_MAGIC "PK\x05\x06"
#define FF_ZIP_EOCD_SIZE 22
#define FF_ZIP_EOCD_MAX_COMMENT 0xffffu
#define FF_ZIP_OFF_EOCD_DIRECTORY_SIZE 12
#define FF_ZIP_OFF_EOCD_DIRECTORY 16
// A size or offset of 0xFFFFFFFF means the real one is in a zip64 extra field. AOSP's framework jars
// are orders of magnitude below that, and carrying the extra field reader for them is not worth it.
#define FF_ZIP_ZIP64_SENTINEL 0xffffffffu
#define FF_ZIP_METHOD_STORED 0
#define FF_ZIP_METHOD_DEFLATED 8
#define FF_DEX_ENTRY "classes.dex"
// How many `classesN.dex` entries a jar is walked for. A jar that spreads its classes over more
// than this still resolves everything in the entries that were walked; only a class that lives in
// one of the ones past the bound is reported as missing.
#define FF_DEX_MAX_ENTRIES 10
#define FF_DEX_MAGIC "dex\n"
static uint16_t dexU16(const uint8_t* p) {
@@ -128,34 +96,18 @@ static uint32_t dexUleb128(const uint8_t** p, const uint8_t* end) {
}
// The type tag a dex encoded_value carries in the low five bits of its header byte, per the
// `encoded_value` table of the dex format.
//
// VALUE_NULL and VALUE_BOOLEAN are the trap. Their value *is* the header -- the type for a null,
// the size nibble for a boolean -- and they carry **no payload bytes at all**, so the
// `((size - 1) << 5) | type` rule that describes every other tag does not describe them. Taking the
// size nibble at face value and reading one payload byte walks one byte too far, which pairs every
// field after that with the wrong value. It is not a hypothetical: `IAudioService$Stub` declares ten
// `PERMISSIONS_*` arrays that are all null, right behind its `DESCRIPTOR` string and ahead of its
// 324 transaction constants, and those ten spurious bytes made
// `TRANSACTION_getDevicesForAttributesUnprotected` read as 210 instead of 167 -- a code that reaches
// a different method. `IWifiManager$Stub` has none ahead of the field the wifi module reads, which
// is the only reason this went unnoticed.
//
// Naming VALUE_BOOLEAN also matters for a second reason: it is the tag a `static final boolean`
// carries, and treating it as VALUE_INT would hand back a boolean where an int was asked for.
typedef enum FFDexValueType : uint32_t {
FF_DEX_VALUE_INT = 0x04, // sign-extended; one to four payload bytes
FF_DEX_VALUE_NULL = 0x1e, // no payload
FF_DEX_VALUE_BOOLEAN = 0x1f, // no payload; the boolean is in the header's size nibble
} FFDexValueType;
// `encoded_value` table of the dex format (`VALUE_INT = 0x04`, sign-extended four-byte integer).
// 0x1f is VALUE_BOOLEAN in that same table, which is the trap: naming it VALUE_INT and comparing
// against it rejects every real int and accepts booleans.
#define FF_DEX_VALUE_INT 0x04
// encoded_value: one header byte holding ((size - 1) << 5) | type, then that many payload bytes.
//
// The `static_values` array is not a list of ints: `IInterface.DESCRIPTOR` is a `static final
// String` and sits in it, and a real Stub class mixes ints, strings, nulls and booleans. So a value
// whose tag is not VALUE_INT is skipped -- its payload is stepped over to keep the walk aligned with
// the field list -- and only `isInt` reports whether the *wanted* field ended up being one. Failing
// on the first non-int instead would abort on `DESCRIPTOR`, before the transaction constants that
// String` and sits in it, and a real Stub class mixes ints, strings and booleans. So a value whose
// tag is not VALUE_INT is skipped -- its payload is stepped over to keep the walk aligned with the
// field list -- and only `isInt` reports whether the *wanted* field ended up being one. Failing on
// the first non-int instead would abort on `DESCRIPTOR`, before the transaction constants that
// follow it are ever reached. Measured on a device: `IWifiManager$Stub` carries 347 static fields
// whose values are mostly VALUE_INT but include that string, and `getConnectionInfo` is 98 there.
//
@@ -169,15 +121,6 @@ static bool dexEncodedValue(const uint8_t** p, const uint8_t* end, int32_t* resu
}
const uint8_t header = *(*p)++;
const uint32_t type = (uint32_t) (header & 0x1f);
// The two tags whose value is the header byte itself. Returning here is what keeps the walk in
// step; `isInt` is false because neither is an int.
if (type == FF_DEX_VALUE_NULL || type == FF_DEX_VALUE_BOOLEAN) {
*result = 0;
*isInt = false;
return true;
}
const uint32_t size = (uint32_t) (header >> 5) + 1;
if (size > 8) {
return false;
@@ -230,18 +173,12 @@ static const char* dexString(const uint8_t* dex, const uint8_t* end, uint32_t in
return memchr(p, '\0', (size_t) (end - p)) != nullptr ? (const char*) p : nullptr;
}
// One dex entry, validated. Every offset in the header comes out of the file, so each table is range
// checked before the walk uses it.
typedef struct FFDexTables {
const uint8_t* dex;
const uint8_t* end;
const uint8_t* types;
uint32_t typeCount;
const uint8_t* classes;
uint32_t classCount;
} FFDexTables;
// Walks the class's static field list and the class's static value array in step, and returns the
// value paired with `fieldName`. Both lists are ordered by field index and cover exactly the same
// fields -- when they do not, the pairing is not trustworthy and nothing is returned.
static const char* dexStaticInt(const uint8_t* dex, size_t size, const char* classDescriptor, const char* fieldName, int32_t* result) {
const uint8_t* end = dex + size;
static const char* dexOpen(const uint8_t* dex, size_t size, FFDexTables* tables) {
if (size < FF_DEX_HEADER_SIZE || dexU32(dex + FF_DEX_OFF_ENDIAN_TAG) != FF_DEX_ENDIAN_TAG) {
return "Not a dex file";
}
@@ -249,13 +186,25 @@ static const char* dexOpen(const uint8_t* dex, size_t size, FFDexTables* tables)
if (fileSize > size || fileSize < FF_DEX_HEADER_SIZE) {
return "The dex file size is out of range";
}
const uint8_t* end = dex + fileSize;
end = dex + fileSize;
// The class's type index, so that the class_def_item and the field ids can be filtered by class.
const uint8_t* types = dex + dexU32(dex + FF_DEX_OFF_TYPE_IDS);
const uint32_t typeCount = dexU32(dex + FF_DEX_OFF_TYPE_IDS_SIZE);
if (!dexTableInRange(dex, end, types, typeCount, 4)) {
return "The dex type table is out of range";
}
uint32_t typeIndex = UINT32_MAX;
for (uint32_t i = 0; i < typeCount; ++i) {
const char* descriptor = dexString(dex, end, dexU32(types + (size_t) i * 4));
if (descriptor != nullptr && strcmp(descriptor, classDescriptor) == 0) {
typeIndex = i;
break;
}
}
if (typeIndex == UINT32_MAX) {
return "The class is not in the dex";
}
const uint8_t* classes = dex + dexU32(dex + FF_DEX_OFF_CLASS_DEFS);
const uint32_t classCount = dexU32(dex + FF_DEX_OFF_CLASS_DEFS_SIZE);
@@ -263,75 +212,11 @@ static const char* dexOpen(const uint8_t* dex, size_t size, FFDexTables* tables)
return "The dex class table is out of range";
}
tables->dex = dex;
tables->end = end;
tables->types = types;
tables->typeCount = typeCount;
tables->classes = classes;
tables->classCount = classCount;
return nullptr;
}
// One pass over the type table, resolving the type index of every class that still has a request
// open. This pass is what a lookup costs: `framework.jar`'s classes.dex lists 8428 descriptors, each
// one a `string_data_item` read from a different part of a 51 MB mapping. Matching every request
// against the same pass is what makes a batch cheaper than the sum of its fields -- the descriptor is
// read once however many classes are being looked for, and the pass ends as soon as the last request
// has an index rather than at the end of the table.
//
// `typeIndexes` is parallel to `requests` and rebuilt here rather than kept across entries: a type
// index is a property of the entry's own table, and the next entry numbers its types differently.
static void dexFindTypes(const FFDexTables* tables, const FFDexStaticIntRequest* requests, const uint8_t* settled, uint32_t* typeIndexes, uint32_t count) {
uint32_t remaining = 0;
for (uint32_t r = 0; r < count; ++r) {
typeIndexes[r] = UINT32_MAX;
if (!settled[r]) {
++remaining;
}
}
for (uint32_t i = 0; i < tables->typeCount && remaining > 0; ++i) {
const char* descriptor = dexString(tables->dex, tables->end, dexU32(tables->types + (size_t) i * 4));
if (descriptor == nullptr) {
continue;
}
for (uint32_t r = 0; r < count; ++r) {
if (settled[r] || typeIndexes[r] != UINT32_MAX || strcmp(descriptor, requests[r].classDescriptor) != 0) {
continue;
}
typeIndexes[r] = i;
--remaining;
}
}
}
// Walks the static field list of the class that `typeIndex` names and the class's static value array
// in step, writing the value of every request for that class whose field name matches. Both lists are
// ordered by field index and cover exactly the same fields -- when they do not, the pairing is not
// trustworthy and nothing is written.
//
// `defined` says whether this dex *defines* the class, which is not the same as mentioning it: a dex
// lists every type it references in its type table, so a class that a later entry of the jar defines
// still has a type index here, with no class_def_item behind it. Only a class_def_item makes the
// answer this dex's; without one the caller moves on to the next entry.
//
// A request this class cannot answer -- a field it does not declare, or one it declares as something
// other than an int -- is closed here rather than reported: the class is this dex's, so no later
// entry of the jar could give it a different answer, and one field the caller can live without must
// not hold back the rest of the batch. It is closed with the sentinel, and what this returns is only
// ever a dex that cannot be read, which is the one thing no later entry can fix.
static const char* dexClassStaticInts(const FFDexTables* tables, uint32_t typeIndex, const FFDexStaticIntRequest* requests, const uint32_t* typeIndexes, uint8_t* settled, uint32_t count, bool* defined) {
*defined = false;
const uint8_t* dex = tables->dex;
const uint8_t* end = tables->end;
for (uint32_t i = 0; i < tables->classCount; ++i) {
const uint8_t* classDef = tables->classes + (size_t) i * FF_DEX_CLASS_DEF_SIZE;
for (uint32_t i = 0; i < classCount; ++i) {
const uint8_t* classDef = classes + (size_t) i * FF_DEX_CLASS_DEF_SIZE;
if (dexU32(classDef) != typeIndex) {
continue;
}
// Defined here, so from this point on the answer is this dex's, whatever it turns out to be.
*defined = true;
const uint8_t* fields = dex + dexU32(classDef + FF_DEX_OFF_CLASS_DEF_DATA);
const uint8_t* values = dex + dexU32(classDef + FF_DEX_OFF_CLASS_DEF_STATIC_VALUES);
@@ -373,40 +258,20 @@ static const char* dexClassStaticInts(const FFDexTables* tables, uint32_t typeIn
return "The dex field table is out of range";
}
const char* name = dexString(dex, end, dexU32(fieldIds + (size_t) fieldIndex * FF_DEX_FIELD_ID_SIZE + FF_DEX_OFF_FIELD_ID_NAME));
if (name == nullptr) {
continue;
}
for (uint32_t r = 0; r < count; ++r) {
if (settled[r] || typeIndexes[r] != typeIndex || strcmp(name, requests[r].fieldName) != 0) {
continue;
}
if (name != nullptr && strcmp(name, fieldName) == 0) {
if (!isInt) {
// The field exists but is not an int, so its value is not the constant asked
// for. Closed the way a field the class does not declare is, because it is this
// dex's answer too. The check is here rather than on the first non-int in the
// list because the list legitimately holds strings (`DESCRIPTOR`) and booleans.
*requests[r].result = FF_DEX_STATIC_INT_UNRESOLVED;
settled[r] = 1;
continue;
// for. Reported here rather than on the first non-int in the list, because the
// list legitimately holds strings (`DESCRIPTOR`) and booleans.
return "The dex static field is not an int";
}
*requests[r].result = value;
settled[r] = 1;
*result = value;
return nullptr;
}
}
// The class is defined here, so a request of its own that went unanswered is one whose field
// the class does not declare as a static field. Closed here for the same reason as above.
for (uint32_t r = 0; r < count; ++r) {
if (!settled[r] && typeIndexes[r] == typeIndex) {
*requests[r].result = FF_DEX_STATIC_INT_UNRESOLVED;
settled[r] = 1;
}
}
return nullptr;
return "The field is not a static field of the class";
}
// The descriptor is in this dex's type table but the class itself is defined by another entry of
// the jar. That is not a failure: `defined` stays false and the caller reads the next entry.
return nullptr;
return "The class has no class_def_item";
}
// ---------------------------------------------------------------------------------------------
@@ -414,114 +279,59 @@ static const char* dexClassStaticInts(const FFDexTables* tables, uint32_t typeIn
// ---------------------------------------------------------------------------------------------
typedef struct FFDexMapping {
uint8_t* mapped; // the jar, as mapped; nullptr once released
uint8_t* mapped; // the jar, as mapped; nullptr once released
size_t mappedSize;
const uint8_t* data; // the dex bytes, inside `mapped`
const uint8_t* data; // the dex bytes: inside `mapped`, or `inflated`
size_t size;
uint8_t* inflated; // owned; only set when the entry had to be decompressed
} FFDexMapping;
static void wrapDexMapping(FFDexMapping* mapping) {
assert(mapping);
free(mapping->inflated);
if (mapping->mapped != nullptr) {
munmap(mapping->mapped, mapping->mappedSize);
}
}
// Locates an entry through the jar's central directory.
//
// The sizes and the compression method are read from the directory rather than from the entry's own
// local header, which is the more reliable of the two: a local header is allowed to leave its sizes
// at zero and put them in a data descriptor behind the payload, and an archive writer is free to
// choose that. Only the local header's two length fields are used, because they are what says where
// the payload begins -- the local extra field does not have to match the central one.
static const char* findDexEntry(const uint8_t* jar, size_t jarSize, const char* entry, const uint8_t** data, size_t* dataSize, uint32_t* uncompressedSize, uint16_t* method) {
const size_t entryLength = strlen(entry);
if (jarSize < FF_ZIP_EOCD_SIZE) {
return "The jar is too small to be a zip";
}
// The record is the last thing in the file, so it is searched for backwards -- over at most the
// 64 KiB a trailing comment can take, not over the whole archive.
const size_t newest = jarSize - FF_ZIP_EOCD_SIZE;
const size_t oldest = jarSize > FF_ZIP_EOCD_MAX_COMMENT + FF_ZIP_EOCD_SIZE
? jarSize - FF_ZIP_EOCD_MAX_COMMENT - FF_ZIP_EOCD_SIZE
: 0;
size_t eocd = SIZE_MAX;
for (size_t i = newest;; --i) {
if (memcmp(jar + i, FF_ZIP_EOCD_MAGIC, sizeof(FF_ZIP_EOCD_MAGIC) - 1) == 0) {
eocd = i;
break;
// The local file header of `classes.dex` carries both the payload bounds and its compression, so a
// single walk over the jar is enough to find it either way. AOSP builds framework jars with the
// entry STORED so that ART can map it, which is the common case and needs no decompression.
static const char* findDexEntry(const uint8_t* jar, size_t jarSize, const uint8_t** data, size_t* dataSize, uint32_t* uncompressedSize, uint16_t* method) {
for (size_t i = 0; i + FF_ZIP_LOCAL_HEADER_SIZE <= jarSize; ++i) {
if (memcmp(jar + i, "PK\x03\x04", 4) != 0) {
continue;
}
if (i == oldest) {
// The walk stops here rather than at zero: a `size_t` countdown that has to reach zero
// before it stops would wrap and read the whole file backwards, which is the very scan
// this is here to avoid.
break;
const uint16_t nameLength = dexU16(jar + i + FF_ZIP_OFF_NAME_LENGTH);
const uint16_t extraLength = dexU16(jar + i + FF_ZIP_OFF_EXTRA_LENGTH);
if (nameLength != sizeof(FF_DEX_ENTRY) - 1) {
continue;
}
}
if (eocd == SIZE_MAX) {
FF_DEBUG("No end-of-central-directory record in the last %zu bytes of a %zu byte jar",
newest - oldest + FF_ZIP_EOCD_SIZE, jarSize);
return "The jar has no zip directory";
}
const uint32_t directorySize = dexU32(jar + eocd + FF_ZIP_OFF_EOCD_DIRECTORY_SIZE);
const uint32_t directoryOffset = dexU32(jar + eocd + FF_ZIP_OFF_EOCD_DIRECTORY);
if (directoryOffset > jarSize || directorySize > jarSize - directoryOffset) {
return "The zip directory is out of range";
}
const size_t directoryEnd = (size_t) directoryOffset + directorySize;
for (size_t offset = directoryOffset; offset + FF_ZIP_CENTRAL_SIZE <= directoryEnd;) {
if (memcmp(jar + offset, FF_ZIP_CENTRAL_MAGIC, sizeof(FF_ZIP_CENTRAL_MAGIC) - 1) != 0) {
return "A zip directory entry is malformed";
const size_t headerSize = FF_ZIP_LOCAL_HEADER_SIZE + (size_t) nameLength + (size_t) extraLength;
if (headerSize > jarSize - i) {
continue;
}
const uint16_t nameLength = dexU16(jar + offset + FF_ZIP_OFF_CENTRAL_NAME_LENGTH);
const uint16_t extraLength = dexU16(jar + offset + FF_ZIP_OFF_CENTRAL_EXTRA_LENGTH);
const uint16_t commentLength = dexU16(jar + offset + FF_ZIP_OFF_CENTRAL_COMMENT_LENGTH);
const size_t entrySize = FF_ZIP_CENTRAL_SIZE + (size_t) nameLength + (size_t) extraLength + (size_t) commentLength;
if (entrySize > directoryEnd - offset) {
FF_DEBUG("A zip entry claims %zu bytes but only %zu remain in the directory",
entrySize, (size_t) (directoryEnd - offset));
return "A zip directory entry runs past the directory";
if (memcmp(jar + i + FF_ZIP_LOCAL_HEADER_SIZE, FF_DEX_ENTRY, sizeof(FF_DEX_ENTRY) - 1) != 0) {
continue;
}
if (nameLength == entryLength && memcmp(jar + offset + FF_ZIP_CENTRAL_SIZE, entry, entryLength) == 0) {
const uint32_t compressedSize = dexU32(jar + offset + FF_ZIP_OFF_CENTRAL_COMPRESSED_SIZE);
const uint32_t uncompressed = dexU32(jar + offset + FF_ZIP_OFF_CENTRAL_UNCOMPRESSED_SIZE);
const uint32_t localOffset = dexU32(jar + offset + FF_ZIP_OFF_CENTRAL_LOCAL_OFFSET);
if (compressedSize == FF_ZIP_ZIP64_SENTINEL || uncompressed == FF_ZIP_ZIP64_SENTINEL || localOffset == FF_ZIP_ZIP64_SENTINEL) {
FF_DEBUG("The dex entry is zip64: compressed=%u, uncompressed=%u, localOffset=%u",
compressedSize, uncompressed, localOffset);
return "A dex entry is stored as zip64, which is not supported";
}
if (localOffset > jarSize || FF_ZIP_LOCAL_HEADER_SIZE > jarSize - localOffset) {
return "A zip local header is out of range";
}
const uint16_t localNameLength = dexU16(jar + localOffset + FF_ZIP_OFF_NAME_LENGTH);
const uint16_t localExtraLength = dexU16(jar + localOffset + FF_ZIP_OFF_EXTRA_LENGTH);
const size_t headerSize = FF_ZIP_LOCAL_HEADER_SIZE + (size_t) localNameLength + (size_t) localExtraLength;
if (headerSize > jarSize - localOffset || compressedSize > jarSize - localOffset - headerSize) {
return "The zip entry bounds are out of range";
}
*method = dexU16(jar + offset + FF_ZIP_OFF_CENTRAL_METHOD);
*uncompressedSize = uncompressed;
*data = jar + localOffset + headerSize;
*dataSize = compressedSize;
return nullptr;
const uint32_t compressedSize = dexU32(jar + i + FF_ZIP_OFF_COMPRESSED_SIZE);
if (compressedSize == 0 || compressedSize > jarSize - i - headerSize) {
// A zero size means a data descriptor follows the payload instead of the header; the
// magic scan in the caller still covers that case as long as the entry is STORED.
continue;
}
offset += entrySize;
*method = dexU16(jar + i + FF_ZIP_OFF_METHOD);
*uncompressedSize = dexU32(jar + i + FF_ZIP_OFF_UNCOMPRESSED_SIZE);
*data = jar + i + headerSize;
*dataSize = compressedSize;
return nullptr;
}
FF_DEBUG("No entry in the %zu byte jar is named %s", jarSize, entry);
return "The jar has no usable dex entry";
return "The jar has no usable classes.dex entry";
}
// Fallback for a jar whose entry is STORED but whose header has no sizes: the dex magic is then a
// literal run of bytes in the file, and the header that follows it validates or it does not. It
// only ever locates the *first* dex in the jar, so it is a fallback for `classes.dex` alone.
// literal run of bytes in the file, and the header that follows it validates or it does not.
static const uint8_t* findDexMagic(const uint8_t* jar, size_t jarSize, size_t* dexSize) {
for (size_t i = 0; i + FF_DEX_HEADER_SIZE <= jarSize; ++i) {
if (memcmp(jar + i, FF_DEX_MAGIC, sizeof(FF_DEX_MAGIC) - 1) != 0) {
@@ -548,11 +358,8 @@ static const char* inflateDex(const uint8_t* data, size_t dataSize, uint32_t unc
FF_LIBRARY_LOAD_SYMBOL(zlib, inflate, "dlsym(inflate) failed")
FF_LIBRARY_LOAD_SYMBOL(zlib, inflateEnd, "dlsym(inflateEnd) failed")
// 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.
FF_AUTO_FREE uint8_t* buffer = malloc(uncompressedSize);
uint8_t* buffer = malloc(uncompressedSize);
if (buffer == nullptr) {
FF_DEBUG("malloc(%u) for the inflated dex failed", uncompressedSize);
return "malloc failed";
}
@@ -565,190 +372,75 @@ 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));
if (initStatus != Z_OK) {
FF_DEBUG("inflateInit2() failed: zlib status %d", initStatus);
if (ffinflateInit2_(&stream, -MAX_WBITS, ZLIB_VERSION, (int) sizeof(z_stream)) != Z_OK) {
free(buffer);
return "inflateInit2 failed";
}
const int status = ffinflate(&stream, Z_FINISH);
ffinflateEnd(&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);
return "Inflating the dex failed";
free(buffer);
return "Inflating classes.dex failed";
}
*out = buffer;
buffer = nullptr; // the caller owns it from here
return nullptr;
}
#endif
const char* ffDexStaticInts(const char* jarPath, const FFDexStaticIntRequest* requests, uint32_t count) {
// Every request is written the sentinel before anything can fail, so that a caller reads a result
// rather than whatever its own stack held there, however this returns. Nothing below has to
// remember to do it on its way out.
for (uint32_t r = 0; r < count; ++r) {
*requests[r].result = FF_DEX_STATIC_INT_UNRESOLVED;
}
if (count == 0) {
return nullptr;
}
const char* ffDexStaticInt(const char* jarPath, const char* classDescriptor, const char* fieldName, int32_t* result) {
[[gnu::cleanup(wrapDexMapping)]] FFDexMapping mapping = {};
{
FF_AUTO_CLOSE_FD const int fd = open(jarPath, O_RDONLY | O_CLOEXEC);
if (fd < 0) {
FF_DEBUG("open(%s) failed: %s", jarPath, strerror(errno));
return "open(jar) failed";
}
struct stat st = {};
const int statStatus = fstat(fd, &st);
if (statStatus != 0 || st.st_size <= 0) {
FF_DEBUG("fstat(%s) reported %s (%lld bytes)", jarPath,
statStatus != 0 ? strerror(errno) : "an empty file", (long long) st.st_size);
return "fstat(jar) failed";
}
mapping.mappedSize = (size_t) st.st_size;
mapping.mapped = mmap(nullptr, mapping.mappedSize, PROT_READ, MAP_PRIVATE, fd, 0);
// The mapping outlives the descriptor, so the descriptor can go either way from here.
if (mapping.mapped == MAP_FAILED) {
FF_DEBUG("mmap(%s, %zu bytes) failed: %s", jarPath, mapping.mappedSize, strerror(errno));
mapping.mapped = nullptr;
return "mmap(jar) failed";
}
const int fd = open(jarPath, O_RDONLY | O_CLOEXEC);
if (fd < 0) {
return "open(jar) failed";
}
// One byte per request saying whether it is closed -- answered, or given up on -- and the type
// index it was found under in the entry being walked. Both are sized from `count` rather than
// from a fixed bound, and both are released by their cleanup however this function returns.
FF_AUTO_FREE uint8_t* settled = calloc(count, 1);
FF_AUTO_FREE uint32_t* typeIndexes = malloc((size_t) count * sizeof(uint32_t));
if (settled == nullptr || typeIndexes == nullptr) {
FF_DEBUG("Allocating %zu bytes for %u static int requests failed",
(size_t) count * (1 + sizeof(uint32_t)), count);
return "malloc failed";
struct stat st = {};
if (fstat(fd, &st) != 0 || st.st_size <= 0) {
close(fd);
return "fstat(jar) failed";
}
mapping.mappedSize = (size_t) st.st_size;
mapping.mapped = mmap(nullptr, mapping.mappedSize, PROT_READ, MAP_PRIVATE, fd, 0);
// The mapping outlives the descriptor, so the descriptor can go either way from here.
close(fd);
if (mapping.mapped == MAP_FAILED) {
mapping.mapped = nullptr;
return "mmap(jar) failed";
}
// A jar spreads its classes over `classes.dex`, `classes2.dex`, `classes3.dex`, ... and the
// class being looked for is in whichever one the build put it in: `IAudioService$Stub` is in the
// second dex of framework.jar, while `IWifiManager$Stub` is in the first of framework-wifi.jar.
// The entries are therefore tried in order, and the first one that defines the class answers.
// The count is a bound on the walk, not a claim about the jar -- AOSP has never shipped more
// than a handful, and a class that is in none of them is left unanswered either way.
//
// The walk ends as soon as the last request is closed, so an entry that holds every class asked
// for -- which is what the modules here ask for, one class per interface -- costs one entry read
// and one pass over its type table.
uint32_t pending = count;
const char* error = nullptr;
for (uint32_t index = 1; index <= FF_DEX_MAX_ENTRIES && pending > 0; ++index) {
char entry[sizeof("classes999.dex")];
if (index == 1) {
memcpy(entry, FF_DEX_ENTRY, sizeof(FF_DEX_ENTRY));
} else {
(void) snprintf(entry, sizeof(entry), "classes%u.dex", index);
const uint8_t* data = nullptr;
size_t dataSize = 0;
uint32_t uncompressedSize = 0;
uint16_t method = FF_ZIP_METHOD_STORED;
if (findDexEntry(mapping.mapped, mapping.mappedSize, &data, &dataSize, &uncompressedSize, &method) != nullptr) {
data = findDexMagic(mapping.mapped, mapping.mappedSize, &dataSize);
if (data == nullptr) {
return "No dex in the jar";
}
const uint8_t* data = nullptr;
size_t dataSize = 0;
uint32_t uncompressedSize = 0;
uint16_t method = FF_ZIP_METHOD_STORED;
// The decompressed dex, when the entry needed one. The cleanup releases it at the end of
// the iteration, so every entry gets a buffer of its own and no way out of the loop -- an
// early return included -- can leak one.
FF_AUTO_FREE uint8_t* inflated = nullptr;
if (findDexEntry(mapping.mapped, mapping.mappedSize, entry, &data, &dataSize, &uncompressedSize, &method) != nullptr) {
if (index > 1) {
// The magic scan only ever finds the first dex, so it cannot stand in for a later
// entry: running past the end of the entries that exist ends the walk.
break;
}
data = findDexMagic(mapping.mapped, mapping.mappedSize, &dataSize);
if (data == nullptr) {
FF_DEBUG("Neither %s nor a dex magic signature is in the %zu byte jar", entry, mapping.mappedSize);
return "No dex in the jar";
}
} else if (method == FF_ZIP_METHOD_DEFLATED) {
#ifdef FF_HAVE_ZLIB
error = inflateDex(data, dataSize, uncompressedSize, &inflated);
if (error != nullptr) {
return error;
}
data = inflated;
dataSize = uncompressedSize;
#else
return "The jar deflates its dex entries and fastfetch was built without zlib";
#endif
} else if (method != FF_ZIP_METHOD_STORED) {
FF_DEBUG("%s uses compression method %u, only %u (stored) and %u (deflated) are handled",
entry, method, FF_ZIP_METHOD_STORED, FF_ZIP_METHOD_DEFLATED);
return "A dex entry uses an unsupported compression method";
}
// A STORED entry needs no further work: `dataSize` already holds the `compressedSize` that
// `findDexEntry` checked against the mapping, and for a STORED entry the payload is the file
// itself. The header's `uncompressedSize` is deliberately not used for it -- nothing bounds
// that value, so a corrupt one reaches past the end of the mapping, which is exactly what the
// dex header would then be validated against.
mapping.data = data;
mapping.size = dataSize;
FFDexTables tables = {};
error = dexOpen(mapping.data, mapping.size, &tables);
} else if (method == FF_ZIP_METHOD_DEFLATED) {
#ifdef FF_HAVE_ZLIB
const char* error = inflateDex(data, dataSize, uncompressedSize, &mapping.inflated);
if (error != nullptr) {
// The dex cannot be read at all, which no later entry can fix.
return error;
}
dexFindTypes(&tables, requests, settled, typeIndexes, count);
// Every class this entry defines is walked once, however many of its fields were asked for.
// A request whose class is not in this entry's type table keeps `UINT32_MAX` and is left for
// the next entry; one whose class is only referenced here is left the same way by the walk
// below, which is what `defined` reports.
for (uint32_t r = 0; r < count; ++r) {
if (settled[r] || typeIndexes[r] == UINT32_MAX) {
continue;
}
const uint32_t typeIndex = typeIndexes[r];
bool defined = false;
error = dexClassStaticInts(&tables, typeIndex, requests, typeIndexes, settled, count, &defined);
if (error != nullptr) {
// The dex cannot be read, which no later entry can fix.
return error;
}
if (!defined) {
// Only referenced here, so the class is left for the next entry -- and the requests
// that share it are marked as walked, since one lookup of a class answers all of
// them and the class table should not be scanned once per field.
for (uint32_t s = r; s < count; ++s) {
if (typeIndexes[s] == typeIndex) {
typeIndexes[s] = UINT32_MAX;
}
}
}
}
pending = 0;
for (uint32_t r = 0; r < count; ++r) {
if (!settled[r]) {
++pending;
}
}
data = mapping.inflated;
dataSize = uncompressedSize;
#else
return "The jar deflates classes.dex and fastfetch was built without zlib";
#endif
} else if (method != FF_ZIP_METHOD_STORED) {
return "classes.dex uses an unsupported compression method";
}
// A STORED entry needs no further work: `dataSize` already holds the `compressedSize` that
// `findDexEntry` checked against the mapping, and for a STORED entry the payload is the file
// itself. The header's `uncompressedSize` is deliberately not used for it -- nothing bounds that
// value, so a corrupt one reaches past the end of the mapping, which is exactly what the dex
// header would then be validated against.
// What is still open is a class no entry of the jar defines. That is an answer like any other --
// the results already hold the sentinel, written before the walk began -- so the only thing left
// is to say so for a debug build, where the sentinel on its own does not tell "this build has no
// such method" apart from "the reader went wrong".
for (uint32_t r = 0; r < count; ++r) {
if (*requests[r].result == FF_DEX_STATIC_INT_UNRESOLVED) {
FF_DEBUG("\"%s\" is not a static int field of \"%s\"", requests[r].fieldName, requests[r].classDescriptor);
}
}
return nullptr;
mapping.data = data;
mapping.size = dataSize;
return dexStaticInt(mapping.data, mapping.size, classDescriptor, fieldName, result);
}
+4 -38
View File
@@ -24,41 +24,7 @@
#include "fastfetch.h"
// What a request is written when the jar does not answer it, on every path out of ffDexStaticInts --
// a jar that cannot be opened and a field the class does not declare both leave it behind, so a
// caller reads a result rather than what its own stack happened to hold there. No transaction code
// is negative: AIDL numbers them from `IBinder.FIRST_CALL_TRANSACTION`, which is 1.
//
// This is what keeps the reader a lookup and nothing more. Whether a code is one the caller cannot do
// without or one it only uses when the build declares it, whether what came back is enough to carry
// on, and whether the module reports a value or an error are the caller's decisions, made by reading
// the results. The reader finds numbers and says which ones it could not.
#define FF_DEX_STATIC_INT_UNRESOLVED (-1)
// One `<classDescriptor>.<fieldName>` lookup, and where its value is written. `classDescriptor` is
// the dex type descriptor, e.g. "Landroid/net/wifi/IWifiManager$Stub;".
typedef struct FFDexStaticIntRequest {
const char* classDescriptor;
const char* fieldName;
int32_t* result;
} FFDexStaticIntRequest;
// Resolves the int value of every request out of the dex entries of `jarPath`, walking `classes.dex`,
// `classes2.dex`, ... in order until one of them defines the class.
//
// Asking for several fields at once is not a convenience. A lookup is dominated by the type table,
// which lists every type the dex references -- 8428 descriptors in `framework.jar`'s `classes.dex`,
// each one read from a different part of a 51 MB mapping -- and a single pass answers every request
// whose class is named in it. Measured over three devices' own `framework.jar`, four fields of two
// classes cost one pass rather than four.
//
// Every request is written a code or FF_DEX_STATIC_INT_UNRESOLVED. A request is never allowed to
// take the others down with it: the classes a caller asks for are looked for independently, and one
// the jar does not declare leaves its own result at the sentinel while the rest are answered.
//
// Not finding a code is not a failure. A jar that is readable answers the call, whether it declares
// every field asked for, some of them, or none -- the results say which, and what that means for the
// module is the caller's to decide. The return value is therefore reserved for the one thing the
// caller cannot read out of the results: the jar itself, which is reported when it cannot be opened
// or does not hold a dex that can be read. nullptr means the lookup ran.
[[gnu::nonnull(1, 2), nodiscard]] const char* ffDexStaticInts(const char* jarPath, const FFDexStaticIntRequest* requests, uint32_t count);
// Resolves the int value of `<classDescriptor>.<fieldName>` out of the `classes.dex` of `jarPath`.
// `classDescriptor` is the dex type descriptor, e.g. "Landroid/net/wifi/IWifiManager$Stub;".
// Returns nullptr on success, a static message otherwise.
[[gnu::nonnull(1, 2, 3, 4), nodiscard]] const char* ffDexStaticInt(const char* jarPath, const char* classDescriptor, const char* fieldName, int32_t* result);
+2 -1
View File
@@ -1,6 +1,7 @@
#pragma once
#include "fastfetch.h"
#include <stdbool.h>
#include <stddef.h>
// The one package name the shell UID owns. android.os.Process.SHELL_UID is a single app UID that
// owns exactly one package, so a process that cannot derive a package of its own -- a static build
-32
View File
@@ -1,47 +1,36 @@
#include "cf_helpers.h"
#include "common/debug.h"
const char* ffCfNumGetInt64(CFTypeRef cf, int64_t* result) {
if (CFGetTypeID(cf) == CFNumberGetTypeID()) {
if (!CFNumberGetValue((CFNumberRef) cf, kCFNumberSInt64Type, result)) {
FF_DEBUG("CFNumberGetValue(kCFNumberSInt64Type) failed, the number is of type 0x%08lx",
(unsigned long) CFNumberGetType((CFNumberRef) cf));
return "Number type is not SInt64";
}
return nullptr;
} else if (CFGetTypeID(cf) == CFDataGetTypeID()) {
if (CFDataGetLength((CFDataRef) cf) != sizeof(int64_t)) {
FF_DEBUG("CFData holds %ld bytes, int64_t needs %zu",
(long) CFDataGetLength((CFDataRef) cf), sizeof(int64_t));
return "Data length is not sizeof(int64_t)";
}
CFDataGetBytes((CFDataRef) cf, CFRangeMake(0, sizeof(int64_t)), (uint8_t*) result);
return nullptr;
}
FF_DEBUG("Expected a CFNumber or CFData, got TypeID 0x%08lx", (unsigned long) CFGetTypeID(cf));
return "TypeID is neither 'CFNumber' nor 'CFData'";
}
const char* ffCfNumGetInt(CFTypeRef cf, int32_t* result) {
if (CFGetTypeID(cf) == CFNumberGetTypeID()) {
if (!CFNumberGetValue((CFNumberRef) cf, kCFNumberSInt32Type, result)) {
FF_DEBUG("CFNumberGetValue(kCFNumberSInt32Type) failed, the number is of type 0x%08lx",
(unsigned long) CFNumberGetType((CFNumberRef) cf));
return "Number type is not SInt32";
}
return nullptr;
} else if (CFGetTypeID(cf) == CFDataGetTypeID()) {
if (CFDataGetLength((CFDataRef) cf) != sizeof(*result)) {
FF_DEBUG("CFData holds %ld bytes, int32_t needs %zu",
(long) CFDataGetLength((CFDataRef) cf), sizeof(*result));
return "Data length is not sizeof(int32_t)";
}
CFDataGetBytes((CFDataRef) cf, CFRangeMake(0, sizeof(*result)), (uint8_t*) result);
return nullptr;
}
FF_DEBUG("Expected a CFNumber or CFData, got TypeID 0x%08lx", (unsigned long) CFGetTypeID(cf));
return "TypeID is neither 'CFNumber' nor 'CFData'";
}
@@ -49,20 +38,16 @@ const char* ffCfNumGetDouble(CFTypeRef cf, double* result) {
if (CFGetTypeID(cf) == CFNumberGetTypeID()) {
if (!CFNumberGetValue((CFNumberRef) cf, kCFNumberDoubleType, result) &&
!CFNumberGetValue((CFNumberRef) cf, kCFNumberFloatType, result)) {
FF_DEBUG("CFNumber is of type 0x%08lx, neither Double nor Float",
(unsigned long) CFNumberGetType((CFNumberRef) cf));
return "Number type is not Double or Float";
}
return nullptr;
}
FF_DEBUG("Expected a CFNumber, got TypeID 0x%08lx", (unsigned long) CFGetTypeID(cf));
return "TypeID is neither 'CFNumber'";
}
const char* ffCfDateGetEpoch(CFTypeRef cf, uint64_t* result) {
if (CFGetTypeID(cf) != CFDateGetTypeID()) {
FF_DEBUG("Expected a CFDate, got TypeID 0x%08lx", (unsigned long) CFGetTypeID(cf));
return "TypeID is not 'CFDate'";
}
@@ -91,7 +76,6 @@ const char* ffCfStrGetString(CFTypeRef cf, FFstrbuf* result) {
}
ffStrbufEnsureFixedLengthFree(result, (uint32_t) CFStringGetMaximumSizeForEncoding(length, kCFStringEncodingUTF8));
if (!CFStringGetCString(cfStr, result->chars, result->allocated, kCFStringEncodingUTF8)) {
FF_DEBUG("CFStringGetCString() failed for a %u character string", (unsigned) length);
return "CFStringGetCString() failed";
}
// CFStringGetCString ensures the buffer is NUL terminated
@@ -109,7 +93,6 @@ const char* ffCfStrGetString(CFTypeRef cf, FFstrbuf* result) {
result->length = (uint32_t) strnlen(result->chars, length);
result->chars[result->length] = '\0';
} else {
FF_DEBUG("Expected a CFString or CFData, got TypeID 0x%08lx", (unsigned long) CFGetTypeID(cf));
return "TypeID is neither 'CFString' nor 'CFData'";
}
@@ -133,7 +116,6 @@ const char* ffCfDataGetDataAsString(CFTypeRef cf, FFstrbuf* result) {
result->length = length;
result->chars[result->length] = '\0';
} else {
FF_DEBUG("Expected a CFData, got TypeID 0x%08lx", (unsigned long) CFGetTypeID(cf));
return "TypeID is not 'CFData'";
}
@@ -143,7 +125,6 @@ const char* ffCfDataGetDataAsString(CFTypeRef cf, FFstrbuf* result) {
const char* ffCfDictGetString(CFDictionaryRef dict, CFStringRef key, FFstrbuf* result) {
CFTypeRef cf = (CFTypeRef) CFDictionaryGetValue(dict, key);
if (cf == nullptr) {
FF_DEBUG("The dictionary has no value for the key");
return "CFDictionaryGetValue() failed";
}
@@ -153,7 +134,6 @@ const char* ffCfDictGetString(CFDictionaryRef dict, CFStringRef key, FFstrbuf* r
const char* ffCfDictGetDataAsString(CFDictionaryRef dict, CFStringRef key, FFstrbuf* result) {
CFTypeRef cf = (CFTypeRef) CFDictionaryGetValue(dict, key);
if (cf == nullptr) {
FF_DEBUG("The dictionary has no value for the key");
return "CFDictionaryGetValue() failed";
}
@@ -163,12 +143,10 @@ const char* ffCfDictGetDataAsString(CFDictionaryRef dict, CFStringRef key, FFstr
const char* ffCfDictGetBool(CFDictionaryRef dict, CFStringRef key, bool* result) {
CFBooleanRef cf = (CFBooleanRef) CFDictionaryGetValue(dict, key);
if (cf == nullptr) {
FF_DEBUG("The dictionary has no value for the key");
return "CFDictionaryGetValue() failed";
}
if (CFGetTypeID(cf) != CFBooleanGetTypeID()) {
FF_DEBUG("Expected a CFBoolean, got TypeID 0x%08lx", (unsigned long) CFGetTypeID(cf));
return "TypeID is not 'CFBoolean'";
}
@@ -179,7 +157,6 @@ const char* ffCfDictGetBool(CFDictionaryRef dict, CFStringRef key, bool* result)
const char* ffCfDictGetInt(CFDictionaryRef dict, CFStringRef key, int* result) {
CFTypeRef cf = (CFTypeRef) CFDictionaryGetValue(dict, key);
if (cf == nullptr) {
FF_DEBUG("The dictionary has no value for the key");
return "CFDictionaryGetValue() failed";
}
@@ -189,7 +166,6 @@ const char* ffCfDictGetInt(CFDictionaryRef dict, CFStringRef key, int* result) {
const char* ffCfDictGetInt64(CFDictionaryRef dict, CFStringRef key, int64_t* result) {
CFTypeRef cf = (CFTypeRef) CFDictionaryGetValue(dict, key);
if (cf == nullptr) {
FF_DEBUG("The dictionary has no value for the key");
return "CFDictionaryGetValue() failed";
}
@@ -199,7 +175,6 @@ const char* ffCfDictGetInt64(CFDictionaryRef dict, CFStringRef key, int64_t* res
const char* ffCfDictGetDouble(CFDictionaryRef dict, CFStringRef key, double* result) {
CFTypeRef cf = (CFTypeRef) CFDictionaryGetValue(dict, key);
if (cf == nullptr) {
FF_DEBUG("The dictionary has no value for the key");
return "CFDictionaryGetValue() failed";
}
@@ -209,20 +184,16 @@ const char* ffCfDictGetDouble(CFDictionaryRef dict, CFStringRef key, double* res
const char* ffCfDictGetData(CFDictionaryRef dict, CFStringRef key, uint32_t offset, uint32_t size, uint8_t* result, uint32_t* length) {
CFTypeRef cf = (CFTypeRef) CFDictionaryGetValue(dict, key);
if (cf == nullptr) {
FF_DEBUG("The dictionary has no value for the key");
return "CFDictionaryGetValue() failed";
}
if (CFGetTypeID(cf) != CFDataGetTypeID()) {
FF_DEBUG("Expected a CFData, got TypeID 0x%08lx", (unsigned long) CFGetTypeID(cf));
return "TypeID is not 'CFData'";
}
CFIndex trueLength = CFDataGetLength((CFDataRef) cf);
if (trueLength < offset + size) {
FF_DEBUG("The CFData holds %ld bytes, the caller asked for [%u, %u)",
(long) trueLength, offset, offset + size);
return "Data length is less than offset + size";
}
@@ -237,8 +208,6 @@ const char* ffCfDictGetData(CFDictionaryRef dict, CFStringRef key, uint32_t offs
const char* ffCfDictGetDict(CFDictionaryRef dict, CFStringRef key, CFDictionaryRef* result) {
CFDictionaryRef cf = (CFDictionaryRef) CFDictionaryGetValue(dict, key);
if (cf == nullptr || CFGetTypeID(cf) != CFDictionaryGetTypeID()) {
FF_DEBUG("Expected a CFDictionary, got %s",
cf == nullptr ? "no value at all" : "a value of another type");
return "TypeID is not 'CFDictionary'";
}
@@ -249,7 +218,6 @@ const char* ffCfDictGetDict(CFDictionaryRef dict, CFStringRef key, CFDictionaryR
const char* ffCfDictGetDateAsEpoch(CFDictionaryRef dict, CFStringRef key, uint64_t* result) {
CFTypeRef cf = (CFTypeRef) CFDictionaryGetValue(dict, key);
if (cf == nullptr) {
FF_DEBUG("The dictionary has no value for the key");
return "CFDictionaryGetValue() failed";
}
+2 -28
View File
@@ -1,6 +1,5 @@
#include "smc_temps.h"
#include "common/apple/cf_helpers.h"
#include "common/debug.h"
#include "common/strutil.h"
#include <stdint.h>
@@ -84,10 +83,7 @@ static void smcUltostr(char* str, uint32_t val) {
static const char* smcCall(io_connect_t conn, uint32_t selector, SmcKeyData_t* inputStructure, SmcKeyData_t* outputStructure) {
size_t size = sizeof(SmcKeyData_t);
const IOReturn callStatus = IOConnectCallStructMethod(conn, selector, inputStructure, size, outputStructure, &size);
if (callStatus != kIOReturnSuccess) {
FF_DEBUG("IOConnectCallStructMethod(selector=%u, command=%u) failed: IOReturn 0x%08x",
selector, (uint32_t) (unsigned char) inputStructure->data8, (unsigned) callStatus);
if (IOConnectCallStructMethod(conn, selector, inputStructure, size, outputStructure, &size) != kIOReturnSuccess) {
return "IOConnectCallStructMethod(conn) failed";
}
return nullptr;
@@ -140,13 +136,10 @@ static const char* smcReadSmcVal(io_connect_t conn, const UInt32Char_t key, SmcV
static const char* smcOpen(io_connect_t* conn) {
FF_IOOBJECT_AUTO_RELEASE io_object_t device = IOServiceGetMatchingService(MACH_PORT_NULL, IOServiceMatching("AppleSMC"));
if (!device) {
FF_DEBUG("IOKit has no AppleSMC service");
return "No SMC device found";
}
const IOReturn openStatus = IOServiceOpen(device, mach_task_self(), 0, conn);
if (openStatus != kIOReturnSuccess) {
FF_DEBUG("IOServiceOpen(AppleSMC) failed: IOReturn 0x%08x", (unsigned) openStatus);
if (IOServiceOpen(device, mach_task_self(), 0, conn) != kIOReturnSuccess) {
return "IOServiceOpen() failed";
}
@@ -160,7 +153,6 @@ static const char* smcReadValue(io_connect_t conn, const UInt32Char_t key, doubl
return error;
}
if (val.dataSize == 0) {
FF_DEBUG("The SMC key %s reported a data size of 0", key);
return "Empty SMC result";
}
@@ -181,12 +173,9 @@ static const char* smcReadValue(io_connect_t conn, const UInt32Char_t key, doubl
*value = (double) ntohll(*(uint64_t*) (val.bytes));
break;
default:
FF_DEBUG("SMC key %s: unsigned type %s has an unsupported size of %u bytes",
key, val.dataType, val.dataSize);
return "Unsupported SMC unsigned integer data size";
}
} else {
FF_DEBUG("SMC key %s: unsupported unsigned type %s", key, val.dataType);
return "Unsupported SMC unsigned data type";
}
break;
@@ -215,13 +204,9 @@ static const char* smcReadValue(io_connect_t conn, const UInt32Char_t key, doubl
} else if (ffStrEquals(val.dataType, "fpe2")) {
*value = ntohs(*(uint16_t*) (val.bytes)) / 4.0;
} else {
FF_DEBUG("SMC key %s: unsupported fixed point type %s of %u bytes",
key, val.dataType, val.dataSize);
return "Unsupported SMC floating point data type";
}
} else {
FF_DEBUG("SMC key %s: unsupported floating point type %s of %u bytes",
key, val.dataType, val.dataSize);
return "Unsupported SMC floating point data type";
}
break;
@@ -242,8 +227,6 @@ static const char* smcReadValue(io_connect_t conn, const UInt32Char_t key, doubl
*value = (double) ntohll(*(int64_t*) (val.bytes));
break;
default:
FF_DEBUG("SMC key %s: signed type %s has an unsupported size of %u bytes",
key, val.dataType, val.dataSize);
return "Unsupported SMC signed integer data size";
}
} else if (val.dataType[1] == 'p' && val.dataSize == 2) // signed fixed point types
@@ -269,12 +252,9 @@ static const char* smcReadValue(io_connect_t conn, const UInt32Char_t key, doubl
} else if (ffStrEquals(val.dataType, "spf0")) {
*value = (int16_t) ntohs(*(int16_t*) (val.bytes)) / 1.0;
} else {
FF_DEBUG("SMC key %s: unsupported signed fixed point type %s of %u bytes",
key, val.dataType, val.dataSize);
return "Unsupported SMC signed integer data type";
}
} else {
FF_DEBUG("SMC key %s: unsupported signed type %s", key, val.dataType);
return "Unsupported SMC signed data type";
}
break;
@@ -283,15 +263,11 @@ static const char* smcReadValue(io_connect_t conn, const UInt32Char_t key, doubl
if (ffStrEquals(val.dataType, "{pwm") && val.dataSize == 2) {
*value = (double) ntohs(*(uint16_t*) (val.bytes)) * 100 / 65536.0;
} else {
FF_DEBUG("SMC key %s: unsupported special type %s of %u bytes", key, val.dataType, val.dataSize);
return "Unsupported SMC special data type";
}
break;
default:
FF_DEBUG("SMC key %s: unknown data type '%c%c' (%s) of %u bytes",
key, val.dataType[0] ? val.dataType[0] : '?', val.dataType[1] ? val.dataType[1] : '?',
val.dataType, val.dataSize);
return "Unsupported SMC data type";
}
return nullptr;
@@ -324,7 +300,6 @@ const char* ffDetectSmcSpecificTemp(const char* sensor, double* result) {
}
if (!detectTemp(conn, sensor, result)) {
FF_DEBUG("The SMC sensor %s could not be read, or read a value outside 10..120", sensor);
return "Could not read SMC temperature";
}
@@ -515,7 +490,6 @@ const char* ffDetectSmcTemps(enum FFTempType type, double* result) {
break;
}
FF_DEBUG("None of the SMC sensors for temp type %d reported a usable reading", (int) type);
if (count == 0) {
return "No temperatures detected";
}
+28 -12
View File
@@ -1,22 +1,38 @@
#pragma once
#include "fastfetch.h"
#include "common/time.h"
[[gnu::nonnull(1), gnu::pure, nodiscard]] static inline const char* ffFindFileName(const char* file) {
const char* lastSlash = __builtin_strrchr(file, '/');
#ifdef _WIN32
if (lastSlash == nullptr) {
lastSlash = __builtin_strrchr(file, '\\');
}
#endif
if (lastSlash != nullptr) {
return lastSlash + 1;
}
return file;
}
#ifndef NDEBUG
#include "fastfetch.h"
#include "common/time.h"
#define FF_DEBUG_PRINT(file_, line_, format_, ...) \
do { \
if (instance.config.display.debugMode) \
fprintf(stderr, "[%s%4d, %s] " format_ "\n", ffFindFileName(file_), line_, ffTimeToTimeStr(ffTimeGetNow()), ##__VA_ARGS__); \
} while (0)
#else
#define FF_DEBUG_PRINT(file_, line_, format_, ...) \
do { \
} while (0)
#endif
[[gnu::nonnull(1, 3), gnu::format(printf, 3, 4)]]
void ffDebugPrint(const char* file, int line, const char* format, ...);
#define FF_DEBUG(format, ...) FF_DEBUG_PRINT(__FILE__, __LINE__, format, ##__VA_ARGS__)
#define FF_DEBUG(format, ...) ffDebugPrint(__FILE__, __LINE__, format, ##__VA_ARGS__)
#if _WIN32
#if _WIN32
const char* ffDebugWin32Error(DWORD errorCode);
const char* ffDebugNtStatus(NTSTATUS status);
const char* ffDebugConfigRet(unsigned long /*CONFIGRET*/ ret);
const char* ffDebugHResult(HRESULT hr);
#endif
#else
#define FF_DEBUG(format, ...) ((void) 0)
#endif
+43 -43
View File
@@ -2,13 +2,13 @@
#include "common/io.h"
#include "common/library.h"
#include "common/strutil.h"
#include "common/windows/folders.h"
#include "common/windows/unicode.h"
#include "common/windows/registry.h"
#include "common/windows/nt.h"
#include <stdalign.h>
#include <windows.h>
#include <shlobj.h>
#include <sddl.h>
#define SECURITY_WIN32 1 // For secext.h
@@ -43,27 +43,43 @@ static void getExePath(FFPlatform* platform) {
ffStrbufReplaceAllC(&platform->exePath, '\\', '/');
}
static void getCacheDir(FFPlatform* platform) {
if (!ffGetKnownFolderPath(FF_KNOWN_FOLDER_LOCAL_APP_DATA, &platform->cacheDir)) {
ffStrbufAppend(&platform->cacheDir, &platform->homeDir);
ffStrbufAppendS(&platform->cacheDir, "AppData/Local/");
return;
static void getHomeDir(FFPlatform* platform) {
PWSTR pPath = nullptr;
if (SUCCEEDED(SHGetKnownFolderPath(&FOLDERID_Profile, KF_FLAG_DEFAULT, nullptr, &pPath))) {
ffStrbufSetWS(&platform->homeDir, pPath);
ffStrbufReplaceAllC(&platform->homeDir, '\\', '/');
ffStrbufEnsureEndsWithC(&platform->homeDir, '/');
} else {
ffStrbufSetS(&platform->homeDir, getenv("USERPROFILE"));
ffStrbufReplaceAllC(&platform->homeDir, '\\', '/');
ffStrbufEnsureEndsWithC(&platform->homeDir, '/');
}
ffStrbufReplaceAllC(&platform->cacheDir, '\\', '/');
ffStrbufEnsureEndsWithC(&platform->cacheDir, '/');
CoTaskMemFree(pPath);
}
static void platformPathAddKnownFolder(FFlist* dirs, FFKnownFolder folder) {
FF_STRBUF_AUTO_DESTROY buffer = ffStrbufCreate();
if (!ffGetKnownFolderPath(folder, &buffer)) {
return;
static void getCacheDir(FFPlatform* platform) {
PWSTR pPath = nullptr;
if (SUCCEEDED(SHGetKnownFolderPath(&FOLDERID_LocalAppData, KF_FLAG_DEFAULT, nullptr, &pPath))) {
ffStrbufSetWS(&platform->cacheDir, pPath);
ffStrbufReplaceAllC(&platform->cacheDir, '\\', '/');
ffStrbufEnsureEndsWithC(&platform->cacheDir, '/');
} else {
ffStrbufAppend(&platform->cacheDir, &platform->homeDir);
ffStrbufAppendS(&platform->cacheDir, "AppData/Local/");
}
CoTaskMemFree(pPath);
}
ffStrbufReplaceAllC(&buffer, '\\', '/');
ffStrbufEnsureEndsWithC(&buffer, '/');
if (!FF_LIST_CONTAINS(*dirs, &buffer, ffStrbufEqual)) {
ffStrbufInitMove(FF_LIST_ADD(FFstrbuf, *dirs), &buffer);
static void platformPathAddKnownFolder(FFlist* dirs, REFKNOWNFOLDERID folderId) {
PWSTR pPath = nullptr;
if (SUCCEEDED(SHGetKnownFolderPath(folderId, KF_FLAG_DEFAULT, nullptr, &pPath))) {
FF_STRBUF_AUTO_DESTROY buffer = ffStrbufCreateWS(pPath);
CoTaskMemFree(pPath);
ffStrbufReplaceAllC(&buffer, '\\', '/');
ffStrbufEnsureEndsWithC(&buffer, '/');
if (!FF_LIST_CONTAINS(*dirs, &buffer, ffStrbufEqual)) {
ffStrbufInitMove(FF_LIST_ADD(FFstrbuf, *dirs), &buffer);
}
}
}
@@ -96,9 +112,9 @@ static void getConfigDirs(FFPlatform* platform) {
}
ffPlatformPathAddHome(&platform->configDirs, platform, ".config/");
platformPathAddKnownFolder(&platform->configDirs, FF_KNOWN_FOLDER_PROGRAM_DATA);
platformPathAddKnownFolder(&platform->configDirs, FF_KNOWN_FOLDER_ROAMING_APP_DATA);
platformPathAddKnownFolder(&platform->configDirs, FF_KNOWN_FOLDER_LOCAL_APP_DATA);
platformPathAddKnownFolder(&platform->configDirs, &FOLDERID_ProgramData);
platformPathAddKnownFolder(&platform->configDirs, &FOLDERID_RoamingAppData);
platformPathAddKnownFolder(&platform->configDirs, &FOLDERID_LocalAppData);
ffPlatformPathAddHome(&platform->configDirs, platform, "");
}
@@ -110,9 +126,9 @@ static void getDataDirs(FFPlatform* platform) {
platformPathAddEnvSuffix(&platform->dataDirs, "MINGW_PREFIX", "share");
}
ffPlatformPathAddHome(&platform->dataDirs, platform, ".local/share/");
platformPathAddKnownFolder(&platform->dataDirs, FF_KNOWN_FOLDER_PROGRAM_DATA);
platformPathAddKnownFolder(&platform->dataDirs, FF_KNOWN_FOLDER_ROAMING_APP_DATA);
platformPathAddKnownFolder(&platform->dataDirs, FF_KNOWN_FOLDER_LOCAL_APP_DATA);
platformPathAddKnownFolder(&platform->dataDirs, &FOLDERID_ProgramData);
platformPathAddKnownFolder(&platform->dataDirs, &FOLDERID_RoamingAppData);
platformPathAddKnownFolder(&platform->dataDirs, &FOLDERID_LocalAppData);
ffPlatformPathAddHome(&platform->dataDirs, platform, "");
}
@@ -140,25 +156,9 @@ static void getUserName(FFPlatform* platform) {
TOKEN_USER* tokenUser = (TOKEN_USER*) buf;
UNICODE_STRING sidString = { .Buffer = buffer, .Length = 0, .MaximumLength = sizeof(buffer) };
if (NT_SUCCESS(RtlConvertSidToUnicodeString(&sidString, tokenUser->User.Sid, FALSE))) {
uint32_t sidLength = sidString.Length / sizeof(wchar_t);
ffStrbufSetNWS(&platform->sid, sidLength, sidString.Buffer);
// The SID is already in hand, so the profile directory costs one registry read rather
// than a second NtQueryInformationToken(). ffGetProfilePath() needs a null-terminated
// string, which RtlConvertSidToUnicodeString() does not produce.
if (sidLength < ARRAY_SIZE(buffer)) {
buffer[sidLength] = L'\0';
ffGetProfilePath(buffer, &platform->homeDir);
}
ffStrbufSetNWS(&platform->sid, sidString.Length / sizeof(wchar_t), sidString.Buffer);
}
}
if (platform->homeDir.length == 0) {
ffStrbufSetS(&platform->homeDir, getenv("USERPROFILE")); // Last resort; ffGetProfilePath() covers the normal case
}
ffStrbufReplaceAllC(&platform->homeDir, '\\', '/');
ffStrbufEnsureEndsWithC(&platform->homeDir, '/');
}
static void getHostName(FFPlatform* platform) {
@@ -280,12 +280,12 @@ void ffPlatformInitImpl(FFPlatform* platform) {
platform->pid = (uint32_t) (uintptr_t) ffGetTeb()->ClientId.UniqueProcess;
getExePath(platform);
getCwd(platform);
getUserName(platform);
getHomeDir(platform);
getCacheDir(platform);
getConfigDirs(platform);
getDataDirs(platform);
getUserName(platform);
getHostName(platform);
getUserShell(platform);
-11
View File
@@ -1,9 +1,7 @@
#include "common/binary.h"
#include "common/debug.h"
#include "common/io.h"
#include "common/strutil.h"
#include <errno.h>
#include <string.h>
#include <fcntl.h>
#include <sys/mman.h>
@@ -110,7 +108,6 @@ static const char* dumpMachHeader(const FFMemoryMapping* mapping, off_t offset,
if (is_64) {
const struct mach_header_64* header = readData(mapping, sizeof(struct mach_header_64), offset);
if (!header) {
FF_DEBUG("Failed to read the 64-bit mach header at offset %lld", (long long) offset);
return "read mach header failed";
}
@@ -119,7 +116,6 @@ static const char* dumpMachHeader(const FFMemoryMapping* mapping, off_t offset,
} else {
const struct mach_header* header = readData(mapping, sizeof(struct mach_header), offset);
if (!header) {
FF_DEBUG("Failed to read the mach header at offset %lld", (long long) offset);
return "read mach header failed";
}
@@ -204,7 +200,6 @@ static const char* dumpMachHeader(const FFMemoryMapping* mapping, off_t offset,
static const char* dumpFatHeader(const FFMemoryMapping* mapping, bool (*cb)(const char* str, uint32_t len, void* userdata), void* userdata, uint32_t minLength) {
const struct fat_header* headerRaw = readData(mapping, sizeof(struct fat_header), 0);
if (!headerRaw) {
FF_DEBUG("Failed to read the fat header");
return "read fat header failed";
}
@@ -256,7 +251,6 @@ static const char* dumpFatHeader(const FFMemoryMapping* mapping, bool (*cb)(cons
return dumpMachHeader(mapping, machHeaderOffset, *magic == MH_MAGIC_64, cb, userdata, minLength);
}
}
FF_DEBUG("No supported Mach-O slice was found in the fat binary");
return "Unsupported fat header";
}
@@ -271,13 +265,11 @@ static const char* dumpFatHeader(const FFMemoryMapping* mapping, bool (*cb)(cons
const char* ffBinaryExtractStrings(const char* machoFile, bool (*cb)(const char* str, uint32_t len, void* userdata), void* userdata, uint32_t minLength) {
FF_AUTO_CLOSE_FD int fd = open(machoFile, O_RDONLY | O_CLOEXEC);
if (fd < 0) {
FF_DEBUG("open(\"%s\") failed: %s", machoFile, strerror(errno));
return "File could not be opened";
}
struct stat st;
if (fstat(fd, &st) != 0 || st.st_size <= 0) {
FF_DEBUG("fstat(\"%s\") failed or the file is empty", machoFile);
return "Failed to stat file";
}
@@ -286,14 +278,12 @@ const char* ffBinaryExtractStrings(const char* machoFile, bool (*cb)(const char*
.length = (size_t) st.st_size,
};
if (mapping.data == MAP_FAILED) {
FF_DEBUG("mmap(\"%s\", %lld bytes) failed: %s", machoFile, (long long) st.st_size, strerror(errno));
return "mmap failed";
}
// Read the magic number to determine the type of binary
const uint32_t* magic = readData(&mapping, sizeof(uint32_t), 0);
if (!magic) {
FF_DEBUG("Failed to read the magic number");
return "read magic number failed";
}
@@ -301,7 +291,6 @@ const char* ffBinaryExtractStrings(const char* machoFile, bool (*cb)(const char*
// MH_CIGAM and MH_CIGAM_64 seem to be no longer used, as `swap_mach_header` is marked as deprecated.
// However FAT_CIGAM and FAT_CIGAM_64 are still used (/usr/bin/vim).
if (*magic != MH_MAGIC && *magic != MH_MAGIC_64 && *magic != FAT_CIGAM && *magic != FAT_CIGAM_64 && *magic != FAT_MAGIC && *magic != FAT_MAGIC_64) {
FF_DEBUG("Unsupported magic number 0x%08x", (unsigned) *magic);
return "Unsupported format or big endian mach-o file";
}
-7
View File
@@ -1,5 +1,4 @@
#include "common/binary.h"
#include "common/debug.h"
#if defined(FF_HAVE_ELF) || defined(__sun) || (defined(__FreeBSD__) && !defined(__DragonFly__)) || defined(__OpenBSD__) || defined(__NetBSD__)
@@ -44,7 +43,6 @@ const char* ffBinaryExtractStrings(const char* elfFile, bool (*cb)(const char* s
FF_LIBRARY_LOAD_MESSAGE(libelf, "libelf" FF_LIBRARY_EXTENSION, 1);
FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(libelf, elfData, elf_version)
if (elfData.ffelf_version(EV_CURRENT) == EV_NONE) {
FF_DEBUG("elf_version() failed");
return "elf_version() failed";
}
@@ -62,27 +60,23 @@ const char* ffBinaryExtractStrings(const char* elfFile, bool (*cb)(const char* s
}
if (elfData.ffelf_end == nullptr) {
FF_DEBUG("load libelf failed");
return "load libelf failed";
}
// Open the ELF file
FF_AUTO_CLOSE_FD int fd = open(elfFile, O_RDONLY | O_CLOEXEC);
if (fd < 0) {
FF_DEBUG("open(%s) failed: %s", elfFile, strerror(errno));
return "open() failed";
}
Elf* elf = elfData.ffelf_begin(fd, ELF_C_READ, nullptr);
if (elf == nullptr) {
FF_DEBUG("elf_begin() failed");
return "elf_begin() failed";
}
// Get the section header string table index
size_t shstrndx = 0;
if (elfData.ffelf_getshdrstrndx(elf, &shstrndx) < 0) {
FF_DEBUG("elf_getshdrstrndx() failed");
elfData.ffelf_end(elf);
return "elf_getshdrstrndx() failed";
}
@@ -147,7 +141,6 @@ const char* ffBinaryExtractStrings(const char* elfFile, bool (*cb)(const char* s
*/
const char* ffBinaryExtractStrings(const char* file, bool (*cb)(const char* str, uint32_t len, void* userdata), void* userdata, uint32_t minLength) {
FF_UNUSED(file, cb, userdata, minLength);
FF_DEBUG("Fastfetch was built without libelf support");
return "Fastfetch was built without libelf support";
}
+2 -9
View File
@@ -1,5 +1,4 @@
#include "common/binary.h"
#include "common/debug.h"
#include "common/io.h"
#include "common/strutil.h"
#include "common/windows/nt.h"
@@ -18,28 +17,22 @@
const char* ffBinaryExtractStrings(const char* peFile, bool (*cb)(const char* str, uint32_t len, void* userdata), void* userdata, uint32_t minLength) {
FF_AUTO_CLOSE_FD HANDLE hFile = CreateFileA(peFile, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr);
if (hFile == INVALID_HANDLE_VALUE) {
FF_DEBUG("CreateFileA() failed: %s", ffDebugWin32Error(GetLastError()));
return "CreateFileA() failed";
}
FF_AUTO_CLOSE_FD HANDLE hSection = nullptr;
NTSTATUS status = NtCreateSection(&hSection, SECTION_MAP_READ, nullptr, nullptr, PAGE_READONLY, SEC_COMMIT, hFile);
if (!NT_SUCCESS(status)) {
FF_DEBUG("NtCreateSection() failed: %s", ffDebugNtStatus(status));
if (!NT_SUCCESS(NtCreateSection(&hSection, SECTION_MAP_READ, nullptr, nullptr, PAGE_READONLY, SEC_COMMIT, hFile))) {
return "NtCreateSection() failed";
}
PVOID base = nullptr;
SIZE_T viewSize = 0;
status = NtMapViewOfSection(hSection, NtCurrentProcess(), &base, 0, 0, nullptr, &viewSize, ViewUnmap, 0, PAGE_READONLY);
if (!NT_SUCCESS(status)) {
FF_DEBUG("NtMapViewOfSection() failed: %s", ffDebugNtStatus(status));
if (!NT_SUCCESS(NtMapViewOfSection(hSection, NtCurrentProcess(), &base, 0, 0, nullptr, &viewSize, ViewUnmap, 0, PAGE_READONLY))) {
return "NtMapViewOfSection() failed";
}
PIMAGE_NT_HEADERS ntHeaders = RtlImageNtHeader(base);
if (!ntHeaders) {
FF_DEBUG("RtlImageNtHeader() failed");
NtUnmapViewOfSection(NtCurrentProcess(), base);
return "RtlImageNtHeader() failed";
}
-3
View File
@@ -2,7 +2,6 @@
#ifdef FF_HAVE_DBUS
#include "common/debug.h"
#include "common/thread.h"
#include "common/strutil.h"
@@ -40,13 +39,11 @@ static const FFDBusLibrary* loadLib(void) {
const char* ffDBusLoadData(DBusBusType busType, FFDBusData* data) {
data->lib = loadLib();
if (data->lib == nullptr) {
FF_DEBUG("libdbus-1 could not be loaded");
return "Failed to load DBus library";
}
data->connection = data->lib->ffdbus_bus_get(busType, nullptr);
if (data->connection == nullptr) {
FF_DEBUG("dbus_bus_get(busType=%d) returned no connection", (int) busType);
return "Failed to connect to DBus";
}
@@ -1,50 +1,8 @@
#include <errno.h>
#include "common/debug.h"
#include "common/windows/nt.h"
#ifndef NDEBUG
#include "common/debug.h"
#include "common/thread.h"
[[gnu::nonnull(1), gnu::pure, gnu::always_inline, nodiscard]]
static inline const char* ffFindFileName(const char* file) {
const char* lastSlash = __builtin_strrchr(file, '/');
#ifdef _WIN32
if (lastSlash == nullptr) {
lastSlash = __builtin_strrchr(file, '\\');
}
#endif
if (lastSlash != nullptr) {
return lastSlash + 1;
}
return file;
}
#if __GNUC__ && !__clang__
[[gnu::optimize("O2")]]
#endif
void ffDebugPrint(const char* file, int line, const char* format, ...) {
if (!instance.config.display.debugMode) {
return;
}
static FFThreadMutex debugMutex = FF_THREAD_MUTEX_INITIALIZER;
ffThreadMutexLock(&debugMutex);
int errno_ = errno;
static char errmsg_[1024];
va_list args;
va_start(args, format);
vsnprintf(errmsg_, sizeof(errmsg_), format, args);
va_end(args);
fprintf(stderr, "[%s%4d, %s] %s\n", ffFindFileName(file), line, ffTimeToTimeStr(ffTimeGetNow()), errmsg_);
errno = errno_;
ffThreadMutexUnlock(&debugMutex);
}
#if _WIN32
#include "common/windows/nt.h"
#include <windows.h>
#include <cfgmgr32.h>
#include <windows.h>
#include <cfgmgr32.h>
const char* ffDebugWin32Error(DWORD errorCode) {
static char buffer[512];
@@ -100,6 +58,3 @@ static inline DWORD HRESULTToWin32Error(HRESULT hr) {
const char* ffDebugHResult(HRESULT hr) {
return ffDebugWin32Error(HRESULTToWin32Error(hr));
}
#endif
#endif
+183
View File
@@ -280,6 +280,183 @@ static bool parseLuaString(FFstrbuf* buffer, const char* script, uint32_t script
}
#endif
#if FF_HAVE_QUICKJS
#include <quickjs.h>
struct FFQuickJSData {
FF_LIBRARY_SYMBOL(JS_NewRuntime)
FF_LIBRARY_SYMBOL(JS_NewContext)
FF_LIBRARY_SYMBOL(JS_FreeRuntime)
FF_LIBRARY_SYMBOL(JS_EvalThis)
FF_LIBRARY_SYMBOL(JS_GetException)
FF_LIBRARY_SYMBOL(JS_ToCStringLen2)
FF_LIBRARY_SYMBOL(JS_FreeCString)
FF_LIBRARY_SYMBOL(JS_NewStringLen)
FF_LIBRARY_SYMBOL(JS_NewArray)
FF_LIBRARY_SYMBOL(JS_NewBigUint64)
FF_LIBRARY_SYMBOL(JS_SetPropertyUint32)
FF_LIBRARY_SYMBOL(JS_NewObject)
FF_LIBRARY_SYMBOL(JS_SetPropertyStr)
FF_LIBRARY_SYMBOL(JS_FreeValue)
JSRuntime* rt;
JSContext* ctx;
bool inited;
} qjsData;
static const char* loadQuickJSState(void) {
if (qjsData.inited) {
if (qjsData.ctx == nullptr) {
return "QuickJS is not available";
}
return nullptr;
}
qjsData.inited = true;
#ifdef _WIN32
FF_LIBRARY_LOAD_MESSAGE(libqjs, "libqjs-0" FF_LIBRARY_EXTENSION, 0)
#else
FF_LIBRARY_LOAD_MESSAGE(libqjs, "libqjs" FF_LIBRARY_EXTENSION, 0)
#endif
FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(libqjs, qjsData, JS_NewRuntime)
FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(libqjs, qjsData, JS_NewContext)
FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(libqjs, qjsData, JS_FreeRuntime)
FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(libqjs, qjsData, JS_EvalThis)
FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(libqjs, qjsData, JS_GetException)
FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(libqjs, qjsData, JS_ToCStringLen2)
FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(libqjs, qjsData, JS_FreeCString)
FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(libqjs, qjsData, JS_NewStringLen)
FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(libqjs, qjsData, JS_NewArray)
FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(libqjs, qjsData, JS_SetPropertyUint32)
FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(libqjs, qjsData, JS_NewObject)
FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(libqjs, qjsData, JS_SetPropertyStr)
FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(libqjs, qjsData, JS_FreeValue)
qjsData.rt = qjsData.ffJS_NewRuntime();
if (qjsData.rt == nullptr) {
return "JS_NewRuntime() failed";
}
qjsData.ctx = qjsData.ffJS_NewContext(qjsData.rt);
if (qjsData.ctx == nullptr) {
qjsData.ffJS_FreeRuntime(qjsData.rt);
qjsData.rt = nullptr;
return "JS_NewContext() failed";
}
libqjs = nullptr; // don't close quickjs
return nullptr;
}
static bool parseQuickJSString(FFstrbuf* buffer, const char* script, uint32_t scriptLen, uint32_t numArgs, const FFformatarg* arguments) {
const char* err = loadQuickJSState();
if (err) {
ffStrbufAppendF(buffer, "Qjs init error: %s", err);
return false;
}
JSContext* ctx = qjsData.ctx;
JSValue argsObj = qjsData.ffJS_NewObject(ctx);
FF_STRBUF_AUTO_DESTROY argNameBuf = ffStrbufCreate();
for (uint32_t i = 0; i < numArgs; ++i) {
const FFformatarg* arg = &arguments[i];
JSValue value;
switch (arg->type) {
case FF_ARG_TYPE_INT:
value = JS_NewInt32(ctx, *(int32_t*) arg->value);
break;
case FF_ARG_TYPE_UINT:
value = JS_NewUint32(ctx, *(uint32_t*) arg->value);
break;
case FF_ARG_TYPE_UINT64: {
uint64_t val = *(uint64_t*) arg->value;
if (val <= INT32_MAX) {
value = JS_NewInt32(ctx, (int32_t) val);
} else {
value = JS_NewFloat64(ctx, (double) val);
}
break;
}
case FF_ARG_TYPE_UINT16:
value = JS_NewUint32(ctx, *(uint16_t*) arg->value);
break;
case FF_ARG_TYPE_UINT8:
value = JS_NewUint32(ctx, *(uint8_t*) arg->value);
break;
case FF_ARG_TYPE_FLOAT:
value = JS_NewFloat64(ctx, *(float*) arg->value);
break;
case FF_ARG_TYPE_DOUBLE:
value = JS_NewFloat64(ctx, *(double*) arg->value);
break;
case FF_ARG_TYPE_BOOL:
value = JS_NewBool(ctx, *(bool*) arg->value);
break;
case FF_ARG_TYPE_STRING:
value = qjsData.ffJS_NewStringLen(ctx, (const char*) arg->value, strlen((const char*) arg->value));
break;
case FF_ARG_TYPE_STRBUF: {
const FFstrbuf* sb = (const FFstrbuf*) arg->value;
value = qjsData.ffJS_NewStringLen(ctx, sb->chars, sb->length);
break;
}
case FF_ARG_TYPE_LIST: {
const FFlist* list = (const FFlist*) arg->value;
JSValue arr = qjsData.ffJS_NewArray(ctx);
for (uint32_t li = 0; li < list->length; ++li) {
const FFstrbuf* item = FF_LIST_GET(FFstrbuf, *list, li);
JSValue itemValue = qjsData.ffJS_NewStringLen(ctx, item->chars, item->length);
qjsData.ffJS_SetPropertyUint32(ctx, arr, li, itemValue);
}
value = arr;
break;
}
default:
value = JS_UNDEFINED;
break;
}
if (arg->name && arg->name[0]) {
normalizeArgName(&argNameBuf, arg->name);
} else {
ffStrbufSetF(&argNameBuf, "arg%" PRIu32, i + 1);
}
qjsData.ffJS_SetPropertyStr(ctx, argsObj, argNameBuf.chars, value);
}
JSValue result = qjsData.ffJS_EvalThis(ctx, argsObj, script, scriptLen, "", JS_EVAL_TYPE_GLOBAL | JS_EVAL_FLAG_STRICT);
qjsData.ffJS_FreeValue(ctx, argsObj);
bool ret = false;
if (JS_IsException(result)) {
JSValue exc = qjsData.ffJS_GetException(ctx);
const char* message = qjsData.ffJS_ToCStringLen2(ctx, nullptr, exc, false);
qjsData.ffJS_FreeValue(ctx, exc);
ffStrbufAppendF(buffer, "Qjs runtime error: %s", message ?: "unknown");
if (message) {
qjsData.ffJS_FreeCString(ctx, message);
}
} else if (JS_IsUndefined(result)) {
ffStrbufAppendS(buffer, "Qjs result error: undefined result");
} else {
size_t len;
const char* res = qjsData.ffJS_ToCStringLen2(ctx, &len, result, false);
if (res) {
ffStrbufAppendNS(buffer, (uint32_t) len, res);
qjsData.ffJS_FreeCString(ctx, res);
}
ret = true;
}
qjsData.ffJS_FreeValue(ctx, result);
return ret;
}
#endif
// Returns the length of the ANSI CSI sequence at `str`, or 0 if there is none.
// A CSI sequence is `ESC [` + parameter bytes (0x30-0x3F) + intermediate bytes (0x20-0x2F) + final byte (0x40-0x7E)
static uint32_t ansiEscapeLength(const char* str) {
@@ -636,6 +813,12 @@ static bool parseFormatString(FFstrbuf* buffer, const FFstrbuf* formatstr, uint3
}
bool ffParseFormatString(FFstrbuf* buffer, const FFstrbuf* formatstr, uint32_t numArgs, const FFformatarg* arguments) {
#if FF_HAVE_QUICKJS
if (ffStrbufStartsWithS(formatstr, "qjs:")) {
return parseQuickJSString(buffer, formatstr->chars + 4, formatstr->length - 4, numArgs, arguments);
}
#endif
#if FF_HAVE_LUA
if (ffStrbufStartsWithS(formatstr, "lua:")) {
// If outputFormat starts with "lua:", treat the rest as a Lua script
+12
View File
@@ -178,6 +178,9 @@ void ffDestroyInstance(void) {
#if FF_HAVE_LUA
#include <lua.h>
#endif
#if FF_HAVE_QUICKJS
#include <quickjs.h>
#endif
// Must be in a file compiled with the libfastfetch target, because the FF_HAVE* macros are not defined for the executable targets
void ffListFeatures(void) {
@@ -245,6 +248,12 @@ void ffListFeatures(void) {
#if FF_HAVE_FREETYPE
"freetype\n"
#endif
#if FF_HAVE_PULSE
"libpulse\n"
#endif
#if FF_HAVE_DDCUTIL
"libddcutil\n"
#endif
#if FF_HAVE_ELF || __sun || (__FreeBSD__ && !__DragonFly__) || __OpenBSD__ || __NetBSD__
"libelf\n"
#endif
@@ -286,6 +295,9 @@ void ffListFeatures(void) {
#endif
#if FF_HAVE_LUA
LUA_VERSION "\n"
#endif
#if FF_HAVE_QUICKJS
"QuickJS " FF_STR(QJS_VERSION_MAJOR) "." FF_STR(QJS_VERSION_MINOR) "." FF_STR(QJS_VERSION_PATCH) QJS_VERSION_SUFFIX "\n"
#endif
"",
stdout);
+2 -17
View File
@@ -1,6 +1,5 @@
#include "common/io.h"
#include "fastfetch.h"
#include "common/debug.h"
#include "common/strutil.h"
#include "common/time.h"
@@ -42,11 +41,9 @@ bool ffWriteFileData(const char* fileName, size_t dataSize, const void* data) {
createSubfolders(fileName);
fd = open(fileName, openFlagsModes, openFlagsRights);
if (fd == -1) {
FF_DEBUG("open(%s) failed after creating the parent folders: %s", fileName, strerror(errno));
return false;
}
} else {
FF_DEBUG("open(%s) failed: %s", fileName, strerror(errno));
return false;
}
}
@@ -149,19 +146,16 @@ const char* ffGetTerminalResponse(const char* request, int nParams, const char*
if (ftty < 0) {
ftty = open("/dev/tty", O_RDWR | O_NOCTTY | O_CLOEXEC);
if (ftty < 0) {
FF_DEBUG("open(/dev/tty) failed: %s", strerror(errno));
return "open(\"/dev/tty\", O_RDWR | O_NOCTTY | O_CLOEXEC) failed";
}
if (tcgetattr(ftty, &oldTerm) == -1) {
FF_DEBUG("tcgetattr(/dev/tty) failed: %s", strerror(errno));
return "tcgetattr(STDIN_FILENO, &oldTerm) failed";
}
struct termios newTerm = oldTerm;
newTerm.c_lflag &= (tcflag_t) ~(ICANON | ECHO);
if (tcsetattr(ftty, TCSAFLUSH, &newTerm) == -1) {
FF_DEBUG("tcsetattr(/dev/tty) failed: %s", strerror(errno));
return "tcsetattr(STDIN_FILENO, TCSAFLUSH, &newTerm)";
}
atexit(restoreTerm);
@@ -171,10 +165,7 @@ const char* ffGetTerminalResponse(const char* request, int nParams, const char*
// Give the terminal some time to respond
#ifndef __APPLE__
const int pollResult = poll(&(struct pollfd) { .fd = ftty, .events = POLLIN }, 1, FF_IO_TERM_RESP_WAIT_MS);
if (pollResult <= 0) {
FF_DEBUG("poll(/dev/tty) returned %d after %d ms: %s",
pollResult, FF_IO_TERM_RESP_WAIT_MS, pollResult == 0 ? "timed out" : strerror(errno));
if (poll(&(struct pollfd) { .fd = ftty, .events = POLLIN }, 1, FF_IO_TERM_RESP_WAIT_MS) <= 0) {
return "poll(/dev/tty) timeout or failed";
}
#else
@@ -184,10 +175,7 @@ const char* ffGetTerminalResponse(const char* request, int nParams, const char*
fd_set rd;
FD_ZERO(&rd);
FD_SET(ftty, &rd);
const int selectResult = select(ftty + 1, &rd, nullptr, nullptr, &(struct timeval) { .tv_sec = FF_IO_TERM_RESP_WAIT_MS / 1000, .tv_usec = (FF_IO_TERM_RESP_WAIT_MS % 1000) * 1000 });
if (selectResult <= 0) {
FF_DEBUG("select(/dev/tty) returned %d after %d ms: %s",
selectResult, FF_IO_TERM_RESP_WAIT_MS, selectResult == 0 ? "timed out" : strerror(errno));
if (select(ftty + 1, &rd, nullptr, nullptr, &(struct timeval) { .tv_sec = FF_IO_TERM_RESP_WAIT_MS / 1000, .tv_usec = (FF_IO_TERM_RESP_WAIT_MS % 1000) * 1000 }) <= 0) {
return "select(/dev/tty) timeout or failed";
}
}
@@ -203,8 +191,6 @@ const char* ffGetTerminalResponse(const char* request, int nParams, const char*
ssize_t nRead = read(ftty, buffer + bytesRead, sizeof(buffer) - bytesRead - 1);
if (nRead <= 0) {
FF_DEBUG("read(/dev/tty) returned %zd after %zu bytes: %s",
nRead, bytesRead, nRead == 0 ? "the terminal closed the connection" : strerror(errno));
va_end(args);
return "read(STDIN_FILENO, buffer, sizeof(buffer) - 1) failed";
}
@@ -218,7 +204,6 @@ const char* ffGetTerminalResponse(const char* request, int nParams, const char*
va_end(cargs);
if (ret <= 0) {
FF_DEBUG("vsscanf(\"%s\") matched %d of %d parameters", buffer, ret, nParams);
va_end(args);
return "vsscanf(buffer, format, args) failed";
}
+2 -7
View File
@@ -441,9 +441,7 @@ const char* ffGetTerminalResponse(const char* request, int nParams, const char*
}
while (true) {
NTSTATUS status = NtWaitForSingleObject(hInput, FALSE, &(LARGE_INTEGER) { .QuadPart = (int64_t) FF_IO_TERM_RESP_WAIT_MS * -10000 });
if (status != STATUS_WAIT_0) {
FF_DEBUG("NtWaitForSingleObject() failed or timeout: %s", ffDebugNtStatus(status));
if (NtWaitForSingleObject(hInput, FALSE, &(LARGE_INTEGER) { .QuadPart = (int64_t) FF_IO_TERM_RESP_WAIT_MS * -10000 }) != STATUS_WAIT_0) {
SetConsoleMode(hInput, inputMode);
return "NtWaitForSingleObject() failed or timeout";
}
@@ -473,9 +471,7 @@ const char* ffGetTerminalResponse(const char* request, int nParams, const char*
while (true) {
DWORD bytes = 0;
BOOL readOk = ReadFile(hInput, buffer + bytesRead, (DWORD) (sizeof(buffer) - 1 - bytesRead), &bytes, nullptr);
if (!readOk || bytes == 0) {
FF_DEBUG("ReadFile() %s, bytes=%lu: %s", readOk ? "returned no data" : "failed", bytes, readOk ? "no error" : ffDebugWin32Error(GetLastError()));
if (!ReadFile(hInput, buffer + bytesRead, (DWORD) (sizeof(buffer) - 1 - bytesRead), &bytes, nullptr) || bytes == 0) {
va_end(args);
return "ReadFile() failed";
}
@@ -494,7 +490,6 @@ const char* ffGetTerminalResponse(const char* request, int nParams, const char*
if (ret <= 0) {
va_end(args);
FF_DEBUG("vsscanf(buffer, format, args) failed: %s", strerror(errno));
return "vsscanf(buffer, format, args) failed";
}
if (ret >= nParams) {
-14
View File
@@ -1,6 +1,5 @@
#include "fastfetch.h"
#include "common/color.h"
#include "common/debug.h"
#include "common/jsonconfig.h"
#include "common/printing.h"
#include "common/io.h"
@@ -88,7 +87,6 @@ const char* ffJsonConfigParseEnum(yyjson_val* val, int* result, FFKeyValuePair p
}
}
FF_DEBUG("The enum value %d is not one of the accepted integers", intVal);
return "Invalid enum integer";
} else if (yyjson_is_str(val)) {
const char* strVal = yyjson_get_str(val);
@@ -99,11 +97,8 @@ const char* ffJsonConfigParseEnum(yyjson_val* val, int* result, FFKeyValuePair p
}
}
FF_DEBUG("The enum string \"%s\" is not one of the accepted names", strVal);
return "Invalid enum string";
} else {
FF_DEBUG("The enum value is of JSON type %s, not a string or an integer",
yyjson_get_type_desc(val));
return "Invalid enum value type; must be a string or integer";
}
}
@@ -278,7 +273,6 @@ static const char* printJsonConfig(FFdata* data, bool prepare) {
assert(root);
if (!yyjson_is_obj(root)) {
FF_DEBUG("The config root is of JSON type %s", yyjson_get_type_desc(root));
return "Invalid JSON config format. Root value must be an object";
}
@@ -287,7 +281,6 @@ static const char* printJsonConfig(FFdata* data, bool prepare) {
return nullptr;
}
if (!yyjson_is_arr(modules)) {
FF_DEBUG("'modules' is of JSON type %s", yyjson_get_type_desc(modules));
return "Property 'modules' must be an array of strings or objects";
}
@@ -310,7 +303,6 @@ static const char* printJsonConfig(FFdata* data, bool prepare) {
yyjson_val* conditions = yyjson_obj_get(module, "condition");
if (conditions) {
if (!yyjson_is_obj(conditions)) {
FF_DEBUG("'condition' is of JSON type %s", yyjson_get_type_desc(conditions));
return "Property 'condition' must be an object";
}
@@ -337,8 +329,6 @@ static const char* printJsonConfig(FFdata* data, bool prepare) {
yyjson_val* previousSucceeded = yyjson_obj_get(conditions, "succeeded");
if (previousSucceeded && !unsafe_yyjson_is_null(previousSucceeded)) {
if (!unsafe_yyjson_is_bool(previousSucceeded)) {
FF_DEBUG("'condition.succeeded' is of JSON type %s",
yyjson_get_type_desc(previousSucceeded));
return "Property 'succeeded' in 'condition' must be a boolean";
}
gatedOnSucceeded = true;
@@ -365,7 +355,6 @@ static const char* printJsonConfig(FFdata* data, bool prepare) {
if (gatedOnSucceeded && ffStrEqualsIgnCase(yyjson_get_str(yyjson_obj_get(module, "type")), ffCommandModuleInfo.name)) {
yyjson_val* parallel = yyjson_obj_get(module, "parallel");
if (parallel == nullptr || yyjson_get_bool(parallel)) {
FF_DEBUG("The command module at index %zu is gated on 'condition.succeeded' and runs in parallel", idx);
return "Module \"command\" cannot be combined with \"condition.succeeded\" while it runs in "
"parallel: whether it is printed is only known in the print pass, so it cannot be "
"prepared, and the prepared results are matched to modules by position. Set "
@@ -382,14 +371,12 @@ static const char* printJsonConfig(FFdata* data, bool prepare) {
type = yyjson_get_str(yyjson_obj_get(module, "type"));
if (!type) {
FF_DEBUG("The module object at index %zu has no \"type\" key, or it is not a string", idx);
return "module object must contain a \"type\" key ( case sensitive )";
}
if (yyjson_obj_size(module) == 1) { // contains only Property type
module = nullptr;
}
} else {
FF_DEBUG("The modules entry at index %zu is of JSON type %s", idx, yyjson_get_type_desc(module));
return "modules must be an array of strings or objects";
}
@@ -430,7 +417,6 @@ static const char* printJsonConfig(FFdata* data, bool prepare) {
return nullptr;
}
[[gnu::cold]]
void ffPrintJsonConfig(FFdata* data, bool prepare) {
yyjson_mut_doc* jsonDoc = data->resultDoc;
const char* error = printJsonConfig(data, prepare);
-3
View File
@@ -1,7 +1,6 @@
#if FF_HAVE_LUA
#include "common/lua.h"
#include "common/debug.h"
#include "common/mallocHelper.h"
struct FFLuaData luaData;
@@ -142,7 +141,6 @@ static int yyjsonEncode(lua_State* L) {
const char* ffLuaLoadState(void) {
if (luaData.inited) {
if (luaData.L == nullptr) {
FF_DEBUG("The Lua state was already initialised and is gone");
return "Lua library is not available";
}
return nullptr;
@@ -214,7 +212,6 @@ const char* ffLuaLoadState(void) {
lua_State* L = ffluaL_newstate();
if (L == nullptr) {
FF_DEBUG("luaL_newstate() could not allocate an interpreter");
return "luaL_newstate() failed";
}
#if LUA_VERSION_NUM >= 505
+190
View File
@@ -1,7 +1,197 @@
#include "fastfetch.h"
#include "common/library.h"
#include "common/networking.h"
#include "common/strutil.h"
#include "common/debug.h"
#ifdef FF_HAVE_ZLIB
#include <zlib.h>
struct FFZlibLibrary {
FF_LIBRARY_SYMBOL(inflateInit2_)
FF_LIBRARY_SYMBOL(inflate)
FF_LIBRARY_SYMBOL(inflateEnd)
bool inited;
} zlibData;
const char* ffNetworkingLoadZlibLibrary(void) {
if (!zlibData.inited) {
zlibData.inited = true;
FF_LIBRARY_LOAD_MESSAGE(zlib,
#ifdef _WIN32
"zlib1"
#else
"libz"
#endif
FF_LIBRARY_EXTENSION,
2)
FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(zlib, zlibData, inflateInit2_)
FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(zlib, zlibData, inflate)
FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(zlib, zlibData, inflateEnd)
zlib = nullptr; // don't auto dlclose
}
return zlibData.ffinflateEnd == nullptr ? "Failed to load libz" : nullptr;
}
// Try to pre-read gzip header to determine uncompressed size
static uint32_t guessGzipOutputSize(const void* data, uint32_t dataSize) {
// gzip file format: http://www.zlib.org/rfc-gzip.html
if (dataSize < 10 || ((const uint8_t*) data)[0] != 0x1f || ((const uint8_t*) data)[1] != 0x8b) {
return 0;
}
// Uncompressed size in gzip format is stored in the last 4 bytes, but only valid if data is less than 4GB
if (dataSize > 18) {
// Get ISIZE value from the end of file (little endian)
const uint8_t* tail = (const uint8_t*) data + dataSize - 4;
uint32_t uncompressedSize = (uint32_t) tail[0] | ((uint32_t) tail[1] << 8u) | ((uint32_t) tail[2] << 16u) | ((uint32_t) tail[3] << 24u);
// For valid gzip files, this value is the length of the uncompressed data modulo 2^32
if (uncompressedSize > 0) {
FF_DEBUG("Read uncompressed size from GZIP trailer: %u bytes", uncompressedSize);
// Add some margin to the estimated size for safety
return uncompressedSize + 64;
}
}
// If unable to get size from trailer or size is 0, use estimated value
// Typically, text data compression ratio is between 3-5x, we use the larger value
uint32_t estimatedSize = dataSize * 5;
FF_DEBUG("Unable to read exact uncompressed size, estimated as 5x of compressed data: %u bytes", estimatedSize);
return estimatedSize;
}
// Decompress gzip content
bool ffNetworkingDecompressGzip(FFstrbuf* buffer, char* headerEnd) {
// `headerEnd` itself is non-null per the `nonnull(2)` contract; what it points to is not expressible as an attribute
assert(*headerEnd == '\r');
// Calculate header size
uint32_t headerSize = (uint32_t) (headerEnd - buffer->chars);
*headerEnd = '\0'; // Replace delimiter with null character for easier processing
// Ensure Content-Encoding is in response headers, not in response body
bool hasGzip = strcasestr(buffer->chars, "\nContent-Encoding: gzip") != nullptr;
*headerEnd = '\r'; // Restore delimiter
if (!hasGzip) {
FF_DEBUG("No gzip compressed content detected, skipping decompression");
return true;
}
FF_DEBUG("Gzip compressed content detected, preparing for decompression");
const char* bodyStart = headerEnd + 4; // Skip delimiter
if (buffer->length <= headerSize + 4) {
// No content to decompress
FF_DEBUG("Compressed content size is 0, skipping decompression");
return true;
}
// Calculate compressed content size
uint32_t compressedSize = buffer->length - headerSize - 4;
// Check if content is actually in gzip format (gzip header magic is 0x1f 0x8b)
if (compressedSize < 2 || (uint8_t) bodyStart[0] != 0x1f || (uint8_t) bodyStart[1] != 0x8b) {
FF_DEBUG("Content is not valid gzip format, skipping decompression");
return false;
}
// Predict uncompressed size
uint32_t estimatedSize = guessGzipOutputSize(bodyStart, compressedSize);
// Create decompression buffer with estimated size
FF_STRBUF_AUTO_DESTROY decompressedBuffer = ffStrbufCreateA(estimatedSize > 0 ? estimatedSize : compressedSize * 5);
FF_DEBUG("Created decompression buffer: %u bytes", decompressedBuffer.allocated);
// Initialize decompression
z_stream zs = {
.zalloc = Z_NULL,
.zfree = Z_NULL,
.opaque = Z_NULL,
.avail_in = (uInt) compressedSize,
.next_in = (Bytef*) bodyStart,
.avail_out = (uInt) ffStrbufGetFree(&decompressedBuffer),
.next_out = (Bytef*) decompressedBuffer.chars,
};
// Initialize decompression engine
if (zlibData.ffinflateInit2_(&zs, 16 + MAX_WBITS, ZLIB_VERSION, (int) sizeof(z_stream)) != Z_OK) {
FF_DEBUG("Failed to initialize decompression engine");
return false;
}
uInt availableOut = zs.avail_out;
// Perform decompression
int result = zlibData.ffinflate(&zs, Z_FINISH);
// If output buffer is insufficient, try to extend buffer
while (result == Z_BUF_ERROR || (result != Z_STREAM_END && zs.avail_out == 0)) {
FF_DEBUG("Output buffer insufficient, trying to extend");
// Save already decompressed data amount
uint32_t alreadyDecompressed = (uint32_t) (availableOut - zs.avail_out);
decompressedBuffer.length += alreadyDecompressed;
decompressedBuffer.chars[decompressedBuffer.length] = '\0';
ffStrbufEnsureFree(&decompressedBuffer, decompressedBuffer.length / 2);
// Set output parameters to point to new buffer
zs.avail_out = (uInt) ffStrbufGetFree(&decompressedBuffer);
zs.next_out = (Bytef*) (decompressedBuffer.chars + decompressedBuffer.length);
availableOut = zs.avail_out;
// Decompress again
result = zlibData.ffinflate(&zs, Z_FINISH);
}
// Check for decompression errors before using result
if (result != Z_STREAM_END) {
FF_DEBUG("Decompression failed with zlib error: %d", result);
zlibData.ffinflateEnd(&zs);
return false;
}
zlibData.ffinflateEnd(&zs);
// Calculate decompressed size (from the last inflate call)
uint32_t decompressedSize = (uint32_t) (availableOut - zs.avail_out);
decompressedBuffer.length += decompressedSize;
decompressedBuffer.chars[decompressedBuffer.length] = '\0';
FF_DEBUG("Successfully decompressed %u bytes compressed data to %u bytes", compressedSize, decompressedBuffer.length);
// Modify Content-Length header and remove Content-Encoding header
// Use decompressedBuffer.length (total) not decompressedSize (last chunk only)
FF_STRBUF_AUTO_DESTROY newBuffer = ffStrbufCreateA(headerSize + decompressedBuffer.length + 64);
char* line = nullptr;
size_t len = 0;
while (ffStrbufGetline(&line, &len, buffer)) {
if (ffStrStartsWithIgnCase(line, "Content-Encoding:")) {
continue;
} else if (ffStrStartsWithIgnCase(line, "Content-Length:")) {
ffStrbufAppendF(&newBuffer, "Content-Length: %u\r\n", decompressedBuffer.length);
continue;
} else if (line[0] == '\r') {
ffStrbufAppendS(&newBuffer, "\r\n");
ffStrbufGetlineRestore(&line, &len, buffer);
break;
}
ffStrbufAppendS(&newBuffer, line); // Including the trailing \r
ffStrbufAppendC(&newBuffer, '\n');
}
ffStrbufAppend(&newBuffer, &decompressedBuffer);
ffStrbufDestroy(buffer);
ffStrbufInitMove(buffer, &newBuffer);
return true;
}
#endif // FF_HAVE_ZLIB
const char* ffNetworkingFindHeader(const char* headers, uint32_t headerEnd, const char* name, uint32_t* valueLen) {
uint32_t nameLen = (uint32_t) strlen(name);
uint32_t pos = 0;
+39 -2
View File
@@ -146,7 +146,6 @@ static const char* tryNonThreadingFastPath(FFNetworkingState* state) {
#endif
#else
FF_UNUSED(state);
FF_DEBUG("TFO support is not available");
return "TFO support is not available";
#endif
}
@@ -214,6 +213,12 @@ static const char* initNetworkingState(FFNetworkingState* state, const char* hos
}
ffStrbufAppendS(&state->command, "\r\nConnection: close\r\n"); // Explicitly tell the server we don't need to keep the connection
// If compression needs to be enabled
if (state->compression) {
FF_DEBUG("Enabling HTTP content compression");
ffStrbufAppendS(&state->command, "Accept-Encoding: gzip\r\n");
}
ffStrbufAppendS(&state->command, headers);
ffStrbufAppendS(&state->command, "\r\n");
@@ -325,6 +330,26 @@ error:
const char* ffNetworkingSendHttpRequest(FFNetworkingState* state, const char* host, uint16_t port, const char* path, const char* headers) {
FF_DEBUG("Preparing to send HTTP request: host=%s, port=%u, path=%s", host, port, path);
if (state->compression) {
FF_DEBUG("Compression enabled, checking if zlib is available");
#ifdef FF_HAVE_ZLIB
const char* zlibError = ffNetworkingLoadZlibLibrary();
// Only enable compression if zlib library is successfully loaded
if (zlibError == nullptr) {
FF_DEBUG("Successfully loaded zlib library, compression enabled");
} else {
FF_DEBUG("Failed to load zlib library, compression disabled: %s", zlibError);
state->compression = false;
}
#else
FF_DEBUG("zlib not supported at build time, compression disabled");
state->compression = false;
#endif
} else {
FF_DEBUG("Compression disabled");
}
const char* initResult = initNetworkingState(state, host, port, path, headers);
if (initResult != nullptr) {
FF_DEBUG("Initialization failed: %s", initResult);
@@ -568,7 +593,6 @@ const char* ffNetworkingRecvHttpResponse(FFNetworkingState* state, FFstrbuf* buf
}
if (chunked && !ffNetworkingDecodeChunked(buffer, &headerEnd)) {
FF_DEBUG("Failed to decode chunked response");
return "Failed to decode chunked response";
}
@@ -586,5 +610,18 @@ const char* ffNetworkingRecvHttpResponse(FFNetworkingState* state, FFstrbuf* buf
return "Content length mismatch";
}
// If compression was used, try to decompress
#ifdef FF_HAVE_ZLIB
if (state->compression) {
FF_DEBUG("Content received, checking if compressed");
if (!ffNetworkingDecompressGzip(buffer, buffer->chars + headerEnd)) {
FF_DEBUG("Decompression failed or invalid compression format");
return "Failed to decompress or invalid format";
} else {
FF_DEBUG("Decompression successful or no decompression needed, total length after decompression: %u bytes", buffer->length);
}
}
#endif
return nullptr;
}
+49 -16
View File
@@ -14,9 +14,8 @@ static LPFN_CONNECTEX ConnectEx;
static const char* initWsaData(WSADATA* wsaData) {
FF_DEBUG("Initializing WinSock");
int ret = WSAStartup(MAKEWORD(2, 2), wsaData);
if (ret != 0) {
FF_DEBUG("WSAStartup() failed: %s", ffDebugWin32Error((DWORD) ret));
if (WSAStartup(MAKEWORD(2, 2), wsaData) != 0) {
FF_DEBUG("WSAStartup() failed");
return "WSAStartup() failed";
}
@@ -29,7 +28,7 @@ static const char* initWsaData(WSADATA* wsaData) {
// Dummy socket needed for WSAIoctl
SOCKET sockfd = WSASocketW(AF_INET, SOCK_STREAM, 0, nullptr, 0, 0);
if (sockfd == INVALID_SOCKET) {
FF_DEBUG("WSASocketW(AF_INET, SOCK_STREAM) failed: %s", ffDebugWin32Error((DWORD) WSAGetLastError()));
FF_DEBUG("WSASocketW(AF_INET, SOCK_STREAM) failed");
WSACleanup();
return "WSASocketW(AF_INET, SOCK_STREAM) failed";
}
@@ -37,7 +36,7 @@ static const char* initWsaData(WSADATA* wsaData) {
DWORD dwBytes;
GUID guid = WSAID_CONNECTEX;
if (WSAIoctl(sockfd, SIO_GET_EXTENSION_FUNCTION_POINTER, &guid, sizeof(guid), &ConnectEx, sizeof(ConnectEx), &dwBytes, nullptr, nullptr) != 0) {
FF_DEBUG("WSAIoctl(sockfd, SIO_GET_EXTENSION_FUNCTION_POINTER) failed: %s", ffDebugWin32Error((DWORD) WSAGetLastError()));
FF_DEBUG("WSAIoctl(sockfd, SIO_GET_EXTENSION_FUNCTION_POINTER) failed");
closesocket(sockfd);
WSACleanup();
return "WSAIoctl(sockfd, SIO_GET_EXTENSION_FUNCTION_POINTER) failed";
@@ -52,6 +51,24 @@ static const char* initWsaData(WSADATA* wsaData) {
const char* ffNetworkingSendHttpRequest(FFNetworkingState* state, const char* host, uint16_t port, const char* path, const char* headers) {
FF_DEBUG("Preparing to send HTTP request: host=%s, port=%u, path=%s", host, port, path);
if (state->compression) {
#ifdef FF_HAVE_ZLIB
const char* zlibError = ffNetworkingLoadZlibLibrary();
// Only enable compression if zlib library is successfully loaded
if (zlibError == nullptr) {
FF_DEBUG("Successfully loaded zlib library, compression enabled");
} else {
FF_DEBUG("Failed to load zlib library, compression disabled: %s", zlibError);
state->compression = false;
}
#else
FF_DEBUG("zlib not supported at build time, compression disabled");
state->compression = false;
#endif
} else {
FF_DEBUG("Compression disabled");
}
static WSADATA wsaData;
if (wsaData.wVersion == 0) {
const char* error = initWsaData(&wsaData);
@@ -73,9 +90,8 @@ const char* ffNetworkingSendHttpRequest(FFNetworkingState* state, const char* ho
};
wchar_t hostW[256];
NTSTATUS status = RtlUTF8ToUnicodeN(hostW, (ULONG) sizeof(hostW), nullptr, host, (ULONG) strlen(host) + 1);
if (!NT_SUCCESS(status)) {
FF_DEBUG("Failed to convert host to wide string: %s: %s", host, ffDebugNtStatus(status));
if (!NT_SUCCESS(RtlUTF8ToUnicodeN(hostW, (ULONG) sizeof(hostW), nullptr, host, (ULONG) strlen(host) + 1))) {
FF_DEBUG("Failed to convert host to wide string: %s", host);
return "Failed to convert host to wide string";
}
@@ -83,15 +99,14 @@ const char* ffNetworkingSendHttpRequest(FFNetworkingState* state, const char* ho
_itow(port, portW, 10);
FF_DEBUG("Resolving address: %s:%u (%s)", host, port, state->ipv6 ? "IPv6" : "IPv4");
int ret = GetAddrInfoW(hostW, portW, &hints, &addr);
if (ret != 0) {
FF_DEBUG("GetAddrInfoW() failed: %s", ffDebugWin32Error((DWORD) ret));
if (GetAddrInfoW(hostW, portW, &hints, &addr) != 0) {
FF_DEBUG("GetAddrInfoW() failed");
return "GetAddrInfoW() failed";
}
state->sockfd = WSASocketW(addr->ai_family, addr->ai_socktype, addr->ai_protocol, nullptr, 0, 0);
if (state->sockfd == INVALID_SOCKET) {
FF_DEBUG("WSASocketW() failed: %s", ffDebugWin32Error((DWORD) WSAGetLastError()));
FF_DEBUG("WSASocketW() failed");
FreeAddrInfoW(addr);
return "WSASocketW() failed";
}
@@ -134,9 +149,8 @@ const char* ffNetworkingSendHttpRequest(FFNetworkingState* state, const char* ho
// Initialize overlapped structure with WSA event for asynchronous I/O
state->overlapped = (OVERLAPPED) {};
NTSTATUS eventStatus = NtCreateEvent(&state->overlapped.hEvent, EVENT_ALL_ACCESS, nullptr, NotificationEvent, FALSE);
if (!NT_SUCCESS(eventStatus)) {
FF_DEBUG("NtCreateEvent() failed: %s", ffDebugNtStatus(eventStatus));
if (!NT_SUCCESS(NtCreateEvent(&state->overlapped.hEvent, EVENT_ALL_ACCESS, nullptr, NotificationEvent, FALSE))) {
FF_DEBUG("NtCreateEvent() failed");
closesocket(state->sockfd);
FreeAddrInfoW(addr);
state->sockfd = INVALID_SOCKET;
@@ -162,6 +176,13 @@ const char* ffNetworkingSendHttpRequest(FFNetworkingState* state, const char* ho
ffStrbufAppendF(&state->command, ":%u", port);
}
ffStrbufAppendS(&state->command, "\r\nConnection: close\r\n"); // Explicitly request connection closure
// Add compression support if enabled
if (state->compression) {
FF_DEBUG("Enabling HTTP content compression");
ffStrbufAppendS(&state->command, "Accept-Encoding: gzip\r\n");
}
ffStrbufAppendS(&state->command, headers);
ffStrbufAppendS(&state->command, "\r\n");
@@ -396,7 +417,6 @@ const char* ffNetworkingRecvHttpResponse(FFNetworkingState* state, FFstrbuf* buf
}
if (chunked && !ffNetworkingDecodeChunked(buffer, &headerEnd)) {
FF_DEBUG("Failed to decode chunked response");
return "Failed to decode chunked response";
}
@@ -416,5 +436,18 @@ const char* ffNetworkingRecvHttpResponse(FFNetworkingState* state, FFstrbuf* buf
return "Content length mismatch";
}
// If compression was used, try to decompress
#ifdef FF_HAVE_ZLIB
if (state->compression) {
FF_DEBUG("Content received, checking if compressed");
if (!ffNetworkingDecompressGzip(buffer, buffer->chars + headerEnd)) {
FF_DEBUG("Decompression failed or invalid compression format");
return "Failed to decompress or invalid format";
} else {
FF_DEBUG("Decompression successful or no decompression needed, total length after decompression: %u bytes", buffer->length);
}
}
#endif
return nullptr;
}
-4
View File
@@ -1,5 +1,4 @@
#include "common/path.h"
#include "common/debug.h"
#include "common/io.h"
#include "common/arrutil.h"
@@ -7,7 +6,6 @@
const char* ffFindExecutableInPath(const char* name, FFstrbuf* result) {
char* path = getenv("PATH");
if (!path) {
FF_DEBUG("The PATH environment variable is not set");
return "$PATH not set";
}
@@ -52,7 +50,6 @@ const char* ffFindExecutableInPath(const char* name, FFstrbuf* result) {
return nullptr;
}
ffStrbufClear(result);
FF_DEBUG("%s is not executable in any of the %s entries", name, "PATH");
return "Executable not found";
}
#else
@@ -65,7 +62,6 @@ const char* ffFindExecutableInPath(const char* name, FFstrbuf* result) {
char buffer[MAX_PATH + 1];
DWORD length = SearchPathA(nullptr, name, ".exe", sizeof(buffer), buffer, nullptr);
if (length == 0) {
FF_DEBUG("SearchPathA(%s) failed: %s", name, ffDebugWin32Error(GetLastError()));
ffStrbufClear(result);
return "Executable not found";
}
-6
View File
@@ -1,5 +1,4 @@
#include "fastfetch.h"
#include "common/debug.h"
#include "common/percent.h"
#include "common/color.h"
#include "common/option.h"
@@ -28,8 +27,6 @@ const char* ffPercentParseTypeJsonConfig(yyjson_val* jsonVal, FFPercentageTypeFl
yyjson_arr_foreach (jsonVal, idx, max, item) {
const char* flag = yyjson_get_str(item);
if (!flag) {
FF_DEBUG("A percent.type array entry is of JSON type %s, not a string",
yyjson_get_type_desc(item));
return "Error: percent.type: invalid flag string";
}
if (ffStrEqualsIgnCase(flag, "num")) {
@@ -43,7 +40,6 @@ const char* ffPercentParseTypeJsonConfig(yyjson_val* jsonVal, FFPercentageTypeFl
} else if (ffStrEqualsIgnCase(flag, "bar-monochrome")) {
flags |= FF_PERCENTAGE_TYPE_BAR_MONOCHROME_BIT;
} else {
FF_DEBUG("Unknown percent.type flag \"%s\"", flag);
return "Error: percent.type: unknown flag string";
}
}
@@ -52,7 +48,6 @@ const char* ffPercentParseTypeJsonConfig(yyjson_val* jsonVal, FFPercentageTypeFl
return nullptr;
}
FF_DEBUG("percent.type is of JSON type %s", yyjson_get_type_desc(jsonVal));
return "Error: usage: percent.type must be a number or an array of strings";
}
@@ -290,7 +285,6 @@ bool ffPercentParseJsonObject(yyjson_val* key, yyjson_val* value, FFPercentageMo
return true;
}
[[gnu::cold]]
void ffPercentGenerateJsonConfig(yyjson_mut_doc* doc, yyjson_mut_val* module, FFPercentageModuleConfig config) {
yyjson_mut_val* percent = yyjson_mut_obj_add_obj(doc, module, "percent");
yyjson_mut_obj_add_uint(doc, percent, "green", config.green);
+14 -36
View File
@@ -1,5 +1,4 @@
#include "fastfetch.h"
#include "common/debug.h"
#include "common/processing.h"
#include "common/io.h"
#include "common/strutil.h"
@@ -43,30 +42,23 @@ extern char** environ;
enum { FF_PIPE_BUFSIZ = 8192 };
static inline int ffPipe2(int* readEnd, int* writeEnd, int flags) {
int fds[2];
static inline int ffPipe2(int* fds, int flags) {
#ifndef FF_HAVE_PIPE2
if (pipe(fds) == -1) {
return -1;
}
fcntl(fds[0], F_SETFL, fcntl(fds[0], F_GETFL) | flags);
fcntl(fds[1], F_SETFL, fcntl(fds[1], F_GETFL) | flags);
#else
if (pipe2(fds, flags) == -1) {
return -1;
}
#endif
*readEnd = fds[0];
*writeEnd = fds[1];
return 0;
#else
return pipe2(fds, flags);
#endif
}
// Not thread-safe
const char* ffProcessSpawn(char* const argv[], bool useStdErr, FFNativeFD stdinFd, FFProcessHandle* outHandle) {
FF_AUTO_CLOSE_FD int pipeRead = -1;
FF_AUTO_CLOSE_FD int pipeWrite = -1;
if (ffPipe2(&pipeRead, &pipeWrite, O_CLOEXEC) == -1) {
FF_DEBUG("pipe() failed: %s", strerror(errno));
int pipes[2];
if (ffPipe2(pipes, O_CLOEXEC) == -1) {
return "pipe() failed";
}
@@ -83,7 +75,7 @@ const char* ffProcessSpawn(char* const argv[], bool useStdErr, FFNativeFD stdinF
posix_spawn_file_actions_t file_actions;
posix_spawn_file_actions_init(&file_actions);
posix_spawn_file_actions_adddup2(&file_actions, pipeWrite, useStdErr ? STDERR_FILENO : STDOUT_FILENO);
posix_spawn_file_actions_adddup2(&file_actions, pipes[1], useStdErr ? STDERR_FILENO : STDOUT_FILENO);
posix_spawn_file_actions_adddup2(&file_actions, nullFile, useStdErr ? STDOUT_FILENO : STDERR_FILENO);
if (ffIsValidNativeFD(stdinFd)) {
posix_spawn_file_actions_adddup2(&file_actions, stdinFd, STDIN_FILENO);
@@ -131,7 +123,8 @@ const char* ffProcessSpawn(char* const argv[], bool useStdErr, FFNativeFD stdinF
posix_spawn_file_actions_destroy(&file_actions);
if (ret != 0) {
FF_DEBUG("posix_spawnp() failed: %s", strerror(ret));
close(pipes[0]);
close(pipes[1]);
if (ret == ENOENT) {
return "command not found";
}
@@ -143,13 +136,14 @@ const char* ffProcessSpawn(char* const argv[], bool useStdErr, FFNativeFD stdinF
// https://github.com/termux/termux-packages/issues/25369
childPid = fork();
if (childPid == -1) {
FF_DEBUG("fork() failed: %s", strerror(errno));
close(pipes[0]);
close(pipes[1]);
return "fork() failed";
}
if (childPid == 0) {
// Child process
dup2(pipeWrite, useStdErr ? STDERR_FILENO : STDOUT_FILENO);
dup2(pipes[1], useStdErr ? STDERR_FILENO : STDOUT_FILENO);
dup2(nullFile, useStdErr ? STDOUT_FILENO : STDERR_FILENO);
if (ffIsValidNativeFD(stdinFd)) {
dup2(stdinFd, STDIN_FILENO);
@@ -161,9 +155,9 @@ const char* ffProcessSpawn(char* const argv[], bool useStdErr, FFNativeFD stdinF
#endif
close(pipes[1]);
outHandle->pid = childPid;
outHandle->pipeRead = pipeRead;
pipeRead = -1; // ownership moved into outHandle, which ffProcessReadOutput() lets go
outHandle->pipeRead = pipes[0];
return nullptr;
}
@@ -183,7 +177,6 @@ const char* ffProcessReadOutput(FFProcessHandle* handle, FFstrbuf* buffer) {
struct pollfd pollfd = { childPipeFd, POLLIN, 0 };
int pollret = poll(&pollfd, 1, timeout);
if (pollret == 0) {
FF_DEBUG("poll(&pollfd, 1, timeout) timeout (try increasing --processing-timeout)");
kill(childPid, SIGTERM);
waitpid(childPid, nullptr, 0);
return "poll(&pollfd, 1, timeout) timeout (try increasing --processing-timeout)";
@@ -206,7 +199,6 @@ const char* ffProcessReadOutput(FFProcessHandle* handle, FFstrbuf* buffer) {
return "child process exited abnormally";
}
if (WEXITSTATUS(stat_loc) == 127) {
FF_DEBUG("command not found");
return "command not found";
}
// We only handle 127 as an error. See `getTerminalVersionUrxvt` in `terminalshell.c`
@@ -218,7 +210,6 @@ const char* ffProcessReadOutput(FFProcessHandle* handle, FFstrbuf* buffer) {
}
}
FF_DEBUG("read(childPipeFd, str, FF_PIPE_BUFSIZ) failed: %s", strerror(errno));
return "read(childPipeFd, str, FF_PIPE_BUFSIZ) failed";
}
@@ -484,7 +475,6 @@ void ffProcessGetInfoLinux(pid_t pid, FFstrbuf* processName, FFstrbuf* exe, cons
const char* ffProcessGetBasicInfoLinux(pid_t pid, FFstrbuf* name, pid_t* ppid, int32_t* tty) {
if (pid <= 0) {
FF_DEBUG("Invalid pid");
return "Invalid pid";
}
@@ -499,7 +489,6 @@ const char* ffProcessGetBasicInfoLinux(pid_t pid, FFstrbuf* name, pid_t* ppid, i
char buf[PROC_FILE_BUFFSIZ];
ssize_t nRead = ffReadFileData(procFilePath, sizeof(buf) - 1, buf);
if (nRead <= 8) {
FF_DEBUG("ffReadFileData(/proc/pid/stat, PROC_FILE_BUFFSIZ-1, buf) failed");
return "ffReadFileData(/proc/pid/stat, PROC_FILE_BUFFSIZ-1, buf) failed";
}
buf[nRead] = '\0'; // pid (comm) state ppid pgrp session tty
@@ -510,19 +499,16 @@ const char* ffProcessGetBasicInfoLinux(pid_t pid, FFstrbuf* name, pid_t* ppid, i
// comm in `/proc/pid/stat` is not encoded, and may contain ' ', ')' or even `\n`
const char* start = memchr(buf, '(', (size_t) nRead);
if (!start) {
FF_DEBUG("memchr(stat, '(') failed");
return "memchr(stat, '(') failed";
}
start++;
const char* end = memrchr(start, ')', (size_t) nRead - (size_t) (start - buf));
if (!end) {
FF_DEBUG("memrchr(stat, ')') failed");
return "memrchr(stat, ')') failed";
}
ffStrbufSetNS(name, (uint32_t) (end - start), start);
ffStrbufTrimRightSpace(name);
if (name->chars[0] == '\0') {
FF_DEBUG("process name is empty");
return "process name is empty";
}
pState = end + 2; // skip ") "
@@ -534,7 +520,6 @@ const char* ffProcessGetBasicInfoLinux(pid_t pid, FFstrbuf* name, pid_t* ppid, i
{
int ppid_, tty_;
if (sscanf(pState + 2, "%d %*d %*d %d", &ppid_, &tty_) < 2) {
FF_DEBUG("sscanf(stat) failed");
return "sscanf(stat) failed";
}
@@ -551,7 +536,6 @@ const char* ffProcessGetBasicInfoLinux(pid_t pid, FFstrbuf* name, pid_t* ppid, i
snprintf(procFilePath, sizeof(procFilePath), "/proc/%d/comm", (int) pid);
ssize_t nRead = ffReadFileBuffer(procFilePath, name);
if (nRead <= 0) {
FF_DEBUG("ffReadFileBuffer(/proc/pid/comm, name) failed");
return "ffReadFileBuffer(/proc/pid/comm, name) failed";
}
ffStrbufTrimRightSpace(name);
@@ -564,7 +548,6 @@ const char* ffProcessGetBasicInfoLinux(pid_t pid, FFstrbuf* name, pid_t* ppid, i
size_t size = sizeof(proc);
if (sysctl(
(int[]) { CTL_KERN, KERN_PROC, KERN_PROC_PID, pid }, 4, &proc, &size, nullptr, 0)) {
FF_DEBUG("sysctl(KERN_PROC_PID) failed: %s", strerror(errno));
return "sysctl(KERN_PROC_PID) failed";
}
@@ -591,7 +574,6 @@ const char* ffProcessGetBasicInfoLinux(pid_t pid, FFstrbuf* name, pid_t* ppid, i
size_t size = sizeof(proc);
if (sysctl(
(int[]) { CTL_KERN, KERN_PROC, KERN_PROC_PID, pid }, 4, &proc, &size, nullptr, 0)) {
FF_DEBUG("sysctl(KERN_PROC_PID) failed: %s", strerror(errno));
return "sysctl(KERN_PROC_PID) failed";
}
@@ -618,7 +600,6 @@ const char* ffProcessGetBasicInfoLinux(pid_t pid, FFstrbuf* name, pid_t* ppid, i
size_t size = sizeof(proc);
if (sysctl(
(int[]) { CTL_KERN, KERN_PROC2, KERN_PROC_PID, pid, sizeof(proc), 1 }, 6, &proc, &size, nullptr, 0) != 0) {
FF_DEBUG("sysctl(KERN_PROC_PID) failed: %s", strerror(errno));
return "sysctl(KERN_PROC_PID) failed";
}
@@ -644,7 +625,6 @@ const char* ffProcessGetBasicInfoLinux(pid_t pid, FFstrbuf* name, pid_t* ppid, i
snprintf(path, sizeof(path), "/proc/%d/psinfo", (int) pid);
psinfo_t proc;
if (ffReadFileData(path, sizeof(proc), &proc) != sizeof(proc)) {
FF_DEBUG("ffReadFileData(psinfo) failed");
return "ffReadFileData(psinfo) failed";
}
@@ -670,7 +650,6 @@ const char* ffProcessGetBasicInfoLinux(pid_t pid, FFstrbuf* name, pid_t* ppid, i
*tty = (int) proc.p_tdev;
}
} else {
FF_DEBUG("sysctl(KERN_PROC_PID) failed: %s", strerror(errno));
return "sysctl(KERN_PROC_PID) failed";
}
@@ -688,7 +667,6 @@ const char* ffProcessGetBasicInfoLinux(pid_t pid, FFstrbuf* name, pid_t* ppid, i
#else
FF_DEBUG("Unsupported platform");
return "Unsupported platform";
#endif
+4 -18
View File
@@ -1,5 +1,4 @@
#include "fastfetch.h"
#include "common/debug.h"
#include "common/mallocHelper.h"
#include "common/processing.h"
#include "common/io.h"
@@ -101,7 +100,6 @@ const char* ffProcessSpawn(char* const argv[], bool useStdErr, FFNativeFD stdinF
0,
nullptr);
if (hChildPipeRead == INVALID_HANDLE_VALUE) {
FF_DEBUG("CreateNamedPipeW() failed: %s", ffDebugWin32Error(GetLastError()));
return "CreateNamedPipeW(L\"\\\\.\\pipe\\FASTFETCH-$(PID)\") failed";
}
@@ -118,7 +116,6 @@ const char* ffProcessSpawn(char* const argv[], bool useStdErr, FFNativeFD stdinF
0,
nullptr);
if (hChildPipeWrite == INVALID_HANDLE_VALUE) {
FF_DEBUG("CreateFileW() failed: %s", ffDebugWin32Error(GetLastError()));
return "CreateFileW(L\"\\\\.\\pipe\\FASTFETCH-$(PID)\") failed";
}
@@ -144,9 +141,7 @@ const char* ffProcessSpawn(char* const argv[], bool useStdErr, FFNativeFD stdinF
argvToCmdline(argv, &buf);
uint32_t cmdlineBytes = (buf.length + 1) * sizeof(wchar_t);
cmdline = malloc(cmdlineBytes);
NTSTATUS status = RtlUTF8ToUnicodeN(cmdline, cmdlineBytes, nullptr, buf.chars, buf.length + 1);
if (!NT_SUCCESS(status)) {
FF_DEBUG("RtlUTF8ToUnicodeN() failed: %s", ffDebugNtStatus(status));
if (!NT_SUCCESS(RtlUTF8ToUnicodeN(cmdline, cmdlineBytes, nullptr, buf.chars, buf.length + 1))) {
return "RtlUTF8ToUnicodeN() failed";
}
}
@@ -170,7 +165,6 @@ const char* ffProcessSpawn(char* const argv[], bool useStdErr, FFNativeFD stdinF
if (GetLastError() == ERROR_FILE_NOT_FOUND) {
return "command not found";
}
FF_DEBUG("CreateProcessW() failed: %s", ffDebugWin32Error(GetLastError()));
return "CreateProcessW() failed";
}
@@ -203,9 +197,7 @@ const char* ffProcessReadOutput(FFProcessHandle* handle, FFstrbuf* buffer) {
handle->pid = INVALID_HANDLE_VALUE;
handle->pipeRead = INVALID_HANDLE_VALUE;
NTSTATUS createStatus = STATUS_SUCCESS;
if (timeout >= 0 && !NT_SUCCESS(createStatus = NtCreateEvent(&hReadEvent, EVENT_ALL_ACCESS, nullptr, SynchronizationEvent, FALSE))) {
FF_DEBUG("NtCreateEvent() failed: %s", ffDebugNtStatus(createStatus));
if (timeout >= 0 && !NT_SUCCESS(NtCreateEvent(&hReadEvent, EVENT_ALL_ACCESS, nullptr, SynchronizationEvent, FALSE))) {
return "NtCreateEvent() failed";
}
@@ -224,8 +216,7 @@ const char* ffProcessReadOutput(FFProcessHandle* handle, FFstrbuf* buffer) {
nullptr,
nullptr);
if (status == STATUS_PENDING) {
NTSTATUS waitStatus = NtWaitForSingleObject(hReadEvent, FALSE, &(LARGE_INTEGER) { .QuadPart = (int64_t) timeout * -10000 });
switch (waitStatus) {
switch (NtWaitForSingleObject(hReadEvent, FALSE, &(LARGE_INTEGER) { .QuadPart = (int64_t) timeout * -10000 })) {
case STATUS_WAIT_0:
status = iosb.Status;
break;
@@ -236,7 +227,6 @@ const char* ffProcessReadOutput(FFProcessHandle* handle, FFstrbuf* buffer) {
}
default:
FF_DEBUG("NtWaitForSingleObject(hReadEvent) failed: %s", ffDebugNtStatus(waitStatus));
terminateChildProcess(hProcess, hChildPipeRead, hReadEvent, &iosb);
return "NtWaitForSingleObject(hReadEvent) failed";
}
@@ -247,7 +237,6 @@ const char* ffProcessReadOutput(FFProcessHandle* handle, FFstrbuf* buffer) {
}
if (!NT_SUCCESS(status)) {
FF_DEBUG("NtReadFile(hChildPipeRead) failed: %s", ffDebugNtStatus(status));
terminateChildProcess(hProcess, hChildPipeRead, nullptr, &iosb);
return "NtReadFile(hChildPipeRead) failed";
}
@@ -259,15 +248,12 @@ const char* ffProcessReadOutput(FFProcessHandle* handle, FFstrbuf* buffer) {
exit: {
PROCESS_BASIC_INFORMATION info = {};
ULONG size;
NTSTATUS status = NtQueryInformationProcess(hProcess, ProcessBasicInformation, &info, sizeof(info), &size);
if (NT_SUCCESS(status)) {
if (NT_SUCCESS(NtQueryInformationProcess(hProcess, ProcessBasicInformation, &info, sizeof(info), &size))) {
assert(size == sizeof(info));
if (info.ExitStatus != STILL_ACTIVE && info.ExitStatus != 0) {
FF_DEBUG("Child process exited with an error");
return "Child process exited with an error";
}
} else {
FF_DEBUG("NtQueryInformationProcess(ProcessBasicInformation) failed: %s", ffDebugNtStatus(status));
return "NtQueryInformationProcess(ProcessBasicInformation) failed";
}
}
-7
View File
@@ -1,22 +1,17 @@
#include "common/sysctl.h"
#include "common/debug.h"
#include <errno.h>
#include <stdlib.h>
#include <string.h>
#ifdef __OpenBSD__
const char* ffSysctlGetString(int mib1, int mib2, FFstrbuf* result) {
size_t neededLength;
if (sysctl((int[]) { mib1, mib2 }, 2, nullptr, &neededLength, nullptr, 0) != 0 || neededLength == 1) { // neededLength is 1 for empty strings, because of the null terminator
FF_DEBUG("sysctl({%d, %d}) length query failed: %s", mib1, mib2, strerror(errno));
return "sysctl() length query failed";
}
ffStrbufEnsureFree(result, (uint32_t) neededLength - 1);
if (sysctl((int[]) { mib1, mib2 }, 2, result->chars + result->length, &neededLength, nullptr, 0) != 0) {
FF_DEBUG("sysctl({%d, %d}) failed to retrieve %zu bytes: %s", mib1, mib2, neededLength, strerror(errno));
return "sysctl() failed to retrieve string data";
}
@@ -67,14 +62,12 @@ int64_t ffSysctlGetInt64(int mib1, int mib2, int64_t defaultValue) {
const char* ffSysctlGetString(const char* propName, FFstrbuf* result) {
size_t neededLength;
if (sysctlbyname(propName, nullptr, &neededLength, nullptr, 0) != 0 || neededLength == 1) { // neededLength is 1 for empty strings, because of the null terminator
FF_DEBUG("sysctlbyname(%s) length query failed: %s", propName, strerror(errno));
return "sysctlbyname() failed";
}
ffStrbufEnsureFree(result, (uint32_t) neededLength - 1);
if (sysctlbyname(propName, result->chars + result->length, &neededLength, nullptr, 0) != 0) {
FF_DEBUG("sysctlbyname(%s) failed to retrieve %zu bytes: %s", propName, neededLength, strerror(errno));
return "sysctlbyname() failed to retrieve string data";
}
-1
View File
@@ -154,7 +154,6 @@ bool ffTempsParseJsonObject(yyjson_val* key, yyjson_val* value, bool* useTemp, F
return true;
}
[[gnu::cold]]
void ffTempsGenerateJsonConfig(yyjson_mut_doc* doc, yyjson_mut_val* module, bool temp, FFColorRangeConfig config) {
if (!temp) {
yyjson_mut_obj_add_bool(doc, module, "temp", false);
+1
View File
@@ -25,6 +25,7 @@ typedef struct FFNetworkingState {
FFstrbuf command;
uint32_t timeout;
bool ipv6;
bool compression; // if true, HTTP content compression will be enabled if supported
bool tfo; // if true, TCP Fast Open will be attempted first, and fallback to traditional connection if it fails
} FFNetworkingState;
+17 -18
View File
@@ -21,24 +21,23 @@ typedef struct FFModuleFormatArgList {
typedef struct FFModuleDisplayName {
const char* en; // English
const char* ar; // Arabic
const char* cs; // Czech
const char* de; // German
const char* es; // Spanish
const char* fi; // Finnish
const char* fr; // French
const char* gl; // Galician
const char* he; // Hebrew
const char* id; // Indonesian
const char* it; // Italian
const char* ja; // Japanese
const char* ko; // Korean
const char* pl; // Polish
const char* pt; // (Brazilian) Portuguese
const char* ru; // Russian
const char* tr; // Turkish
const char* uk; // Ukrainian
const char* vi; // Vietnamese
const char* ar; // Arabic
const char* cs; // Czech
const char* de; // German
const char* es; // Spanish
const char* fr; // French
const char* gl; // Galician
const char* he; // Hebrew
const char* id; // Indonesian
const char* it; // Italian
const char* ja; // Japanese
const char* ko; // Korean
const char* pl; // Polish
const char* pt; // (Brazilian) Portuguese
const char* ru; // Russian
const char* tr; // Turkish
const char* uk; // Ukrainian
const char* vi; // Vietnamese
const char* zh_CN; // Simplified Chinese
const char* zh_TW; // Traditional Chinese
} FFModuleDisplayName;
+1 -1
View File
@@ -1,6 +1,6 @@
#pragma once
#include "fastfetch.h"
#include "common/FFstrbuf.h"
// Not `pure`: it trims the trailing spaces of `value` before reporting whether it is set
[[gnu::nonnull(1), nodiscard]] bool ffIsSmbiosValueSet(FFstrbuf* value);
+8 -32
View File
@@ -1,34 +1,10 @@
#!/bin/sh
# Convert logo.svg to logo.ico.
#
# Every frame is stored as PNG. ImageMagick's ICO writer only uses PNG for frames that are
# 256x256 or larger and falls back to an uncompressed DIB for everything smaller, which
# inflates the icon from ~19 KB to ~108 KB (and to ~370 KB on ImageMagick 7, which also
# stores 256x256 as a DIB). Packing the frames here keeps the icon small and predictable.
set -e
cd "$(dirname "$0")"
for size in 16 32 48 64 128 256; do
rsvg-convert -w "$size" -h "$size" ../../logo/logo.svg > "logo$size.png"
done
python3 - <<'PY'
import struct
sizes = [16, 32, 48, 64, 128, 256]
frames = [open(f"logo{s}.png", "rb").read() for s in sizes]
header = struct.pack("<HHH", 0, 1, len(sizes))
offset = 6 + 16 * len(sizes)
entries = b""
for size, frame in zip(sizes, frames):
dim = 0 if size == 256 else size # 0 means 256 in the ICO directory
entries += struct.pack("<BBBBHHII", dim, dim, 0, 0, 1, 32, len(frame), offset)
offset += len(frame)
with open("logo.ico", "wb") as f:
f.write(header + entries + b"".join(frames))
PY
# Convert logo.svg to logo.ico
rsvg-convert -w 16 -h 16 logo.svg > logo16.png
rsvg-convert -w 32 -h 32 logo.svg > logo32.png
rsvg-convert -w 48 -h 48 logo.svg > logo48.png
rsvg-convert -w 64 -h 64 logo.svg > logo64.png
rsvg-convert -w 128 -h 128 logo.svg > logo128.png
rsvg-convert -w 256 -h 256 logo.svg > logo256.png
convert logo16.png logo32.png logo48.png logo64.png logo128.png logo256.png logo.ico
rm logo16.png logo32.png logo48.png logo64.png logo128.png logo256.png
-8
View File
@@ -1,5 +1,4 @@
#include "com.h"
#include "common/debug.h"
#include <stdlib.h>
@@ -15,19 +14,15 @@ static const char* doInitCom() {
if (FAILED(res)) {
switch (res) {
case E_INVALIDARG:
FF_DEBUG("RoInitialize() failed: invalid argument: 0x%08lX (%s)", res, ffDebugHResult(res));
return "RoInitialize() failed: invalid argument";
case E_OUTOFMEMORY:
FF_DEBUG("RoInitialize() failed: out of memory: 0x%08lX (%s)", res, ffDebugHResult(res));
return "RoInitialize() failed: out of memory";
case E_UNEXPECTED:
FF_DEBUG("RoInitialize() failed: unexpected error: 0x%08lX (%s)", res, ffDebugHResult(res));
return "RoInitialize() failed: unexpected error";
case RPC_E_CHANGED_MODE:
// COM was already initialized with a different concurrency model
return nullptr;
default:
FF_DEBUG("RoInitialize() failed: unknown error: 0x%08lX (%s)", res, ffDebugHResult(res));
return "RoInitialize() failed: unknown error";
}
}
@@ -47,16 +42,13 @@ static const char* doInitCom() {
if (FAILED(res)) {
switch (res) {
case E_INVALIDARG:
FF_DEBUG("CoInitializeEx() failed: invalid argument: 0x%08lX (%s)", res, ffDebugHResult(res));
return "CoInitializeEx() failed: invalid argument";
case E_OUTOFMEMORY:
FF_DEBUG("CoInitializeEx() failed: out of memory: 0x%08lX (%s)", res, ffDebugHResult(res));
return "CoInitializeEx() failed: out of memory";
case RPC_E_CHANGED_MODE:
// COM was already initialized with a different concurrency model
return nullptr;
default:
FF_DEBUG("CoInitializeEx() failed: unknown error: 0x%08lX (%s)", res, ffDebugHResult(res));
return "CoInitializeEx() failed: unknown error";
}
}
-237
View File
@@ -1,237 +0,0 @@
#include "folders.h"
#include "common/debug.h"
#include "common/mallocHelper.h"
#include "common/windows/nt.h"
#include "common/windows/registry.h"
#include "common/windows/unicode.h"
#include <ntstatus.h>
// Where the shell keeps the values SHGetKnownFolderPath() would compute.
//
// `User Shell Folders` is the store folder redirection writes to -- HKCU for the user's own
// folders, HKLM for the machine-wide ones -- and it holds the *unexpanded* form, which is why the
// environment expansion below is not optional. `ProfileList\<SID>\ProfileImagePath` is what the
// logon session recorded as the profile directory, and is read by ffGetProfilePath().
#define FF_FOLDERS_HKCU_SHELL_FOLDERS L"Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\User Shell Folders"
#define FF_FOLDERS_HKLM_SHELL_FOLDERS L"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Explorer\\User Shell Folders"
#define FF_FOLDERS_HKLM_CURRENT_VERSION L"SOFTWARE\\Microsoft\\Windows\\CurrentVersion"
#define FF_FOLDERS_HKLM_PROFILE_LIST L"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\ProfileList"
// The environment fallbacks. Two of the registry values above are self-referential -- `Common
// AppData` is literally `%ProgramData%` -- so these are the same answer, not a different one.
#define FF_FOLDERS_ENV_LOCAL_APP_DATA L"%LOCALAPPDATA%"
#define FF_FOLDERS_ENV_ROAMING_APP_DATA L"%APPDATA%"
#define FF_FOLDERS_ENV_PROGRAM_DATA L"%ProgramData%"
#define FF_FOLDERS_ENV_PROGRAM_FILES L"%ProgramFiles%"
// Every folder is resolved once and then kept. ffGetKnownFolderPath() is called about a dozen times
// per process -- getCacheDir, getConfigDirs and getDataDirs all ask for the same folders -- and
// each answer is a pure function of the registry and this process' environment. `states` separates
// "not looked up yet" from "looked up and not there", which the path itself cannot: an empty
// FFstrbuf is a valid answer.
//
// No lock guards this: the only thread fastfetch ever creates is the networking module's
// asynchronous connect, and no module that resolves a known folder runs on it.
typedef enum FFFolderState : uint8_t {
FF_FOLDER_UNRESOLVED,
FF_FOLDER_FOUND,
FF_FOLDER_MISSING,
} FFFolderState;
static FFstrbuf folderPaths[FF_KNOWN_FOLDER_COUNT];
static FFFolderState folderStates[FF_KNOWN_FOLDER_COUNT];
// Expands `%NAME%` placeholders from this process' environment block.
//
// The work is done in UTF-16 so that a user name outside the ANSI code page survives: getenv()
// goes through the CRT's narrow copy of the environment and would mangle it.
//
// RtlExpandEnvironmentStrings() substitutes nothing and still reports success for a variable it
// cannot find (measured), so a leftover '%' is treated as a failure. A directory name that
// legitimately contains one is a false negative, and the only thing that costs is the caller's
// fallback.
static bool expandEnv(const wchar_t* source, uint32_t sourceLen, FFstrbuf* result) {
// Asking for the required length is done with a null destination, which the call reports as
// STATUS_BUFFER_TOO_SMALL -- a *failure* status that still fills in `size`. The status is
// therefore deliberately not checked here; a call that writes nothing leaves `size` at zero.
SIZE_T size = 0; // in characters, including the null terminator
(void) RtlExpandEnvironmentStrings(nullptr, source, sourceLen, nullptr, 0, &size);
if (size == 0) {
return false;
}
FF_AUTO_FREE wchar_t* expanded = (wchar_t*) malloc(size * sizeof(wchar_t));
SIZE_T written = 0;
if (!NT_SUCCESS(RtlExpandEnvironmentStrings(nullptr, source, sourceLen, expanded, size, &written)) || written == 0) {
return false;
}
if (wcschr(expanded, L'%')) {
return false;
}
ffStrbufSetNWS(result, (uint32_t) (written - 1), expanded);
return true;
}
static bool expandEnvZ(const wchar_t* source, FFstrbuf* result) {
return expandEnv(source, (uint32_t) wcslen(source), result);
}
// `raw` is the payload of a REG_SZ / REG_EXPAND_SZ value: UTF-16, usually null terminated.
static bool expandBuffer(const FFArgBuffer* raw, FFstrbuf* result) {
uint32_t length = raw->length / sizeof(wchar_t);
if (length == 0) {
return false;
}
const wchar_t* valueW = (const wchar_t*) raw->data;
if (valueW[length - 1] == L'\0') {
--length;
}
return length > 0 && expandEnv(valueW, length, result);
}
// FF_ARG_TYPE_BUFFER rather than FF_ARG_TYPE_STRBUF, because the value has to stay UTF-16 for
// expandEnv(); the strbuf flavour converts it to UTF-8 on the way out. The buffer flavour does not
// report the registry type either, so REG_SZ and REG_EXPAND_SZ are not told apart -- expanding a
// string without placeholders is a no-op, which is what makes that acceptable.
static bool readExpandedValue(HANDLE hKey, const wchar_t* valueNameW, FFstrbuf* result) {
FFArgBuffer raw = {};
if (!ffRegReadData(hKey, valueNameW, &raw, nullptr)) {
return false;
}
FF_AUTO_FREE void* data = raw.data;
return expandBuffer(&raw, result);
}
static bool readExpandedValueFrom(HKEY hRootKey, const wchar_t* subKeyW, const wchar_t* valueNameW, FFstrbuf* result) {
FF_AUTO_CLOSE_FD HANDLE hKey = nullptr;
if (!ffRegOpenKeyForRead(hRootKey, subKeyW, &hKey, nullptr)) {
return false;
}
return readExpandedValue(hKey, valueNameW, result);
}
// The SID comes from the caller rather than from a token query here: FFPlatform_windows.c already
// resolves it for `platform->sid`, and the two are the same SID by definition.
bool ffGetProfilePath(const wchar_t* sidW, FFstrbuf* result) {
assert(sidW);
FF_AUTO_CLOSE_FD HANDLE hProfileList = nullptr;
if (!ffRegOpenKeyForRead(HKEY_LOCAL_MACHINE, FF_FOLDERS_HKLM_PROFILE_LIST, &hProfileList, nullptr)) {
FF_DEBUG("Cannot open %ls", FF_FOLDERS_HKLM_PROFILE_LIST);
return false;
}
FF_AUTO_CLOSE_FD HANDLE hProfile = nullptr;
if (!ffRegOpenSubkeyForRead(hProfileList, sidW, &hProfile, nullptr)) {
FF_DEBUG("No profile is registered for %ls", sidW);
return false;
}
return readExpandedValue(hProfile, L"ProfileImagePath", result);
}
// `Local AppData` and `AppData` share a key, so they are read together.
//
// The names have to be exact, spaces and all: `Local AppData` is `Local`, one space, `AppData` --
// there is no `Local App Data`, and asking for it fails the whole batch below.
static const struct {
FFKnownFolder folder;
const wchar_t* valueNameW;
const wchar_t* envW;
} shellFolderEntries[] = {
{ FF_KNOWN_FOLDER_LOCAL_APP_DATA, L"Local AppData", FF_FOLDERS_ENV_LOCAL_APP_DATA },
{ FF_KNOWN_FOLDER_ROAMING_APP_DATA, L"AppData", FF_FOLDERS_ENV_ROAMING_APP_DATA },
};
// One open plus one NtQueryMultipleValueKey resolves both -- and whichever of the two is asked for
// first caches the pair, which is what the callers want anyway.
//
// NtQueryMultipleValueKey is all-or-nothing: one absent name fails the batch, which would push
// *both* folders to the environment fallback. That is survivable, because these values are
// themselves just `%LOCALAPPDATA%` and `%APPDATA%` expanded -- but it is also invisible, and an
// invisible fallback is how a wrong value name went unnoticed here. So a failed batch is retried
// one value at a time, and the extra queries are only paid when the batch has already failed.
static void resolveUserShellFolders(void) {
FFArgBuffer buffers[ARRAY_SIZE(shellFolderEntries)] = {};
FFRegValueArg args[ARRAY_SIZE(shellFolderEntries)];
for (uint32_t i = 0; i < ARRAY_SIZE(shellFolderEntries); ++i) {
args[i] = (FFRegValueArg) { .type = FF_ARG_TYPE_BUFFER, .value = &buffers[i], .name = shellFolderEntries[i].valueNameW };
}
FF_AUTO_CLOSE_FD HANDLE hKey = nullptr;
if (ffRegOpenKeyForRead(HKEY_CURRENT_USER, FF_FOLDERS_HKCU_SHELL_FOLDERS, &hKey, nullptr)) {
if (ffRegReadValues(hKey, ARRAY_SIZE(shellFolderEntries), args, nullptr)) {
for (uint32_t i = 0; i < ARRAY_SIZE(shellFolderEntries); ++i) {
if (expandBuffer(&buffers[i], &folderPaths[shellFolderEntries[i].folder])) {
folderStates[shellFolderEntries[i].folder] = FF_FOLDER_FOUND;
}
}
} else {
FF_DEBUG("Batched read of %ls failed; retrying one value at a time", FF_FOLDERS_HKCU_SHELL_FOLDERS);
for (uint32_t i = 0; i < ARRAY_SIZE(shellFolderEntries); ++i) {
if (readExpandedValue(hKey, shellFolderEntries[i].valueNameW, &folderPaths[shellFolderEntries[i].folder])) {
folderStates[shellFolderEntries[i].folder] = FF_FOLDER_FOUND;
}
}
}
}
for (uint32_t i = 0; i < ARRAY_SIZE(shellFolderEntries); ++i) {
// Every buffer has to be freed even when the read failed part way through, which is why the
// free is not tied to the success path above.
free(buffers[i].data);
FFKnownFolder folder = shellFolderEntries[i].folder;
if (folderStates[folder] == FF_FOLDER_UNRESOLVED &&
expandEnvZ(shellFolderEntries[i].envW, &folderPaths[folder])) {
folderStates[folder] = FF_FOLDER_FOUND;
}
if (folderStates[folder] == FF_FOLDER_UNRESOLVED) {
folderStates[folder] = FF_FOLDER_MISSING;
}
}
}
static bool resolveFolder(FFKnownFolder folder) {
switch (folder) {
case FF_KNOWN_FOLDER_LOCAL_APP_DATA:
case FF_KNOWN_FOLDER_ROAMING_APP_DATA:
resolveUserShellFolders(); // Resolves both of them
return folderStates[folder] == FF_FOLDER_FOUND;
case FF_KNOWN_FOLDER_PROGRAM_DATA:
return readExpandedValueFrom(HKEY_LOCAL_MACHINE, FF_FOLDERS_HKLM_SHELL_FOLDERS, L"Common AppData", &folderPaths[folder]) ||
expandEnvZ(FF_FOLDERS_ENV_PROGRAM_DATA, &folderPaths[folder]);
case FF_KNOWN_FOLDER_PROGRAM_FILES:
return readExpandedValueFrom(HKEY_LOCAL_MACHINE, FF_FOLDERS_HKLM_CURRENT_VERSION, L"ProgramFilesDir", &folderPaths[folder]) ||
expandEnvZ(FF_FOLDERS_ENV_PROGRAM_FILES, &folderPaths[folder]);
default:
assert(false);
return false;
}
}
bool ffGetKnownFolderPath(FFKnownFolder folder, FFstrbuf* result) {
assert(result);
assert(folder < FF_KNOWN_FOLDER_COUNT);
if (folderStates[folder] == FF_FOLDER_UNRESOLVED) {
folderStates[folder] = resolveFolder(folder) ? FF_FOLDER_FOUND : FF_FOLDER_MISSING;
}
if (folderStates[folder] != FF_FOLDER_FOUND) {
return false;
}
ffStrbufSet(result, &folderPaths[folder]);
return true;
}
-47
View File
@@ -1,47 +0,0 @@
#pragma once
#include "fastfetch.h"
#include "common/io.h"
#include <wchar.h> // ffGetProfilePath() takes a SID as a wide string
// The known folders fastfetch needs. They are spelled out instead of taking a REFKNOWNFOLDERID so
// that this header does not have to drag in <shlobj.h> -- the call sites include it for reasons of
// their own, and a header that only works when <windows.h> came first is a trap.
typedef enum FFKnownFolder : uint8_t {
FF_KNOWN_FOLDER_LOCAL_APP_DATA,
FF_KNOWN_FOLDER_ROAMING_APP_DATA,
FF_KNOWN_FOLDER_PROGRAM_DATA,
FF_KNOWN_FOLDER_PROGRAM_FILES,
FF_KNOWN_FOLDER_COUNT,
} FFKnownFolder;
// Resolves a known folder without initializing the shell.
//
// The first SHGetKnownFolderPath() call in a process costs ~2.4 ms (measured on Windows 10 22H2):
// the shell's known-folder machinery is built on demand, and whichever folder is asked for first
// pays for it. Every later call is free, so a process that needs a handful of folders pays that
// price once and can never amortize it. Each folder below is instead one registry value or one
// environment variable away -- ~0.03 ms for all of them together -- so the shell is never touched.
//
// The answer is cached: these five folders are asked for about a dozen times per process, and each
// answer is a pure function of the registry and this process' environment. That is safe without a
// lock, because nothing that resolves a known folder ever runs on the one thread fastfetch creates.
//
// Returns false, leaving `result` untouched, when the value is missing or unusable, so that the
// caller keeps whatever fallback it had for the shell API failing. The path uses backslashes,
// exactly like SHGetKnownFolderPath().
bool ffGetKnownFolderPath(FFKnownFolder folder, FFstrbuf* result);
// Resolves the profile directory recorded for `sidW`, a SID string as produced by
// RtlConvertSidToUnicodeString().
//
// There is no FF_KNOWN_FOLDER_PROFILE because the caller already has the SID:
// FFPlatform_windows.c resolves it once for `platform->sid`, and deriving it a second time here
// would mean a second NtQueryInformationToken(). The answer is deliberately not cached -- this is
// called once per process, and from one place.
//
// Returns false, leaving `result` untouched, when the value is missing or unusable, so that the
// caller keeps whatever fallback it had for the shell API failing. The path uses backslashes,
// exactly like SHGetKnownFolderPath().
bool ffGetProfilePath(const wchar_t* sidW, FFstrbuf* result);
Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

After

Width:  |  Height:  |  Size: 105 KiB

+1
View File
@@ -0,0 +1 @@
../../logo/logo.svg
+215 -456
View File
@@ -2,119 +2,35 @@
#include "battery.h"
#include "common/android/api.h"
#include "common/android/binder.h"
#include "common/android/dex.h"
#include "common/debug.h"
#include "common/io.h"
#include "common/processing.h"
#include "common/properties.h"
#include "common/settings.h"
#include "common/strutil.h"
#include <dirent.h>
#include <errno.h>
#include <fcntl.h>
#include <inttypes.h>
#include <string.h>
// Android exposes the battery through BatteryManager, which the NDK does not wrap and whose sysfs
// backing is unreadable from an app UID -- /sys/class/power_supply is closed to untrusted_app
// outright, directory and all. What does work without any permission is
// android.os.IBatteryPropertiesRegistrar in system_server, over /dev/binder (see
// common/android/binder.h).
// backing is unreadable from an app UID. What does work without any permission is
// android.os.IBatteryPropertiesRegistrar in system_server, reachable either over /dev/binder (see
// common/android/binder.h) or through a `dumpsys battery` subprocess. The binder route is the cheap
// one: ~0.3 ms for both properties, and no child process.
//
// `dumpsys battery` is the other route, tried first for shell and root because it is the richer of
// the two: a flat `key: value` list read by name, so it carries what neither the registrar nor a
// property has -- the technology and the critical capacity level -- and neither a vendor that prints
// extra keys in the middle nor a release that appends a field can break it. The binder reply is
// positional and breaks silently instead. What it costs is a fork/exec, which is why it is only
// reached where it can succeed; see the UID gate in ffDetectBattery.
//
// An app UID gets none of that, and not because Android withholds it. `ACTION_BATTERY_CHANGED` is a
// sticky broadcast, and a sticky broadcast is read without a permission: it carries the technology,
// and a cycle count since Android 14 -- the device's own BatteryManager declares `EXTRA_CYCLE_COUNT`,
// spelled "android.os.extra.CYCLE_COUNT". What is out of reach is the call that fetches it. A sticky
// broadcast is read by `registerReceiver`, the NDK does not wrap it, and a native binary has no Java
// side to make that call from, so every route to it is a Java process. termux-api is one, and it is
// not the answer: on the device this was written on it hung on six of six runs, each killed at 30
// seconds having written nothing, after exec'ing `am broadcast` and waiting on a socket that has no
// timeout of its own.
//
// What is left within reach is a pair of system properties BatteryService mirrors out of every update
// it processes: `debug.tracing.plug_type` and `debug.tracing.battery_status`. A system property is
// world readable, which is the whole point of them here -- the charger bits used to need `dumpsys
// battery`, and so the shell UID, while the status used to cost a second transaction. Both are now
// free, to any UID; see the two defines below.
//
// The temperature comes from neither service. The kernel publishes it as a thermal zone, and
// /sys/class/thermal is readable from an app UID -- unlike /sys/class/power_supply, which is closed
// outright, directory and all -- so it is read there whenever `temp` asks for it. The dump has a
// `temperature` line of its own and that one wins where the dump runs; see `detectTemperature` below
// for why the zone is looked up by name rather than addressed by number.
//
// The registrar answers nothing else. `BatteryProperty` has no temperature, no technology and no
// cycle count, and manufacturer and model name are not battery data at all. Of the properties that
// do exist, ids 7 to 12 -- the ones Android 15 added -- are behind BATTERY_STATS, and ids 1 to 6 are
// not. The one reading the module wants that is not a property either, how long the charge will
// last, comes from a different service; see the `batterystats` block below.
// The registrar only answers properties that do not need BATTERY_STATS, and that rules out
// everything the module would otherwise like to show. Manufacturer, model name, technology, serial
// number, manufacture date, temperature and cycle count all sit behind that permission.
// The reply layout is positional and is not negotiated with the service, so it only stays correct as
// long as AOSP keeps the order it already has. It has, because `BatteryProperty` has only ever grown
// at the end -- API 35 appended [string8 mValueString] to it -- so the field read below is still the
// first one written.
//
// The transaction code is positional for the same reason, and it has *not* stayed: the interface
// still declared registerListener / unregisterListener / getProperty up to Android 9, and Android 10
// dropped the two listener methods, which moved `getProperty` from the third position to the first.
// The number is therefore read out of the device's own jar rather than guessed from the API level,
// because a vendor fork is free to insert a method ahead of it -- and a wrong code reaches a
// different method, or none. See common/android/dex.h.
// The transaction code and the reply layout are both positional, and neither is negotiated with the
// service, so each only stays correct as long as AOSP keeps the order it already has. The code has
// not: `IBatteryPropertiesRegistrar` still declared registerListener / unregisterListener /
// getProperty up to Android 9, and Android 10 dropped the two listener methods, which moved
// `getProperty` from the third position to the first. The layout has, because `BatteryProperty` has
// only ever grown at the end -- API 35 appended [string8 mValueString] to it -- so the field read
// below is still the first one written.
//
// Getting either wrong is quiet rather than loud: `ffBinderReadU64` returns 0 for a read past the
// end of the reply, so a drift shows up as a wrong capacity, not as a failed call.
#define FF_BATTERY_ANDROID_SERVICE "batteryproperties"
#define FF_BATTERY_ANDROID_DESCRIPTOR "android.os.IBatteryPropertiesRegistrar"
// IBatteryPropertiesRegistrar has always been part of framework.jar -- it is not one of the
// frameworks that moved into an APEX -- so there is one path and no fallback. The method this module
// calls is named by the constant the dex carries for it, which is what the lookup turns into whatever
// number this build uses.
#define FF_BATTERY_ANDROID_JAR "/system/framework/framework.jar"
#define FF_BATTERY_ANDROID_STUB "Landroid/os/IBatteryPropertiesRegistrar$Stub;"
#define FF_BATTERY_ANDROID_GET_PROPERTY "TRANSACTION_getProperty"
// How long the charge will last is not a property of the pack: it is a forecast BatteryStatsService
// makes from the discharge history, so it lives on a second service. That service is `batterystats`,
// whose interface is com.android.internal.app.IBatteryStats, and it is also in framework.jar --
// com.android.internal.app is not one of the namespaces that moved into an APEX -- so the same read
// resolves this code as the one above.
//
// The interface has 120 methods and the dex cannot say which of them an app UID may call: neither
// IBatteryStats nor its $Stub carries an `_enforcePermission` wrapper, so the wall is inside
// BatteryStatsService, which lives in services.jar and is not available to read. It was therefore
// measured, as uid 10478 on an Android 16 device. The three this module needs answer with no
// permission at all -- and so do `getAllWakeLocks` (117) and `getAllWakeLocksEx` (118), which is why
// the claim is not "only three of 120". Every read-only statistic getter tried (`getAwakeTimeBattery`,
// `getAwakeTimePlugged`, and the cellular / wifi / gps / bluetooth ones) came back with
// "Access denied, requires: android.permission.BATTERY_STATS", or an `anyOf` of that and
// UPDATE_DEVICE_STATS.
//
// The code is read out of the jar for the same reason as `getProperty`, and the numbers make the
// point better than the argument does: this method is transaction 19 on that Android 16 device, 22
// on a Mi 10 (Android 14) and 20 on a Mi 9 (Android 10). A hardcoded 19 would reach a different
// method on two of the three.
#define FF_BATTERY_ANDROID_STATS_SERVICE "batterystats"
#define FF_BATTERY_ANDROID_STATS_DESCRIPTOR "com.android.internal.app.IBatteryStats"
#define FF_BATTERY_ANDROID_STATS_STUB "Lcom/android/internal/app/IBatteryStats$Stub;"
#define FF_BATTERY_ANDROID_COMPUTE_BATTERY_TIME_REMAINING "TRANSACTION_computeBatteryTimeRemaining"
// The temperature is a third source again, and not a service this time: the kernel publishes it as a
// thermal zone, and the zone Android's own thermal HAL names `battery` is the one to read. The name
// is what is matched -- not the number, and not a prefix -- for the reasons spelled out in
// detectTemperature below.
#define FF_BATTERY_ANDROID_THERMAL_DIR "/sys/class/thermal/"
#define FF_BATTERY_ANDROID_THERMAL_ZONE_PREFIX "thermal_zone"
#define FF_BATTERY_ANDROID_THERMAL_BATTERY_TYPE "battery"
// BatteryManager.BATTERY_PROPERTY_*
typedef enum FFBatteryAndroidProperty : uint32_t {
FF_BATTERY_ANDROID_PROPERTY_CAPACITY = 4,
@@ -122,49 +38,19 @@ typedef enum FFBatteryAndroidProperty : uint32_t {
} FFBatteryAndroidProperty;
// BatteryManager.BATTERY_STATUS_*, which the health HAL's `BatteryStatus` mirrors value for value.
// The module has something to say about only three of the five: NOT_CHARGING (4) and FULL (5) both
// mean a charger is attached, and it answers nothing about the charger to put that on, while the dump
// route maps UNKNOWN (1) as well, because there it is a different statement from the service not
// answering at all. The two are named anyway, because the property route validates the whole range
// before it trusts a value it did not get from the service.
// Only the three the module has something to say about are carried: NOT_CHARGING (4) and FULL (5)
// both mean a charger is attached, which the powered bits already say.
typedef enum FFBatteryAndroidStatus : uint32_t {
FF_BATTERY_ANDROID_STATUS_UNKNOWN = 1,
FF_BATTERY_ANDROID_STATUS_CHARGING = 2,
FF_BATTERY_ANDROID_STATUS_DISCHARGING = 3,
FF_BATTERY_ANDROID_STATUS_NOT_CHARGING = 4,
FF_BATTERY_ANDROID_STATUS_FULL = 5,
} FFBatteryAndroidStatus;
// The pair of system properties that stand in for two of the readings above. Both are published by
// `BatteryService.processValuesLocked` -- the two strings sit next to each other in the constant pool
// of classes.dex in this device's services.jar, which is what a pair of `SystemProperties.set` calls
// looks like after the fact -- and neither is a `BatteryProperty`, so nothing about the registrar's
// positional reply applies to them.
//
// debug.tracing.plug_type -- BatteryManager.BATTERY_PLUGGED_*: 1 AC, 2 USB, 4 wireless,
// 8 dock, and 0 when nothing is attached. The dock bit has no
// `FF_BATTERY_STATUS_*` counterpart and is masked off below.
// debug.tracing.battery_status -- BatteryManager.BATTERY_STATUS_*, the value
// `getProperty(BATTERY_PROPERTY_STATUS)` answers with as well:
// 1 UNKNOWN, 2 CHARGING, 3 DISCHARGING, 4 NOT_CHARGING, 5 FULL.
#define FF_BATTERY_ANDROID_PROP_PLUG_TYPE "debug.tracing.plug_type"
#define FF_BATTERY_ANDROID_PROP_BATTERY_STATUS "debug.tracing.battery_status"
// The encoding of plug_type, read out of the device's own framework.jar rather than assumed: the
// values are bits of one field and not a sequence, so `value != 0` is not a test for "wired", and a
// device reporting the dock would be told it is on neither AC nor USB nor wireless.
typedef enum FFBatteryAndroidPlugged : uint32_t {
FF_BATTERY_ANDROID_PLUGGED_AC = 1,
FF_BATTERY_ANDROID_PLUGGED_USB = 2,
FF_BATTERY_ANDROID_PLUGGED_WIRELESS = 4,
} FFBatteryAndroidPlugged;
// BatteryCapacityLevel.BATTERY_CAPACITY_LEVEL_CRITICAL, the level the framework itself shuts the
// device down on. This is the health HAL's numbering, not the one the older
// `BatteryManager.BATTERY_CAPACITY_LEVEL_*` constants used, which had CRITICAL at 4. The rest of the
// enum is skipped: -1 (UNSUPPORTED) and 0 (UNKNOWN) are what a HAL that does not implement the
// property reports, and 2 to 5 only restate the percentage. Only the dump carries this -- it is not a
// `BatteryProperty`, so the binder route never sees it.
// enum -- -1 UNSUPPORTED, 0 UNKNOWN, 2 LOW, 3 NORMAL, 4 HIGH, 5 FULL -- only restates the
// percentage, so only this value is carried.
typedef enum FFBatteryAndroidCapacityLevel : int32_t {
FF_BATTERY_ANDROID_CAPACITY_LEVEL_CRITICAL = 1,
} FFBatteryAndroidCapacityLevel;
@@ -176,11 +62,6 @@ typedef enum FFBatteryAndroidCapacityLevel : int32_t {
#define FF_BATTERY_ANDROID_VALUE_OFFSET 12
#define FF_BATTERY_ANDROID_VALUE_UNSET 0x8000000000000000ULL // Long.MIN_VALUE, i.e. never filled in
// A method that answers with a bare `long` writes [int32 exception][int64 value] and nothing else:
// there is no out parameter, so no non-null marker sits in front of the value the way it does in a
// `getProperty` reply. Twelve bytes in all on the device.
#define FF_BATTERY_ANDROID_LONG_VALUE_OFFSET 4
// One `getProperty` reply is [exception code][return value][value present][int64 mValueLong], and it
// measures 24 bytes on the device: API 35 appended [string8 mValueString] to `BatteryProperty`, and a
// reply carries a word for it even when it is empty. A dump of the first 32 covers every word.
@@ -222,8 +103,15 @@ static void debugDumpReply(const uint8_t* data, size_t size) {
#define FF_BATTERY_ANDROID_DEBUG_DUMP(data, size) ((void) 0)
#endif
static const char* getProperty(FFBinder* binder, uint32_t handle, uint32_t transaction, uint32_t property, uint64_t* value) {
FF_DEBUG("Property %u goes out as transaction %u", property, transaction);
static const char* getProperty(FFBinder* binder, uint32_t handle, uint32_t property, uint64_t* value) {
// `getProperty` is the third method declared in `IBatteryPropertiesRegistrar` up to Android 9 and
// the first one from Android 10 on. Written as an `if` because clang rejects `__builtin_available`
// -- which is what FF_ANDROID_API_AT_LEAST expands to -- in any other position.
uint32_t transaction = 3u;
if (FF_ANDROID_API_AT_LEAST(29)) {
transaction = 1u;
}
FF_DEBUG("Property %u goes out as transaction %u (3 up to Android 9, 1 from Android 10)", property, transaction);
uint8_t parcelBuffer[128];
FFBinderParcel parcel = ffBinderParcelCreate(parcelBuffer, sizeof(parcelBuffer));
@@ -276,277 +164,9 @@ static const char* getProperty(FFBinder* binder, uint32_t handle, uint32_t trans
return nullptr;
}
// Reads either of the two properties above as a number. Both are written by `Integer.toString`, so a
// buffer whose text is not a number is the property saying nothing rather than a 0 -- 0 is a plug type,
// and it is not a status.
static bool readPropertyUInt(const char* name, uint32_t* value) {
FF_STRBUF_AUTO_DESTROY buffer = ffStrbufCreate();
if (!ffSettingsGetAndroidProperty(name, &buffer)) {
FF_DEBUG("The \"%s\" property is not set, so nothing is read from it", name);
return false;
}
// `ffStrbufToUInt` answers the default for text that does not start with a digit, which is why the
// default is a value neither property can hold rather than a 0.
const uint64_t parsed = ffStrbufToUInt(&buffer, UINT64_MAX);
if (parsed == UINT64_MAX) {
FF_DEBUG("The \"%s\" property reads \"%s\", which is not a number", name, buffer.chars);
return false;
}
*value = (uint32_t) parsed;
return true;
}
// The charger type. This is what the property route has and the registrar does not: `getProperty`
// answers nothing about the charger, so the three bits used to come from `dumpsys battery`, i.e. from
// the shell UID and root alone. A device that does not publish the property leaves the bits clear,
// which is also what an unplugged charger looks like -- the module cannot tell those apart, and neither
// can the framework, whose sticky broadcast carries this same value as `EXTRA_PLUGGED`.
static void fillPlugged(FFBatteryResult* battery) {
uint32_t plugged = 0;
if (!readPropertyUInt(FF_BATTERY_ANDROID_PROP_PLUG_TYPE, &plugged)) {
return;
}
// A mask rather than a comparison: the three are bits of one field, and it is the bits that have a
// `FF_BATTERY_STATUS_*` counterpart. BATTERY_PLUGGED_DOCK (8) is the one that does not, so a device
// on a dock reports no powered bit at all -- and `plugged != 0` would have called that "wired".
if (plugged & FF_BATTERY_ANDROID_PLUGGED_AC) {
battery->status |= FF_BATTERY_STATUS_AC_CONNECTED;
}
if (plugged & FF_BATTERY_ANDROID_PLUGGED_USB) {
battery->status |= FF_BATTERY_STATUS_USB_CONNECTED;
}
if (plugged & FF_BATTERY_ANDROID_PLUGGED_WIRELESS) {
battery->status |= FF_BATTERY_STATUS_WIRELESS_CONNECTED;
}
FF_DEBUG("The \"%s\" property is %u, which becomes status bits 0x%x (1 AC, 2 USB, 4 wireless; 0 is "
"what both an unplugged charger and a device that does not publish the property look like)",
FF_BATTERY_ANDROID_PROP_PLUG_TYPE, plugged, (unsigned) battery->status);
}
// The status, read the cheap way first. The property carries the same `mHealthInfo.batteryStatus` the
// transaction would fetch, so the two can only differ inside the window between the health HAL reporting
// a value and the service storing it, while what the property costs is a read of a shared page instead
// of a round trip through /dev/binder -- a round trip measured at about 0.04 ms on the device this was
// written on, and what it saves is a positional reply that has to be decoded as well.
//
// It stays a preference rather than a replacement: a property that is not set (an early boot, or a
// build whose BatteryService does not publish it) or that does not carry one of the five values sends
// the caller to the service, which is the one answer that cannot be stale.
static bool detectStatusFromProperty(uint64_t* status) {
uint32_t value = 0;
if (!readPropertyUInt(FF_BATTERY_ANDROID_PROP_BATTERY_STATUS, &value)) {
return false;
}
if (value < FF_BATTERY_ANDROID_STATUS_UNKNOWN || value > FF_BATTERY_ANDROID_STATUS_FULL) {
FF_DEBUG("The \"%s\" property is %u, which is not a battery status, so the service is asked",
FF_BATTERY_ANDROID_PROP_BATTERY_STATUS, value);
return false;
}
*status = value;
return true;
}
// Calls a method that takes no argument and answers with one `long`, which is how
// IBatteryStats.computeBatteryTimeRemaining() is declared. `transaction` comes out of the jar, and the
// sentinel the reader leaves for a code it could not find is caught here, so that a build which does
// not declare the method is a message rather than a transaction that means something else.
//
// A negative answer is not an error: it is how the interface spells "no estimate", so it is passed
// back as it came and the caller decides what that is worth. The unit is milliseconds.
static const char* getTimeRemaining(FFBinder* binder, uint32_t handle, int32_t transaction, int64_t* milliseconds) {
if (transaction == FF_DEX_STATIC_INT_UNRESOLVED) {
FF_DEBUG("computeBatteryTimeRemaining is unresolved in the jar (transaction %d)", (int) transaction);
return "The battery stats service does not declare computeBatteryTimeRemaining";
}
uint8_t parcelBuffer[128];
FFBinderParcel parcel = ffBinderParcelCreate(parcelBuffer, sizeof(parcelBuffer));
ffBinderParcelPutInterfaceToken(&parcel, FF_BATTERY_ANDROID_STATS_DESCRIPTOR);
uint8_t replyBuffer[128];
FFBinderReply reply = ffBinderReplyCreate(replyBuffer, sizeof(replyBuffer));
const char* error = ffBinderTransact(binder, handle, (uint32_t) transaction, 0, &parcel, &reply);
if (error != nullptr) {
FF_DEBUG("computeBatteryTimeRemaining could not be transacted: %s", error);
return error;
}
if (ffBinderReplyIsStatus(&reply)) {
FF_DEBUG("computeBatteryTimeRemaining came back as a status reply of %d instead of a parcel",
(int) ffBinderReadI32(reply.data, reply.size, 0));
return "The battery stats service rejected the request";
}
// The exception word is read first and the value only once it is known to be a value: a denied
// call answers [exception -1][string16 message], so the eight bytes at the value's offset are
// then the length and the first characters of the message rather than a number.
const int32_t exception = ffBinderReadI32(reply.data, reply.size, 0);
if (exception != 0) {
FF_DEBUG("computeBatteryTimeRemaining is transaction %d, reply %zu bytes, exception %d",
transaction, reply.size, exception);
return "The battery stats service raised an exception";
}
*milliseconds = (int64_t) ffBinderReadU64(reply.data, reply.size, FF_BATTERY_ANDROID_LONG_VALUE_OFFSET);
FF_DEBUG("computeBatteryTimeRemaining is transaction %d: reply is %zu bytes, value %" PRId64 " ms",
transaction, reply.size, *milliseconds);
return nullptr;
}
// Writes the time estimate onto the battery just reported. It is not one of the registrar's
// properties -- it is a forecast BatteryStatsService makes -- so it comes from a second service and
// is filled in here, after the capacity and the status are already in place.
//
// `binder` and `handle` are the caller's, already open on `batterystats`. Nothing here can fail the
// module: an estimate that is not available is what `FFBatteryResult` already spells with -1.
static void fillTimeRemaining(FFBinder* binder, uint32_t handle, int32_t transaction, FFlist* results) {
if (results->length == 0) {
FF_DEBUG("No time remaining is reported: no battery was reported to put it on");
return;
}
int64_t milliseconds = 0;
const char* error = getTimeRemaining(binder, handle, transaction, &milliseconds);
if (error != nullptr) {
FF_DEBUG("The time remaining could not be computed, so none is reported: %s", error);
return;
}
if (milliseconds <= 0) {
FF_DEBUG("The battery stats service answered %" PRId64 " ms, which is how it says it has no estimate",
milliseconds);
return;
}
// The interface answers in milliseconds and `FFBatteryResult` carries seconds, which is what the
// other platform modules fill in. An answer under a second truncates to 0, which the module reads
// as "unknown" as well, so there is nothing to guard against.
FF_LIST_GET(FFBatteryResult, *results, 0)->timeRemaining = (int32_t) (milliseconds / 1000);
}
// The battery temperature is neither a registrar property nor part of the health HAL: the kernel
// publishes it as a thermal zone, and the zone Android's thermal HAL names `battery` is the one to
// read. It is read from /sys/class/thermal, which an app UID may open -- the reason the module does
// not simply read /sys/class/power_supply/battery/temp is that that directory is closed to it.
//
// The zone is *found*, not addressed. Its number is a property of the probe order rather than of the
// hardware -- the battery is `thermal_zone67` on the device this was written on, and nothing promises
// the next one -- so the directory is walked and each entry is matched on the `type` it reports.
//
// That match is an exact string, and has to stay one. A prefix would be cheaper and wrong: the same
// directory holds hardware trip points (`cpu-hw-trip-0` reports 95000, a perfectly believable 95
// degrees), current and battery levels (`pmih010x-ibat-lvl0`, 0 or a small integer) and raw registers
// (`vbat`, in millivolts). None of those are a temperature, and every one of them would be printed as
// one. Reading a different zone is not a smaller error than reading none.
//
// A device that names its battery zone something else therefore reports no temperature rather than a
// wrong one, which is what FF_BATTERY_TEMP_UNSET already means.
static double detectTemperature(void) {
FF_AUTO_CLOSE_DIR DIR* dirp = opendir(FF_BATTERY_ANDROID_THERMAL_DIR);
if (dirp == nullptr) {
FF_DEBUG("Opening \"%s\" failed, so no temperature is reported: %s", FF_BATTERY_ANDROID_THERMAL_DIR, strerror(errno));
return FF_BATTERY_TEMP_UNSET;
}
FF_STRBUF_AUTO_DESTROY buffer = ffStrbufCreate();
struct dirent* entry;
while ((entry = readdir(dirp)) != nullptr) {
if (!ffStrStartsWith(entry->d_name, FF_BATTERY_ANDROID_THERMAL_ZONE_PREFIX)) {
continue;
}
FF_AUTO_CLOSE_FD int zone = openat(dirfd(dirp), entry->d_name, O_RDONLY | O_DIRECTORY | O_CLOEXEC);
if (zone < 0) {
continue;
}
// The type file ends with a newline, which the comparison below would otherwise never survive.
if (!ffReadFileBufferRelative(zone, "type", &buffer)) {
continue;
}
ffStrbufTrimRightSpace(&buffer);
if (!ffStrbufEqualS(&buffer, FF_BATTERY_ANDROID_THERMAL_BATTERY_TYPE)) {
continue;
}
if (!ffReadFileBufferRelative(zone, "temp", &buffer)) {
FF_DEBUG("The battery zone \"%s\" has no readable temperature", entry->d_name);
return FF_BATTERY_TEMP_UNSET;
}
// sysfs thermal publishes millidegrees Celsius. That is the one thing every zone here agrees
// on, even the ones whose reading is not a temperature at all.
const double value = ffStrbufToDouble(&buffer, FF_BATTERY_TEMP_UNSET);
if (value == FF_BATTERY_TEMP_UNSET) {
FF_DEBUG("The battery zone \"%s\" reports \"%s\", which is not a number", entry->d_name, buffer.chars);
return FF_BATTERY_TEMP_UNSET;
}
FF_DEBUG("The battery zone is \"%s\", reporting %.2f degrees", entry->d_name, value / 1000.);
return value / 1000.;
}
FF_DEBUG("No zone in \"%s\" is named \"%s\", so no temperature is reported",
FF_BATTERY_ANDROID_THERMAL_DIR, FF_BATTERY_ANDROID_THERMAL_BATTERY_TYPE);
return FF_BATTERY_TEMP_UNSET;
}
// Temperature is opt-in on every platform, and on Android that is not only about the display: the
// walk above touches a directory with a hundred-odd entries on the device this was written on, and
// none of that is paid for unless `temp` asks for it.
static void fillTemperature(FFBatteryOptions* options, FFlist* results) {
if (!options->temp) {
FF_DEBUG("Temperature is not enabled, so the thermal zones are not walked");
return;
}
if (results->length == 0) {
return;
}
const double temperature = detectTemperature();
if (temperature != FF_BATTERY_TEMP_UNSET) {
FF_LIST_GET(FFBatteryResult, *results, 0)->temperature = temperature;
}
}
const char* ffDetectBattery(FFBatteryOptions* options, FFlist* results) {
// Resolved before the binder is opened, so that a jar which does not declare the method says so
// rather than the module failing later with a message about the transport. Both codes are asked
// for in one call, which is cheaper than two: the reader walks the jar's dex entries in order and
// stops once every request has been answered, so a second request only costs the walk to the
// entry that defines the second class. That walk is not free here, because the two classes are
// not in the same entry -- `IBatteryPropertiesRegistrar$Stub` is in classes3.dex and
// `IBatteryStats$Stub` in classes5.dex -- and on the device this was measured on it added 1.3 ms
// to a module that had been 3.3 ms, while the service lookup and the transaction behind it stayed
// inside the noise. The reader reports a code it cannot find by leaving the sentinel in the
// result and says nothing more -- what a missing code means for this module is decided here, not
// there.
//
// The two are not equally important. `getProperty` is what the module is made of, so a build
// without it has nothing to report; `computeBatteryTimeRemaining` only adds the time estimate,
// and a build without it still has the capacity and the status, so its sentinel is carried on to
// where the estimate is asked for instead of failing the route here.
int32_t transaction = FF_DEX_STATIC_INT_UNRESOLVED;
int32_t timeRemainingTransaction = FF_DEX_STATIC_INT_UNRESOLVED;
const FFDexStaticIntRequest requests[] = {
{ FF_BATTERY_ANDROID_STUB, FF_BATTERY_ANDROID_GET_PROPERTY, &transaction },
{ FF_BATTERY_ANDROID_STATS_STUB, FF_BATTERY_ANDROID_COMPUTE_BATTERY_TIME_REMAINING, &timeRemainingTransaction },
};
const char* error = ffDexStaticInts(FF_BATTERY_ANDROID_JAR, requests, ARRAY_SIZE(requests));
if (error != nullptr) {
FF_DEBUG("Reading the transaction codes from \"%s\" failed: %s", FF_BATTERY_ANDROID_JAR, error);
return error;
}
if (transaction == FF_DEX_STATIC_INT_UNRESOLVED) {
FF_DEBUG("getProperty is unresolved in the jar (transaction %d)", (int) transaction);
return "The battery service does not declare getProperty";
}
static const char* parseBinder(FFlist* results) {
[[gnu::cleanup(ffBinderClose)]] FFBinder binder = { .fd = -1 };
error = ffBinderOpen(&binder);
const char* error = ffBinderOpen(&binder);
if (error != nullptr) {
FF_DEBUG("Opening /dev/binder failed: %s", error);
return error;
@@ -559,30 +179,20 @@ const char* ffDetectBattery(FFBatteryOptions* options, FFlist* results) {
FF_DEBUG("Looking up the \"%s\" service failed: %s", FF_BATTERY_ANDROID_SERVICE, error);
return error;
}
FF_DEBUG("The \"%s\" service is handle %u, getProperty is transaction %d", FF_BATTERY_ANDROID_SERVICE, service.handle, transaction);
FF_DEBUG("The \"%s\" service is handle %u", FF_BATTERY_ANDROID_SERVICE, service.handle);
uint64_t capacity = 0;
error = getProperty(&binder, service.handle, (uint32_t) transaction, FF_BATTERY_ANDROID_PROPERTY_CAPACITY, &capacity);
error = getProperty(&binder, service.handle, FF_BATTERY_ANDROID_PROPERTY_CAPACITY, &capacity);
if (error != nullptr) {
FF_DEBUG("The capacity is what failed, so no battery is reported at all");
return error;
}
// The status is asked of the property first, because that is where the second transaction on the
// service is saved. The service is still asked when the property has nothing to say, and a failure
// there is still fatal: the capacity and the status are what this route is made of, and a battery
// whose status is unknown is not a battery this route can describe.
uint64_t status = 0;
if (detectStatusFromProperty(&status)) {
FF_DEBUG("The status came from the \"%s\" property and is %" PRIu64 ", so the service is not asked "
"for it",
FF_BATTERY_ANDROID_PROP_BATTERY_STATUS, status);
} else {
error = getProperty(&binder, service.handle, (uint32_t) transaction, FF_BATTERY_ANDROID_PROPERTY_STATUS, &status);
if (error != nullptr) {
FF_DEBUG("The status is what failed, so no battery is reported at all");
return error;
}
error = getProperty(&binder, service.handle, FF_BATTERY_ANDROID_PROPERTY_STATUS, &status);
if (error != nullptr) {
FF_DEBUG("The status is what failed, so no battery is reported at all");
return error;
}
FFBatteryResult* battery = FF_LIST_ADD(FFBatteryResult, *results);
@@ -593,38 +203,187 @@ const char* ffDetectBattery(FFBatteryOptions* options, FFlist* results) {
} else if (status == FF_BATTERY_ANDROID_STATUS_DISCHARGING) {
battery->status |= FF_BATTERY_STATUS_DISCHARGING;
}
// The charger type is the one reading this route has that the registrar does not, and it is a
// property as well: the three bits used to come from `dumpsys battery`, and so from the shell UID
// and root alone.
fillPlugged(battery);
// The temperature comes from outside this route entirely, so it is asked for here rather than
// inside it: nothing about the binder session above has any bearing on it, and a device without a
// zone by that name simply leaves the field at the sentinel.
fillTemperature(options, results);
// The time estimate is the one part of this route that is allowed to be missing: a jar without
// the code, a service that is not running and a service that answers -1 all leave the field at
// the -1 the module starts it with, which is how `FFBatteryResult` spells "unknown". It is asked
// for last for that reason -- a failure here cannot cost the capacity and the status that have
// already been read.
[[gnu::cleanup(ffBinderServiceHandleRelease)]] FFBinderServiceHandle stats = { .binder = &binder };
error = ffBinderLookupService(&binder, FF_BATTERY_ANDROID_STATS_SERVICE, FF_BINDER_SM_GET_SERVICE, &stats.handle);
if (error != nullptr) {
FF_DEBUG("Looking up the \"%s\" service failed, so no time remaining is reported: %s",
FF_BATTERY_ANDROID_STATS_SERVICE, error);
} else {
fillTimeRemaining(&binder, stats.handle, timeRemainingTransaction, results);
}
// `BatteryStatus` has five values and only two of them say something the powered bits do not, so
// the trace spells the mapping out: anything that is not 2 or 3 leaves the charging / discharging
// bits clear, which is what UNKNOWN (1), NOT_CHARGING (4) and FULL (5) all come to -- that a
// charger is attached is stated by the bits the plug_type property fills in, not by this one. The
// temperature is not repeated here: it was traced where it was read, and it is the field of the
// three that can be missing without anything being wrong.
// the trace spells the mapping out: anything that is not 2 or 3 leaves the bits clear, which is
// what UNKNOWN (1), NOT_CHARGING (4) and FULL (5) all come to.
FF_DEBUG("Reporting one battery: capacity %.0f%%, status %" PRIu64 " -> bits 0x%x "
"(2 charging, 3 discharging; 1/4/5 leave them clear), %d s remaining (-1 when there is no "
"estimate). The technology stays unset: it rides the sticky broadcast, which this route "
"does not read",
battery->capacity, status, (unsigned) battery->status, battery->timeRemaining);
"(2 charging, 3 discharging; 1/4/5 leave them clear). The registrar answers no "
"temperature and no technology, so both stay unset",
battery->capacity, status, (unsigned) battery->status);
return nullptr;
}
// `dumpsys battery` prints what `BatteryService.dumpInternal()` prints: the whole of the health HAL's
// `HealthInfo`, plus the timestamps the service keeps for its own broadcast rate limiter. It is a flat
// `key: value` list read by name, which is what makes it the sturdier of the two routes -- a vendor
// that prints extra keys (some vendors do: `engine`, `soc decimal`, `adapter power`, `board temp status`,
// `low bat status`, `reverse wl chg status`, `reverse wl chg exception`, `chg shut vbat`,
// `last mode flag` and `last mode keep time`, in the middle of the list) only adds keys nobody asks
// for, and a release that appends a field costs nothing. The binder reply is positional and breaks
// silently instead.
//
// What each key carries, per `android.hardware.health.HealthInfo`:
//
// AC / USB / Wireless / Dock powered -- `charger*Online`, the `plugged` bits of the
// ACTION_BATTERY_CHANGED broadcast. Only the first three have a `FF_BATTERY_STATUS_*`
// counterpart; `Dock powered` (`BATTERY_PLUGGED_DOCK`) has none and is not read.
// Max charging current / Max charging voltage -- `maxChargingCurrentMicroamps` (µA) and
// `maxChargingVoltageMicrovolts` (µV): what the charger offers, not what the battery is drawing.
// Charge counter -- `batteryChargeCounterUah` (µAh), what is left in the pack. The unit is not
// honoured everywhere: MIUI reports the same number as its own dump and three orders of
// magnitude below what the registrar answers for the same battery.
// status -- `batteryStatus`, i.e. `BatteryStatus`: 1 UNKNOWN, 2 CHARGING, 3 DISCHARGING,
// 4 NOT_CHARGING (a charger is attached and the battery is deliberately not taking current),
// 5 FULL.
// health -- `batteryHealth`, i.e. `BatteryHealth`: 1 UNKNOWN, 2 GOOD, 3 OVERHEAT, 4 DEAD,
// 5 OVER_VOLTAGE, 6 UNSPECIFIED_FAILURE, 7 COLD, and, in newer releases, 8 FAIR, 11 NOT_AVAILABLE
// and 12 INCONSISTENT.
// present -- `batteryPresent`, whether the pack is in the device at all.
// level / scale -- `batteryLevel`, the remaining capacity in percent, and `BATTERY_SCALE`, the value
// 100% is expressed in. The scale has been 100 on every release, but it is printed rather than
// assumed, so it is read.
// voltage -- `batteryVoltageMillivolts`. The unit in the name is the recent one: the HAL 1.0 field
// was `batteryVoltage` in microvolts, and the AIDL notes that implementations had always filled it
// in millivolts, so the rename only wrote down the de-facto unit.
// temperature -- `batteryTemperatureTenthsCelsius`, tenths of a degree Celsius.
// technology -- `batteryTechnology`, e.g. "Li-poly". The registrar answers no such property, so
// together with the temperature this is what the route is here for.
// Charging state -- `chargingState`, i.e. `BatteryChargingState`: 0 INVALID, 1 NORMAL, 2 TOO_COLD,
// 3 TOO_HOT, 4 LONG_LIFE, 5 ADAPTIVE.
// Charging policy -- `chargingPolicy`, i.e. `BatteryChargingPolicy`: 0 INVALID, 1 DEFAULT,
// 2 LONG_LIFE, 3 ADAPTIVE. 0 in either means the HAL did not report it.
// Capacity level -- `batteryCapacityLevel`, i.e. `BatteryCapacityLevel`: -1 UNSUPPORTED,
// 0 UNKNOWN, 1 CRITICAL, 2 LOW, 3 NORMAL, 4 HIGH, 5 FULL.
//
// Three keys are not battery readings at all and are skipped: the two `Time when the latest updated
// value of the ... was sent via battery changed broadcast` lines and `The last voltage value sent via
// the battery changed broadcast` are what `BatteryService` keeps to rate limit the broadcast.
//
// `FFBatteryResult` has a place for the status enum, the level pair, the temperature and the
// technology, and for the three powered booleans and the capacity level in the form of status bits.
// `voltage`, `health`, `Charge counter` and the two maximums are read by nothing -- there is no field
// to put them in -- and are described above so that the next reader does not have to go back to the
// service to find out what they are.
static const char* parseDumpsys(FFBatteryOptions* options, FFlist* results) {
FF_STRBUF_AUTO_DESTROY buf = ffStrbufCreate();
if (ffProcessAppendStdOut(&buf, (char*[]) {
"/system/bin/dumpsys",
"battery",
nullptr,
}) != nullptr ||
buf.length == 0) {
return "Executing `/system/bin/dumpsys battery` failed";
}
if (!ffStrbufStartsWithS(&buf, "Current Battery Service state:\n")) {
// A refusal, which ffDetectBattery has already ruled out by looking at the UID: an app UID gets
// `Can't find service: battery` on Android 16, and the permission denial some other
// release would print lands here too. Both exit 0, so this is the only place that notices.
return "Invalid `/system/bin/dumpsys battery` result";
}
const char* start = buf.chars + strlen("Current Battery Service state:\n");
FF_STRBUF_AUTO_DESTROY temp = ffStrbufCreate();
if (!ffParsePropLines(start, "present: ", &temp) || !ffStrbufEqualS(&temp, "true")) {
return nullptr;
}
ffStrbufClear(&temp);
FFBatteryResult* battery = FF_LIST_ADD(FFBatteryResult, *results);
initResult(battery);
if (ffParsePropLines(start, "AC powered: ", &temp) && ffStrbufEqualS(&temp, "true")) {
battery->status |= FF_BATTERY_STATUS_AC_CONNECTED;
}
ffStrbufClear(&temp);
if (ffParsePropLines(start, "USB powered: ", &temp) && ffStrbufEqualS(&temp, "true")) {
battery->status |= FF_BATTERY_STATUS_USB_CONNECTED;
}
ffStrbufClear(&temp);
if (ffParsePropLines(start, "Wireless powered: ", &temp) && ffStrbufEqualS(&temp, "true")) {
battery->status |= FF_BATTERY_STATUS_WIRELESS_CONNECTED;
}
ffStrbufClear(&temp);
// NOT_CHARGING and FULL both mean a charger is attached, which the powered bits already say, and
// neither is charging or discharging in the sense `FF_BATTERY_STATUS` means. UNKNOWN is mapped
// because it is a different statement from the service not answering at all.
if (ffParsePropLines(start, "status: ", &temp)) {
switch (ffStrbufToUInt(&temp, 0)) {
case FF_BATTERY_ANDROID_STATUS_CHARGING:
battery->status |= FF_BATTERY_STATUS_CHARGING;
break;
case FF_BATTERY_ANDROID_STATUS_DISCHARGING:
battery->status |= FF_BATTERY_STATUS_DISCHARGING;
break;
case FF_BATTERY_ANDROID_STATUS_UNKNOWN:
battery->status |= FF_BATTERY_STATUS_UNKNOWN;
break;
default:
break;
}
}
ffStrbufClear(&temp);
// CRITICAL is the one capacity level that says something the percentage does not. -1 (UNSUPPORTED)
// and 0 (UNKNOWN) are what a HAL that does not implement the property reports, and 2 to 5 only
// restate the level.
if (ffParsePropLines(start, "Capacity level: ", &temp) &&
ffStrbufToSInt(&temp, -1) == FF_BATTERY_ANDROID_CAPACITY_LEVEL_CRITICAL) {
battery->status |= FF_BATTERY_STATUS_CRITICAL;
}
ffStrbufClear(&temp);
{
double level = 0, scale = 0;
if (ffParsePropLines(start, "level: ", &temp)) {
level = ffStrbufToDouble(&temp, -DBL_MAX);
}
ffStrbufClear(&temp);
if (ffParsePropLines(start, "scale: ", &temp)) {
scale = ffStrbufToDouble(&temp, -DBL_MAX);
}
ffStrbufClear(&temp);
if (level > 0 && scale > 0) {
battery->capacity = level * 100 / scale;
}
}
if (options->temp) {
if (ffParsePropLines(start, "temperature: ", &temp)) {
battery->temperature = ffStrbufToDouble(&temp, FF_BATTERY_TEMP_UNSET);
if (battery->temperature != FF_BATTERY_TEMP_UNSET) {
battery->temperature /= 10.0; // Android returns temperature in tenths of a degree
}
}
ffStrbufClear(&temp);
}
ffParsePropLines(start, "technology: ", &battery->technology);
return nullptr;
}
const char* ffDetectBattery(FFBatteryOptions* options, FFlist* results) {
// `dumpsys battery` needs android.permission.DUMP, which only the shell UID and root hold: an app
// UID is answered with `Can't find service: battery` on stdout and a zero exit status, so forking
// it there costs a child process and cannot succeed. It is also the richer of the two routes --
// the registrar answers neither temperature nor technology, and the dump is what spells out the
// status enum -- so it is tried first wherever it is allowed to run, and the binder route covers
// everything else at ~0.3 ms. termux-api was dropped: it returns nothing on this device and can
// hang for minutes, the same reason its camera path was removed.
if (ffAndroidIsRootOrShell(instance.state.platform.uid)) {
const char* error = parseDumpsys(options, results);
if (error == nullptr) {
return nullptr;
}
FF_DEBUG("`/system/bin/dumpsys battery` failed: %s. Using the binder route instead", error);
}
return parseBinder(results);
}
-2
View File
@@ -1,6 +1,5 @@
#include "fastfetch.h"
#include "battery.h"
#include "common/debug.h"
#include "common/apple/cf_helpers.h"
#include "common/apple/smc_temps.h"
@@ -10,7 +9,6 @@
const char* ffDetectBattery(FFBatteryOptions* options, FFlist* results) {
FF_IOOBJECT_AUTO_RELEASE io_iterator_t iterator = IO_OBJECT_NULL;
if (IOServiceGetMatchingServices(MACH_PORT_NULL, IOServiceMatching("AppleSmartBattery"), &iterator) != kIOReturnSuccess) {
FF_DEBUG("IOServiceGetMatchingServices() failed");
return "IOServiceGetMatchingServices() failed";
}
-5
View File
@@ -1,5 +1,4 @@
#include "fastfetch.h"
#include "common/debug.h"
#include "common/sysctl.h"
#include "common/io.h"
#include "battery.h"
@@ -7,8 +6,6 @@
#include <dev/acpica/acpiio.h>
#include <sys/ioctl.h>
#include <sys/fcntl.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>
const char* ffDetectBattery([[maybe_unused]] FFBatteryOptions* options, FFlist* results) {
@@ -17,7 +14,6 @@ const char* ffDetectBattery([[maybe_unused]] FFBatteryOptions* options, FFlist*
int units = ffSysctlGetInt("hw.acpi.battery.units", -100);
if (units < 0) {
FF_DEBUG("sysctlbyname(\"hw.acpi.battery.units\") failed: %s", strerror(errno));
return "sysctlbyname(\"hw.acpi.battery.units\") failed";
}
@@ -27,7 +23,6 @@ const char* ffDetectBattery([[maybe_unused]] FFBatteryOptions* options, FFlist*
FF_AUTO_CLOSE_FD int acpifd = open("/dev/acpi", O_RDONLY | O_CLOEXEC);
if (acpifd < 0) {
FF_DEBUG("open(\"/dev/acpi\", O_RDONLY | O_CLOEXEC) failed: %s", strerror(errno));
return "open(\"/dev/acpi\", O_RDONLY | O_CLOEXEC) failed";
}
-6
View File
@@ -1,28 +1,23 @@
#include "fastfetch.h"
#include "battery.h"
#include "common/debug.h"
#include "common/io.h"
#include <private/device/power_managment.h>
#include <sys/ioctl.h>
#include <fcntl.h>
#include <string.h>
const char* parseBattery(int dfd, const char* battId, FFlist* results) {
FF_AUTO_CLOSE_FD int fd = openat(dfd, battId, O_RDWR);
if (fd < 0) {
FF_DEBUG("openat(%s) failed: %s", battId, strerror(errno));
return "openat() failed";
}
acpi_battery_info basic = {};
if (ioctl(fd, GET_BATTERY_INFO, &basic, sizeof(basic)) != 0) {
FF_DEBUG("ioctl(GET_BATTERY_INFO) failed");
return "ioctl(GET_BATTERY_INFO) failed";
}
acpi_extended_battery_info extended = {};
if (ioctl(fd, GET_EXTENDED_BATTERY_INFO, &extended, sizeof(extended)) != 0) {
FF_DEBUG("ioctl(GET_EXTENDED_BATTERY_INFO) failed");
return "ioctl(GET_EXTENDED_BATTERY_INFO) failed";
}
@@ -61,7 +56,6 @@ const char* parseBattery(int dfd, const char* battId, FFlist* results) {
const char* ffDetectBattery([[maybe_unused]] FFBatteryOptions* options, FFlist* results) {
FF_AUTO_CLOSE_DIR DIR* dir = opendir("/dev/power/acpi_battery/");
if (!dir) {
FF_DEBUG("opendir(\"/dev/power/acpi_battery/\") failed: %s", strerror(errno));
return "opendir(/dev/power/acpi_battery) failed";
}
-2
View File
@@ -6,7 +6,6 @@
#include <dirent.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
// https://www.kernel.org/doc/Documentation/ABI/testing/sysfs-class-power
@@ -177,7 +176,6 @@ static bool parseBattery(int dfd, const char* id, FFBatteryOptions* options, FFl
const char* ffDetectBattery(FFBatteryOptions* options, FFlist* results) {
FF_AUTO_CLOSE_DIR DIR* dirp = opendir("/sys/class/power_supply/");
if (dirp == nullptr) {
FF_DEBUG("opendir(\"/sys/class/power_supply/\") failed: %s", strerror(errno));
return "opendir(\"/sys/class/power_supply/\") == nullptr";
}
-3
View File
@@ -1,5 +1,4 @@
#include "battery.h"
#include "common/debug.h"
#include "common/io.h"
#include "common/FFstrbuf.h"
#include "common/strutil.h"
@@ -18,13 +17,11 @@
const char* ffDetectBattery([[maybe_unused]] FFBatteryOptions* options, FFlist* results) {
FF_AUTO_CLOSE_FD int fd = open(_PATH_SYSMON, O_RDONLY | O_CLOEXEC);
if (fd < 0) {
FF_DEBUG("open(_PATH_SYSMON) failed: %s", strerror(errno));
return "open(_PATH_SYSMON, O_RDONLY | O_CLOEXEC) failed";
}
prop_dictionary_t root = nullptr;
if (prop_dictionary_recv_ioctl(fd, ENVSYS_GETDICTIONARY, &root) < 0) {
FF_DEBUG("prop_dictionary_recv_ioctl(ENVSYS_GETDICTIONARY) failed");
return "prop_dictionary_recv_ioctl(ENVSYS_GETDICTIONARY) failed";
}
-4
View File
@@ -1,8 +1,6 @@
#include "battery.h"
#include "common/debug.h"
#include "common/io.h"
#include <string.h>
#include <machine/apmvar.h>
#include <sys/ioctl.h>
#include <unistd.h>
@@ -12,14 +10,12 @@ const char* ffDetectBattery([[maybe_unused]] FFBatteryOptions* options, FFlist*
FF_AUTO_CLOSE_FD int devfd = open("/dev/apm", O_RDONLY | O_CLOEXEC);
if (devfd < 0) {
FF_DEBUG("open(\"/dev/apm\") failed: %s", strerror(errno));
return "open(dev/apm, O_RDONLY | O_CLOEXEC) failed";
}
struct apm_power_info info = {};
if (ioctl(devfd, APM_IOC_GETPOWER, &info) < 0) {
FF_DEBUG("ioctl(APM_IOC_GETPOWER) failed: %s", strerror(errno));
return "ioctl(APM_IOC_GETPOWER) failed";
}
-3
View File
@@ -1,5 +1,4 @@
#include "battery.h"
#include "common/debug.h"
#include <kstat.h>
@@ -51,7 +50,6 @@ const char* ffDetectBattery([[maybe_unused]] FFBatteryOptions* options, FFlist*
// https://github.com/illumos/illumos-gate/blob/master/usr/src/cmd/powertop/common/battery.c
[[gnu::cleanup(kstatFreeWrap)]] kstat_ctl_t* kc = kstat_open();
if (!kc) {
FF_DEBUG("kstat_open() failed");
return "kstat_open() failed";
}
@@ -77,7 +75,6 @@ const char* ffDetectBattery([[maybe_unused]] FFBatteryOptions* options, FFlist*
if (!getUint32(bif, "bif_last_cap", &lastCapacity) ||
!getUint32(bst, "bst_rem_cap", &remainingCapacity) ||
!getUint32(bst, "bst_state", &state)) {
FF_DEBUG("kstat_data_lookup() failed for bif_last_cap, bst_rem_cap or bst_state");
return "kstat_data_lookup() failed";
}
+3 -4
View File
@@ -1,10 +1,10 @@
#define INITGUID
#include "battery.h"
#include "common/debug.h"
#include "common/endian.h"
#include "common/debug.h"
#include "common/mallocHelper.h"
#include "common/windows/nt.h"
#include "common/windows/unicode.h"
#include "common/windows/wmi.h"
@@ -51,7 +51,7 @@ static FFBatteryWmiEntry* getBatteryEntry(FFlist* entries, FFlist* results, ULON
return entry;
}
static const char* queryWmiAllData(const GUID* guid, [[maybe_unused]] const char* guidStr, PWNODE_ALL_DATA* pAllData, ULONG* pBufferSize) {
static const char* queryWmiAllData(const GUID* guid, const char* guidStr, PWNODE_ALL_DATA* pAllData, ULONG* pBufferSize) {
[[gnu::cleanup(ffCloseWmiBlock)]] HANDLE hBlock = nullptr;
ULONG status = WmiOpenBlock(guid, WMIGUID_QUERY, &hBlock);
if (status != ERROR_SUCCESS) {
@@ -66,7 +66,6 @@ static const char* queryWmiAllData(const GUID* guid, [[maybe_unused]] const char
}
if (*pBufferSize == 0) {
FF_DEBUG("WMI: WmiQueryAllDataW(nullptr) returned no data");
return "WmiQueryAllDataW(nullptr) returned no data";
}
-4
View File
@@ -1,6 +1,5 @@
#include "bios.h"
#include "common/apple/cf_helpers.h"
#include "common/debug.h"
#include <IOKit/IOKitLib.h>
@@ -11,13 +10,11 @@ const char* ffDetectBios(FFBiosResult* bios) {
// For Intel
FF_IOOBJECT_AUTO_RELEASE io_registry_entry_t deviceRom = IORegistryEntryFromPath(MACH_PORT_NULL, "IODeviceTree:/rom");
if (!deviceRom) {
FF_DEBUG("IODeviceTree:/rom not found");
return "IODeviceTree:/rom not found";
}
FF_CFTYPE_AUTO_RELEASE CFMutableDictionaryRef deviceRomProps = nullptr;
if (IORegistryEntryCreateCFProperties(deviceRom, &deviceRomProps, kCFAllocatorDefault, kNilOptions) != kIOReturnSuccess) {
FF_DEBUG("IORegistryEntryCreateCFProperties(deviceRom) failed");
return "IORegistryEntryCreateCFProperties(deviceRom) failed";
}
@@ -31,7 +28,6 @@ const char* ffDetectBios(FFBiosResult* bios) {
// For arm64
FF_IOOBJECT_AUTO_RELEASE io_registry_entry_t device = IORegistryEntryFromPath(MACH_PORT_NULL, "IODeviceTree:/");
if (!device) {
FF_DEBUG("IODeviceTree:/ not found");
return "IODeviceTree:/ not found";
}
-3
View File
@@ -1,5 +1,4 @@
#include "bios.h"
#include "common/debug.h"
#include "common/smbios.h"
#ifdef _WIN32
@@ -47,13 +46,11 @@ static_assert(offsetof(FFSmbiosBios, ExtendedBiosRomSize) == 0x18,
const char* ffDetectBios(FFBiosResult* bios) {
const FFSmbiosHeaderTable* smbiosTable = ffGetSmbiosHeaderTable();
if (!smbiosTable) {
FF_DEBUG("BIOS: failed to get SMBIOS data");
return "Failed to get SMBIOS data";
}
const FFSmbiosBios* data = (const FFSmbiosBios*) (*smbiosTable)[FF_SMBIOS_TYPE_BIOS];
if (!data) {
FF_DEBUG("BIOS: section is not found in SMBIOS data");
return "BIOS section is not found in SMBIOS data";
}
-2
View File
@@ -1,5 +1,4 @@
#include "bluetooth.h"
#include "common/debug.h"
#define L2CAP_SOCKET_CHECKED
#include <bluetooth.h>
@@ -32,7 +31,6 @@ const char* ffDetectBluetooth(FFBluetoothOptions* options, FFlist* devices /* FF
// struct hostent* ent = bt_gethostent();
if (bt_devenum((void*) enumDev, devices) < 0) {
FF_DEBUG("bt_devenum() failed");
return "bt_devenum() failed";
}
@@ -1,5 +1,4 @@
#include "bluetooth.h"
#include "common/debug.h"
#include "common/percent.h"
#include "common/strutil.h"
@@ -305,13 +304,11 @@ static const char* detectBluetooth(FFBluetoothOptions* options, FFlist* devices,
DBusMessage* managedObjects = ffDBusGetMethodReply(&dbus, "org.bluez", "/", "org.freedesktop.DBus.ObjectManager", "GetManagedObjects", nullptr, nullptr);
if (!managedObjects) {
FF_DEBUG("Failed to call GetManagedObjects");
return "Failed to call GetManagedObjects";
}
DBusMessageIter rootIter;
if (!dbus.lib->ffdbus_message_iter_init(managedObjects, &rootIter)) {
FF_DEBUG("Failed to get root iterator of GetManagedObjects");
dbus.lib->ffdbus_message_unref(managedObjects);
return "Failed to get root iterator of GetManagedObjects";
}
@@ -354,7 +351,6 @@ const char* ffDetectBluetooth(FFBluetoothOptions* options, FFlist* devices /* FF
return detectBluetooth(options, devices, connectedCount);
#else
FF_UNUSED(options, devices);
FF_DEBUG("Fastfetch was compiled without DBus support");
return "Fastfetch was compiled without DBus support";
#endif
}
@@ -1,5 +1,4 @@
#include "bluetooth.h"
#include "common/debug.h"
#include "common/library.h"
#include "common/mallocHelper.h"
#include "common/windows/unicode.h"
@@ -65,7 +64,6 @@ static const char* detectBattery(FFlist* devices) {
// not in the media class at all, so narrowing the filter would lose it.
CONFIGRET status = CM_Get_Device_ID_List_SizeW(&idListLength, GUID_DEVCLASS_MEDIA_STRING, CM_GETIDLIST_FILTER_PRESENT);
if (status != CR_SUCCESS) {
FF_DEBUG("CM_Get_Device_ID_List_SizeW failed: %s", ffDebugConfigRet(status));
return "CM_Get_Device_ID_List_SizeW failed";
}
@@ -75,13 +73,11 @@ static const char* detectBattery(FFlist* devices) {
FF_AUTO_FREE wchar_t* idList = (wchar_t*) malloc((size_t) idListLength * sizeof(wchar_t));
if (!idList) {
FF_DEBUG("malloc() failed");
return "malloc() failed";
}
status = CM_Get_Device_ID_ListW(GUID_DEVCLASS_MEDIA_STRING, idList, idListLength, CM_GETIDLIST_FILTER_PRESENT);
if (status != CR_SUCCESS) {
FF_DEBUG("CM_Get_Device_ID_ListW failed: %s", ffDebugConfigRet(status));
return "CM_Get_Device_ID_ListW failed";
}
@@ -141,7 +137,6 @@ static const char* detectClassic(FFBluetoothOptions* options, FFlist* devices /*
&btdi);
if (!hFind) {
if (GetLastError() != ERROR_NO_MORE_ITEMS) {
FF_DEBUG("BluetoothFindFirstDevice() failed: %s", ffDebugWin32Error(GetLastError()));
return "BluetoothFindFirstDevice() failed";
}
// "Nothing found" is not an error here: this search only ever sees BR/EDR devices, and a
+4 -14
View File
@@ -1,6 +1,5 @@
extern "C" {
#include "bluetooth.h"
#include "common/debug.h"
#include "common/mallocHelper.h"
#include "common/percent.h"
#include "common/strutil.h"
@@ -281,32 +280,24 @@ extern "C" const char* ffBluetoothDetectLe(FFBluetoothOptions* options, FFlist*
}
FF_AUTO_RELEASE_COM_OBJECT abi_t<winrt::Windows::Devices::Bluetooth::IBluetoothLEDeviceStatics2>* ledeviceStatics = nullptr;
HRESULT ledeviceHr = ffGetActivationFactory<winrt::Windows::Devices::Bluetooth::IBluetoothLEDeviceStatics2>(L"Windows.Devices.Bluetooth.BluetoothLEDevice", &ledeviceStatics);
if (FAILED(ledeviceHr) || !ledeviceStatics) {
FF_DEBUG("winrt: RoGetActivationFactory(BluetoothLEDevice) failed: %s", ffDebugHResult(ledeviceHr));
if (FAILED(ffGetActivationFactory<winrt::Windows::Devices::Bluetooth::IBluetoothLEDeviceStatics2>(L"Windows.Devices.Bluetooth.BluetoothLEDevice", &ledeviceStatics)) || !ledeviceStatics) {
return "winrt: RoGetActivationFactory(BluetoothLEDevice) failed";
}
[[gnu::cleanup(ffDeleteHstring)]] HSTRING selector = nullptr;
HRESULT selectorHr = ledeviceStatics->GetDeviceSelectorFromPairingState(true, reinterpret_cast<void**>(&selector));
if (FAILED(selectorHr) || !selector) {
FF_DEBUG("winrt: GetDeviceSelectorFromPairingState() failed: %s", ffDebugHResult(selectorHr));
if (FAILED(ledeviceStatics->GetDeviceSelectorFromPairingState(true, reinterpret_cast<void**>(&selector))) || !selector) {
return "winrt: GetDeviceSelectorFromPairingState() failed";
}
// Without additional properties the map carries only the eight generic ones, and every AEP key
// is absent, so the list is not optional.
FF_AUTO_RELEASE_COM_OBJECT abi_t<winrt::Windows::Foundation::Collections::IIterable<winrt::hstring>>* properties = nullptr;
HRESULT propertiesHr = ffWinrtCreateHstringIterable(ffBluetoothLeProperties, (uint32_t) ARRAY_SIZE(ffBluetoothLeProperties), &properties);
if (FAILED(propertiesHr) || !properties) {
FF_DEBUG("winrt: building the additional-properties list failed: %s", ffDebugHResult(propertiesHr));
if (FAILED(ffWinrtCreateHstringIterable(ffBluetoothLeProperties, (uint32_t) ARRAY_SIZE(ffBluetoothLeProperties), &properties)) || !properties) {
return "winrt: building the additional-properties list failed";
}
FF_AUTO_RELEASE_COM_OBJECT abi_t<winrt::Windows::Devices::Enumeration::IDeviceInformationStatics>* enumerationStatics = nullptr;
HRESULT deviceInfoHr = ffGetActivationFactory<winrt::Windows::Devices::Enumeration::IDeviceInformationStatics>(L"Windows.Devices.Enumeration.DeviceInformation", &enumerationStatics);
if (FAILED(deviceInfoHr) || !enumerationStatics) {
FF_DEBUG("winrt: RoGetActivationFactory(DeviceInformation) failed: %s", ffDebugHResult(deviceInfoHr));
if (FAILED(ffGetActivationFactory<winrt::Windows::Devices::Enumeration::IDeviceInformationStatics>(L"Windows.Devices.Enumeration.DeviceInformation", &enumerationStatics)) || !enumerationStatics) {
return "winrt: RoGetActivationFactory(DeviceInformation) failed";
}
@@ -316,7 +307,6 @@ extern "C" const char* ffBluetoothDetectLe(FFBluetoothOptions* options, FFlist*
},
&found);
if (FAILED(hr) || !found) {
FF_DEBUG("winrt: DeviceInformation::FindAllAsync() failed: %s", ffDebugHResult(hr));
return "winrt: DeviceInformation::FindAllAsync() failed";
}
@@ -1,5 +1,4 @@
#include "bluetoothradio.h"
#include "common/debug.h"
#include "common/strutil.h"
#ifdef FF_HAVE_DBUS
@@ -37,7 +36,6 @@ array [
static const char* detectBluetoothProperty(FFBluetoothRadioResult* device, FFDBusData* dbus, DBusMessageIter* iter) {
if (dbus->lib->ffdbus_message_iter_get_arg_type(iter) != DBUS_TYPE_DICT_ENTRY) {
FF_DEBUG("Expected dict entry");
return "Expected dict entry";
}
@@ -45,7 +43,6 @@ static const char* detectBluetoothProperty(FFBluetoothRadioResult* device, FFDBu
dbus->lib->ffdbus_message_iter_recurse(iter, &dictIter);
if (dbus->lib->ffdbus_message_iter_get_arg_type(&dictIter) != DBUS_TYPE_STRING) {
FF_DEBUG("Expected dict entry key to be a string");
return "Expected dict entry key to be a string";
}
@@ -85,20 +82,17 @@ static const char* detectBluetoothRoot(FFBluetoothRadioResult* device, const cha
DBusMessage* properties = ffDBusGetMethodReply(dbus, "org.bluez", objPath, "org.freedesktop.DBus.Properties", "GetAll", "org.bluez.Adapter1", nullptr);
if (!properties) {
FF_DEBUG("Failed to call org.freedesktop.DBus.Properties.GetAll");
return "Failed to call org.freedesktop.DBus.Properties.GetAll";
}
DBusMessageIter rootIter;
if (!dbus->lib->ffdbus_message_iter_init(properties, &rootIter)) {
dbus->lib->ffdbus_message_unref(properties);
FF_DEBUG("Failed to get root iterator of org.freedesktop.DBus.Properties.GetAll");
return "Failed to get root iterator of org.freedesktop.DBus.Properties.GetAll";
}
if (dbus->lib->ffdbus_message_iter_get_arg_type(&rootIter) != DBUS_TYPE_ARRAY) {
dbus->lib->ffdbus_message_unref(properties);
FF_DEBUG("Expected array");
return "Expected array";
}
@@ -116,7 +110,6 @@ static const char* detectBluetoothRoot(FFBluetoothRadioResult* device, const cha
static const char* detectBluetooth(FFlist* devices) {
FF_AUTO_CLOSE_DIR DIR* dirp = opendir("/sys/class/bluetooth");
if (dirp == nullptr) {
FF_DEBUG("opendir(\"/sys/class/bluetooth\") failed: %s", strerror(errno));
return "Failed to open /sys/class/bluetooth";
}
@@ -155,7 +148,6 @@ const char* ffDetectBluetoothRadio(FFlist* devices /* FFBluetoothRadioResult */)
return detectBluetooth(devices);
#else
FF_UNUSED(devices)
FF_DEBUG("Fastfetch was compiled without DBus support");
return "Fastfetch was compiled without DBus support";
#endif
}
@@ -1,5 +1,4 @@
#include "bluetoothradio.h"
#include "common/debug.h"
#include "common/library.h"
#include "common/io.h"
#include "common/windows/unicode.h"
@@ -65,20 +64,16 @@ const char* ffDetectBluetoothRadio(FFlist* devices /* FFBluetoothRadioResult */)
&hRadio);
if (!hFind) {
if (GetLastError() == ERROR_NO_MORE_ITEMS) {
FF_DEBUG("No Bluetooth radios found or service disabled");
return "No Bluetooth radios found or service disabled";
} else {
FF_DEBUG("BluetoothFindFirstRadio() failed: %s", ffDebugWin32Error(GetLastError()));
return "BluetoothFindFirstRadio() failed";
}
}
do {
FF_AUTO_CLOSE_FD HANDLE hCurrent = hRadio;
BTH_LOCAL_RADIO_INFO blri;
DWORD returned;
if (!DeviceIoControl(hCurrent, IOCTL_BTH_GET_LOCAL_INFO, nullptr, 0, &blri, sizeof(blri), &returned, nullptr)) {
if (!DeviceIoControl(hRadio, IOCTL_BTH_GET_LOCAL_INFO, nullptr, 0, &blri, sizeof(blri), &returned, nullptr)) {
continue;
}
@@ -92,8 +87,10 @@ const char* ffDetectBluetoothRadio(FFlist* devices /* FFBluetoothRadioResult */)
device->lmpSubversion = blri.radioInfo.lmpSubversion;
ffStrbufInitStatic(&device->vendor, ffBluetoothRadioGetVendor(blri.radioInfo.mfg));
device->enabled = true;
device->connectable = ffBluetoothIsConnectable(hCurrent);
device->discoverable = ffBluetoothIsDiscoverable(hCurrent);
device->connectable = ffBluetoothIsConnectable(hRadio);
device->discoverable = ffBluetoothIsDiscoverable(hRadio);
NtClose(hRadio);
} while (ffBluetoothFindNextRadio(hFind, &hRadio));
ffBluetoothFindRadioClose(hFind);
-2
View File
@@ -1,12 +1,10 @@
#include "board.h"
#include "common/debug.h"
#include "common/apple/cf_helpers.h"
const char* ffDetectBoard(FFBoardResult* result) {
FF_IOOBJECT_AUTO_RELEASE io_registry_entry_t service = IOServiceGetMatchingService(MACH_PORT_NULL, IOServiceMatching("IOPlatformExpertDevice"));
if (!service) {
FF_DEBUG("No IOPlatformExpertDevice found");
return "No IOPlatformExpertDevice found";
}
-3
View File
@@ -1,5 +1,4 @@
#include "board.h"
#include "common/debug.h"
#include "common/smbios.h"
typedef struct [[gnu::packed]] FFSmbiosBaseboard {
@@ -24,13 +23,11 @@ static_assert(offsetof(FFSmbiosBaseboard, ContainedObjectHandles) == 0x0F,
const char* ffDetectBoard(FFBoardResult* board) {
const FFSmbiosHeaderTable* smbiosTable = ffGetSmbiosHeaderTable();
if (!smbiosTable) {
FF_DEBUG("Baseboard information: failed to get SMBIOS data");
return "Failed to get SMBIOS data";
}
const FFSmbiosBaseboard* data = (const FFSmbiosBaseboard*) (*smbiosTable)[FF_SMBIOS_TYPE_BASEBOARD_INFO];
if (!data) {
FF_DEBUG("Baseboard information: section is not found in SMBIOS data");
return "Baseboard information section is not found in SMBIOS data";
}
-7
View File
@@ -1,6 +1,5 @@
#include "bootmgr.h"
#include "common/io.h"
#include "common/debug.h"
#include "common/apple/cf_helpers.h"
#include <IOKit/IOKitLib.h>
@@ -9,18 +8,15 @@ static const char* detectSecureBoot(bool* result) {
#if __aarch64__
FF_IOOBJECT_AUTO_RELEASE io_registry_entry_t entryDevice = IORegistryEntryFromPath(MACH_PORT_NULL, "IODeviceTree:/chosen");
if (!entryDevice) {
FF_DEBUG("IORegistryEntryFromPath(\"IODeviceTree:/chosen\") failed");
return "IORegistryEntryFromPath() failed";
}
FF_CFTYPE_AUTO_RELEASE CFTypeRef prop = IORegistryEntryCreateCFProperty(entryDevice, CFSTR("secure-boot"), kCFAllocatorDefault, kNilOptions);
if (!prop) {
FF_DEBUG("IORegistryEntryCreateCFProperty(\"secure-boot\") failed");
return "IORegistryEntryCreateCFProperty() failed";
}
if (CFGetTypeID(prop) != CFDataGetTypeID() || CFDataGetLength((CFDataRef) prop) == 0) {
FF_DEBUG("Invalid secure-boot property");
return "Invalid secure-boot property";
}
@@ -28,18 +24,15 @@ static const char* detectSecureBoot(bool* result) {
#else
FF_IOOBJECT_AUTO_RELEASE io_registry_entry_t entryDevice = IORegistryEntryFromPath(MACH_PORT_NULL, "IODeviceTree:/options");
if (!entryDevice) {
FF_DEBUG("IORegistryEntryFromPath(\"IODeviceTree:/options\") failed");
return "IORegistryEntryFromPath() failed";
}
FF_CFTYPE_AUTO_RELEASE CFTypeRef prop = IORegistryEntryCreateCFProperty(entryDevice, CFSTR("94b73556-2197-4702-82a8-3e1337dafbfb:AppleSecureBootPolicy"), kCFAllocatorDefault, 0);
if (!prop) {
FF_DEBUG("IORegistryEntryCreateCFProperty(AppleSecureBootPolicy) failed");
return "IORegistryEntryCreateCFProperty() failed";
}
if (CFGetTypeID(prop) != CFDataGetTypeID() || CFDataGetLength((CFDataRef) prop) == 0) {
FF_DEBUG("Invalid secure-boot property");
return "Invalid secure-boot property";
}
-4
View File
@@ -1,6 +1,5 @@
#include "bootmgr.h"
#include "efi_helper.h"
#include "common/debug.h"
#include "common/io.h"
#ifdef __OpenBSD__
@@ -23,7 +22,6 @@ typedef uint16_t efi_char;
const char* ffDetectBootmgr(FFBootmgrResult* result) {
FF_AUTO_CLOSE_FD int efifd = open("/dev/efi", O_RDWR | O_CLOEXEC);
if (efifd < 0) {
FF_DEBUG("open(\"/dev/efi\") failed: %s", strerror(errno));
return "open(/dev/efi) failed";
}
@@ -37,7 +35,6 @@ const char* ffDetectBootmgr(FFBootmgrResult* result) {
ioc.name = (efi_char[]) { 'B', 'o', 'o', 't', 'C', 'u', 'r', 'r', 'e', 'n', 't', '\0' };
ioc.namesize = sizeof("BootCurrent") * 2;
if (ioctl(efifd, EFIIOC_VAR_GET, &ioc) < 0 || ioc.datasize != 2) {
FF_DEBUG("ioctl(EFIIOC_VAR_GET, BootCurrent) failed");
return "ioctl(EFIIOC_VAR_GET, BootCurrent) failed";
}
@@ -49,7 +46,6 @@ const char* ffDetectBootmgr(FFBootmgrResult* result) {
ioc.name = (efi_char[]) { 'B', 'o', 'o', 't', hex[0], hex[1], hex[2], hex[3], '\0' };
ioc.namesize = sizeof("Boot####") * 2;
if (ioctl(efifd, EFIIOC_VAR_GET, &ioc) < 0 || ioc.datasize == sizeof(buffer)) {
FF_DEBUG("ioctl(EFIIOC_VAR_GET, Boot####) failed");
return "ioctl(EFIIOC_VAR_GET, Boot####) failed";
}
-3
View File
@@ -1,5 +1,4 @@
#include "bootmgr.h"
#include "common/debug.h"
#include "common/io.h"
#include "efi_helper.h"
@@ -12,7 +11,6 @@ const char* ffDetectBootmgr(FFBootmgrResult* result) {
alignas(uint16_t) uint8_t buffer[2048];
if (ffReadFileData(FF_EFIVARS_PATH_PREFIX "BootCurrent-" FF_EFI_GLOBAL_GUID, sizeof(buffer), buffer) != 6) {
FF_DEBUG("Failed to read efivar: BootCurrent");
return "Failed to read efivar: BootCurrent";
}
@@ -22,7 +20,6 @@ const char* ffDetectBootmgr(FFBootmgrResult* result) {
ssize_t size = ffReadFileData((const char*) buffer, sizeof(buffer), buffer);
if (size < 5 + (int) sizeof(FFEfiLoadOption) || size == (ssize_t) sizeof(buffer)) {
FF_DEBUG("Failed to read efivar: Boot####");
return "Failed to read efivar: Boot####";
}
+4 -16
View File
@@ -1,5 +1,4 @@
#include "bootmgr.h"
#include "common/debug.h"
#include "efi_helper.h"
#include "common/io.h"
#include "common/windows/nt.h"
@@ -9,9 +8,7 @@
const char* enablePrivilege(const wchar_t* privilege) {
FF_AUTO_CLOSE_FD HANDLE token = nullptr;
NTSTATUS status = NtOpenProcessToken(NtCurrentProcess(), TOKEN_ADJUST_PRIVILEGES, &token);
if (!NT_SUCCESS(status)) {
FF_DEBUG("NtOpenProcessToken() failed: %s", ffDebugNtStatus(status));
if (!NT_SUCCESS(NtOpenProcessToken(NtCurrentProcess(), TOKEN_ADJUST_PRIVILEGES, &token))) {
return "NtOpenProcessToken() failed";
}
@@ -21,18 +18,15 @@ const char* enablePrivilege(const wchar_t* privilege) {
(LUID_AND_ATTRIBUTES) { .Attributes = SE_PRIVILEGE_ENABLED } },
};
if (!LookupPrivilegeValueW(nullptr, privilege, &tp.Privileges[0].Luid)) {
FF_DEBUG("LookupPrivilegeValue() failed: %s", ffDebugWin32Error(GetLastError()));
return "LookupPrivilegeValue() failed";
}
status = NtAdjustPrivilegesToken(token, false, &tp, sizeof(tp), nullptr, nullptr);
NTSTATUS status = NtAdjustPrivilegesToken(token, false, &tp, sizeof(tp), nullptr, nullptr);
if (!NT_SUCCESS(status)) {
FF_DEBUG("NtAdjustPrivilegesToken() failed: %s", ffDebugNtStatus(status));
return "NtAdjustPrivilegesToken() failed";
}
if (status == STATUS_NOT_ALL_ASSIGNED) {
FF_DEBUG("NtAdjustPrivilegesToken() did not assign the privilege: %s", ffDebugNtStatus(status));
return "The token does not have the specified privilege; try sudo please";
}
@@ -53,13 +47,10 @@ const char* ffDetectBootmgr(FFBootmgrResult* result) {
};
ULONG size = sizeof(result->order);
NTSTATUS status = NtQuerySystemEnvironmentValueEx(&(UNICODE_STRING) RTL_CONSTANT_STRING(L"BootCurrent"), &efiGlobalGuid, &result->order, &size, nullptr);
if (!NT_SUCCESS(status)) {
FF_DEBUG("NtQuerySystemEnvironmentValueEx(BootCurrent) failed: %s", ffDebugNtStatus(status));
if (!NT_SUCCESS(NtQuerySystemEnvironmentValueEx(&(UNICODE_STRING) RTL_CONSTANT_STRING(L"BootCurrent"), &efiGlobalGuid, &result->order, &size, nullptr))) {
return "NtQuerySystemEnvironmentValueEx(BootCurrent) failed";
}
if (size != sizeof(result->order)) {
FF_DEBUG("NtQuerySystemEnvironmentValueEx(BootCurrent) returned unexpected size");
return "NtQuerySystemEnvironmentValueEx(BootCurrent) returned unexpected size";
}
@@ -67,13 +58,10 @@ const char* ffDetectBootmgr(FFBootmgrResult* result) {
wchar_t key[9];
swprintf(key, ARRAY_SIZE(key), L"Boot%04X", result->order);
size = sizeof(buffer);
status = NtQuerySystemEnvironmentValueEx(&(UNICODE_STRING) RTL_CONSTANT_STRING(key), &efiGlobalGuid, buffer, &size, nullptr);
if (!NT_SUCCESS(status)) {
FF_DEBUG("NtQuerySystemEnvironmentValueEx(Boot####) failed: %s", ffDebugNtStatus(status));
if (!NT_SUCCESS(NtQuerySystemEnvironmentValueEx(&(UNICODE_STRING) RTL_CONSTANT_STRING(key), &efiGlobalGuid, buffer, &size, nullptr))) {
return "NtQuerySystemEnvironmentValueEx(Boot####) failed";
}
if (size < sizeof(FFEfiLoadOption) || size == ARRAY_SIZE(buffer)) {
FF_DEBUG("NtQuerySystemEnvironmentValueEx(Boot####) returned unexpected size");
return "NtQuerySystemEnvironmentValueEx(Boot####) returned unexpected size";
}
-48
View File
@@ -11,7 +11,6 @@ typedef struct FFBrightnessResult {
enum {
FF_DDC_EDID_ADDR = 0x50u,
FF_DDC_EDID_WRITE_ADDR = FF_DDC_EDID_ADDR << 1,
FF_DDC_CI_ADDR = 0x37u,
FF_DDC_CI_WRITE_ADDR = FF_DDC_CI_ADDR << 1,
FF_DDC_CI_READ_ADDR = FF_DDC_CI_WRITE_ADDR | 1,
@@ -20,55 +19,8 @@ enum {
FF_DDC_CI_SET_VCP = 0x03u,
FF_DDC_CI_COMMAND_PACKET = 0x80u,
FF_DDC_CI_LUMINANCE_OPCODE = 0x10u,
FF_DDC_CI_VCP_RESPONSE_LENGTH = 8u,
FF_DDC_CI_VCP_RESPONSE_PACKET_SIZE = 11u,
FF_DDC_CI_VCP_RESPONSE_BUFFER_SIZE = 12u,
FF_DDC_CI_RESPONSE_CHECKSUM_ADDR = 0x50u,
};
#define FF_BRIGHTNESS_DDCCI_SLEEP_SKIP ((uint32_t) -1)
#define FF_DDC_CI_MAKE_HEADER(len) (FF_DDC_CI_COMMAND_PACKET | ((len) & 0x7F))
static inline uint8_t ffDdcCiChecksum(uint8_t address, const uint8_t* data, size_t length) {
uint8_t checksum = address;
for (size_t i = 0; i < length; ++i) {
checksum ^= data[i];
}
return checksum;
}
static inline void ffDdcCiBuildGetVcpRequest(uint8_t request[5]) {
request[0] = FF_DDC_CI_VCP_COMMAND;
request[1] = FF_DDC_CI_MAKE_HEADER(2);
request[2] = FF_DDC_CI_GET_VCP;
request[3] = FF_DDC_CI_LUMINANCE_OPCODE;
request[4] = ffDdcCiChecksum(FF_DDC_CI_WRITE_ADDR, request, 4);
}
static inline bool ffDdcCiValidateGetVcpResponse(const uint8_t response[FF_DDC_CI_VCP_RESPONSE_BUFFER_SIZE]) {
if (response[0] != FF_DDC_CI_WRITE_ADDR
|| response[1] != FF_DDC_CI_MAKE_HEADER(FF_DDC_CI_VCP_RESPONSE_LENGTH)
|| response[2] != 0x02u
|| response[3] != 0x00u
|| response[4] != FF_DDC_CI_LUMINANCE_OPCODE
|| response[5] != 0x00u) {
return false;
}
return ffDdcCiChecksum(FF_DDC_CI_RESPONSE_CHECKSUM_ADDR, response, FF_DDC_CI_VCP_RESPONSE_PACKET_SIZE - 1u) == response[FF_DDC_CI_VCP_RESPONSE_PACKET_SIZE - 1u];
}
#define FF_I2C_DEVICE_CACHE_MAX 32
typedef struct FFBrightnessDdcCache {
int devices[FF_I2C_DEVICE_CACHE_MAX];
size_t count;
} FFBrightnessDdcCache;
static inline FFstrbuf ffBrightnessDdcCachePath(void) {
FFstrbuf path = ffStrbufCreateCopy(&instance.state.platform.cacheDir);
ffStrbufEnsureEndsWithC(&path, '/');
ffStrbufAppendS(&path, "fastfetch/brightness/i2c_cache.txt");
return path;
}
const char* ffDetectBrightness(FFBrightnessOptions* options, FFlist* result); // list of FFBrightnessResult
@@ -1,7 +1,6 @@
#include "brightness.h"
#include "detection/displayserver/displayserver.h"
#include "common/apple/cf_helpers.h"
#include "common/debug.h"
#include "common/edidHelper.h"
#include "common/time.h"
@@ -29,7 +28,6 @@ typedef CFTypeRef IOAVServiceRef;
// Works for internal display
static const char* detectWithDisplayServices(const FFDisplayServerResult* displayServer, FFlist* result) {
if (DisplayServicesGetBrightness == nullptr) {
FF_DEBUG("DisplayServices function DisplayServicesGetBrightness is not available");
return "DisplayServices function DisplayServicesGetBrightness is not available";
}
@@ -55,13 +53,11 @@ static const char* detectWithDisplayServices(const FFDisplayServerResult* displa
// Works for Apple Silicon and USB-C adapter connection ( but not HTMI )
static const char* detectWithDdcci([[maybe_unused]] const FFDisplayServerResult* displayServer, FFBrightnessOptions* options, FFlist* result) {
if (!IOAVServiceCreate || !IOAVServiceReadI2C || !IOAVServiceWriteI2C) {
FF_DEBUG("IOAVService is not available");
return "IOAVService is not available";
}
FF_IOOBJECT_AUTO_RELEASE io_iterator_t iterator = IO_OBJECT_NULL;
if (IOServiceGetMatchingServices(MACH_PORT_NULL, IOServiceMatching("DCPAVServiceProxy"), &iterator) != kIOReturnSuccess) {
FF_DEBUG("IOServiceGetMatchingServices() failed");
return "IOServiceGetMatchingServices() failed";
}
@@ -158,12 +154,10 @@ static IOOptionBits getSupportedTransactionType(void) {
static const char* detectWithDdcci(const FFDisplayServerResult* displayServer, FFBrightnessOptions* options, FFlist* result) {
if (!CGSServiceForDisplayNumber) {
FF_DEBUG("CGSServiceForDisplayNumber is not available");
return "CGSServiceForDisplayNumber is not available";
}
IOOptionBits transactionType = getSupportedTransactionType();
if (transactionType == kIOI2CNoTransactionType) {
FF_DEBUG("No supported IOI2C transaction type found");
return "No supported IOI2C transaction type found";
}
+45 -253
View File
@@ -4,11 +4,7 @@
#include "common/kmod.h"
#include "common/debug.h"
#include "common/time.h"
#include "common/edidHelper.h"
#include <ctype.h>
#include <errno.h>
#include <string.h>
#include <sys/ioctl.h>
#include <sys/fcntl.h>
#include <unistd.h>
@@ -16,261 +12,65 @@
#if __has_include(<dev/iicbus/iic.h>)
#include <dev/iicbus/iic.h>
// Try to load cache from file
static bool loadCacheFromFile(FFBrightnessDdcCache* cache) {
cache->count = 0;
FF_STRBUF_AUTO_DESTROY cacheDir = ffBrightnessDdcCachePath();
FF_STRBUF_AUTO_DESTROY cacheContent = ffStrbufCreate();
bool valid = false;
FF_DEBUG("Brightness DDC/CI: loading I2C cache from %s", cacheDir.chars);
if (ffReadFileBuffer(cacheDir.chars, &cacheContent)) {
// Parse cache: first number is count, rest are device numbers
char* ptr = cacheContent.chars;
char* end = ptr + cacheContent.length;
const char* detectWithDdcci([[maybe_unused]] FFBrightnessOptions* options, FFlist* result) {
// FIXME: doesn't work for me
for (char i = '0'; i <= '9'; ++i) {
char path[] = "/dev/iic0";
path[ARRAY_SIZE(path) - 2] = i;
// Read count
char* next;
long count = strtol(ptr, &next, 10);
if (next != ptr && count > 0 && count <= FF_I2C_DEVICE_CACHE_MAX) {
ptr = next;
// Read device numbers and reject truncated or trailing data.
size_t parsedCount = 0;
for (; parsedCount < (size_t) count && ptr < end; ++parsedCount) {
long dev = strtol(ptr, &next, 10);
if (next == ptr || dev < 0 || dev >= 100) {
break;
}
cache->devices[parsedCount] = (int) dev;
ptr = next;
}
while (ptr < end && isspace((unsigned char) *ptr)) {
++ptr;
}
if (parsedCount == (size_t) count && ptr == end) {
cache->count = parsedCount;
valid = true;
for (size_t i = 0; i < cache->count; ++i) {
char path[32];
snprintf(path, sizeof(path), "/dev/iic%d", cache->devices[i]);
FF_AUTO_CLOSE_FD int fd = open(path, O_RDWR | O_CLOEXEC);
if (fd < 0) {
int error = errno;
FF_DEBUG("Brightness DDC/CI: cached bus %d is unavailable (errno=%d: %s)", cache->devices[i], error, strerror(error));
valid = false;
break;
}
}
}
FF_AUTO_CLOSE_FD int fd = open(path, O_RDWR | O_CLOEXEC);
if (fd < 0) {
int err = errno;
if (err == ENOENT) {
FF_DEBUG("No more I2C devices");
break;
} else if (i == '0' && !ffKmodLoaded("iicbus")) {
FF_DEBUG("iicbus module is not loaded");
return "No I2C devices found and iicbus module is not loaded";
}
FF_DEBUG("open(/dev/iic%c) failed: %s", i, strerror(errno));
continue;
}
if (!valid) {
FF_DEBUG("Brightness DDC/CI: I2C cache is missing or invalid");
cache->count = 0;
return false;
}
uint8_t i2cIn[] = { FF_DDC_CI_VCP_COMMAND, FF_DDC_CI_MAKE_HEADER(2), FF_DDC_CI_GET_VCP, FF_DDC_CI_LUMINANCE_OPCODE, 0 };
i2cIn[4] = FF_DDC_CI_WRITE_ADDR ^ i2cIn[0] ^ i2cIn[1] ^ i2cIn[2] ^ i2cIn[3];
FF_DEBUG("Brightness DDC/CI: loaded %zu bus(es) from cache", cache->count);
return true;
}
// Save cache to file
static void saveCacheToFile(const FFBrightnessDdcCache* cache) {
if (cache->count == 0) {
return;
}
FF_STRBUF_AUTO_DESTROY cacheDir = ffBrightnessDdcCachePath();
FF_STRBUF_AUTO_DESTROY cacheContent = ffStrbufCreateF("%zu", cache->count);
for (size_t i = 0; i < cache->count; i++) {
ffStrbufAppendF(&cacheContent, " %d", cache->devices[i]);
}
if (ffWriteFileBuffer(cacheDir.chars, &cacheContent)) {
FF_DEBUG("Brightness DDC/CI: saved %zu bus(es) to %s", cache->count, cacheDir.chars);
} else {
FF_DEBUG("Brightness DDC/CI: failed to save cache to %s", cacheDir.chars);
}
}
static bool readDdcCiResponse(int fd, FFBrightnessOptions* options, uint8_t response[FF_DDC_CI_VCP_RESPONSE_BUFFER_SIZE]) {
uint8_t request[5];
ffDdcCiBuildGetVcpRequest(request);
struct iic_msg msg = {
.slave = FF_DDC_CI_WRITE_ADDR,
.flags = IIC_M_WR,
.len = sizeof(request),
.buf = request
};
struct iic_rdwr_data data = {
.msgs = &msg,
.nmsgs = 1
};
int ioctlResult = ioctl(fd, I2CRDWR, &data);
if (ioctlResult < 0) {
int error = errno;
FF_DEBUG("Brightness DDC/CI: write request failed (fd=%d, result=%d, errno=%d: %s)", fd, ioctlResult, error, strerror(error));
return false;
int ret = ioctl(fd, I2CRDWR, &(struct iic_rdwr_data){ .msgs = &(struct iic_msg){ .slave = FF_DDC_CI_WRITE_ADDR, .flags = IIC_M_WR, .len = ARRAY_SIZE(i2cIn), .buf = i2cIn }, .nmsgs = 1 });
if (ret < 0) {
FF_DEBUG("First ioctl(/dev/iic%c, I2CRDWR) failed: %s", i, strerror(errno));
continue;
}
ffTimeSleep(options->ddcciSleep);
msg.slave = FF_DDC_CI_WRITE_ADDR;
msg.flags = IIC_M_RD;
msg.len = FF_DDC_CI_VCP_RESPONSE_BUFFER_SIZE;
msg.buf = response;
ioctlResult = ioctl(fd, I2CRDWR, &data);
if (ioctlResult < 0) {
int error = errno;
FF_DEBUG("Brightness DDC/CI: read response failed (fd=%d, result=%d, errno=%d: %s)", fd, ioctlResult, error, strerror(error));
return false;
uint8_t i2cOut[12] = {};
ret = ioctl(fd, I2CRDWR, &(struct iic_rdwr_data){ .msgs = &(struct iic_msg){ .slave = FF_DDC_CI_READ_ADDR, // LSB will be overridden by kernel to set read bit
.flags = IIC_M_RD,
.len = ARRAY_SIZE(i2cOut),
.buf = i2cOut },
.nmsgs = 1 });
if (ret < 0) {
FF_DEBUG("Second ioctl(/dev/iic%c, I2CRDWR) failed: %s", i, strerror(errno));
continue;
}
if (i2cOut[2] != 0x02 || i2cOut[3] != 0x00) {
FF_DEBUG("i2c out validation failed: i2cOut[2] = 0x%02x, i2cOut[3] = 0x%02x", i2cOut[2], i2cOut[3]);
continue;
}
if (!ffDdcCiValidateGetVcpResponse(response)) {
FF_DEBUG("Brightness DDC/CI: invalid response (addr=0x%02x, header=0x%02x, type=0x%02x, opcode=0x%02x, checksum=0x%02x)",
(unsigned) response[0], (unsigned) response[1], (unsigned) response[2], (unsigned) response[4], (unsigned) response[10]);
return false;
}
uint32_t current = ((uint32_t) i2cOut[8] << 8u) + (uint32_t) i2cOut[9];
uint32_t max = ((uint32_t) i2cOut[6] << 8u) + (uint32_t) i2cOut[7];
FF_DEBUG("Brightness DDC/CI: valid response (fd=%d, current=%u, max=%u)", fd,
((unsigned) response[8] << 8u) | (unsigned) response[9],
((unsigned) response[6] << 8u) | (unsigned) response[7]);
return true;
FFBrightnessResult* brightness = FF_LIST_ADD(FFBrightnessResult, *result);
brightness->max = max;
brightness->min = 0;
brightness->current = current;
ffStrbufInitS(&brightness->name, path + strlen("/dev/"));
brightness->builtin = false;
}
static bool scanI2cDevices(FFBrightnessOptions* options, FFBrightnessDdcCache* cache) {
FF_DEBUG("Brightness DDC/CI: scanning FreeBSD I2C buses");
cache->count = 0;
for (int i = 0; i < 100 && cache->count < FF_I2C_DEVICE_CACHE_MAX; i++) {
char path[32];
snprintf(path, sizeof(path), "/dev/iic%d", i);
FF_AUTO_CLOSE_FD int fd = open(path, O_RDWR | O_CLOEXEC);
if (fd < 0) {
continue;
}
FF_DEBUG("Brightness DDC/CI: probing %s", path);
uint8_t response[FF_DDC_CI_VCP_RESPONSE_BUFFER_SIZE];
if (!readDdcCiResponse(fd, options, response)) {
continue;
}
cache->devices[cache->count++] = i;
FF_DEBUG("Brightness DDC/CI: found compatible display on %s", path);
}
FF_DEBUG("Brightness DDC/CI: scan finished with %zu compatible bus(es)", cache->count);
return cache->count > 0;
}
static bool readEdid(int fd, uint8_t* edidData) {
uint8_t offset = 0;
struct iic_msg msgs[2];
msgs[0].slave = FF_DDC_EDID_WRITE_ADDR;
msgs[0].flags = IIC_M_WR;
msgs[0].len = 1;
msgs[0].buf = &offset;
msgs[1].slave = FF_DDC_EDID_WRITE_ADDR;
msgs[1].flags = IIC_M_RD;
msgs[1].len = 128;
msgs[1].buf = edidData;
struct iic_rdwr_data data = {
.msgs = msgs,
.nmsgs = 2
};
return ioctl(fd, I2CRDWR, &data) >= 0;
}
static bool detectCachedDevices(FFBrightnessOptions* options, FFlist* result, FFBrightnessDdcCache* cache) {
const size_t cachedCount = cache->count;
const uint32_t resultLength = result->length;
size_t validCount = 0;
FF_DEBUG("Brightness DDC/CI: validating %zu cached bus(es)", cachedCount);
for (size_t i = 0; i < cachedCount; ++i) {
const int device = cache->devices[i];
char path[32];
snprintf(path, sizeof(path), "/dev/iic%d", device);
FF_AUTO_CLOSE_FD int fd = open(path, O_RDWR | O_CLOEXEC);
if (fd < 0) {
int error = errno;
FF_DEBUG("Brightness DDC/CI: failed to open cached bus %d (errno=%d: %s)", device, error, strerror(error));
continue;
}
uint8_t response[FF_DDC_CI_VCP_RESPONSE_BUFFER_SIZE];
if (!readDdcCiResponse(fd, options, response)) {
continue;
}
uint32_t current = ((uint32_t) response[8] << 8u) + (uint32_t) response[9];
uint32_t max = ((uint32_t) response[6] << 8u) + (uint32_t) response[7];
if (max == 0 || current > max) {
FF_DEBUG("Brightness DDC/CI: ignoring bus %d with invalid brightness range %u/%u", device, current, max);
continue;
}
cache->devices[validCount++] = device;
// Try to read EDID for monitor name
uint8_t edidData[128];
FF_STRBUF_AUTO_DESTROY name = ffStrbufCreateA(32);
if (readEdid(fd, edidData)) {
ffEdidGetName(edidData, &name);
}
if (name.length == 0) {
ffStrbufSetF(&name, "I2C-%d", device);
}
FFBrightnessResult* brightness = FF_LIST_ADD(FFBrightnessResult, *result);
brightness->max = max;
brightness->min = 0;
brightness->current = current;
brightness->builtin = false;
ffStrbufInitMove(&brightness->name, &name);
FF_DEBUG("Brightness DDC/CI: added display '%s' from bus %d (%u/%u)", brightness->name.chars, device, current, max);
}
cache->count = validCount;
FF_DEBUG("Brightness DDC/CI: cache validation kept %zu bus(es), result count is %u", validCount, result->length);
return result->length > resultLength;
}
const char* detectWithDdcci(FFBrightnessOptions* options, FFlist* result) {
FFBrightnessDdcCache cache = {0};
FF_DEBUG("Brightness DDC/CI: detection started (result count=%u)", result->length);
if (!loadCacheFromFile(&cache)) {
if (!scanI2cDevices(options, &cache)) {
FF_DEBUG("Brightness DDC/CI: no compatible I2C device found during initial scan");
return "No I2C devices found";
}
}
if (!detectCachedDevices(options, result, &cache)) {
FF_DEBUG("Brightness DDC/CI: cached detection found no usable display; rescanning");
if (!scanI2cDevices(options, &cache) || !detectCachedDevices(options, result, &cache)) {
FF_DEBUG("Brightness DDC/CI: rescan found no compatible display");
return "No DDC/CI compatible displays found";
}
}
saveCacheToFile(&cache);
FF_DEBUG("Brightness DDC/CI: detection finished with %u result(s)", result->length);
return nullptr;
}
return nullptr;
}
#else
@@ -338,18 +138,10 @@ const char* detectWithBacklight([[maybe_unused]] FFBrightnessOptions* options, [
#endif
const char* ffDetectBrightness([[maybe_unused]] FFBrightnessOptions* options, FFlist* result) {
FF_DEBUG("Brightness: detection started (result count=%u)", result->length);
detectWithBacklight(options, result);
FF_DEBUG("Brightness: backlight detection finished (result count=%u)", result->length);
if (options->ddcciSleep != FF_BRIGHTNESS_DDCCI_SLEEP_SKIP && result->length == 0) {
FF_DEBUG("Brightness: trying DDC/CI because no backlight result was found");
detectWithDdcci(options, result);
} else if (options->ddcciSleep == FF_BRIGHTNESS_DDCCI_SLEEP_SKIP) {
FF_DEBUG("Brightness: skipping DDC/CI because it is disabled");
} else {
FF_DEBUG("Brightness: skipping DDC/CI because backlight detection already found %u result(s)", result->length);
}
FF_DEBUG("Brightness: detection finished (result count=%u)", result->length);
return nullptr;
}
+88 -284
View File
@@ -2,277 +2,9 @@
#include "common/io.h"
#include "common/edidHelper.h"
#include "common/strutil.h"
#include "common/debug.h"
#include "common/time.h"
#include <ctype.h>
#include <dirent.h>
#include <errno.h>
#include <limits.h>
#include <string.h>
#if defined(__linux__)
#include <linux/i2c.h>
#include <linux/i2c-dev.h>
#include <sys/ioctl.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/stat.h>
// Try to load cache from file
static bool loadCacheFromFile(FFBrightnessDdcCache* cache) {
cache->count = 0;
FF_STRBUF_AUTO_DESTROY cacheDir = ffBrightnessDdcCachePath();
FF_STRBUF_AUTO_DESTROY cacheContent = ffStrbufCreate();
bool valid = false;
FF_DEBUG("Brightness DDC/CI: loading I2C cache from %s", cacheDir.chars);
if (ffReadFileBuffer(cacheDir.chars, &cacheContent)) {
// Parse cache: first number is count, rest are device numbers
char* ptr = cacheContent.chars;
char* end = ptr + cacheContent.length;
// Read count
char* next;
long count = strtol(ptr, &next, 10);
if (next != ptr && count > 0 && count <= FF_I2C_DEVICE_CACHE_MAX) {
ptr = next;
// Read device numbers and reject truncated or trailing data.
size_t parsedCount = 0;
for (; parsedCount < (size_t) count && ptr < end; ++parsedCount) {
long dev = strtol(ptr, &next, 10);
if (next == ptr || dev < 0 || dev >= 100) {
break;
}
cache->devices[parsedCount] = (int) dev;
ptr = next;
}
while (ptr < end && isspace((unsigned char) *ptr)) {
++ptr;
}
if (parsedCount == (size_t) count && ptr == end) {
cache->count = parsedCount;
valid = true;
for (size_t i = 0; i < cache->count; ++i) {
char path[32];
snprintf(path, sizeof(path), "/dev/i2c-%d", cache->devices[i]);
FF_AUTO_CLOSE_FD int fd = open(path, O_RDWR | O_CLOEXEC);
if (fd < 0) {
FF_DEBUG("Brightness DDC/CI: cached bus %d is unavailable (errno=%d: %s)", cache->devices[i], errno, strerror(errno));
valid = false;
break;
}
}
}
}
}
if (!valid) {
FF_DEBUG("Brightness DDC/CI: I2C cache is missing or invalid");
cache->count = 0;
return false;
}
FF_DEBUG("Brightness DDC/CI: loaded %zu bus(es) from cache", cache->count);
return true;
}
// Save cache to file
static void saveCacheToFile(const FFBrightnessDdcCache* cache) {
if (cache->count == 0) {
return;
}
FF_STRBUF_AUTO_DESTROY cacheDir = ffBrightnessDdcCachePath();
FF_STRBUF_AUTO_DESTROY cacheContent = ffStrbufCreateF("%zu", cache->count);
for (size_t i = 0; i < cache->count; i++) {
ffStrbufAppendF(&cacheContent, " %d", cache->devices[i]);
}
if (ffWriteFileBuffer(cacheDir.chars, &cacheContent)) {
FF_DEBUG("Brightness DDC/CI: saved %zu bus(es) to %s", cache->count, cacheDir.chars);
} else {
FF_DEBUG("Brightness DDC/CI: failed to save cache to %s", cacheDir.chars);
}
}
static bool readDdcCiResponse(int fd, FFBrightnessOptions* options, uint8_t response[FF_DDC_CI_VCP_RESPONSE_BUFFER_SIZE]) {
uint8_t request[5];
ffDdcCiBuildGetVcpRequest(request);
// i2c_msg.addr is a 7-bit address; the 8-bit write/read addresses are
// used only for the DDC/CI checksum.
struct i2c_msg msg = {
.addr = FF_DDC_CI_ADDR,
.flags = 0,
.len = sizeof(request),
.buf = request
};
struct i2c_rdwr_ioctl_data data = {
.msgs = &msg,
.nmsgs = 1
};
int ioctlResult = ioctl(fd, I2C_RDWR, &data);
if (ioctlResult != 1) {
FF_DEBUG("Brightness DDC/CI: write request failed (fd=%d, result=%d, errno=%d: %s)", fd, ioctlResult, errno, strerror(errno));
return false;
}
ffTimeSleep(options->ddcciSleep);
msg.flags = I2C_M_RD;
msg.len = FF_DDC_CI_VCP_RESPONSE_BUFFER_SIZE;
msg.buf = response;
ioctlResult = ioctl(fd, I2C_RDWR, &data);
if (ioctlResult != 1) {
FF_DEBUG("Brightness DDC/CI: read response failed (fd=%d, result=%d, errno=%d: %s)", fd, ioctlResult, errno, strerror(errno));
return false;
}
if (!ffDdcCiValidateGetVcpResponse(response)) {
FF_DEBUG("Brightness DDC/CI: invalid response (addr=0x%02x, header=0x%02x, type=0x%02x, opcode=0x%02x, checksum=0x%02x)",
(unsigned) response[0], (unsigned) response[1], (unsigned) response[2], (unsigned) response[4], (unsigned) response[10]);
return false;
}
FF_DEBUG("Brightness DDC/CI: valid response (fd=%d, current=%u, max=%u)", fd,
((unsigned) response[8] << 8u) | (unsigned) response[9],
((unsigned) response[6] << 8u) | (unsigned) response[7]);
return true;
}
static bool scanI2cDevices(FFBrightnessOptions* options, FFBrightnessDdcCache* cache) {
FF_DEBUG("Brightness DDC/CI: scanning Linux I2C buses");
cache->count = 0;
for (int i = 0; i < 100 && cache->count < FF_I2C_DEVICE_CACHE_MAX; i++) {
char path[32];
snprintf(path, sizeof(path), "/dev/i2c-%d", i);
FF_AUTO_CLOSE_FD int fd = open(path, O_RDWR | O_CLOEXEC);
if (fd < 0) {
continue;
}
FF_DEBUG("Brightness DDC/CI: probing %s", path);
uint8_t response[FF_DDC_CI_VCP_RESPONSE_BUFFER_SIZE];
if (!readDdcCiResponse(fd, options, response)) {
continue;
}
cache->devices[cache->count++] = i;
FF_DEBUG("Brightness DDC/CI: found compatible display on %s", path);
}
FF_DEBUG("Brightness DDC/CI: scan finished with %zu compatible bus(es)", cache->count);
return cache->count > 0;
}
static bool readEdid(int fd, uint8_t* edidData) {
uint8_t offset = 0;
struct i2c_msg msgs[2];
msgs[0].addr = FF_DDC_EDID_ADDR;
msgs[0].flags = 0;
msgs[0].len = 1;
msgs[0].buf = &offset;
msgs[1].addr = FF_DDC_EDID_ADDR;
msgs[1].flags = I2C_M_RD;
msgs[1].len = 128;
msgs[1].buf = edidData;
struct i2c_rdwr_ioctl_data data;
data.msgs = msgs;
data.nmsgs = 2;
return ioctl(fd, I2C_RDWR, &data) == 2;
}
static bool detectCachedDevices(FFBrightnessOptions* options, FFlist* result, FFBrightnessDdcCache* cache) {
const size_t cachedCount = cache->count;
const uint32_t resultLength = result->length;
size_t validCount = 0;
FF_DEBUG("Brightness DDC/CI: validating %zu cached bus(es)", cachedCount);
for (size_t i = 0; i < cachedCount; ++i) {
const int device = cache->devices[i];
char path[32];
snprintf(path, sizeof(path), "/dev/i2c-%d", device);
FF_AUTO_CLOSE_FD int fd = open(path, O_RDWR | O_CLOEXEC);
if (fd < 0) {
FF_DEBUG("Brightness DDC/CI: failed to open cached bus %d (errno=%d: %s)", device, errno, strerror(errno));
continue;
}
uint8_t response[FF_DDC_CI_VCP_RESPONSE_BUFFER_SIZE];
if (!readDdcCiResponse(fd, options, response)) {
continue;
}
uint32_t current = ((uint32_t) response[8] << 8u) + (uint32_t) response[9];
uint32_t max = ((uint32_t) response[6] << 8u) + (uint32_t) response[7];
if (max == 0 || current > max) {
FF_DEBUG("Brightness DDC/CI: ignoring bus %d with invalid brightness range %u/%u", device, current, max);
continue;
}
cache->devices[validCount++] = device;
// Try to read EDID for monitor name
uint8_t edidData[128];
FF_STRBUF_AUTO_DESTROY name = ffStrbufCreateA(32);
if (readEdid(fd, edidData)) {
ffEdidGetName(edidData, &name);
}
if (name.length == 0) {
ffStrbufSetF(&name, "I2C-%d", device);
}
FFBrightnessResult* brightness = FF_LIST_ADD(FFBrightnessResult, *result);
brightness->max = max;
brightness->min = 0;
brightness->current = current;
brightness->builtin = false;
ffStrbufInitMove(&brightness->name, &name);
FF_DEBUG("Brightness DDC/CI: added display '%s' from bus %d (%u/%u)", brightness->name.chars, device, current, max);
}
cache->count = validCount;
FF_DEBUG("Brightness DDC/CI: cache validation kept %zu bus(es), result count is %u", validCount, result->length);
return result->length > resultLength;
}
static const char* detectWithDdcciNative(FFBrightnessOptions* options, FFlist* result) {
FFBrightnessDdcCache cache = {0};
FF_DEBUG("Brightness DDC/CI: native detection started (result count=%u)", result->length);
if (!loadCacheFromFile(&cache)) {
if (!scanI2cDevices(options, &cache)) {
FF_DEBUG("Brightness DDC/CI: no compatible I2C device found during initial scan");
return "No I2C devices found";
}
}
if (!detectCachedDevices(options, result, &cache)) {
FF_DEBUG("Brightness DDC/CI: cached detection found no usable display; rescanning");
// A cached bus can remain present after its monitor is unplugged or
// its number is reused. Rebuild the cache when no valid reply arrives.
if (!scanI2cDevices(options, &cache) || !detectCachedDevices(options, result, &cache)) {
FF_DEBUG("Brightness DDC/CI: rescan found no compatible display");
return "No DDC/CI compatible displays found";
}
}
saveCacheToFile(&cache);
FF_DEBUG("Brightness DDC/CI: native detection finished with %u result(s)", result->length);
return nullptr;
}
#endif
static const char* detectWithBacklight(FFlist* result) {
// https://www.kernel.org/doc/Documentation/ABI/stable/sysfs-class-backlight
@@ -280,7 +12,6 @@ static const char* detectWithBacklight(FFlist* result) {
FF_AUTO_CLOSE_DIR DIR* dirp = opendir(backlightDirPath);
if (dirp == nullptr) {
FF_DEBUG("opendir(\"%s\") failed: %s", backlightDirPath, strerror(errno));
return "Failed to open `/sys/class/backlight/`";
}
@@ -344,30 +75,103 @@ static const char* detectWithBacklight(FFlist* result) {
return nullptr;
}
#include "detection/displayserver/displayserver.h"
#ifdef FF_HAVE_DDCUTIL
#include "detection/displayserver/displayserver.h"
#include "common/library.h"
#include "common/mallocHelper.h"
#include <ddcutil_macros.h>
#include <ddcutil_c_api.h>
// Try to be compatible with ddcutil 2.0
#if DDCUTIL_VMAJOR >= 2
double ddca_set_default_sleep_multiplier(double multiplier); // ddcutil 1.4
#else
DDCA_Status ddca_init(const char* libopts, int syslog_level, int opts);
#endif
static const char* detectWithDdcci([[maybe_unused]] FFBrightnessOptions* options, FFlist* result) {
FF_LIBRARY_LOAD_MESSAGE(libddcutil, "libddcutil" FF_LIBRARY_EXTENSION, 5);
FF_LIBRARY_LOAD_SYMBOL_MESSAGE(libddcutil, ddca_get_display_info_list2)
FF_LIBRARY_LOAD_SYMBOL_MESSAGE(libddcutil, ddca_open_display2)
FF_LIBRARY_LOAD_SYMBOL_MESSAGE(libddcutil, ddca_get_any_vcp_value_using_explicit_type)
FF_LIBRARY_LOAD_SYMBOL_MESSAGE(libddcutil, ddca_free_any_vcp_value)
FF_LIBRARY_LOAD_SYMBOL_MESSAGE(libddcutil, ddca_close_display)
// libddcutil may print trace messages to stdout during display detection, not only in ddca_init
FF_SUPPRESS_IO();
#ifndef FF_DISABLE_DLOPEN
FF_LIBRARY_LOAD_SYMBOL_LAZY(libddcutil, ddca_init)
if (ffddca_init) {
// Ref: https://github.com/rockowitz/ddcutil/issues/344
if (ffddca_init(nullptr, -1 /*DDCA_SYSLOG_NOT_SET*/, 1 /*DDCA_INIT_OPTIONS_DISABLE_CONFIG_FILE*/) < 0) {
return "ddca_init() failed";
}
} else {
FF_LIBRARY_LOAD_SYMBOL_LAZY(libddcutil, ddca_set_default_sleep_multiplier);
if (ffddca_set_default_sleep_multiplier) {
ffddca_set_default_sleep_multiplier(options->ddcciSleep / 40.0);
}
libddcutil = nullptr; // Don't dlclose libddcutil. See https://github.com/rockowitz/ddcutil/issues/330
}
#else
#if DDCUTIL_VMAJOR >= 2
if (ddca_init(nullptr, -1 /*DDCA_SYSLOG_NOT_SET*/, 1 /*DDCA_INIT_OPTIONS_DISABLE_CONFIG_FILE*/) < 0) {
return "ddca_init() failed";
}
#else
ddca_set_default_sleep_multiplier(options->ddcciSleep / 40.0);
#endif
#endif
FF_AUTO_FREE DDCA_Display_Info_List* infoList = nullptr;
if (ffddca_get_display_info_list2(false, &infoList) < 0) {
return "ddca_get_display_info_list2(false, &infoList) failed";
}
if (infoList->ct == 0) {
return "No DDC/CI compatible displays found";
}
for (int index = 0; index < infoList->ct; ++index) {
const DDCA_Display_Info* display = &infoList->info[index];
DDCA_Display_Handle handle;
if (ffddca_open_display2(display->dref, false, &handle) >= 0) {
DDCA_Any_Vcp_Value* vcpValue = nullptr;
if (ffddca_get_any_vcp_value_using_explicit_type(handle, 0x10 /*brightness*/, DDCA_NON_TABLE_VCP_VALUE, &vcpValue) >= 0) {
assert(vcpValue->value_type == DDCA_NON_TABLE_VCP_VALUE);
int current = VALREC_CUR_VAL(vcpValue), max = VALREC_MAX_VAL(vcpValue);
ffddca_free_any_vcp_value(vcpValue);
FFBrightnessResult* brightness = FF_LIST_ADD(FFBrightnessResult, *result);
brightness->max = max;
brightness->min = 0;
brightness->current = current;
brightness->builtin = false;
ffStrbufInitS(&brightness->name, display->model_name);
}
ffddca_close_display(handle);
}
}
return nullptr;
}
#endif
const char* ffDetectBrightness([[maybe_unused]] FFBrightnessOptions* options, FFlist* result) {
FF_DEBUG("Brightness: detection started (result count=%u)", result->length);
detectWithBacklight(result);
FF_DEBUG("Brightness: backlight detection finished (result count=%u)", result->length);
#ifdef FF_HAVE_DDCUTIL
if (options->ddcciSleep != FF_BRIGHTNESS_DDCCI_SLEEP_SKIP) {
const FFDisplayServerResult* displayServer = ffConnectDisplayServer();
if (result->length < displayServer->displays.length) {
FF_DEBUG("Brightness: trying native DDC/CI (result count=%u, display count=%u)", result->length, displayServer->displays.length);
#if defined(__linux__)
const char* nativeError = detectWithDdcciNative(options, result);
if (nativeError != nullptr) {
FF_DEBUG("Native I2C DDC/CI failed: %s", nativeError);
}
#endif
} else {
FF_DEBUG("Brightness: skipping DDC/CI because result count=%u meets display count=%u", result->length, displayServer->displays.length);
detectWithDdcci(options, result);
}
} else {
FF_DEBUG("Brightness: skipping DDC/CI because it is disabled");
}
#endif
FF_DEBUG("Brightness: detection finished (result count=%u)", result->length);
return nullptr;
}
@@ -1,5 +1,4 @@
#include "brightness.h"
#include "common/debug.h"
#include "common/io.h"
#include <dev/wscons/wsconsio.h>
@@ -16,7 +15,6 @@ const char* ffDetectBrightness([[maybe_unused]] FFBrightnessOptions* options, FF
if (devfd < 0) {
if (errno == EACCES && i == '0') {
FF_DEBUG("open(%s) failed: %s", path, strerror(errno));
return "Permission denied when opening tty device";
}
if (errno == ENOENT) {
-8
View File
@@ -1,6 +1,5 @@
#include "btrfs.h"
#include "common/debug.h"
#include "common/io.h"
#include <fcntl.h>
@@ -9,13 +8,11 @@ enum { uuidLen = (uint32_t) __builtin_strlen("00000000-0000-0000-0000-0000000000
static const char* enumerateDevices(FFBtrfsResult* item, int dfd, FFstrbuf* buffer) {
int subfd = openat(dfd, "devices", O_RDONLY | O_CLOEXEC | O_DIRECTORY);
if (subfd < 0) {
FF_DEBUG("openat(\"devices\") failed: %s", strerror(errno));
return "openat(\"/sys/fs/btrfs/UUID/devices\") == -1";
}
FF_AUTO_CLOSE_DIR DIR* dirp = fdopendir(subfd);
if (dirp == nullptr) {
FF_DEBUG("fdopendir(\"devices\") failed: %s", strerror(errno));
close(subfd);
return "fdopendir(\"/sys/fs/btrfs/UUID/devices\") == nullptr";
}
@@ -45,13 +42,11 @@ static const char* enumerateDevices(FFBtrfsResult* item, int dfd, FFstrbuf* buff
static const char* enumerateFeatures(FFBtrfsResult* item, int dfd) {
int subfd = openat(dfd, "features", O_RDONLY | O_CLOEXEC | O_DIRECTORY);
if (subfd < 0) {
FF_DEBUG("openat(\"features\") failed: %s", strerror(errno));
return "openat(\"/sys/fs/btrfs/UUID/features\") == -1";
}
FF_AUTO_CLOSE_DIR DIR* dirp = fdopendir(subfd);
if (dirp == nullptr) {
FF_DEBUG("fdopendir(\"features\") failed: %s", strerror(errno));
return "fdopendir(\"/sys/fs/btrfs/UUID/features\") == nullptr";
}
@@ -72,14 +67,12 @@ static const char* enumerateFeatures(FFBtrfsResult* item, int dfd) {
static const char* detectAllocation(FFBtrfsResult* item, int dfd, FFstrbuf* buffer) {
FF_AUTO_CLOSE_FD int subfd = openat(dfd, "allocation", O_RDONLY | O_CLOEXEC | O_PATH | O_DIRECTORY);
if (subfd < 0) {
FF_DEBUG("openat(\"allocation\") failed: %s", strerror(errno));
return "openat(\"/sys/fs/btrfs/UUID/allocation\") == -1";
}
if (ffReadFileBufferRelative(subfd, "global_rsv_size", buffer)) {
item->globalReservationTotal = ffStrbufToUInt(buffer, 0);
} else {
FF_DEBUG("ffReadFileBuffer(\"global_rsv_size\") failed");
return "ffReadFileBuffer(\"/sys/fs/btrfs/UUID/allocation/global_rsv_size\") == nullptr";
}
@@ -131,7 +124,6 @@ static const char* detectAllocation(FFBtrfsResult* item, int dfd, FFstrbuf* buff
const char* ffDetectBtrfs(FFlist* result) {
FF_AUTO_CLOSE_DIR DIR* dirp = opendir("/sys/fs/btrfs/");
if (dirp == nullptr) {
FF_DEBUG("opendir(\"/sys/fs/btrfs\") failed: %s", strerror(errno));
return "opendir(\"/sys/fs/btrfs\") == nullptr";
}
-3
View File
@@ -1,5 +1,4 @@
#include "camera.h"
#include "common/debug.h"
#include <camera/NdkCameraManager.h>
#include <camera/NdkCameraMetadata.h>
@@ -49,13 +48,11 @@ const char* ffDetectCamera(FFlist* result) {
// API 24, so linking it unconditionally in CMakeLists.txt is fine on every supported device.
ACameraManager* manager = ACameraManager_create();
if (!manager) {
FF_DEBUG("ACameraManager_create() failed");
return "ACameraManager_create() failed";
}
ACameraIdList* idList = nullptr;
if (ACameraManager_getCameraIdList(manager, &idList) != ACAMERA_OK || !idList) {
FF_DEBUG("ACameraManager_getCameraIdList() failed");
ACameraManager_delete(manager);
return "ACameraManager_getCameraIdList() failed";
}
-3
View File
@@ -1,10 +1,8 @@
#include "camera.h"
#include "common/debug.h"
#include "common/io.h"
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include <sys/ioctl.h>
#if FF_HAVE_LINUX_VIDEODEV2
@@ -28,7 +26,6 @@ const char* ffDetectCamera(FFlist* result) {
if (errno == ENXIO) {
continue;
}
FF_DEBUG("Failed to open /dev/videoN: %s", strerror(errno));
return "Failed to open /dev/videoN";
}
+4 -11
View File
@@ -1,6 +1,5 @@
extern "C" {
#include "camera.h"
#include "common/debug.h"
#include "common/library.h"
#include "common/windows/com.h"
}
@@ -23,26 +22,20 @@ extern "C" const char* ffDetectCamera([[maybe_unused]] FFlist* result) {
}
FF_AUTO_RELEASE_COM_OBJECT IMFAttributes* attrs = nullptr;
HRESULT hr = ffMFCreateAttributes(&attrs, 1);
if (FAILED(hr)) {
FF_DEBUG("MFCreateAttributes() failed: %s", ffDebugHResult(hr));
if (FAILED(ffMFCreateAttributes(&attrs, 1))) {
return "MFCreateAttributes() failed";
}
hr = attrs->SetGUID(
if (FAILED(attrs->SetGUID(
MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE,
MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_VIDCAP_GUID);
if (FAILED(hr)) {
FF_DEBUG("SetGUID(MF_*) failed: %s", ffDebugHResult(hr));
MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_VIDCAP_GUID))) {
return "SetGUID(MF_*) failed";
}
IMFActivate** devices = nullptr;
uint32_t count;
hr = ffMFEnumDeviceSources(attrs, &devices, &count);
if (FAILED(hr)) {
FF_DEBUG("MFEnumDeviceSources() failed: %s", ffDebugHResult(hr));
if (FAILED(ffMFEnumDeviceSources(attrs, &devices, &count))) {
return "MFEnumDeviceSources() failed";
}
+1 -4
View File
@@ -1,5 +1,4 @@
#include "chassis.h"
#include "common/debug.h"
#include "detection/host/host.h"
const char* ffDetectChassis(FFChassisResult* result) {
@@ -12,9 +11,7 @@ const char* ffDetectChassis(FFChassisResult* result) {
.uuid = ffStrbufCreate(),
.vendor = ffStrbufCreate(),
};
const char* hostError = ffDetectHost(&host);
if (hostError != nullptr) {
FF_DEBUG("ffDetectHost() failed: %s", hostError);
if (ffDetectHost(&host) != nullptr) {
return "Failed to detect host";
}
-3
View File
@@ -1,5 +1,4 @@
#include "chassis.h"
#include "common/debug.h"
#include "common/smbios.h"
// 7.4
@@ -33,13 +32,11 @@ static_assert(offsetof(FFSmbiosSystemEnclosure, ContainedElements) == 0x15,
const char* ffDetectChassis(FFChassisResult* result) {
const FFSmbiosHeaderTable* smbiosTable = ffGetSmbiosHeaderTable();
if (!smbiosTable) {
FF_DEBUG("System enclosure: failed to get SMBIOS data");
return "Failed to get SMBIOS data";
}
const FFSmbiosSystemEnclosure* data = (const FFSmbiosSystemEnclosure*) (*smbiosTable)[FF_SMBIOS_TYPE_SYSTEM_ENCLOSURE];
if (!data) {
FF_DEBUG("System enclosure: section is not found in SMBIOS data");
return "System enclosure is not found in SMBIOS data";
}
-2
View File
@@ -1,7 +1,6 @@
#include "codec.h"
#include "common/android/api.h"
#include "common/debug.h"
#include "common/strutil.h"
#include <media/NdkMediaCodec.h>
@@ -100,6 +99,5 @@ const char* ffDetectCodecNative(FFCodecOptions* options, FFlist* result /*list o
// Reading the codec name is the only way to tell a hardware codec from a software one. Without
// it there is nothing to report, and listing the codecs as if they were accelerated would be a
// guess, so say why instead.
FF_DEBUG("AMediaCodec_getName() requires Android 9 (API 28)");
return "AMediaCodec_getName() requires Android 9 (API 28)";
}
+1 -12
View File
@@ -6,7 +6,6 @@
#if FF_HAVE_VA || FF_HAVE_VDPAU
#include "common/debug.h"
#include "common/library.h"
#include "common/mallocHelper.h"
#include "common/strutil.h"
@@ -15,7 +14,6 @@
#if FF_HAVE_VA
#include <fcntl.h>
#include <string.h>
#include <va/va.h>
typedef struct FFVAData {
@@ -229,7 +227,6 @@ static const char* detectCodecByVaDrm(FFVAData* vaData, FFCodecOptions* options,
FF_AUTO_CLOSE_DIR DIR* dirp = opendir("/dev/dri/");
if (dirp == nullptr) {
FF_DEBUG("opendir(/dev/dri/) failed: %s", strerror(errno));
return "opendir(/dev/dri/) failed";
}
int drifd = dirfd(dirp);
@@ -271,13 +268,11 @@ static const char* detectCodecByVaX11(FFVAData* vaData, FFCodecOptions* options,
Display* x11Display = ffXOpenDisplay(nullptr);
if (!x11Display) {
FF_DEBUG("XOpenDisplay() failed");
return "XOpenDisplay() failed";
}
VADisplay display = ffvaGetDisplay(x11Display);
if (!display) {
FF_DEBUG("vaGetDisplay() failed");
ffXCloseDisplay(x11Display);
return "vaGetDisplay() failed";
}
@@ -385,18 +380,14 @@ static const char* detectCodecByVdpau(FFCodecOptions* options, FFlist* result) {
Display* x11Display = ffXOpenDisplay(nullptr);
if (!x11Display) {
FF_DEBUG("XOpenDisplay() failed");
return "XOpenDisplay() failed";
}
VdpDevice device = VDP_INVALID_HANDLE;
VdpGetProcAddress* ffvdp_get_proc_address = nullptr;
VdpStatus ffvdp_device_create_x11_status = ffvdp_device_create_x11(x11Display, ffXDefaultScreen(x11Display), &device, &ffvdp_get_proc_address);
if (ffvdp_device_create_x11_status != VDP_STATUS_OK ||
if (ffvdp_device_create_x11(x11Display, ffXDefaultScreen(x11Display), &device, &ffvdp_get_proc_address) != VDP_STATUS_OK ||
device == VDP_INVALID_HANDLE ||
ffvdp_get_proc_address == nullptr) {
FF_DEBUG("vdp_device_create_x11() failed: status=%d, device=%p, getProcAddress=%p",
ffvdp_device_create_x11_status, (void*) device, (void*) ffvdp_get_proc_address);
ffXCloseDisplay(x11Display);
return "vdp_device_create_x11() failed";
}
@@ -407,7 +398,6 @@ static const char* detectCodecByVdpau(FFCodecOptions* options, FFlist* result) {
ffvdp_get_proc_address(device, VDP_FUNC_ID_DECODER_QUERY_CAPABILITIES, (void**) &ffvdp_decoder_query_capabilities) != VDP_STATUS_OK ||
ffvdp_device_destroy == nullptr ||
ffvdp_decoder_query_capabilities == nullptr) {
FF_DEBUG("vdp_device_create_x11() did not provide VDP_FUNC_ID_DEVICE_DESTROY / VDP_FUNC_ID_DECODER_QUERY_CAPABILITIES");
if (ffvdp_device_destroy) {
ffvdp_device_destroy(device);
}
@@ -469,7 +459,6 @@ const char* ffDetectCodecNative(FFCodecOptions* options, FFlist* result /* list
}
#endif
FF_DEBUG("Neither libva nor libvdpau could initialize a display");
return "Both libva and libvdpau fail to initialize";
}
+1 -5
View File
@@ -1,6 +1,5 @@
extern "C" {
#include "codec.h"
#include "common/debug.h"
#include "common/library.h"
#include "common/windows/com.h"
#include "common/windows/unicode.h"
@@ -524,7 +523,6 @@ const char* detectD3d12va(FFCodecOptions* options, FFlist* result /*list of FFCo
});
if (result->length == resultLengthBefore) {
FF_DEBUG("No D3D12 video acceleration support");
return "No D3D12 video acceleration support";
}
@@ -541,9 +539,7 @@ const char* ffDetectCodecNative(FFCodecOptions* options, FFlist* result /*list o
}
FF_AUTO_RELEASE_COM_OBJECT IDXGIFactory1* factory = nullptr;
HRESULT hr = ffCreateDXGIFactory1(__uuidof(IDXGIFactory1), (void**) &factory);
if (FAILED(hr) || !factory) {
FF_DEBUG("CreateDXGIFactory1() failed: %s", ffDebugHResult(hr));
if (FAILED(ffCreateDXGIFactory1(__uuidof(IDXGIFactory1), (void**) &factory)) || !factory) {
return "CreateDXGIFactory1() failed";
}
+1 -9
View File
@@ -1,5 +1,4 @@
#include "cpu.h"
#include "common/debug.h"
#include "common/sysctl.h"
#include "common/apple/smc_temps.h"
#include "common/strutil.h"
@@ -54,18 +53,15 @@ static const char* detectFrequency(FFCPUResult* cpu) {
FF_IOOBJECT_AUTO_RELEASE io_registry_entry_t entryDevice = IOServiceGetMatchingService(MACH_PORT_NULL, IOServiceNameMatching("pmgr"));
if (!entryDevice) {
FF_DEBUG("IOServiceGetMatchingService() failed");
return "IOServiceGetMatchingService() failed";
}
if (!IOObjectConformsTo(entryDevice, "AppleARMIODevice")) {
FF_DEBUG("\"pmgr\" should conform to \"AppleARMIODevice\"");
return "\"pmgr\" should conform to \"AppleARMIODevice\"";
}
FF_CFTYPE_AUTO_RELEASE CFDataRef freqProperty = (CFDataRef) IORegistryEntryCreateCFProperty(entryDevice, CFSTR("voltage-states5-sram"), kCFAllocatorDefault, kNilOptions);
if (!freqProperty || CFGetTypeID(freqProperty) != CFDataGetTypeID()) {
FF_DEBUG("\"voltage-states5-sram\" in \"pmgr\" is not found");
return "\"voltage-states5-sram\" in \"pmgr\" is not found";
}
@@ -73,7 +69,6 @@ static const char* detectFrequency(FFCPUResult* cpu) {
// voltage-states1-sram stores ecores'
CFIndex propLength = CFDataGetLength(freqProperty);
if (propLength == 0 || propLength % (CFIndex) (sizeof(uint32_t) * 2) != 0) {
FF_DEBUG("Invalid \"voltage-states5-sram\" length");
return "Invalid \"voltage-states5-sram\" length";
}
@@ -111,7 +106,6 @@ static const char* detectFrequency(FFCPUResult* cpu) {
static const char* detectCoreCount(FFCPUResult* cpu) {
uint32_t nPerfLevels = (uint32_t) ffSysctlGetInt("hw.nperflevels", 0);
if (nPerfLevels <= 0) {
FF_DEBUG("sysctl(hw.nperflevels) failed");
return "sysctl(hw.nperflevels) failed";
}
@@ -130,9 +124,7 @@ static const char* detectCoreCount(FFCPUResult* cpu) {
}
const char* ffDetectCPUImpl(const FFCPUOptions* options, FFCPUResult* cpu) {
const char* error = ffSysctlGetString("machdep.cpu.brand_string", &cpu->name);
if (error != nullptr) {
FF_DEBUG("sysctlbyname(machdep.cpu.brand_string) failed: %s", error);
if (ffSysctlGetString("machdep.cpu.brand_string", &cpu->name) != nullptr) {
return "sysctlbyname(machdep.cpu.brand_string) failed";
}
+1 -8
View File
@@ -1,10 +1,7 @@
#include "cpu.h"
#include "common/debug.h"
#include "common/sysctl.h"
#include "common/strutil.h"
#include <errno.h>
#include <string.h>
#include <sys/param.h>
#if __has_include(<sys/cpuset.h>)
#include <sys/cpuset.h>
@@ -16,7 +13,6 @@ static const char* detectCpuTemp(const FFCPUOptions* options, double* current) {
if (options->tempSensor.length > 0) {
temp = ffSysctlGetInt(options->tempSensor.chars, -999999);
if (temp == -999999) {
FF_DEBUG("sysctlbyname(%s) failed: %s", options->tempSensor.chars, strerror(errno));
return "ffSysctlGetInt(options->tempSensor) failed";
}
} else {
@@ -25,7 +21,6 @@ static const char* detectCpuTemp(const FFCPUOptions* options, double* current) {
// Thermal zone temperature
temp = ffSysctlGetInt("hw.acpi.thermal.tz0.temperature", -999999);
if (temp == -999999) {
FF_DEBUG("Neither sysctlbyname(dev.cpu.0.temperature) nor sysctlbyname(hw.acpi.thermal.tz0.temperature) is readable: %s", strerror(errno));
return "ffSysctlGetInt(\"dev.cpu.0.temperature\" or \"hw.acpi.thermal.tz0.temperature\") failed";
}
}
@@ -37,9 +32,7 @@ static const char* detectCpuTemp(const FFCPUOptions* options, double* current) {
}
const char* ffDetectCPUImpl(const FFCPUOptions* options, FFCPUResult* cpu) {
const char* modelError = ffSysctlGetString("hw.model", &cpu->name);
if (modelError != nullptr) {
FF_DEBUG("sysctlbyname(hw.model) failed: %s", modelError);
if (ffSysctlGetString("hw.model", &cpu->name) != nullptr) {
return "sysctlbyname(hw.model) failed";
}

Some files were not shown because too many files have changed in this diff Show More