[
  {
    "path": ".editorconfig",
    "content": "# EditorConfig configuration for htop\n# http://EditorConfig.org\n\n# top-most EditorConfig file\nroot = true\n\n# Unix-style newlines with a newline ending every file, utf-8 charset\n[*]\nend_of_line = lf\ninsert_final_newline = true\ncharset = utf-8\n\n# match C source and header files, set indent to three spaces\n[*.{c,h}]\nindent_style = space\nindent_size = 3\ntrim_trailing_whitespace = true\n"
  },
  {
    "path": ".github/FUNDING.yml",
    "content": "custom: [\"https://hcb.hackclub.com/donations/start/htop\"]\n"
  },
  {
    "path": ".github/dependabot.yml",
    "content": "---\n\nversion: 2\n\nupdates:\n  - package-ecosystem: github-actions\n    directory: /\n    schedule:\n      interval: weekly\n"
  },
  {
    "path": ".github/workflows/build_release.yml",
    "content": "name: Build Source Release\n\n# Trigger whenever a release is created\non:\n  release:\n    types:\n      - created\n\njobs:\n  build:\n    name: build\n    runs-on: ubuntu-latest\n    steps:\n    - uses: actions/checkout@v6\n      with:\n        submodules: true\n\n    - name: archive\n      id: archive\n      run: |\n        VERSION=${{ github.event.release.tag_name }}\n        PKGNAME=\"htop-$VERSION\"\n        SHASUM=$PKGNAME.tar.xz.sha256\n        autoreconf -i\n        mkdir -p /tmp/$PKGNAME\n        mv * /tmp/$PKGNAME\n        mv /tmp/$PKGNAME .\n        TARBALL=$PKGNAME.tar.xz\n        tar cJf $TARBALL $PKGNAME\n        sha256sum $TARBALL > $SHASUM\n        echo \"::set-output name=tarball::$TARBALL\"\n        echo \"::set-output name=shasum::$SHASUM\"\n    - name: upload tarball\n      uses: actions/upload-release-asset@v1\n      env:\n        GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n      with:\n        upload_url: ${{ github.event.release.upload_url }}\n        asset_path: ./${{ steps.archive.outputs.tarball }}\n        asset_name: ${{ steps.archive.outputs.tarball }}\n        asset_content_type: application/gzip\n\n    - name: upload shasum\n      uses: actions/upload-release-asset@v1\n      env:\n        GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n      with:\n        upload_url: ${{ github.event.release.upload_url }}\n        asset_path: ./${{ steps.archive.outputs.shasum }}\n        asset_name: ${{ steps.archive.outputs.shasum }}\n        asset_content_type: text/plain\n"
  },
  {
    "path": ".github/workflows/ci.yml",
    "content": "name: CI\n\non: [ push, pull_request ]\n\nenv:\n  # Enable format attributes in ncurses headers\n  # Enable fortified memory/string handling\n  CPPFLAGS: -DGCC_PRINTF -DGCC_SCANF -D_FORTIFY_SOURCE=2\n\njobs:\n  build-ubuntu-latest-minimal-gcc:\n    runs-on: ubuntu-latest\n    steps:\n    - uses: actions/checkout@v6\n    - name: Install Dependencies\n      run: sudo apt-get install --no-install-recommends libncursesw5-dev\n    - name: Bootstrap\n      run: ./autogen.sh\n    - name: Configure\n      run: ./configure --enable-werror --enable-affinity --disable-unicode --disable-sensors || (cat config.log; exit 1)\n    - name: Enable compatibility modes\n      run: |\n        sed -i 's/#define HAVE_FSTATAT 1/#undef HAVE_FSTATAT/g' config.h\n        sed -i 's/#define HAVE_OPENAT 1/#undef HAVE_OPENAT/g' config.h\n        sed -i 's/#define HAVE_READLINKAT 1/#undef HAVE_READLINKAT/g' config.h\n    - name: Build\n      run: make -k\n    - name: Distcheck\n      run: make distcheck DISTCHECK_CONFIGURE_FLAGS=\"--enable-werror --enable-affinity --disable-unicode --disable-sensors\"\n\n  build-ubuntu-latest-minimal-clang:\n    runs-on: ubuntu-latest\n    env:\n      CC: clang-22\n    steps:\n    - uses: actions/checkout@v6\n    - name: install clang repo\n      run: |\n        ubuntu_codename=`LC_ALL=C sed 's/^ *UBUNTU_CODENAME *= *\\([a-z]*\\).*$/\\1/p; d' /etc/os-release`\n        wget -O - https://apt.llvm.org/llvm-snapshot.gpg.key 2>/dev/null | sudo apt-key add -\n        sudo add-apt-repository \"deb http://apt.llvm.org/${ubuntu_codename}/ llvm-toolchain-${ubuntu_codename}-22 main\" -y\n        sudo apt-get update -q\n    - name: Install Dependencies\n      run: sudo apt-get install --no-install-recommends clang-22 libncursesw5-dev\n    - name: Bootstrap\n      run: ./autogen.sh\n    - name: Configure\n      run: ./configure --enable-werror --enable-affinity --disable-unicode --disable-sensors || ( cat config.log; exit 1; )\n    - name: Build\n      run: make -k\n    - name: Distcheck\n      run: make distcheck DISTCHECK_CONFIGURE_FLAGS=\"--enable-werror --enable-affinity --disable-unicode --disable-sensors\"\n\n  build-ubuntu-latest-full-featured-gcc:\n    runs-on: ubuntu-latest\n    # Enable LTO, might trigger additional warnings on advanced inlining\n    env:\n      CFLAGS: -O3 -g -flto\n      LDFLAGS: -O3 -g -flto -Wl,--as-needed\n    steps:\n    - uses: actions/checkout@v6\n    - name: Install Dependencies\n      run: sudo apt-get install --no-install-recommends libncursesw5-dev libhwloc-dev libnl-3-dev libnl-genl-3-dev libsensors-dev libcap-dev\n    - name: Bootstrap\n      run: ./autogen.sh\n    - name: Configure\n      run: ./configure --enable-werror --enable-openvz --enable-vserver --enable-ancient-vserver --enable-unicode --enable-hwloc --enable-delayacct --enable-sensors --enable-capabilities || ( cat config.log; exit 1; )\n    - name: Build\n      run: make -k\n    - name: Distcheck\n      run: make distcheck DISTCHECK_CONFIGURE_FLAGS='--enable-werror --enable-openvz --enable-vserver --enable-ancient-vserver --enable-unicode --enable-hwloc --enable-delayacct --enable-sensors --enable-capabilities'\n\n  build-ubuntu-latest-full-featured-clang:\n    runs-on: ubuntu-latest\n    env:\n      CC: clang-22\n    steps:\n    - uses: actions/checkout@v6\n    - name: install clang repo\n      run: |\n        ubuntu_codename=`LC_ALL=C sed 's/^ *UBUNTU_CODENAME *= *\\([a-z]*\\).*$/\\1/p; d' /etc/os-release`\n        wget -O - https://apt.llvm.org/llvm-snapshot.gpg.key 2>/dev/null | sudo apt-key add -\n        sudo add-apt-repository \"deb http://apt.llvm.org/${ubuntu_codename}/ llvm-toolchain-${ubuntu_codename}-22 main\" -y\n        sudo apt-get update -q\n    - name: Install Dependencies\n      run: sudo apt-get install --no-install-recommends clang-22 libncursesw5-dev libhwloc-dev libnl-3-dev libnl-genl-3-dev libsensors-dev libcap-dev\n    - name: Bootstrap\n      run: ./autogen.sh\n    - name: Configure\n      run: ./configure --enable-werror --enable-openvz --enable-vserver --enable-ancient-vserver --enable-unicode --enable-hwloc --enable-delayacct --enable-sensors --enable-capabilities || ( cat config.log; exit 1; )\n    - name: Build\n      run: make -k\n    - name: Distcheck\n      run: make distcheck DISTCHECK_CONFIGURE_FLAGS='--enable-werror --enable-openvz --enable-vserver --enable-ancient-vserver --enable-unicode --enable-hwloc --enable-delayacct --enable-sensors --enable-capabilities'\n\n  build-ubuntu-latest-gcc-static:\n    runs-on: ubuntu-latest\n    # Enable LTO, might trigger additional warnings on advanced inlining\n    env:\n      CFLAGS: -O3 -g -flto\n      LDFLAGS: -O3 -g -flto\n    steps:\n    - uses: actions/checkout@v6\n    - name: Install Dependencies\n      run: sudo apt-get install --no-install-recommends libncursesw5-dev libtinfo-dev libgpm-dev libsensors-dev libcap-dev\n    - name: Bootstrap\n      run: ./autogen.sh\n    - name: Configure\n      run: ./configure --enable-static --enable-werror --enable-openvz --enable-vserver --enable-ancient-vserver --enable-unicode --disable-hwloc --disable-delayacct --enable-sensors --enable-capabilities || ( cat config.log; exit 1; )\n    - name: Build\n      run: make -k\n    - name: Distcheck\n      run: make distcheck DISTCHECK_CONFIGURE_FLAGS='--enable-static --enable-werror --enable-openvz --enable-vserver --enable-ancient-vserver --enable-unicode --disable-hwloc --disable-delayacct --enable-sensors --enable-capabilities'\n\n  build-ubuntu-latest-pcp:\n    # we want PCP v5.2.3+\n    runs-on: ubuntu-22.04\n    steps:\n    - uses: actions/checkout@v6\n    - name: Install Dependencies\n      run: sudo apt-get install --no-install-recommends libpcp3-dev libncursesw5-dev libtinfo-dev libgpm-dev\n    - name: Bootstrap\n      run: ./autogen.sh\n    - name: Configure\n      run: ./configure --enable-werror --enable-pcp --enable-unicode || ( cat config.log; exit 1; )\n    - name: Build\n      run: make -k\n\n  build-unsupported-latest-gcc:\n    # build as if we were on an unsupported platform\n    runs-on: ubuntu-latest\n    steps:\n    - uses: actions/checkout@v6\n    - name: Install Dependencies\n      run: sudo apt-get install --no-install-recommends libncursesw5-dev libtinfo-dev libgpm-dev\n    - name: Bootstrap\n      run: ./autogen.sh\n    - name: Configure\n      run: ./configure --host=`uname -m`-none --enable-werror --enable-unicode || ( cat config.log; exit 1; )\n    - name: Build\n      run: make -k\n\n  build-ubuntu-latest-clang-analyzer:\n    runs-on: ubuntu-latest\n    env:\n      CC: clang-22\n    steps:\n    - uses: actions/checkout@v6\n    - name: install clang repo\n      run: |\n        ubuntu_codename=`LC_ALL=C sed 's/^ *UBUNTU_CODENAME *= *\\([a-z]*\\).*$/\\1/p; d' /etc/os-release`\n        wget -O - https://apt.llvm.org/llvm-snapshot.gpg.key 2>/dev/null | sudo apt-key add -\n        sudo add-apt-repository \"deb http://apt.llvm.org/${ubuntu_codename}/ llvm-toolchain-${ubuntu_codename}-22 main\" -y\n        sudo apt-get update -q\n    - name: Install Dependencies\n      run: sudo apt-get install --no-install-recommends clang-22 clang-tools-22 libncursesw5-dev libnl-3-dev libnl-genl-3-dev libsensors-dev libcap-dev\n    - name: Bootstrap\n      run: ./autogen.sh\n    - name: Configure\n      run: scan-build-22 -analyze-headers --status-bugs ./configure --enable-debug --enable-werror --enable-openvz --enable-vserver --enable-ancient-vserver --enable-unicode --enable-delayacct --enable-sensors --enable-capabilities || ( cat config.log; exit 1; )\n    - name: Build\n      run: scan-build-22 -analyze-headers --status-bugs make -j\"$(nproc)\"\n\n  build-ubuntu-latest-clang-sanitizer:\n    runs-on: ubuntu-latest\n    env:\n      CC: clang-22\n      CFLAGS: '-O1 -g -ftrivial-auto-var-init=pattern -fsanitize=address -fsanitize-address-use-after-scope -fsanitize-address-use-after-return=always -fno-omit-frame-pointer -fsanitize=undefined -fsanitize=nullability -fsanitize=implicit-conversion -fsanitize=integer -fsanitize=float-divide-by-zero -fsanitize=local-bounds'\n      LDFLAGS: '-ftrivial-auto-var-init=pattern -fsanitize=address -fsanitize-address-use-after-scope -fsanitize-address-use-after-return=always -fno-omit-frame-pointer -fsanitize=undefined -fsanitize=nullability -fsanitize=implicit-conversion -fsanitize=integer -fsanitize=float-divide-by-zero -fsanitize=local-bounds'\n      ASAN_OPTIONS: strict_string_checks=1:detect_stack_use_after_return=1:check_initialization_order=1:strict_init_order=1\n      UBSAN_OPTIONS: print_stacktrace=1:print_summary=1:halt_on_error=1\n      TERM: xterm-color\n      HTOPRC: .github/workflows/htoprc\n    steps:\n    - uses: actions/checkout@v6\n    - name: install clang repo\n      run: |\n        ubuntu_codename=`LC_ALL=C sed 's/^ *UBUNTU_CODENAME *= *\\([a-z]*\\).*$/\\1/p; d' /etc/os-release`\n        wget -O - https://apt.llvm.org/llvm-snapshot.gpg.key 2>/dev/null | sudo apt-key add -\n        sudo add-apt-repository \"deb http://apt.llvm.org/${ubuntu_codename}/ llvm-toolchain-${ubuntu_codename}-22 main\" -y\n        sudo apt-get update -q\n    - name: Install LLVM Toolchain\n      run: sudo apt-get install --no-install-recommends clang-22 libclang-rt-22-dev llvm-22\n    - name: Install Dependencies\n      run: sudo apt-get install --no-install-recommends libncursesw5-dev libhwloc-dev libnl-3-dev libnl-genl-3-dev libsensors-dev libcap-dev\n    - name: Bootstrap\n      run: ./autogen.sh\n    - name: Configure\n      run: ./configure --enable-werror --enable-openvz --enable-vserver --enable-ancient-vserver --enable-unicode --enable-hwloc --enable-delayacct --enable-sensors --enable-capabilities || ( cat config.log; exit 1; )\n    - name: Build\n      run: make -k\n    - name: Run sanitized htop (1)\n      run: ./htop -h\n    - name: Run sanitized htop (2)\n      run: ./htop -n 5\n    - name: Run sanitized htop (3)\n      run: ./htop -n 5 -t\n    - name: Run sanitized htop (4)\n      run: ./htop -d 1 -n 50\n\n  build-macos-latest-clang:\n    runs-on: macOS-latest\n    env:\n      CC: clang\n    steps:\n    - uses: actions/checkout@v6\n    - name: Install Dependencies\n      run: brew install automake pkg-config\n    - name: Bootstrap\n      run: ./autogen.sh\n    - name: Configure\n      run: ./configure --enable-werror || ( cat config.log; exit 1; )\n    - name: Build\n      run: make -k\n    - name: Distcheck\n      run: make distcheck DISTCHECK_CONFIGURE_FLAGS=\"--enable-werror\"\n\n  build-macos-latest-pcp:\n    runs-on: macOS-latest\n    env:\n      CC: clang\n      CFLAGS: -O3 -g -flto\n      LDFLAGS: -O3 -g -flto\n    steps:\n    - uses: actions/checkout@v6\n    - name: Install Dependencies\n      run: |\n        pcp_version=7.0.3-1\n        brew install automake pkg-config\n        curl -f -L -O https://github.com/performancecopilot/pcp/releases/download/$(echo ${pcp_version} | sed 's/-.*$//')/pcp-${pcp_version}.dmg\n        hdiutil attach pcp-${pcp_version}.dmg\n        sudo installer -verbose -pkg /Volumes/pcp-${pcp_version}/pcp-${pcp_version}.pkg -target /\n    - name: Bootstrap\n      run: ./autogen.sh\n    - name: Configure\n      run: ./configure --enable-werror --enable-pcp --enable-unicode || ( cat config.log; exit 1; )\n    - name: Build\n      run: make -k\n\n  build-dragonflybsd-latest-gcc:\n      runs-on: ubuntu-22.04\n      timeout-minutes: 20\n      steps:\n        - uses: actions/checkout@v6\n          with:\n            submodules: recursive\n        - name: Compile\n          uses: vmactions/dragonflybsd-vm@v1\n          with:\n            release: '6.4.2'\n            usesh: true\n            prepare: |\n              pkg install -y gmake autoconf automake ncurses git\n              git config --global --add safe.directory /home/runner/work/htop/htop\n            run: |\n              set -e\n              export LDFLAGS=\"-L/usr/local/lib\"\n              export CFLAGS=\"-I/usr/local/include\"\n              ./autogen.sh\n              ./configure --enable-unicode --enable-werror\n              gmake -k\n\n  build-freebsd-latest-clang:\n      runs-on: ubuntu-22.04\n      timeout-minutes: 20\n      steps:\n        - uses: actions/checkout@v6\n          with:\n            submodules: recursive\n        - name: Compile\n          uses: vmactions/freebsd-vm@v1\n          with:\n            release: '15.0'\n            usesh: true\n            prepare: |\n              pkg install -y gmake autoconf automake pkgconf git\n              git config --global --add safe.directory /home/runner/work/htop/htop\n            run: |\n              set -e\n              ./autogen.sh\n              ./configure --enable-unicode --enable-werror\n              gmake -k\n\n  build-netbsd-latest-gcc:\n      runs-on: ubuntu-22.04\n      timeout-minutes: 20\n      steps:\n        - uses: actions/checkout@v6\n          with:\n            submodules: recursive\n        - name: Compile\n          uses: vmactions/netbsd-vm@v1\n          with:\n            release: '10.1'\n            usesh: true\n            prepare: |\n              PATH=\"/usr/pkg/sbin:/usr/pkg/bin:$PATH\"\n              PKG_PATH=\"https://ftp.netbsd.org/pub/pkgsrc/packages/NetBSD/amd64/10.1/All/\"\n              export PATH PKG_PATH\n              /usr/sbin/pkg_add pkgin\n              pkgin -y install autoconf automake libtool ncurses pkg-config gmake git\n              git config --global --add safe.directory /home/runner/work/htop/htop\n            run: |\n              set -e\n              ./autogen.sh\n              ./configure --enable-unicode --enable-werror\n              gmake -k\n\n  build-openbsd-latest-clang:\n      runs-on: ubuntu-22.04\n      timeout-minutes: 20\n      steps:\n        - uses: actions/checkout@v6\n          with:\n            submodules: recursive\n        - name: Compile\n          uses: vmactions/openbsd-vm@v1\n          with:\n            release: '7.8'\n            usesh: true\n            prepare: |\n              pkg_add gmake git\n              git config --global --add safe.directory /home/runner/work/htop/htop\n            run: |\n              set -e\n              autoconf_version_full=$(pkg_info -Q autoconf |\n                LC_ALL=C sed 's/^autoconf-\\([0-9]*\\.[0-9]*\\)\\(p[.0-9]*\\)\\{0,1\\}$/\\1\\2/p; d' |\n                LC_ALL=C sort -n -r -t . -k 1,1 -k 2,2 |\n                sed '1 q')\n              automake_version_full=$(pkg_info -Q automake |\n                LC_ALL=C sed 's/^automake-\\([0-9]*\\.[0-9]*\\)\\(\\.[0-9]*\\)\\{0,1\\}\\(p[0-9]*\\)\\{0,1\\}$/\\1\\2\\3/p; d' |\n                LC_ALL=C sort -n -r -t . -k 1,1 -k 2,2 -k 3,3 |\n                sed '1 q')\n              pkg_add -v autoconf-${autoconf_version_full} automake-${automake_version_full}\n              export AUTOCONF_VERSION=$(echo ${autoconf_version_full} |\n                LC_ALL=C sed 's/^\\([0-9]*\\.[0-9]*\\).*$/\\1/')\n              # Must not include the third version field in $AUTOMAKE_VERSION\n              export AUTOMAKE_VERSION=$(echo ${automake_version_full} |\n                LC_ALL=C sed 's/^\\([0-9]*\\.[0-9]*\\).*$/\\1/')\n              ./autogen.sh\n              ./configure --enable-unicode --enable-werror\n              gmake -k\n\n  build-solaris-latest-gcc:\n      runs-on: ubuntu-22.04\n      timeout-minutes: 20\n      steps:\n        - uses: actions/checkout@v6\n          with:\n            submodules: recursive\n        - name: Compile\n          uses: vmactions/solaris-vm@v1\n          with:\n            release: '11.4'\n            usesh: true\n            prepare: |\n              pkg install gnu-make autoconf automake ncurses git gcc\n              git config --global --add safe.directory /home/runner/work/htop/htop\n            run: |\n              set -e\n              ./autogen.sh\n              ./configure --enable-unicode --enable-werror\n              gmake -k\n\n  lint-whitespace:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v6\n      - name: check-whitespaces\n        run: git diff-tree --check $(git hash-object -t tree /dev/null) HEAD\n\n  lint-pcp-config:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v6\n      - name: check-pcp-style\n        run: ./check-pcp-style.sh\n"
  },
  {
    "path": ".github/workflows/codeql-analysis.yml",
    "content": "name: \"CodeQL\"\n\non:\n  push:\n    branches: [main]\n  pull_request:\n    branches: [main]\n  schedule:\n    - cron: '0 1 * * 0'\n\npermissions:\n  contents: read\n\njobs:\n  analyze:\n    name: Analyze\n    runs-on: ubuntu-latest\n    permissions:\n      actions: read\n      security-events: write\n\n    env:\n        # Enable format attributes in ncurses headers\n        # Enable fortified memory/string handling\n        CPPFLAGS: -DGCC_PRINTF -DGCC_SCANF -D_FORTIFY_SOURCE=2\n\n    steps:\n    - name: Checkout Repository\n      uses: actions/checkout@v6\n\n    - name: Initialize CodeQL\n      uses: github/codeql-action/init@v4\n      with:\n        languages: cpp\n\n    - name: Install Dependencies\n      run: sudo apt-get install --no-install-recommends libncursesw5-dev libhwloc-dev libnl-3-dev libnl-genl-3-dev libsensors-dev libcap-dev\n\n    - name: Bootstrap\n      run: ./autogen.sh\n\n    - name: Configure\n      run: ./configure --enable-werror --enable-openvz --enable-vserver --enable-ancient-vserver --enable-unicode --enable-hwloc --enable-delayacct --enable-sensors --enable-capabilities\n\n    - name: Build\n      run: make\n\n    - name: Perform CodeQL Analysis\n      uses: github/codeql-action/analyze@v4\n"
  },
  {
    "path": ".github/workflows/coverity.yml",
    "content": "name: \"Coverity\"\non:\n  schedule:\n    - cron:  '0 1 * * *'\n\njobs:\n  analyze:\n    name: Analyze\n    if: github.repository == 'htop-dev/htop'\n    runs-on: ubuntu-latest\n\n    steps:\n    - name: Checkout repository\n      uses: actions/checkout@v6\n\n    - name: Download Coverity\n      run: |\n        cd ..\n        wget -q https://scan.coverity.com/download/linux64 --post-data \"token=${TOKEN}&project=htop.dev\" -O coverity_tool.tgz\n        mkdir coverity\n        tar xzf coverity_tool.tgz --strip 1 -C coverity\n        echo \"$(pwd)/coverity/bin\" >> $GITHUB_PATH\n      env:\n        TOKEN: ${{ secrets.COVERITY_TOKEN }}\n\n    - name: Bootstrap\n      run: ./autogen.sh\n\n    - name: Configure\n      run: ./configure\n\n    - name: Build with Coverity\n      run: cov-build --dir cov-int make\n\n    - name: Submit the result to Coverity\n      run: |\n        tar czvf htop.tgz cov-int\n        curl \\\n          --form token=${TOKEN} \\\n          --form email=nathans@redhat.com \\\n          --form file=@htop.tgz \\\n          --form version=${GITHUB_SHA} \\\n          https://scan.coverity.com/builds?project=htop.dev\n      env:\n        TOKEN: ${{ secrets.COVERITY_TOKEN }}\n"
  },
  {
    "path": ".github/workflows/htoprc",
    "content": "# Beware! This file is rewritten by htop when settings are changed in the interface.\n# The parser is also very primitive, and not human-friendly.\nhtop_version=3.3.0-dev\nconfig_reader_min_version=3\nfields=0 48 17 18 38 39 40 2 46 47 49 1\nhide_kernel_threads=1\nhide_userland_threads=0\nhide_running_in_container=0\nshadow_other_users=1\nshow_thread_names=1\nshow_program_path=1\nhighlight_base_name=1\nhighlight_deleted_exe=1\nshadow_distribution_path_prefix=1\nhighlight_megabytes=1\nhighlight_threads=1\nhighlight_changes=1\nhighlight_changes_delay_secs=5\nfind_comm_in_cmdline=1\nstrip_exe_from_cmdline=1\nshow_merged_command=1\nheader_margin=1\nscreen_tabs=1\ndetailed_cpu_time=1\ncpu_count_from_one=0\nshow_cpu_usage=1\nshow_cpu_frequency=1\nshow_cpu_temperature=1\ndegree_fahrenheit=0\nupdate_process_names=1\naccount_guest_in_cpu_meter=1\ncolor_scheme=0\nenable_mouse=1\ndelay=15\nhide_function_bar=0\nheader_layout=two_50_50\ncolumn_meters_0=LeftCPUs4 CPU Memory Swap MemorySwap Zram Clock Date DateTime ZFSARC ZFSCARC SELinux SystemdUser FileDescriptors\ncolumn_meter_modes_0=1 1 1 1 1 1 2 2 2 2 2 2 2 2\ncolumn_meters_1=RightCPUs4 Tasks LoadAverage Load Uptime Battery System HugePages Hostname Blank PressureStallCPUSome PressureStallIOSome PressureStallIOFull PressureStallIRQFull PressureStallMemorySome PressureStallMemoryFull DiskIO NetworkIO\ncolumn_meter_modes_1=1 2 2 2 2 2 2 1 2 2 2 2 2 2 2 2 2 2\ntree_view=0\nsort_key=46\ntree_sort_key=0\nsort_direction=-1\ntree_sort_direction=1\ntree_view_always_by_pid=0\nall_branches_collapsed=0\nscreen:Main=PID USER PRIORITY NICE M_VIRT M_RESIDENT M_SHARE STATE PERCENT_CPU PERCENT_MEM TIME Command\n.sort_key=PERCENT_CPU\n.tree_sort_key=PID\n.tree_view_always_by_pid=0\n.tree_view=0\n.sort_direction=-1\n.tree_sort_direction=1\n.all_branches_collapsed=0\nscreen:I/O=PID USER IO_PRIORITY IO_RATE IO_READ_RATE IO_WRITE_RATE PERCENT_SWAP_DELAY PERCENT_IO_DELAY Command\n.sort_key=IO_RATE\n.tree_sort_key=PID\n.tree_view_always_by_pid=0\n.tree_view=0\n.sort_direction=-1\n.tree_sort_direction=1\n.all_branches_collapsed=0\nscreen:Dump=PID SCHEDULERPOLICY SYSCR CGROUP CCGROUP OOM IO_PRIORITY PERCENT_CPU_DELAY CTXT SECATTR CWD AUTOGROUP_ID Command\n.sort_key=PID\n.tree_sort_key=PID\n.tree_view_always_by_pid=0\n.tree_view=0\n.sort_direction=1\n.tree_sort_direction=1\n.all_branches_collapsed=0\n"
  },
  {
    "path": ".gitignore",
    "content": "# the binaries:\nhtop\npcp-htop\n\n# all object files\n*.o\n\n# skip all backups\n*.bak\n*~\n.*.sw?\n\n# skip coverage files\n*.gcda\n*/*.gcda\n*.gcno\n*/*.gcno\n*.h.gch\n*/.dirstamp\n\n# automake/autoconf related files\n.deps/\nMakefile\nMakefile.in\nINSTALL\naclocal.m4\nautom4te.cache/\ncompile\nconf*/\nconfig.guess\nconfig.h\nconfig.h.in\nconfig.log\nconfig.status\nconfig.cache\nconfig.sub\nconfigure\ndepcomp\nhtop.1\npcp-htop.5\ninstall-sh\nlibtool\nltmain.sh\nm4/\nmissing\nstamp-h1\n\n# files related to valgrind/callgrind\ncallgrind.out.*\n\n# IDE workspace configurations\n/.idea/\n/.vscode/\n\n# Language Servers\n/.cache/clangd/\n"
  },
  {
    "path": ".travis.yml",
    "content": "language: c\n\ncompiler:\n  - clang\n  - gcc\n\nos:\n  - freebsd\n\nscript:\n  - ./autogen.sh\n  - ./configure --enable-werror\n  - make -k\n  - make distcheck DISTCHECK_CONFIGURE_FLAGS=--enable-werror\n  - sudo make install\n  - make installcheck\n"
  },
  {
    "path": "AUTHORS",
    "content": "Originally authored by:\n Hisham H. Muhammad\n\nCurrently maintained by the htop dev team:\n Benny Baumann\n Christian Göttsche\n Daniel Lange\n Nathan Scott\n\nFor the full list of contributors see:\n git log --format=\"%aN\" | sort -u\n"
  },
  {
    "path": "Action.c",
    "content": "/*\nhtop - Action.c\n(C) 2015 Hisham H. Muhammad\nReleased under the GNU GPLv2+, see the COPYING file\nin the source distribution for its full text.\n*/\n\n#include \"config.h\" // IWYU pragma: keep\n\n#include \"Action.h\"\n\n#include <assert.h>\n#include <pwd.h>\n#include <stdbool.h>\n#include <stdlib.h>\n#include <string.h>\n\n#include \"CRT.h\"\n#include \"CategoriesPanel.h\"\n#include \"CommandScreen.h\"\n#include \"DynamicColumn.h\"\n#include \"EnvScreen.h\"\n#include \"FunctionBar.h\"\n#include \"Hashtable.h\"\n#include \"IncSet.h\"\n#include \"InfoScreen.h\"\n#include \"ListItem.h\"\n#include \"Macros.h\"\n#include \"MainPanel.h\"\n#include \"MemoryMeter.h\"\n#include \"OpenFilesScreen.h\"\n#include \"Platform.h\"\n#include \"Process.h\"\n#include \"ProcessLocksScreen.h\"\n#include \"ProvideCurses.h\"\n#include \"Row.h\"\n#include \"RowField.h\"\n#include \"Scheduling.h\"\n#include \"ScreenManager.h\"\n#include \"SignalsPanel.h\"\n#include \"Table.h\"\n#include \"TraceScreen.h\"\n#include \"UsersTable.h\"\n#include \"Vector.h\"\n#include \"XUtils.h\"\n\n#if (defined(HAVE_LIBHWLOC) || defined(HAVE_AFFINITY))\n#include \"Affinity.h\"\n#include \"AffinityPanel.h\"\n#endif\n\n\nObject* Action_pickFromVector(State* st, Panel* list, int x, bool follow) {\n   MainPanel* mainPanel = st->mainPanel;\n   Header* header = st->header;\n   Machine* host = st->host;\n\n   int y = ((Panel*)mainPanel)->y;\n   ScreenManager* scr = ScreenManager_new(header, host, st, false);\n   scr->allowFocusChange = false;\n   ScreenManager_add(scr, list, x);\n   ScreenManager_add(scr, (Panel*)mainPanel, -1);\n   Panel* panelFocus;\n   int ch;\n   bool unfollow = false;\n   int row = follow ? MainPanel_selectedRow(mainPanel) : -1;\n   if (follow && host->activeTable->following == -1) {\n      host->activeTable->following = row;\n      unfollow = true;\n   }\n   ScreenManager_run(scr, &panelFocus, &ch, NULL);\n   if (unfollow) {\n      host->activeTable->following = -1;\n   }\n   ScreenManager_delete(scr);\n   Panel_move((Panel*)mainPanel, 0, y);\n   Panel_resize((Panel*)mainPanel, COLS, LINES - y - 1);\n   if (panelFocus == list && ch == 13) {\n      if (follow) {\n         const Row* selected = (const Row*)Panel_getSelected((Panel*)mainPanel);\n         if (selected && selected->id == row)\n            return Panel_getSelected(list);\n\n         beep();\n      } else {\n         return Panel_getSelected(list);\n      }\n   }\n\n   return NULL;\n}\n\n// ----------------------------------------\n\nstatic void Action_runSetup(State* st) {\n   const Settings* settings = st->host->settings;\n   ScreenManager* scr = ScreenManager_new(st->header, st->host, st, true);\n   CategoriesPanel_new(scr, st->header, st->host);\n   ScreenManager_run(scr, NULL, NULL, \"Setup\");\n   ScreenManager_delete(scr);\n   if (settings->changed) {\n      CRT_setMouse(settings->enableMouse);\n      Header_writeBackToSettings(st->header);\n   }\n}\n\nstatic bool changePriority(MainPanel* panel, int delta) {\n   bool anyTagged;\n   bool ok = MainPanel_foreachRow(panel, Process_rowChangePriorityBy, (Arg) { .i = delta }, &anyTagged);\n   if (!ok)\n      beep();\n   return anyTagged;\n}\n\nstatic void addUserToVector(ht_key_t key, void* userCast, void* panelCast) {\n   const char* user = userCast;\n   Panel* panel = panelCast;\n   Panel_add(panel, (Object*) ListItem_new(user, key));\n}\n\nbool Action_setUserOnly(const char* userName, uid_t* userId) {\n   const struct passwd* user = getpwnam(userName);\n   if (user) {\n      *userId = user->pw_uid;\n      return true;\n   }\n   *userId = (uid_t)-1;\n   return false;\n}\n\nstatic void tagAllChildren(Panel* panel, Row* parent) {\n   parent->tag = true;\n   int parent_id = parent->id;\n   for (int i = 0; i < Panel_size(panel); i++) {\n      Row* row = (Row*) Panel_get(panel, i);\n      if (!row->tag && Row_isChildOf(row, parent_id)) {\n         tagAllChildren(panel, row);\n      }\n   }\n}\n\nstatic bool expandCollapse(Panel* panel) {\n   Row* row = (Row*) Panel_getSelected(panel);\n   if (!row)\n      return false;\n\n   row->showChildren = !row->showChildren;\n   return true;\n}\n\nstatic bool collapseIntoParent(Panel* panel) {\n   const Row* r = (Row*) Panel_getSelected(panel);\n   if (!r)\n      return false;\n\n   int parent_id = Row_getGroupOrParent(r);\n   for (int i = 0; i < Panel_size(panel); i++) {\n      Row* row = (Row*) Panel_get(panel, i);\n      if (row->id == parent_id) {\n         row->showChildren = false;\n         Panel_setSelected(panel, i);\n         return true;\n      }\n   }\n   return false;\n}\n\nHtop_Reaction Action_setSortKey(Settings* settings, ProcessField sortKey) {\n   ScreenSettings_setSortKey(settings->ss, (RowField) sortKey);\n   return HTOP_REFRESH | HTOP_SAVE_SETTINGS | HTOP_UPDATE_PANELHDR | HTOP_KEEP_FOLLOWING;\n}\n\n// ----------------------------------------\n\nstatic bool Action_writeableProcess(State* st) {\n   const Settings* settings = st->host->settings;\n   bool readonly = Settings_isReadonly() || settings->ss->dynamic;\n   return !readonly;\n}\n\nstatic bool Action_readableProcess(State* st) {\n   const Settings* settings = st->host->settings;\n   return !settings->ss->dynamic;\n}\n\nstatic Htop_Reaction actionSetSortColumn(State* st) {\n   Htop_Reaction reaction = HTOP_OK;\n   Panel* sortPanel = Panel_new(0, 0, 0, 0, Class(ListItem), true, FunctionBar_newEnterEsc(\"Sort   \", \"Cancel \"));\n   Panel_setHeader(sortPanel, \"Sort by\");\n   Machine* host = st->host;\n   Settings* settings = host->settings;\n   const RowField* fields = settings->ss->fields;\n   Hashtable* dynamicColumns = settings->dynamicColumns;\n   for (int i = 0; fields[i]; i++) {\n      char* name = NULL;\n      if (fields[i] >= ROW_DYNAMIC_FIELDS) {\n         DynamicColumn* column = Hashtable_get(dynamicColumns, fields[i]);\n         if (!column)\n            continue;\n         name = xStrdup(column->caption ? column->caption : column->name);\n      } else {\n         name = String_trim(Process_fields[fields[i]].name);\n      }\n      Panel_add(sortPanel, (Object*) ListItem_new(name, fields[i]));\n      if (fields[i] == ScreenSettings_getActiveSortKey(settings->ss))\n         Panel_setSelected(sortPanel, i);\n\n      free(name);\n   }\n   const ListItem* field = (const ListItem*) Action_pickFromVector(st, sortPanel, 14, false);\n   if (field) {\n      reaction |= Action_setSortKey(settings, field->key);\n   }\n   Object_delete(sortPanel);\n\n   host->activeTable->needsSort = true;\n\n   return reaction | HTOP_REFRESH | HTOP_REDRAW_BAR | HTOP_UPDATE_PANELHDR;\n}\n\nstatic Htop_Reaction actionSortByPID(State* st) {\n   return Action_setSortKey(st->host->settings, PID);\n}\n\nstatic Htop_Reaction actionSortByMemory(State* st) {\n   return Action_setSortKey(st->host->settings, PERCENT_MEM);\n}\n\nstatic Htop_Reaction actionSortByCPU(State* st) {\n   return Action_setSortKey(st->host->settings, PERCENT_CPU);\n}\n\nstatic Htop_Reaction actionSortByTime(State* st) {\n   return Action_setSortKey(st->host->settings, TIME);\n}\n\nstatic Htop_Reaction actionToggleKernelThreads(State* st) {\n   Settings* settings = st->host->settings;\n   settings->hideKernelThreads = !settings->hideKernelThreads;\n   settings->lastUpdate++;\n\n   Machine_scanTables(st->host); // needed to not have a visible delay showing wrong data\n\n   return HTOP_RECALCULATE | HTOP_SAVE_SETTINGS | HTOP_KEEP_FOLLOWING;\n}\n\nstatic Htop_Reaction actionToggleUserlandThreads(State* st) {\n   Settings* settings = st->host->settings;\n   settings->hideUserlandThreads = !settings->hideUserlandThreads;\n   settings->lastUpdate++;\n\n   Machine_scanTables(st->host); // needed to not have a visible delay showing wrong data\n\n   return HTOP_RECALCULATE | HTOP_SAVE_SETTINGS | HTOP_KEEP_FOLLOWING;\n}\n\nstatic Htop_Reaction actionToggleRunningInContainer(State* st) {\n   Settings* settings = st->host->settings;\n   settings->hideRunningInContainer = !settings->hideRunningInContainer;\n   settings->lastUpdate++;\n\n   return HTOP_RECALCULATE | HTOP_SAVE_SETTINGS | HTOP_KEEP_FOLLOWING;\n}\n\nstatic Htop_Reaction actionToggleProgramPath(State* st) {\n   Settings* settings = st->host->settings;\n   settings->showProgramPath = !settings->showProgramPath;\n   settings->lastUpdate++;\n\n   return HTOP_REFRESH | HTOP_SAVE_SETTINGS | HTOP_KEEP_FOLLOWING;\n}\n\nstatic Htop_Reaction actionToggleMergedCommand(State* st) {\n   Settings* settings = st->host->settings;\n   settings->showMergedCommand = !settings->showMergedCommand;\n   settings->lastUpdate++;\n\n   return HTOP_REFRESH | HTOP_SAVE_SETTINGS | HTOP_KEEP_FOLLOWING | HTOP_UPDATE_PANELHDR;\n}\n\nstatic Htop_Reaction actionToggleTreeView(State* st) {\n   Machine* host = st->host;\n   ScreenSettings* ss = host->settings->ss;\n   ss->treeView = !ss->treeView;\n\n   if (!ss->allBranchesCollapsed)\n      Table_expandTree(host->activeTable);\n\n   host->activeTable->needsSort = true;\n\n   return HTOP_REFRESH | HTOP_SAVE_SETTINGS | HTOP_KEEP_FOLLOWING | HTOP_REDRAW_BAR | HTOP_UPDATE_PANELHDR;\n}\n\nstatic Htop_Reaction actionToggleHideMeters(State* st) {\n   st->hideMeters = !st->hideMeters;\n   return HTOP_RESIZE | HTOP_KEEP_FOLLOWING;\n}\n\nstatic Htop_Reaction actionExpandOrCollapseAllBranches(State* st) {\n   Machine* host = st->host;\n   ScreenSettings* ss = host->settings->ss;\n   if (!ss->treeView) {\n      return HTOP_OK;\n   }\n   ss->allBranchesCollapsed = !ss->allBranchesCollapsed;\n   if (ss->allBranchesCollapsed)\n      Table_collapseAllBranches(host->activeTable);\n   else\n      Table_expandTree(host->activeTable);\n   return HTOP_REFRESH | HTOP_SAVE_SETTINGS;\n}\n\nstatic Htop_Reaction actionIncFilter(State* st) {\n   Machine* host = st->host;\n   IncSet* inc = (st->mainPanel)->inc;\n   IncSet_activate(inc, INC_FILTER, (Panel*)st->mainPanel);\n   host->activeTable->incFilter = IncSet_filter(inc);\n   return HTOP_REFRESH | HTOP_KEEP_FOLLOWING;\n}\n\nstatic Htop_Reaction actionIncSearch(State* st) {\n   IncSet_reset(st->mainPanel->inc, INC_SEARCH);\n   IncSet_activate(st->mainPanel->inc, INC_SEARCH, (Panel*)st->mainPanel);\n   return HTOP_REFRESH | HTOP_KEEP_FOLLOWING;\n}\n\nstatic Htop_Reaction actionHigherPriority(State* st) {\n   if (!Action_writeableProcess(st))\n      return HTOP_OK;\n\n   bool changed = changePriority(st->mainPanel, -1);\n   return changed ? HTOP_REFRESH : HTOP_OK;\n}\n\nstatic Htop_Reaction actionLowerPriority(State* st) {\n   if (!Action_writeableProcess(st))\n      return HTOP_OK;\n\n   bool changed = changePriority(st->mainPanel, 1);\n   return changed ? HTOP_REFRESH : HTOP_OK;\n}\n\nstatic Htop_Reaction actionInvertSortOrder(State* st) {\n   Machine* host = st->host;\n   ScreenSettings_invertSortOrder(host->settings->ss);\n   host->activeTable->needsSort = true;\n   return HTOP_REFRESH | HTOP_SAVE_SETTINGS | HTOP_KEEP_FOLLOWING | HTOP_UPDATE_PANELHDR;\n}\n\nstatic Htop_Reaction actionExpandOrCollapse(State* st) {\n   if (!st->host->settings->ss->treeView)\n      return HTOP_OK;\n\n   bool changed = expandCollapse((Panel*)st->mainPanel);\n   return changed ? HTOP_RECALCULATE : HTOP_OK;\n}\n\nstatic Htop_Reaction actionCollapseIntoParent(State* st) {\n   if (!st->host->settings->ss->treeView) {\n      return HTOP_OK;\n   }\n   bool changed = collapseIntoParent((Panel*)st->mainPanel);\n   return changed ? HTOP_RECALCULATE : HTOP_OK;\n}\n\nstatic Htop_Reaction actionExpandCollapseOrSortColumn(State* st) {\n   return st->host->settings->ss->treeView ? actionExpandOrCollapse(st) : actionSetSortColumn(st);\n}\n\nstatic inline void setActiveScreen(Settings* settings, State* st, unsigned int ssIdx) {\n   assert(settings->ssIndex == ssIdx);\n   Machine* host = st->host;\n\n   settings->ss = settings->screens[ssIdx];\n   if (!settings->ss->table)\n      settings->ss->table = host->processTable;\n   host->activeTable = settings->ss->table;\n\n   // set correct functionBar - readonly if requested, and/or with non-process screens\n   bool readonly = Settings_isReadonly() || (host->activeTable != host->processTable);\n   MainPanel_setFunctionBar(st->mainPanel, readonly);\n}\n\nstatic Htop_Reaction actionNextScreen(State* st) {\n   Settings* settings = st->host->settings;\n   settings->ssIndex++;\n   if (settings->ssIndex == settings->nScreens) {\n      settings->ssIndex = 0;\n   }\n   setActiveScreen(settings, st, settings->ssIndex);\n   return HTOP_UPDATE_PANELHDR | HTOP_REFRESH | HTOP_REDRAW_BAR;\n}\n\nstatic Htop_Reaction actionPrevScreen(State* st) {\n   Settings* settings = st->host->settings;\n   if (settings->ssIndex == 0) {\n      settings->ssIndex = settings->nScreens - 1;\n   } else {\n      settings->ssIndex--;\n   }\n   setActiveScreen(settings, st, settings->ssIndex);\n   return HTOP_UPDATE_PANELHDR | HTOP_REFRESH | HTOP_REDRAW_BAR;\n}\n\nHtop_Reaction Action_setScreenTab(State* st, int x) {\n   Settings* settings = st->host->settings;\n   const int bracketWidth = (int)strlen(\"[]\");\n\n   if (x < SCREEN_TAB_MARGIN_LEFT) {\n      return 0;\n   }\n\n   int rem = x - SCREEN_TAB_MARGIN_LEFT;\n   for (unsigned int i = 0; i < settings->nScreens; i++) {\n      const char* tab = settings->screens[i]->heading;\n      int width = rem >= bracketWidth ? (int)strnlen(tab, rem - bracketWidth + 1) : 0;\n      if (width >= rem - bracketWidth + 1) {\n         settings->ssIndex = i;\n         setActiveScreen(settings, st, i);\n         return HTOP_UPDATE_PANELHDR | HTOP_REFRESH | HTOP_REDRAW_BAR;\n      }\n\n      rem -= bracketWidth + width;\n      if (rem < SCREEN_TAB_COLUMN_GAP) {\n         return 0;\n      }\n\n      rem -= SCREEN_TAB_COLUMN_GAP;\n   }\n   return 0;\n}\n\nstatic Htop_Reaction actionQuit(ATTR_UNUSED State* st) {\n   return HTOP_QUIT;\n}\n\nstatic Htop_Reaction actionSetAffinity(State* st) {\n   if (!Action_writeableProcess(st))\n      return HTOP_OK;\n\n   Machine* host = st->host;\n   if (host->activeCPUs == 1)\n      return HTOP_OK;\n\n#if (defined(HAVE_LIBHWLOC) || defined(HAVE_AFFINITY))\n   const Row* row = (const Row*) Panel_getSelected((Panel*)st->mainPanel);\n   if (!row)\n      return HTOP_OK;\n\n   Affinity* affinity1 = Affinity_rowGet(row, host);\n   if (!affinity1)\n      return HTOP_OK;\n\n   int width;\n   Panel* affinityPanel = AffinityPanel_new(host, affinity1, &width);\n   Affinity_delete(affinity1);\n\n   const void* set = Action_pickFromVector(st, affinityPanel, width, true);\n   if (set) {\n      Affinity* affinity2 = AffinityPanel_getAffinity(affinityPanel, host);\n      bool ok = MainPanel_foreachRow(st->mainPanel, Affinity_rowSet, (Arg) { .v = affinity2 }, NULL);\n      if (!ok)\n         beep();\n      Affinity_delete(affinity2);\n   }\n   Object_delete(affinityPanel);\n   return HTOP_REFRESH | HTOP_REDRAW_BAR | HTOP_UPDATE_PANELHDR;\n#else\n   return HTOP_OK;\n#endif\n}\n\n#ifdef SCHEDULER_SUPPORT\nstatic Htop_Reaction actionSetSchedPolicy(State* st) {\n   if (!Action_writeableProcess(st))\n      return HTOP_KEEP_FOLLOWING;\n\n   static int preSelectedPolicy = SCHEDULINGPANEL_INITSELECTEDPOLICY;\n   static int preSelectedPriority = SCHEDULINGPANEL_INITSELECTEDPRIORITY;\n\n   Panel* schedPanel = Scheduling_newPolicyPanel(preSelectedPolicy);\n\n   const ListItem* policy;\n   for (;;) {\n      policy = (const ListItem*) Action_pickFromVector(st, schedPanel, 18, true);\n\n      if (!policy || policy->key != -1)\n         break;\n\n      Scheduling_togglePolicyPanelResetOnFork(schedPanel);\n   }\n\n   if (policy) {\n      preSelectedPolicy = policy->key;\n\n      Panel* prioPanel = Scheduling_newPriorityPanel(policy->key, preSelectedPriority);\n      if (prioPanel) {\n         const ListItem* prio = (const ListItem*) Action_pickFromVector(st, prioPanel, 14, true);\n         if (prio)\n            preSelectedPriority = prio->key;\n\n         Panel_delete((Object*) prioPanel);\n      }\n\n      SchedulingArg v = { .policy = preSelectedPolicy, .priority = preSelectedPriority };\n\n      bool ok = MainPanel_foreachRow(st->mainPanel, Scheduling_rowSetPolicy, (Arg) { .v = &v }, NULL);\n      if (!ok)\n         beep();\n   }\n\n   Panel_delete((Object*)schedPanel);\n\n   return HTOP_REFRESH | HTOP_REDRAW_BAR | HTOP_KEEP_FOLLOWING;\n}\n#endif  /* SCHEDULER_SUPPORT */\n\nstatic Htop_Reaction actionKill(State* st) {\n   if (!Action_writeableProcess(st))\n      return HTOP_OK;\n\n   static int preSelectedSignal = SIGNALSPANEL_INITSELECTEDSIGNAL;\n\n   Panel* signalsPanel = SignalsPanel_new(preSelectedSignal);\n   const ListItem* sgn = (ListItem*) Action_pickFromVector(st, signalsPanel, 14, true);\n   if (sgn && sgn->key != 0) {\n      preSelectedSignal = sgn->key;\n      Panel_setHeader((Panel*)st->mainPanel, \"Sending...\");\n      Panel_draw((Panel*)st->mainPanel, false, true, true, State_hideFunctionBar(st));\n      refresh();\n      bool ok = MainPanel_foreachRow(st->mainPanel, Process_rowSendSignal, (Arg) { .i = sgn->key }, NULL);\n      if (!ok) {\n         beep();\n      }\n      napms(500);\n   }\n   Panel_delete((Object*)signalsPanel);\n\n   return HTOP_REFRESH | HTOP_REDRAW_BAR | HTOP_UPDATE_PANELHDR;\n}\n\nstatic Htop_Reaction actionFilterByUser(State* st) {\n   Panel* usersPanel = Panel_new(0, 0, 0, 0, Class(ListItem), true, FunctionBar_newEnterEsc(\"Show   \", \"Cancel \"));\n   Panel_setHeader(usersPanel, \"Show processes of:\");\n   Machine* host = st->host;\n   UsersTable_foreach(host->usersTable, addUserToVector, usersPanel);\n   Vector_insertionSort(usersPanel->items);\n   ListItem* allUsers = ListItem_new(\"All users\", -1);\n   Panel_insert(usersPanel, 0, (Object*) allUsers);\n   const ListItem* picked = (ListItem*) Action_pickFromVector(st, usersPanel, 19, false);\n   if (picked) {\n      if (picked == allUsers) {\n         host->userId = (uid_t)-1;\n      } else {\n         Action_setUserOnly(ListItem_getRef(picked), &host->userId);\n      }\n   }\n   Panel_delete((Object*)usersPanel);\n   return HTOP_REFRESH | HTOP_REDRAW_BAR | HTOP_UPDATE_PANELHDR;\n}\n\nHtop_Reaction Action_follow(State* st) {\n   st->host->activeTable->following = MainPanel_selectedRow(st->mainPanel);\n   Panel_setSelectionColor((Panel*)st->mainPanel, PANEL_SELECTION_FOLLOW);\n   return HTOP_KEEP_FOLLOWING;\n}\n\nstatic Htop_Reaction actionSetup(State* st) {\n   Action_runSetup(st);\n   return HTOP_REFRESH | HTOP_REDRAW_BAR | HTOP_UPDATE_PANELHDR | HTOP_RESIZE;\n}\n\nstatic Htop_Reaction actionLsof(State* st) {\n   if (!Action_writeableProcess(st))\n      return HTOP_OK;\n\n   const Process* p = (Process*) Panel_getSelected((Panel*)st->mainPanel);\n   if (!p)\n      return HTOP_OK;\n\n   assert(Object_isA((const Object*) p, (const ObjectClass*) &Process_class));\n\n   OpenFilesScreen* ofs = OpenFilesScreen_new(p);\n   InfoScreen_run((InfoScreen*)ofs);\n   OpenFilesScreen_delete((Object*)ofs);\n   clear();\n   CRT_enableDelay();\n   return HTOP_REFRESH | HTOP_REDRAW_BAR;\n}\n\nstatic Htop_Reaction actionShowLocks(State* st) {\n   if (!Action_readableProcess(st))\n      return HTOP_OK;\n\n   const Process* p = (Process*) Panel_getSelected((Panel*)st->mainPanel);\n   if (!p)\n      return HTOP_OK;\n\n   assert(Object_isA((const Object*) p, (const ObjectClass*) &Process_class));\n\n   ProcessLocksScreen* pls = ProcessLocksScreen_new(p);\n   InfoScreen_run((InfoScreen*)pls);\n   ProcessLocksScreen_delete((Object*)pls);\n   clear();\n   CRT_enableDelay();\n   return HTOP_REFRESH | HTOP_REDRAW_BAR;\n}\n\nstatic Htop_Reaction actionStrace(State* st) {\n   if (!Action_writeableProcess(st))\n      return HTOP_OK;\n\n   const Process* p = (Process*) Panel_getSelected((Panel*)st->mainPanel);\n   if (!p)\n      return HTOP_OK;\n\n   assert(Object_isA((const Object*) p, (const ObjectClass*) &Process_class));\n\n   TraceScreen* ts = TraceScreen_new(p);\n   bool ok = TraceScreen_forkTracer(ts);\n   if (ok) {\n      InfoScreen_run((InfoScreen*)ts);\n   }\n   TraceScreen_delete((Object*)ts);\n   clear();\n   CRT_enableDelay();\n   return HTOP_REFRESH | HTOP_REDRAW_BAR;\n}\n\nstatic Htop_Reaction actionTag(State* st) {\n   Row* r = (Row*) Panel_getSelected((Panel*)st->mainPanel);\n   if (!r)\n      return HTOP_OK;\n\n   Row_toggleTag(r);\n   Panel_onKey((Panel*)st->mainPanel, KEY_DOWN);\n   return HTOP_OK;\n}\n\nstatic Htop_Reaction actionRedraw(ATTR_UNUSED State* st) {\n   clear();\n   // HTOP_RECALCULATE here to make Ctrl-L also refresh the data and not only redraw\n   return HTOP_RECALCULATE | HTOP_REFRESH | HTOP_REDRAW_BAR;\n}\n\nstatic Htop_Reaction actionTogglePauseUpdate(State* st) {\n   st->pauseUpdate = !st->pauseUpdate;\n   return HTOP_REFRESH | HTOP_REDRAW_BAR | HTOP_KEEP_FOLLOWING;\n}\n\nstatic const struct {\n   const char* key;\n   bool roInactive;\n   const char* info;\n} helpLeft[] = {\n   { .key = \"      #: \",  .roInactive = false, .info = \"hide/show header meters\" },\n   { .key = \"    Tab: \",  .roInactive = false, .info = \"switch to next screen tab\" },\n   { .key = \" Arrows: \",  .roInactive = false, .info = \"scroll process list\" },\n   { .key = \" Digits: \",  .roInactive = false, .info = \"incremental PID search\" },\n   { .key = \"   F3 /: \",  .roInactive = false, .info = \"incremental name search\" },\n   { .key = \"   F4 \\\\: \", .roInactive = false, .info = \"incremental name filtering\" },\n   { .key = \"   F5 t: \",  .roInactive = false, .info = \"tree view\" },\n   { .key = \"      p: \",  .roInactive = false, .info = \"toggle program path\" },\n   { .key = \"      m: \",  .roInactive = false, .info = \"toggle merged command\" },\n   { .key = \"      Z: \",  .roInactive = false, .info = \"pause/resume process updates\" },\n   { .key = \"      u: \",  .roInactive = false, .info = \"show processes of a single user\" },\n   { .key = \"      H: \",  .roInactive = false, .info = \"hide/show user process threads\" },\n   { .key = \"      K: \",  .roInactive = false, .info = \"hide/show kernel threads\" },\n   { .key = \"      O: \",  .roInactive = false, .info = \"hide/show processes in containers\" },\n   { .key = \"      F: \",  .roInactive = false, .info = \"cursor follows process\" },\n   { .key = \"  + - *: \",  .roInactive = false, .info = \"expand/collapse tree/toggle all\" },\n   { .key = \"N P M T: \",  .roInactive = false, .info = \"sort by PID, CPU%, MEM% or TIME\" },\n   { .key = \"      I: \",  .roInactive = false, .info = \"invert sort order\" },\n   { .key = \" F6 > .: \",  .roInactive = false, .info = \"select sort column\" },\n   { .key = NULL, .info = NULL }\n};\n\nstatic const struct {\n   const char* key;\n   bool roInactive;\n   const char* info;\n} helpRight[] = {\n   { .key = \"  S-Tab: \", .roInactive = false, .info = \"switch to previous screen tab\" },\n   { .key = \"  Space: \", .roInactive = false, .info = \"tag process\" },\n   { .key = \"      c: \", .roInactive = false, .info = \"tag process and its children\" },\n   { .key = \"      U: \", .roInactive = false, .info = \"untag all processes\" },\n   { .key = \"   F9 k: \", .roInactive = true,  .info = \"kill process/tagged processes\" },\n   { .key = \"   F7 ]: \", .roInactive = true,  .info = \"higher priority (root only)\" },\n   { .key = \"   F8 [: \", .roInactive = true,  .info = \"lower priority (+ nice)\" },\n#if (defined(HAVE_LIBHWLOC) || defined(HAVE_AFFINITY))\n   { .key = \"      a: \", .roInactive = true, .info = \"set CPU affinity\" },\n#endif\n   { .key = \"      e: \", .roInactive = false, .info = \"show process environment\" },\n   { .key = \"      i: \", .roInactive = true,  .info = \"set IO priority\" },\n   { .key = \"      l: \", .roInactive = true,  .info = \"list open files with lsof\" },\n   { .key = \"      x: \", .roInactive = false, .info = \"list file locks of process\" },\n   { .key = \"      s: \", .roInactive = true,  .info = \"trace syscalls with strace\" },\n   { .key = \"      w: \", .roInactive = false, .info = \"wrap process command in multiple lines\" },\n#ifdef SCHEDULER_SUPPORT\n   { .key = \"      Y: \", .roInactive = true,  .info = \"set scheduling policy\" },\n#endif\n   { .key = \" F2 C S: \", .roInactive = false, .info = \"setup\" },\n   { .key = \" F1 h ?: \", .roInactive = false, .info = \"show this help screen\" },\n   { .key = \"  F10 q: \", .roInactive = false, .info = \"quit\" },\n   { .key = NULL, .info = NULL }\n};\n\nstatic inline void addattrstr( int attr, const char* str) {\n   attrset(attr);\n   addstr(str);\n}\n\nstatic Htop_Reaction actionHelp(State* st) {\n   clear();\n   attrset(CRT_colors[HELP_BOLD]);\n\n   for (int i = 0; i < LINES - 1; i++)\n      mvhline(i, 0, ' ', COLS);\n\n   int line = 0;\n\n   mvaddstr(line++, 0, \"htop \" VERSION \" - \" COPYRIGHT);\n   mvaddstr(line++, 0, \"Released under the GNU GPLv2+. See 'man' page for more info.\");\n\n   attrset(CRT_colors[DEFAULT_COLOR]);\n   line++;\n   mvaddstr(line++, 0, \"CPU usage bar: \");\n\n#define addbartext(attr, prefix, text)               \\\n   do {                                              \\\n      addattrstr(CRT_colors[DEFAULT_COLOR], prefix); \\\n      addattrstr(attr, text);                        \\\n   } while(0)\n\n   addattrstr(CRT_colors[BAR_BORDER], \"[\");\n   addbartext(CRT_colors[CPU_NICE_TEXT], \"\", \"low\");\n   addbartext(CRT_colors[CPU_NORMAL], \"/\", \"normal\");\n   addbartext(CRT_colors[CPU_SYSTEM], \"/\", \"kernel\");\n   if (st->host->settings->detailedCPUTime) {\n      addbartext(CRT_colors[CPU_IRQ], \"/\", \"irq\");\n      addbartext(CRT_colors[CPU_SOFTIRQ], \"/\", \"soft-irq\");\n      addbartext(CRT_colors[CPU_STEAL], \"/\", \"steal\");\n      addbartext(CRT_colors[CPU_GUEST], \"/\", \"guest\");\n      addbartext(CRT_colors[CPU_IOWAIT], \"/\", \"io-wait\");\n      addbartext(CRT_colors[BAR_SHADOW], \" \", \"used%\");\n   } else {\n      addbartext(CRT_colors[CPU_GUEST], \"/\", \"virt\");\n      addbartext(CRT_colors[BAR_SHADOW], \"                             \", \"used%\");\n   }\n   addattrstr(CRT_colors[BAR_BORDER], \"]\");\n\n   attrset(CRT_colors[DEFAULT_COLOR]);\n   mvaddstr(line++, 0, \"Memory bar:    \");\n   addattrstr(CRT_colors[BAR_BORDER], \"[\");\n   // memory classes are OS-specific and provided in their <os>/Platform.c implementation\n   // ideal length of memory bar == 56 chars. Any length < 45 requires padding to 45.\n   // [0        1         2         3         4         5      ]\n   // [12345678901234567890123456789012345678901234567890123456]\n   // [                                            ^    5      ]\n   // [class1/class2/class3/.../classN               used/total]\n   int barTxtLen = 0;\n   for (unsigned int i = 0; i < Platform_numberOfMemoryClasses; i++) {\n      if (!st->host->settings->showCachedMemory && Platform_memoryClasses[i].countsAsCache)\n         continue; // skip reclaimable cache memory classes if \"show cached memory\" is not ticked\n      if (!Platform_memoryClasses[i].countsAsUsed && !Platform_memoryClasses[i].countsAsCache)\n         continue; // skip available memory class (special case for the Linux platform)\n      addbartext(CRT_colors[Platform_memoryClasses[i].color], (i == 0 ? \"\" : \"/\"), Platform_memoryClasses[i].label);\n      barTxtLen += (i == 0 ? 0 : 1) + strlen (Platform_memoryClasses[i].label);\n   }\n   for (int i = barTxtLen; i < 45; i++)\n      addattrstr(CRT_colors[BAR_SHADOW], \" \"); // pad to 45 chars if necessary\n   addbartext(CRT_colors[BAR_SHADOW], \" \", \"used\");\n   addbartext(CRT_colors[BAR_SHADOW], \"/\", \"total\");\n   addattrstr(CRT_colors[BAR_BORDER], \"]\");\n\n   attrset(CRT_colors[DEFAULT_COLOR]);\n   mvaddstr(line++, 0, \"Swap bar:      \");\n   addattrstr(CRT_colors[BAR_BORDER], \"[\");\n   addbartext(CRT_colors[SWAP], \"\", \"used\");\n#ifdef HTOP_LINUX\n   addbartext(CRT_colors[SWAP_CACHE], \"/\", \"cache\");\n   addbartext(CRT_colors[SWAP_FRONTSWAP], \"/\", \"frontswap\");\n#else\n   addbartext(CRT_colors[BAR_SHADOW], \"                \", \"\");\n#endif\n   addbartext(CRT_colors[BAR_SHADOW], \"                          \", \"used\");\n   addbartext(CRT_colors[BAR_SHADOW], \"/\", \"total\");\n   addattrstr(CRT_colors[BAR_BORDER], \"]\");\n\n   line++;\n\n#undef addbartext\n\n   attrset(CRT_colors[DEFAULT_COLOR]);\n   mvaddstr(line++, 0, \"Type and layout of header meters are configurable in the setup screen.\");\n   if (CRT_colorScheme == COLORSCHEME_MONOCHROME) {\n      mvaddstr(line, 0, \"In monochrome, meters display as different chars, in order: |#*@$%&.\");\n   }\n   line++;\n\n#define addattrstatestr(attr, state, desc)              \\\n   do {                                                 \\\n      addattrstr(attr, state);                          \\\n      addattrstr(CRT_colors[DEFAULT_COLOR], \": \" desc); \\\n   } while(0)\n\n   mvaddstr(line, 0, \"Process state: \");\n   addattrstatestr(CRT_colors[PROCESS_RUN_STATE], \"R\", \"running; \");\n   addattrstatestr(CRT_colors[PROCESS_SHADOW], \"S\", \"sleeping; \");\n   addattrstatestr(CRT_colors[PROCESS_RUN_STATE], \"t\", \"traced/stopped; \");\n   addattrstatestr(CRT_colors[PROCESS_D_STATE], \"Z\", \"zombie; \");\n   addattrstatestr(CRT_colors[PROCESS_D_STATE], \"D\", \"disk sleep\");\n   attrset(CRT_colors[DEFAULT_COLOR]);\n\n#undef addattrstatestr\n\n   line += 2;\n\n   const bool readonly = Settings_isReadonly();\n\n   int item;\n   for (item = 0; helpLeft[item].key; item++) {\n      attrset((helpLeft[item].roInactive && readonly) ? CRT_colors[HELP_SHADOW] : CRT_colors[DEFAULT_COLOR]);\n      mvaddstr(line + item, 10, helpLeft[item].info);\n      attrset((helpLeft[item].roInactive && readonly) ? CRT_colors[HELP_SHADOW] : CRT_colors[HELP_BOLD]);\n      mvaddstr(line + item, 1,  helpLeft[item].key);\n      if (String_eq(helpLeft[item].key, \"      H: \")) {\n         attrset((helpLeft[item].roInactive && readonly) ? CRT_colors[HELP_SHADOW] : CRT_colors[PROCESS_THREAD]);\n         mvaddstr(line + item, 33, \"threads\");\n      } else if (String_eq(helpLeft[item].key, \"      K: \")) {\n         attrset((helpLeft[item].roInactive && readonly) ? CRT_colors[HELP_SHADOW] : CRT_colors[PROCESS_THREAD]);\n         mvaddstr(line + item, 27, \"threads\");\n      }\n   }\n   int leftHelpItems = item;\n\n   for (item = 0; helpRight[item].key; item++) {\n      attrset((helpRight[item].roInactive && readonly) ? CRT_colors[HELP_SHADOW] : CRT_colors[HELP_BOLD]);\n      mvaddstr(line + item, 43, helpRight[item].key);\n      attrset((helpRight[item].roInactive && readonly) ? CRT_colors[HELP_SHADOW] : CRT_colors[DEFAULT_COLOR]);\n      mvaddstr(line + item, 52, helpRight[item].info);\n   }\n   line += MAXIMUM(leftHelpItems, item);\n   line++;\n\n   attrset(CRT_colors[HELP_BOLD]);\n   mvaddstr(line++, 0, \"Press any key to return.\");\n   attrset(CRT_colors[DEFAULT_COLOR]);\n   refresh();\n   CRT_readKey();\n   clear();\n\n   return HTOP_RECALCULATE | HTOP_REDRAW_BAR | HTOP_KEEP_FOLLOWING;\n}\n\nstatic Htop_Reaction actionUntagAll(State* st) {\n   for (int i = 0; i < Panel_size((Panel*)st->mainPanel); i++) {\n      Row* row = (Row*) Panel_get((Panel*)st->mainPanel, i);\n      row->tag = false;\n   }\n   return HTOP_REFRESH;\n}\n\nstatic Htop_Reaction actionTagAllChildren(State* st) {\n   Row* row = (Row*) Panel_getSelected((Panel*)st->mainPanel);\n   if (!row)\n      return HTOP_OK;\n\n   tagAllChildren((Panel*)st->mainPanel, row);\n   return HTOP_OK;\n}\n\nstatic Htop_Reaction actionShowEnvScreen(State* st) {\n   if (!Action_readableProcess(st))\n      return HTOP_OK;\n\n   Process* p = (Process*) Panel_getSelected((Panel*)st->mainPanel);\n   if (!p)\n      return HTOP_OK;\n\n   assert(Object_isA((const Object*) p, (const ObjectClass*) &Process_class));\n\n   EnvScreen* es = EnvScreen_new(p);\n   InfoScreen_run((InfoScreen*)es);\n   EnvScreen_delete((Object*)es);\n   clear();\n   CRT_enableDelay();\n   return HTOP_REFRESH | HTOP_REDRAW_BAR;\n}\n\nstatic Htop_Reaction actionShowCommandScreen(State* st) {\n   if (!Action_readableProcess(st))\n      return HTOP_OK;\n\n   Process* p = (Process*) Panel_getSelected((Panel*)st->mainPanel);\n   if (!p)\n      return HTOP_OK;\n\n   assert(Object_isA((const Object*) p, (const ObjectClass*) &Process_class));\n\n   CommandScreen* cmdScr = CommandScreen_new(p);\n   InfoScreen_run((InfoScreen*)cmdScr);\n   CommandScreen_delete((Object*)cmdScr);\n   clear();\n   CRT_enableDelay();\n   return HTOP_REFRESH | HTOP_REDRAW_BAR;\n}\n\nvoid Action_setBindings(Htop_Action* keys) {\n   keys[' '] = actionTag;\n   keys['#'] = actionToggleHideMeters;\n   keys['*'] = actionExpandOrCollapseAllBranches;\n   keys['+'] = actionExpandOrCollapse;\n   keys[','] = actionSetSortColumn;\n   keys['-'] = actionExpandOrCollapse;\n   keys['.'] = actionSetSortColumn;\n   keys['/'] = actionIncSearch;\n   keys['<'] = actionSetSortColumn;\n   keys['='] = actionExpandOrCollapse;\n   keys['>'] = actionSetSortColumn;\n   keys['?'] = actionHelp;\n   keys['C'] = actionSetup;\n   keys['F'] = Action_follow;\n   keys['H'] = actionToggleUserlandThreads;\n   keys['I'] = actionInvertSortOrder;\n   keys['K'] = actionToggleKernelThreads;\n   keys['M'] = actionSortByMemory;\n   keys['N'] = actionSortByPID;\n   keys['O'] = actionToggleRunningInContainer;\n   keys['P'] = actionSortByCPU;\n   keys['S'] = actionSetup;\n   keys['T'] = actionSortByTime;\n   keys['U'] = actionUntagAll;\n#ifdef SCHEDULER_SUPPORT\n   keys['Y'] = actionSetSchedPolicy;\n#endif\n   keys['Z'] = actionTogglePauseUpdate;\n   keys['['] = actionLowerPriority;\n   keys['\\014'] = actionRedraw; // Ctrl+L\n   keys['\\177'] = actionCollapseIntoParent;\n   keys['\\\\'] = actionIncFilter;\n   keys[']'] = actionHigherPriority;\n   keys['a'] = actionSetAffinity;\n   keys['c'] = actionTagAllChildren;\n   keys['e'] = actionShowEnvScreen;\n   keys['h'] = actionHelp;\n   keys['k'] = actionKill;\n   keys['l'] = actionLsof;\n   keys['m'] = actionToggleMergedCommand;\n   keys['p'] = actionToggleProgramPath;\n   keys['q'] = actionQuit;\n   keys['s'] = actionStrace;\n   keys['t'] = actionToggleTreeView;\n   keys['u'] = actionFilterByUser;\n   keys['w'] = actionShowCommandScreen;\n   keys['x'] = actionShowLocks;\n   keys[KEY_F(1)] = actionHelp;\n   keys[KEY_F(2)] = actionSetup;\n   keys[KEY_F(3)] = actionIncSearch;\n   keys[KEY_F(4)] = actionIncFilter;\n   keys[KEY_F(5)] = actionToggleTreeView;\n   keys[KEY_F(6)] = actionSetSortColumn;\n   keys[KEY_F(7)] = actionHigherPriority;\n   keys[KEY_F(8)] = actionLowerPriority;\n   keys[KEY_F(9)] = actionKill;\n   keys[KEY_F(10)] = actionQuit;\n   keys[KEY_F(18)] = actionExpandCollapseOrSortColumn;\n   keys[KEY_RECLICK] = actionExpandOrCollapse;\n   keys[KEY_SHIFT_TAB] = actionPrevScreen;\n   keys['\\t'] = actionNextScreen;\n}\n"
  },
  {
    "path": "Action.h",
    "content": "#ifndef HEADER_Action\n#define HEADER_Action\n/*\nhtop - Action.h\n(C) 2015 Hisham H. Muhammad\nReleased under the GNU GPLv2+, see the COPYING file\nin the source distribution for its full text.\n*/\n\n#include <stdbool.h>\n#include <sys/types.h>\n\n#include \"Header.h\"\n#include \"Machine.h\"\n#include \"Object.h\"\n#include \"Panel.h\"\n#include \"Process.h\"\n#include \"Settings.h\"\n\n\ntypedef enum {\n   HTOP_OK              = 0x00,\n   HTOP_REFRESH         = 0x01,\n   HTOP_RECALCULATE     = 0x02 | HTOP_REFRESH,\n   HTOP_SAVE_SETTINGS   = 0x04,\n   HTOP_KEEP_FOLLOWING  = 0x08,\n   HTOP_QUIT            = 0x10,\n   HTOP_REDRAW_BAR      = 0x20,\n   HTOP_UPDATE_PANELHDR = 0x40 | HTOP_REFRESH,\n   HTOP_RESIZE          = 0x80 | HTOP_REFRESH | HTOP_REDRAW_BAR | HTOP_UPDATE_PANELHDR,\n} Htop_Reaction;\n\nstruct MainPanel_; // IWYU pragma: keep\n\ntypedef struct State_ {\n   Machine* host;\n   struct MainPanel_* mainPanel;\n   Header* header;\n   const char* failedUpdate; /* function bar diagnostic or NULL if no error */\n   bool pauseUpdate;\n   bool hideSelection;\n   bool hideMeters;\n} State;\n\nstatic inline bool State_hideFunctionBar(const State* st) {\n   const Settings* settings = st->host->settings;\n   return settings->hideFunctionBar == 2 || (settings->hideFunctionBar == 1 && st->hideSelection);\n}\n\ntypedef Htop_Reaction (*Htop_Action)(State* st);\n\nObject* Action_pickFromVector(State* st, Panel* list, int x, bool follow);\n\nbool Action_setUserOnly(const char* userName, uid_t* userId);\n\nHtop_Reaction Action_setSortKey(Settings* settings, ProcessField sortKey);\n\nHtop_Reaction Action_setScreenTab(State* st, int x);\n\nHtop_Reaction Action_follow(State* st);\n\nvoid Action_setBindings(Htop_Action* keys);\n\n#endif\n"
  },
  {
    "path": "Affinity.c",
    "content": "/*\nhtop - Affinity.c\n(C) 2004-2011 Hisham H. Muhammad\n(C) 2020 Red Hat, Inc.  All Rights Reserved.\nReleased under the GNU GPLv2+, see the COPYING file\nin the source distribution for its full text.\n*/\n\n#include \"config.h\" // IWYU pragma: keep\n\n#include \"Affinity.h\"\n\n#include <assert.h>\n#include <stdlib.h>\n\n#include \"Process.h\"\n#include \"XUtils.h\"\n\n#if defined(HAVE_LIBHWLOC)\n#include <hwloc.h>\n#include <hwloc/bitmap.h>\n#ifdef __linux__\n#define HTOP_HWLOC_CPUBIND_FLAG HWLOC_CPUBIND_THREAD\n#else\n#define HTOP_HWLOC_CPUBIND_FLAG HWLOC_CPUBIND_PROCESS\n#endif\n#elif defined(HAVE_AFFINITY)\n#include <sched.h>\n#endif\n\n\nAffinity* Affinity_new(Machine* host) {\n   Affinity* this = xCalloc(1, sizeof(Affinity));\n   this->size = 8;\n   this->cpus = xCalloc(this->size, sizeof(unsigned int));\n   this->host = host;\n   return this;\n}\n\nvoid Affinity_delete(Affinity* this) {\n   free(this->cpus);\n   free(this);\n}\n\nvoid Affinity_add(Affinity* this, unsigned int id) {\n   if (this->used == this->size) {\n      this->size *= 2;\n      this->cpus = xRealloc(this->cpus, sizeof(unsigned int) * this->size);\n   }\n   this->cpus[this->used] = id;\n   this->used++;\n}\n\n#if defined(HAVE_LIBHWLOC)\n\nstatic Affinity* Affinity_get(const Process* p, Machine* host) {\n   hwloc_cpuset_t cpuset = hwloc_bitmap_alloc();\n   bool ok = (hwloc_get_proc_cpubind(host->topology, Process_getPid(p), cpuset, HTOP_HWLOC_CPUBIND_FLAG) == 0);\n   Affinity* affinity = NULL;\n   if (ok) {\n      affinity = Affinity_new(host);\n      if (hwloc_bitmap_last(cpuset) == -1) {\n         for (unsigned int i = 0; i < host->existingCPUs; i++) {\n            Affinity_add(affinity, i);\n         }\n      } else {\n         int id;\n         hwloc_bitmap_foreach_begin(id, cpuset)\n            Affinity_add(affinity, (unsigned)id);\n         hwloc_bitmap_foreach_end();\n      }\n   }\n   hwloc_bitmap_free(cpuset);\n   return affinity;\n}\n\nstatic bool Affinity_set(Process* p, Arg arg) {\n   Affinity* this = arg.v;\n   hwloc_cpuset_t cpuset = hwloc_bitmap_alloc();\n   for (unsigned int i = 0; i < this->used; i++) {\n      hwloc_bitmap_set(cpuset, this->cpus[i]);\n   }\n   bool ok = (hwloc_set_proc_cpubind(this->host->topology, Process_getPid(p), cpuset, HTOP_HWLOC_CPUBIND_FLAG) == 0);\n   hwloc_bitmap_free(cpuset);\n   return ok;\n}\n\n#elif defined(HAVE_AFFINITY)\n\nstatic Affinity* Affinity_get(const Process* p, Machine* host) {\n   cpu_set_t cpuset;\n   bool ok = (sched_getaffinity(Process_getPid(p), sizeof(cpu_set_t), &cpuset) == 0);\n   if (!ok)\n      return NULL;\n\n   Affinity* affinity = Affinity_new(host);\n   for (unsigned int i = 0; i < host->existingCPUs; i++) {\n      if (CPU_ISSET(i, &cpuset)) {\n         Affinity_add(affinity, i);\n      }\n   }\n   return affinity;\n}\n\nstatic bool Affinity_set(Process* p, Arg arg) {\n   Affinity* this = arg.v;\n   cpu_set_t cpuset;\n   CPU_ZERO(&cpuset);\n   for (unsigned int i = 0; i < this->used; i++) {\n      CPU_SET(this->cpus[i], &cpuset);\n   }\n   bool ok = (sched_setaffinity(Process_getPid(p), sizeof(unsigned long), &cpuset) == 0);\n   return ok;\n}\n\n#endif\n\n#if defined(HAVE_LIBHWLOC) || defined(HAVE_AFFINITY)\n\nbool Affinity_rowSet(Row* row, Arg arg) {\n   Process* p = (Process*) row;\n   assert(Object_isA((const Object*) p, (const ObjectClass*) &Process_class));\n   return Affinity_set(p, arg);\n}\n\nAffinity* Affinity_rowGet(const Row* row, Machine* host) {\n   const Process* p = (const Process*) row;\n   assert(Object_isA((const Object*) p, (const ObjectClass*) &Process_class));\n   return Affinity_get(p, host);\n}\n\n#endif /* HAVE_LIBHWLOC || HAVE_AFFINITY */\n"
  },
  {
    "path": "Affinity.h",
    "content": "#ifndef HEADER_Affinity\n#define HEADER_Affinity\n/*\nhtop - Affinity.h\n(C) 2004-2011 Hisham H. Muhammad\n(C) 2020,2023 Red Hat, Inc.  All Rights Reserved.\nReleased under the GNU GPLv2+, see the COPYING file\nin the source distribution for its full text.\n*/\n\n#include \"Machine.h\"\n\n#if defined(HAVE_LIBHWLOC) || defined(HAVE_AFFINITY)\n#include <stdbool.h>\n\n#include \"Object.h\"\n#include \"Row.h\"\n#endif\n\n\n#if defined(HAVE_LIBHWLOC) && defined(HAVE_AFFINITY)\n#error hwloc and affinity support are mutual exclusive.\n#endif\n\n\ntypedef struct Affinity_ {\n   Machine* host;\n   unsigned int size;\n   unsigned int used;\n   unsigned int* cpus;\n} Affinity;\n\nAffinity* Affinity_new(Machine* host);\n\nvoid Affinity_delete(Affinity* this);\n\nvoid Affinity_add(Affinity* this, unsigned int id);\n\n#if defined(HAVE_LIBHWLOC) || defined(HAVE_AFFINITY)\n\nAffinity* Affinity_rowGet(const Row* row, Machine* host);\n\nbool Affinity_rowSet(Row* row, Arg arg);\n\n#endif /* HAVE_LIBHWLOC || HAVE_AFFINITY */\n\n#endif\n"
  },
  {
    "path": "AffinityPanel.c",
    "content": "/*\nhtop - AffinityPanel.c\n(C) 2004-2011 Hisham H. Muhammad\nReleased under the GNU GPLv2+, see the COPYING file\nin the source distribution for its full text.\n*/\n\n#include \"config.h\" // IWYU pragma: keep\n\n#include \"AffinityPanel.h\"\n\n#include <assert.h>\n#include <limits.h> // IWYU pragma: keep\n#include <stdbool.h>\n#include <stdlib.h>\n#include <string.h>\n\n#include \"CRT.h\"\n#include \"FunctionBar.h\"\n#include \"Object.h\"\n#include \"ProvideCurses.h\"\n#include \"RichString.h\"\n#include \"Settings.h\"\n#include \"Vector.h\"\n#include \"XUtils.h\"\n\n#ifdef HAVE_LIBHWLOC\n#include <hwloc.h>\n#include <hwloc/bitmap.h>\n#endif\n\n\ntypedef struct MaskItem_ {\n   Object super;\n   char* text;\n   char* indent; /* used also as an condition whether this is a tree node */\n   int value; /* tri-state: 0 - off, 1 - some set, 2 - all set */\n   int sub_tree; /* tri-state: 0 - no sub-tree, 1 - open sub-tree, 2 - closed sub-tree */\n   Vector* children;\n   #ifdef HAVE_LIBHWLOC\n   bool ownCpuset;\n   hwloc_bitmap_t cpuset;\n   #else\n   int cpu;\n   #endif\n} MaskItem;\n\nstatic void MaskItem_delete(Object* cast) {\n   MaskItem* this = (MaskItem*) cast;\n   free(this->text);\n   free(this->indent);\n   Vector_delete(this->children);\n   #ifdef HAVE_LIBHWLOC\n   if (this->ownCpuset)\n      hwloc_bitmap_free(this->cpuset);\n   #endif\n   free(this);\n}\n\nstatic void MaskItem_display(const Object* cast, RichString* out) {\n   const MaskItem* this = (const MaskItem*)cast;\n   assert (this != NULL);\n   RichString_appendAscii(out, CRT_colors[CHECK_BOX], \"[\");\n   if (this->value == 2) {\n      RichString_appendAscii(out, CRT_colors[CHECK_MARK], \"x\");\n   } else if (this->value == 1) {\n      RichString_appendAscii(out, CRT_colors[CHECK_MARK], \"o\");\n   } else {\n      RichString_appendAscii(out, CRT_colors[CHECK_MARK], \" \");\n   }\n   RichString_appendAscii(out, CRT_colors[CHECK_BOX], \"]\");\n   RichString_appendAscii(out, CRT_colors[CHECK_TEXT], \" \");\n   if (this->indent) {\n      RichString_appendWide(out, CRT_colors[PROCESS_TREE], this->indent);\n      RichString_appendWide(out, CRT_colors[PROCESS_TREE],\n                            this->sub_tree == 2\n                            ? CRT_treeStr[TREE_STR_OPEN]\n                            : CRT_treeStr[TREE_STR_SHUT]);\n      RichString_appendAscii(out, CRT_colors[CHECK_TEXT], \" \");\n   }\n   RichString_appendWide(out, CRT_colors[CHECK_TEXT], this->text);\n}\n\nstatic const ObjectClass MaskItem_class = {\n   .display = MaskItem_display,\n   .delete  = MaskItem_delete\n};\n\n#ifdef HAVE_LIBHWLOC\n\nstatic MaskItem* MaskItem_newMask(const char* text, const char* indent, hwloc_bitmap_t cpuset, bool owner) {\n   MaskItem* this = AllocThis(MaskItem);\n   this->text = xStrdup(text);\n   this->indent = xStrdup(indent); /* nonnull for tree node */\n   this->value = 0;\n   this->ownCpuset = owner;\n   this->cpuset = cpuset;\n   this->sub_tree = hwloc_bitmap_weight(cpuset) > 1 ? 1 : 0;\n   this->children = Vector_new(Class(MaskItem), true, VECTOR_DEFAULT_SIZE);\n   return this;\n}\n\n#endif\n\nstatic MaskItem* MaskItem_newSingleton(const char* text, int cpu, bool isSet) {\n   MaskItem* this = AllocThis(MaskItem);\n   this->text = xStrdup(text);\n   this->indent = NULL; /* not a tree node */\n   this->sub_tree = 0;\n   this->children = Vector_new(Class(MaskItem), true, VECTOR_DEFAULT_SIZE);\n\n   #ifdef HAVE_LIBHWLOC\n   this->ownCpuset = true;\n   this->cpuset = hwloc_bitmap_alloc();\n   hwloc_bitmap_set(this->cpuset, cpu);\n   #else\n   this->cpu = cpu;\n   #endif\n   this->value = isSet ? 2 : 0;\n\n   return this;\n}\n\ntypedef struct AffinityPanel_ {\n   Panel super;\n   Machine* host;\n   bool topoView;\n   Vector* cpuids;\n   unsigned width;\n\n   #ifdef HAVE_LIBHWLOC\n   MaskItem* topoRoot;\n   hwloc_const_cpuset_t allCpuset;\n   hwloc_bitmap_t workCpuset;\n   #endif\n} AffinityPanel;\n\nstatic void AffinityPanel_delete(Object* cast) {\n   AffinityPanel* this = (AffinityPanel*) cast;\n   Vector_delete(this->cpuids);\n   #ifdef HAVE_LIBHWLOC\n   hwloc_bitmap_free(this->workCpuset);\n   MaskItem_delete((Object*) this->topoRoot);\n   #endif\n   Panel_done(&this->super);\n   free(this);\n}\n\n#ifdef HAVE_LIBHWLOC\n\nstatic void AffinityPanel_updateItem(AffinityPanel* this, MaskItem* item) {\n   Panel* super = &this->super;\n\n   item->value = hwloc_bitmap_isincluded(item->cpuset, this->workCpuset) ? 2 :\n                 hwloc_bitmap_intersects(item->cpuset, this->workCpuset) ? 1 : 0;\n\n   Panel_add(super, (Object*) item);\n}\n\nstatic void AffinityPanel_updateTopo(AffinityPanel* this, MaskItem* item) {\n   AffinityPanel_updateItem(this, item);\n\n   if (item->sub_tree == 2)\n      return;\n\n   for (int i = 0; i < Vector_size(item->children); i++)\n      AffinityPanel_updateTopo(this, (MaskItem*) Vector_get(item->children, i));\n}\n\n#endif\n\nstatic void AffinityPanel_update(AffinityPanel* this, bool keepSelected) {\n   Panel* super = &this->super;\n\n   FunctionBar_setLabel(super->currentBar, KEY_F(3), this->topoView ? \"Collapse/Expand\" : \"\");\n\n   int oldSelected = Panel_getSelectedIndex(super);\n   Panel_prune(super);\n\n   #ifdef HAVE_LIBHWLOC\n   if (this->topoView) {\n      AffinityPanel_updateTopo(this, this->topoRoot);\n   } else {\n      for (int i = 0; i < Vector_size(this->cpuids); i++) {\n         AffinityPanel_updateItem(this, (MaskItem*) Vector_get(this->cpuids, i));\n      }\n   }\n   #else\n   Panel_splice(super, this->cpuids);\n   #endif\n\n   if (keepSelected)\n      Panel_setSelected(super, oldSelected);\n\n   super->needsRedraw = true;\n}\n\nstatic HandlerResult AffinityPanel_eventHandler(Panel* super, int ch) {\n   AffinityPanel* this = (AffinityPanel*) super;\n\n   HandlerResult result = IGNORED;\n   MaskItem* selected = (MaskItem*) Panel_getSelected(super);\n\n   bool keepSelected = true;\n\n   switch (ch) {\n      case KEY_MOUSE:\n      case KEY_RECLICK:\n      case ' ':\n         if (!selected) {\n            return result;\n         }\n\n         #ifdef HAVE_LIBHWLOC\n         if (selected->value == 2) {\n            /* Item was selected, so remove this mask from the top cpuset. */\n            hwloc_bitmap_andnot(this->workCpuset, this->workCpuset, selected->cpuset);\n            selected->value = 0;\n         } else {\n            /* Item was not or only partial selected, so set all bits from this object\n               in the top cpuset. */\n            hwloc_bitmap_or(this->workCpuset, this->workCpuset, selected->cpuset);\n            selected->value = 2;\n         }\n         #else\n         selected->value = selected->value ? 0 : 2; /* toggle between 0 and 2 */\n         #endif\n\n         result = HANDLED;\n         break;\n\n#ifdef HAVE_LIBHWLOC\n\n      case KEY_F(1):\n         hwloc_bitmap_copy(this->workCpuset, this->allCpuset);\n         result = HANDLED;\n         break;\n\n      case KEY_F(2):\n         this->topoView = !this->topoView;\n         keepSelected = false;\n\n         result = HANDLED;\n         break;\n\n      case KEY_F(3):\n      case '-':\n      case '+':\n         if (!selected) {\n            break;\n         }\n\n         if (selected->sub_tree) {\n            selected->sub_tree = 1 + !(selected->sub_tree - 1); /* toggle between 1 and 2 */\n         }\n\n         result = HANDLED;\n         break;\n\n#endif\n\n      case 0x0a:\n      case 0x0d:\n      case KEY_ENTER:\n         result = BREAK_LOOP;\n         break;\n   }\n\n   if (HANDLED == result)\n      AffinityPanel_update(this, keepSelected);\n\n   return result;\n}\n\n#ifdef HAVE_LIBHWLOC\n\nstatic MaskItem* AffinityPanel_addObject(AffinityPanel* this, hwloc_obj_t obj, unsigned indent, MaskItem* parent) {\n   const char* type_name = hwloc_obj_type_string(obj->type);\n   const char* index_prefix = \"#\";\n   unsigned depth = obj->depth;\n   unsigned index = obj->logical_index;\n   size_t off = 0, left = 10 * depth;\n   char buf[64], indent_buf[left + 1];\n\n   if (obj->type == HWLOC_OBJ_PU) {\n      index = Settings_cpuId(this->host->settings, obj->os_index);\n      type_name = \"CPU\";\n      index_prefix = \"\";\n   }\n\n   indent_buf[0] = '\\0';\n   if (depth > 0) {\n      for (unsigned i = 1; i < depth; i++) {\n         xSnprintf(&indent_buf[off], left, \"%s  \", (indent & (1U << i)) ? CRT_treeStr[TREE_STR_VERT] : \" \");\n         size_t len = strlen(&indent_buf[off]);\n         off += len;\n         left -= len;\n      }\n      xSnprintf(&indent_buf[off], left, \"%s\",\n                obj->next_sibling ? CRT_treeStr[TREE_STR_RTEE] : CRT_treeStr[TREE_STR_BEND]);\n      // Uncomment when further appending to indent_buf\n      //size_t len = strlen(&indent_buf[off]);\n      //off += len;\n      //left -= len;\n   }\n\n   xSnprintf(buf, sizeof(buf), \"%s %s%u\", type_name, index_prefix, index);\n\n   MaskItem* item = MaskItem_newMask(buf, indent_buf, obj->complete_cpuset, false);\n   if (parent)\n      Vector_add(parent->children, item);\n\n   if (item->sub_tree && parent && parent->sub_tree == 1) {\n      /* if obj is fully included or fully excluded, collapse the item */\n      hwloc_bitmap_t result = hwloc_bitmap_alloc();\n      hwloc_bitmap_and(result, obj->complete_cpuset, this->workCpuset);\n      int weight = hwloc_bitmap_weight(result);\n      hwloc_bitmap_free(result);\n      if (weight == 0 || weight == (hwloc_bitmap_weight(this->workCpuset) + hwloc_bitmap_weight(obj->complete_cpuset))) {\n         item->sub_tree = 2;\n      }\n   }\n\n   /* \"[x] \" + \"|- \" * depth + (\"- \")?(if root node) + name */\n   unsigned int indent_width = 4 + 3 * depth + (2 * !depth);\n   assert(sizeof(buf) <= INT_MAX - indent_width);\n   unsigned int width = indent_width + (unsigned int)strlen(buf);\n   if (width > this->width) {\n      this->width = width;\n   }\n\n   return item;\n}\n\nstatic MaskItem* AffinityPanel_buildTopology(AffinityPanel* this, hwloc_obj_t obj, unsigned indent, MaskItem* parent) {\n   MaskItem* item = AffinityPanel_addObject(this, obj, indent, parent);\n   if (obj->next_sibling) {\n      indent |= (1U << obj->depth);\n   } else {\n      indent &= ~(1U << obj->depth);\n   }\n\n   for (unsigned i = 0; i < obj->arity; i++) {\n      AffinityPanel_buildTopology(this, obj->children[i], indent, item);\n   }\n\n   return parent == NULL ? item : NULL;\n}\n\n#endif\n\nconst PanelClass AffinityPanel_class = {\n   .super = {\n      .extends = Class(Panel),\n      .delete = AffinityPanel_delete\n   },\n   .eventHandler = AffinityPanel_eventHandler\n};\n\nstatic const char* const AffinityPanelFunctions[] = {\n   \"Set    \",\n   \"Cancel \",\n   #ifdef HAVE_LIBHWLOC\n   \"All\",\n   \"Topology\",\n   \"               \",\n   #endif\n   NULL\n};\nstatic const char* const AffinityPanelKeys[] = {\"Enter\", \"Esc\", \"F1\", \"F2\", \"F3\"};\nstatic const int AffinityPanelEvents[] = {13, 27, KEY_F(1), KEY_F(2), KEY_F(3)};\n\nPanel* AffinityPanel_new(Machine* host, const Affinity* affinity, int* width) {\n   AffinityPanel* this = AllocThis(AffinityPanel);\n   Panel* super = &this->super;\n\n   Panel_init(super, 1, 1, 1, 1, Class(MaskItem), false, FunctionBar_new(AffinityPanelFunctions, AffinityPanelKeys, AffinityPanelEvents));\n\n   this->host = host;\n   /* defaults to 15, this also includes the gap between the panels,\n    * but this will be added by the caller */\n   this->width = 14;\n\n   this->cpuids   = Vector_new(Class(MaskItem), true, VECTOR_DEFAULT_SIZE);\n\n   #ifdef HAVE_LIBHWLOC\n   this->topoView = host->settings->topologyAffinity;\n   #else\n   this->topoView = false;\n   #endif\n\n   #ifdef HAVE_LIBHWLOC\n   this->allCpuset  = hwloc_topology_get_complete_cpuset(host->topology);\n   this->workCpuset = hwloc_bitmap_alloc();\n   #endif\n\n   Panel_setHeader(super, \"Use CPUs:\");\n\n   unsigned int curCpu = 0;\n   for (unsigned int i = 0; i < host->existingCPUs; i++) {\n      if (!Machine_isCPUonline(host, i))\n         continue;\n\n      char number[16];\n      xSnprintf(number, 9, \"CPU %d\", Settings_cpuId(host->settings, i));\n      unsigned int cpu_width = 4 + (unsigned int)strlen(number);\n      if (cpu_width > this->width) {\n         this->width = cpu_width;\n      }\n\n      bool isSet = false;\n      if (curCpu < affinity->used && affinity->cpus[curCpu] == i) {\n         #ifdef HAVE_LIBHWLOC\n         hwloc_bitmap_set(this->workCpuset, i);\n         #endif\n         isSet = true;\n         curCpu++;\n      }\n\n      MaskItem* cpuItem = MaskItem_newSingleton(number, i, isSet);\n      Vector_add(this->cpuids, (Object*) cpuItem);\n   }\n\n   #ifdef HAVE_LIBHWLOC\n   this->topoRoot = AffinityPanel_buildTopology(this, hwloc_get_root_obj(host->topology), 0, NULL);\n   #endif\n\n   if (width) {\n      *width = this->width;\n   }\n\n   AffinityPanel_update(this, false);\n\n   return super;\n}\n\nAffinity* AffinityPanel_getAffinity(Panel* super, Machine* host) {\n   const AffinityPanel* this = (AffinityPanel*) super;\n   Affinity* affinity = Affinity_new(host);\n\n   #ifdef HAVE_LIBHWLOC\n   int i;\n   hwloc_bitmap_foreach_begin(i, this->workCpuset)\n      Affinity_add(affinity, (unsigned)i);\n   hwloc_bitmap_foreach_end();\n   #else\n   for (int i = 0; i < Vector_size(this->cpuids); i++) {\n      const MaskItem* item = (const MaskItem*)Vector_get(this->cpuids, i);\n      if (item->value) {\n         Affinity_add(affinity, item->cpu);\n      }\n   }\n   #endif\n\n   return affinity;\n}\n"
  },
  {
    "path": "AffinityPanel.h",
    "content": "#ifndef HEADER_AffinityPanel\n#define HEADER_AffinityPanel\n/*\nhtop - AffinityPanel.h\n(C) 2004-2011 Hisham H. Muhammad\nReleased under the GNU GPLv2+, see the COPYING file\nin the source distribution for its full text.\n*/\n\n#include \"Affinity.h\"\n#include \"Machine.h\"\n#include \"Panel.h\"\n\n\nextern const PanelClass AffinityPanel_class;\n\nPanel* AffinityPanel_new(Machine* host, const Affinity* affinity, int* width);\n\nAffinity* AffinityPanel_getAffinity(Panel* super, Machine* host);\n\n#endif\n"
  },
  {
    "path": "AvailableColumnsPanel.c",
    "content": "/*\nhtop - AvailableColumnsPanel.c\n(C) 2004-2011 Hisham H. Muhammad\nReleased under the GNU GPLv2+, see the COPYING file\nin the source distribution for its full text.\n*/\n\n#include \"config.h\" // IWYU pragma: keep\n\n#include \"AvailableColumnsPanel.h\"\n\n#include <assert.h>\n#include <ctype.h>\n#include <stdbool.h>\n#include <stdlib.h>\n\n#include \"ColumnsPanel.h\"\n#include \"DynamicColumn.h\"\n#include \"FunctionBar.h\"\n#include \"Hashtable.h\"\n#include \"ListItem.h\"\n#include \"Object.h\"\n#include \"Platform.h\"\n#include \"Process.h\"\n#include \"ProvideCurses.h\"\n#include \"RowField.h\"\n#include \"XUtils.h\"\n\n\nstatic const char* const AvailableColumnsFunctions[] = {\"      \", \"      \", \"      \", \"      \", \"Add   \", \"      \", \"      \", \"      \", \"      \", \"Done  \", NULL};\n\nstatic void AvailableColumnsPanel_delete(Object* object) {\n   AvailableColumnsPanel* this = (AvailableColumnsPanel*) object;\n   Panel_done(&this->super);\n   free(this);\n}\n\nstatic void AvailableColumnsPanel_insert(AvailableColumnsPanel* this, int at, int key) {\n   const char* name;\n   if (key >= ROW_DYNAMIC_FIELDS)\n      name = DynamicColumn_name(key);\n   else\n      name = Process_fields[key].name;\n   Panel_insert(this->columns, at, (Object*) ListItem_new(name, key));\n}\n\nstatic HandlerResult AvailableColumnsPanel_eventHandler(Panel* super, int ch) {\n   AvailableColumnsPanel* this = (AvailableColumnsPanel*) super;\n   HandlerResult result = IGNORED;\n\n   switch (ch) {\n      case 13:\n      case KEY_ENTER:\n      case KEY_F(5): {\n         const ListItem* selected = (ListItem*) Panel_getSelected(super);\n         if (!selected)\n            break;\n\n         int at = Panel_getSelectedIndex(this->columns);\n         AvailableColumnsPanel_insert(this, at, selected->key);\n         Panel_setSelected(this->columns, at + 1);\n         ColumnsPanel_update(this->columns);\n         result = HANDLED;\n         break;\n      }\n      default:\n         if (0 < ch && ch < 255 && isgraph((unsigned char)ch))\n            result = Panel_selectByTyping(super, ch);\n         break;\n   }\n   return result;\n}\n\nconst PanelClass AvailableColumnsPanel_class = {\n   .super = {\n      .extends = Class(Panel),\n      .delete = AvailableColumnsPanel_delete\n   },\n   .eventHandler = AvailableColumnsPanel_eventHandler\n};\n\nstatic void AvailableColumnsPanel_addDynamicColumn(ht_key_t key, void* value, void* data) {\n   const DynamicColumn* column = (const DynamicColumn*) value;\n   if (column->table) /* DynamicScreen, handled differently */\n      return;\n   AvailableColumnsPanel* this = (AvailableColumnsPanel*) data;\n   const char* title = column->heading ? column->heading : column->name;\n   const char* text = column->description ? column->description : column->caption;\n   char description[256];\n   if (text)\n      xSnprintf(description, sizeof(description), \"%s - %s\", title, text);\n   else\n      xSnprintf(description, sizeof(description), \"%s\", title);\n   Panel_add(&this->super, (Object*) ListItem_new(description, key));\n}\n\n// Handle DynamicColumns entries in the AvailableColumnsPanel\nstatic void AvailableColumnsPanel_addDynamicColumns(AvailableColumnsPanel* this, Hashtable* dynamicColumns) {\n   assert(dynamicColumns);\n   Hashtable_foreach(dynamicColumns, AvailableColumnsPanel_addDynamicColumn, this);\n}\n\n// Handle remaining Platform Meter entries in the AvailableColumnsPanel\nstatic void AvailableColumnsPanel_addPlatformColumns(AvailableColumnsPanel* this) {\n   for (int i = 1; i < LAST_PROCESSFIELD; i++) {\n      if (Process_fields[i].description) {\n         char description[256];\n         xSnprintf(description, sizeof(description), \"%s - %s\", Process_fields[i].name, Process_fields[i].description);\n         Panel_add(&this->super, (Object*) ListItem_new(description, i));\n      }\n   }\n}\n\n// Handle DynamicColumns entries associated with DynamicScreens\nstatic void AvailableColumnsPanel_addDynamicScreens(AvailableColumnsPanel* this, const char* screen) {\n   Platform_addDynamicScreenAvailableColumns(&this->super, screen);\n}\n\nvoid AvailableColumnsPanel_fill(AvailableColumnsPanel* this, const char* dynamicScreen, Hashtable* dynamicColumns) {\n   Panel_prune(&this->super);\n   if (dynamicScreen) {\n      AvailableColumnsPanel_addDynamicScreens(this, dynamicScreen);\n   } else {\n      AvailableColumnsPanel_addPlatformColumns(this);\n      AvailableColumnsPanel_addDynamicColumns(this, dynamicColumns);\n   }\n}\n\nAvailableColumnsPanel* AvailableColumnsPanel_new(Panel* columns, Hashtable* dynamicColumns) {\n   AvailableColumnsPanel* this = AllocThis(AvailableColumnsPanel);\n   Panel* super = &this->super;\n\n   FunctionBar* fuBar = FunctionBar_new(AvailableColumnsFunctions, NULL, NULL);\n   Panel_init(super, 1, 1, 1, 1, Class(ListItem), true, fuBar);\n   Panel_setHeader(super, \"Available Columns\");\n\n   this->columns = columns;\n   AvailableColumnsPanel_fill(this, NULL, dynamicColumns);\n\n   return this;\n}\n"
  },
  {
    "path": "AvailableColumnsPanel.h",
    "content": "#ifndef HEADER_AvailableColumnsPanel\n#define HEADER_AvailableColumnsPanel\n/*\nhtop - AvailableColumnsPanel.h\n(C) 2004-2011 Hisham H. Muhammad\nReleased under the GNU GPLv2+, see the COPYING file\nin the source distribution for its full text.\n*/\n\n#include \"Hashtable.h\"\n#include \"Panel.h\"\n\n\ntypedef struct AvailableColumnsPanel_ {\n   Panel super;\n   Panel* columns;\n} AvailableColumnsPanel;\n\nextern const PanelClass AvailableColumnsPanel_class;\n\nAvailableColumnsPanel* AvailableColumnsPanel_new(Panel* columns, Hashtable* dynamicColumns);\n\nvoid AvailableColumnsPanel_fill(AvailableColumnsPanel* this, const char* dynamicScreen, Hashtable* dynamicColumns);\n\n#endif\n"
  },
  {
    "path": "AvailableMetersPanel.c",
    "content": "/*\nhtop - AvailableMetersPanel.c\n(C) 2004-2011 Hisham H. Muhammad\nReleased under the GNU GPLv2+, see the COPYING file\nin the source distribution for its full text.\n*/\n\n#include \"config.h\" // IWYU pragma: keep\n\n#include \"AvailableMetersPanel.h\"\n\n#include <assert.h>\n#include <stdbool.h>\n#include <stdlib.h>\n\n#include \"CPUMeter.h\"\n#include \"DynamicMeter.h\"\n#include \"FunctionBar.h\"\n#include \"Hashtable.h\"\n#include \"Header.h\"\n#include \"ListItem.h\"\n#include \"Macros.h\"\n#include \"Meter.h\"\n#include \"MetersPanel.h\"\n#include \"Object.h\"\n#include \"Platform.h\"\n#include \"ProvideCurses.h\"\n#include \"Settings.h\"\n#include \"XUtils.h\"\n\n\nstatic void AvailableMetersPanel_delete(Object* object) {\n   AvailableMetersPanel* this = (AvailableMetersPanel*) object;\n   free(this->meterPanels);\n   Panel_done(&this->super);\n   free(this);\n}\n\nstatic inline void AvailableMetersPanel_addMeter(Header* header, MetersPanel* panel, const MeterClass* type, unsigned int param, size_t column) {\n   const Meter* meter = Header_addMeterByClass(header, type, param, column);\n   Panel_add((Panel*)panel, (Object*) Meter_toListItem(meter, false));\n   Panel_setSelected((Panel*)panel, Panel_size((Panel*)panel) - 1);\n   MetersPanel_setMoving(panel, true);\n}\n\nstatic HandlerResult AvailableMetersPanel_eventHandler(Panel* super, int ch) {\n   AvailableMetersPanel* this = (AvailableMetersPanel*) super;\n   Header* header = this->header;\n\n   const ListItem* selected = (ListItem*) Panel_getSelected(super);\n   if (!selected)\n      return IGNORED;\n\n   unsigned int param = selected->key & 0xffff;\n   int type = selected->key >> 16;\n   HandlerResult result = IGNORED;\n   bool update = false;\n\n   switch (ch) {\n      case KEY_F(5):\n      case 'l':\n      case 'L':\n         AvailableMetersPanel_addMeter(header, this->meterPanels[0], Platform_meterTypes[type], param, 0);\n         result = HANDLED;\n         update = true;\n         break;\n      case 0x0a:\n      case 0x0d:\n      case KEY_ENTER:\n      case KEY_F(6):\n      case 'r':\n      case 'R':\n         AvailableMetersPanel_addMeter(header, this->meterPanels[this->columns - 1], Platform_meterTypes[type], param, this->columns - 1);\n         result = (KEY_LEFT << 16) | SYNTH_KEY;\n         update = true;\n         break;\n   }\n\n   if (update) {\n      Settings* settings = this->host->settings;\n      settings->changed = true;\n      settings->lastUpdate++;\n      Header_calculateHeight(header);\n      Header_updateData(header);\n      Header_draw(header);\n      ScreenManager_resize(this->scr);\n   }\n\n   return result;\n}\n\nconst PanelClass AvailableMetersPanel_class = {\n   .super = {\n      .extends = Class(Panel),\n      .delete = AvailableMetersPanel_delete\n   },\n   .eventHandler = AvailableMetersPanel_eventHandler\n};\n\n// Handle (&CPUMeter_class) entries in the AvailableMetersPanel\nstatic void AvailableMetersPanel_addCPUMeters(Panel* super, const MeterClass* type, const Machine* host) {\n   if (host->existingCPUs > 1) {\n      Panel_add(super, (Object*) ListItem_new(\"CPU average\", 0));\n      for (unsigned int i = 1; i <= host->existingCPUs; i++) {\n         char buffer[50];\n         xSnprintf(buffer, sizeof(buffer), \"%s %d\", type->uiName, Settings_cpuId(host->settings, i - 1));\n         Panel_add(super, (Object*) ListItem_new(buffer, i));\n      }\n   } else {\n      Panel_add(super, (Object*) ListItem_new(type->uiName, 1));\n   }\n}\n\ntypedef struct {\n   Panel* super;\n   unsigned int id;\n   unsigned int offset;\n} DynamicIterator;\n\nstatic void AvailableMetersPanel_addDynamicMeter(ATTR_UNUSED ht_key_t key, void* value, void* data) {\n   const DynamicMeter* meter = (const DynamicMeter*)value;\n   DynamicIterator* iter = (DynamicIterator*)data;\n   unsigned int identifier = (iter->offset << 16) | iter->id;\n   const char* label = meter->description ? meter->description : meter->caption;\n   if (!label)\n      label = meter->name; /* last fallback to name, guaranteed set */\n   Panel_add(iter->super, (Object*) ListItem_new(label, identifier));\n   iter->id++;\n}\n\n// Handle (&DynamicMeter_class) entries in the AvailableMetersPanel\nstatic void AvailableMetersPanel_addDynamicMeters(Panel* super, const Settings* settings, unsigned int offset) {\n   DynamicIterator iter = { .super = super, .id = 1, .offset = offset };\n   Hashtable* dynamicMeters = settings->dynamicMeters;\n   assert(dynamicMeters != NULL);\n   Hashtable_foreach(dynamicMeters, AvailableMetersPanel_addDynamicMeter, &iter);\n}\n\n// Handle remaining Platform Meter entries in the AvailableMetersPanel\nstatic void AvailableMetersPanel_addPlatformMeter(Panel* super, const MeterClass* type, unsigned int offset) {\n   const char* label = type->description ? type->description : type->uiName;\n   Panel_add(super, (Object*) ListItem_new(label, offset << 16));\n}\n\nAvailableMetersPanel* AvailableMetersPanel_new(Machine* host, Header* header, size_t columns, MetersPanel** meterPanels, ScreenManager* scr) {\n   AvailableMetersPanel* this = AllocThis(AvailableMetersPanel);\n   Panel* super = &this->super;\n\n   FunctionBar* fuBar = FunctionBar_newEnterEsc(\"Add   \", \"Done   \");\n   Panel_init(super, 1, 1, 1, 1, Class(ListItem), true, fuBar);\n\n   this->host = host;\n   this->header = header;\n   this->columns = columns;\n   this->meterPanels = meterPanels;\n   this->scr = scr;\n\n   Panel_setHeader(super, \"Available meters\");\n   // Platform_meterTypes[0] should be always (&CPUMeter_class) which we will\n   // handle separately in the code below.  Likewise, identifiers for Dynamic\n   // Meters are handled separately - similar to CPUs, this allows generation\n   // of multiple different Meters (also using 'param' to distinguish them).\n   for (unsigned int i = 1; Platform_meterTypes[i]; i++) {\n      const MeterClass* type = Platform_meterTypes[i];\n      assert(type != &CPUMeter_class);\n      if (type == &DynamicMeter_class)\n         AvailableMetersPanel_addDynamicMeters(super, host->settings, i);\n      else\n         AvailableMetersPanel_addPlatformMeter(super, type, i);\n   }\n   AvailableMetersPanel_addCPUMeters(super, &CPUMeter_class, host);\n\n   return this;\n}\n"
  },
  {
    "path": "AvailableMetersPanel.h",
    "content": "#ifndef HEADER_AvailableMetersPanel\n#define HEADER_AvailableMetersPanel\n/*\nhtop - AvailableMetersPanel.h\n(C) 2004-2011 Hisham H. Muhammad\nReleased under the GNU GPLv2+, see the COPYING file\nin the source distribution for its full text.\n*/\n\n#include <stddef.h>\n\n#include \"Header.h\"\n#include \"Machine.h\"\n#include \"MetersPanel.h\"\n#include \"Panel.h\"\n#include \"ScreenManager.h\"\n\n\ntypedef struct AvailableMetersPanel_ {\n   Panel super;\n   ScreenManager* scr;\n   Machine* host;\n   Header* header;\n   size_t columns;\n   MetersPanel** meterPanels;\n} AvailableMetersPanel;\n\nextern const PanelClass AvailableMetersPanel_class;\n\nAvailableMetersPanel* AvailableMetersPanel_new(Machine* host, Header* header, size_t columns, MetersPanel** meterPanels, ScreenManager* scr);\n\n#endif\n"
  },
  {
    "path": "BatteryMeter.c",
    "content": "/*\nhtop - BatteryMeter.c\n(C) 2004-2011 Hisham H. Muhammad\nReleased under the GNU GPLv2+, see the COPYING file\nin the source distribution for its full text.\n\nThis meter written by Ian P. Hands (iphands@gmail.com, ihands@redhat.com).\n*/\n\n#include \"config.h\" // IWYU pragma: keep\n\n#include \"BatteryMeter.h\"\n\n#include <math.h>\n\n#include \"CRT.h\"\n#include \"Macros.h\"\n#include \"Object.h\"\n#include \"Platform.h\"\n#include \"XUtils.h\"\n\n\nstatic const int BatteryMeter_attributes[] = {\n   BATTERY\n};\n\nstatic void BatteryMeter_updateValues(Meter* this) {\n   ACPresence isOnAC;\n   double percent;\n\n   Platform_getBattery(&percent, &isOnAC);\n\n   if (!isNonnegative(percent)) {\n      this->values[0] = NAN;\n      xSnprintf(this->txtBuffer, sizeof(this->txtBuffer), \"N/A\");\n      return;\n   }\n\n   this->values[0] = percent;\n\n   const char* text;\n   switch (isOnAC) {\n      case AC_PRESENT:\n         text = this->mode == TEXT_METERMODE ? \" (Running on A/C)\" : \"(A/C)\";\n         break;\n      case AC_ABSENT:\n         text = this->mode == TEXT_METERMODE ? \" (Running on battery)\" : \"(bat)\";\n         break;\n      case AC_ERROR:\n      default:\n         text = \"\";\n         break;\n   }\n\n   xSnprintf(this->txtBuffer, sizeof(this->txtBuffer), \"%.1f%%%s\", percent, text);\n}\n\nconst MeterClass BatteryMeter_class = {\n   .super = {\n      .extends = Class(Meter),\n      .delete = Meter_delete\n   },\n   .updateValues = BatteryMeter_updateValues,\n   .defaultMode = TEXT_METERMODE,\n   .supportedModes = METERMODE_DEFAULT_SUPPORTED,\n   .maxItems = 1,\n   .isPercentChart = true,\n   .total = 100.0,\n   .attributes = BatteryMeter_attributes,\n   .name = \"Battery\",\n   .uiName = \"Battery\",\n   .caption = \"Battery: \"\n};\n"
  },
  {
    "path": "BatteryMeter.h",
    "content": "#ifndef HEADER_BatteryMeter\n#define HEADER_BatteryMeter\n/*\nhtop - BatteryMeter.h\n(C) 2004-2011 Hisham H. Muhammad\nReleased under the GNU GPLv2+, see the COPYING file\nin the source distribution for its full text.\n\nThis meter written by Ian P. Hands (iphands@gmail.com, ihands@redhat.com).\n*/\n\n#include \"Meter.h\"\n\n\ntypedef enum ACPresence_ {\n   AC_ABSENT,\n   AC_PRESENT,\n   AC_ERROR\n} ACPresence;\n\nextern const MeterClass BatteryMeter_class;\n\n#endif\n"
  },
  {
    "path": "CODE_OF_CONDUCT.md",
    "content": "# `htop` Code of Conduct\n\n## Our Pledge\n\nWe as members, contributors, and leaders pledge to make participation in our\ncommunity a harassment-free experience for everyone, regardless of age, body\nsize, visible or invisible disability, ethnicity, sex characteristics, gender\nidentity and expression, level of experience, education, socio-economic status,\nnationality, personal appearance, race, caste, color, religion, or sexual\nidentity and orientation.\n\nWe pledge to act and interact in ways that contribute to an open, welcoming,\ndiverse, inclusive, and healthy community.\n\n## Our Standards\n\nExamples of behavior that contributes to a positive environment for our\ncommunity include:\n\n* Demonstrating empathy and kindness toward other people\n* Being respectful of differing opinions, viewpoints, and experiences\n* Giving and gracefully accepting constructive feedback\n* Accepting responsibility and apologizing to those affected by our mistakes,\n  and learning from the experience\n* Focusing on what is best not just for us as individuals, but for the overall\n  community\n\nExamples of unacceptable behavior include:\n\n* The use of sexualized language or imagery, and sexual attention or advances of\n  any kind\n* Trolling, insulting or derogatory comments, and personal or political attacks\n* Public or private harassment\n* Publishing others' private information, such as a physical or email address,\n  without their explicit permission\n* Other conduct which could reasonably be considered inappropriate in a\n  professional setting\n\n## Enforcement Responsibilities\n\nCommunity leaders are responsible for clarifying and enforcing our standards of\nacceptable behavior and will take appropriate and fair corrective action in\nresponse to any behavior that they deem inappropriate, threatening, offensive,\nor harmful.\n\nCommunity leaders have the right and responsibility to remove, edit, or reject\ncomments, commits, code, wiki edits, issues, and other contributions that are\nnot aligned to this Code of Conduct, and will communicate reasons for moderation\ndecisions when appropriate.\n\n## Scope\n\nThis Code of Conduct applies within all community spaces, and also applies when\nan individual is officially representing the community in public spaces.\nExamples of representing our community include using an official e-mail address,\nposting via an official social media account, or acting as an appointed\nrepresentative at an online or offline event.\n\n## Enforcement\n\nInstances of abusive, harassing, or otherwise unacceptable behavior may be\nreported to the community leaders responsible for enforcement at\n<htop-maintainers@groups.io>.\nAll complaints will be reviewed and investigated promptly and fairly.\n\nAll community leaders are obligated to respect the privacy and security of the\nreporter of any incident.\n\n## Enforcement Guidelines\n\nCommunity leaders will follow these Community Impact Guidelines in determining\nthe consequences for any action they deem in violation of this Code of Conduct:\n\n### 1. Correction\n\n**Community Impact**: Use of inappropriate language or other behavior deemed\nunprofessional or unwelcome in the community.\n\n**Consequence**: A private, written warning from community leaders, providing\nclarity around the nature of the violation and an explanation of why the\nbehavior was inappropriate. A public apology may be requested.\n\n### 2. Warning\n\n**Community Impact**: A violation through a single incident or series of\nactions.\n\n**Consequence**: A warning with consequences for continued behavior. No\ninteraction with the people involved, including unsolicited interaction with\nthose enforcing the Code of Conduct, for a specified period of time. This\nincludes avoiding interactions in community spaces as well as external channels\nlike social media. Violating these terms may lead to a temporary or permanent\nban.\n\n### 3. Temporary Ban\n\n**Community Impact**: A serious violation of community standards, including\nsustained inappropriate behavior.\n\n**Consequence**: A temporary ban from any sort of interaction or public\ncommunication with the community for a specified period of time. No public or\nprivate interaction with the people involved, including unsolicited interaction\nwith those enforcing the Code of Conduct, is allowed during this period.\nViolating these terms may lead to a permanent ban.\n\n### 4. Permanent Ban\n\n**Community Impact**: Demonstrating a pattern of violation of community\nstandards, including sustained inappropriate behavior, harassment of an\nindividual, or aggression toward or disparagement of classes of individuals.\n\n**Consequence**: A permanent ban from any sort of public interaction within the\ncommunity.\n\n## Attribution\n\nThis Code of Conduct is adapted from the [Contributor Covenant][homepage],\nversion 2.1, available at\n[https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1].\n\nCommunity Impact Guidelines were inspired by\n[Mozilla's code of conduct enforcement ladder][Mozilla CoC].\n\nFor answers to common questions about this code of conduct, see the FAQ at\n[https://www.contributor-covenant.org/faq][FAQ]. Translations are available at\n[https://www.contributor-covenant.org/translations][translations].\n\n[homepage]: https://www.contributor-covenant.org\n[v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html\n[Mozilla CoC]: https://github.com/mozilla/diversity\n[FAQ]: https://www.contributor-covenant.org/faq\n[translations]: https://www.contributor-covenant.org/translations\n"
  },
  {
    "path": "CONTRIBUTING.md",
    "content": "Contributing Guide\n==================\n\nThank you so much for taking the time to contribute to htop!\n\nBe Kind\n-------\n\nWe strive for htop to be a welcoming and safe project for all contributors.\nRefer to our [code of conduct](CODE_OF_CONDUCT.md) for details.\n\nIssue Tracker\n-------------\n\nBug reports should be posted in the [Github issue\ntracker](https://github.com/htop-dev/htop/issues).\nBug reports are extremely important since it's impossible for us to test\nhtop in every possible system, distribution and scenario. Your feedback\nis what keeps the tool stable and always improving - thank you!\n\nPlease label Github issues that are feature requests with a `feature request`\nlabel. If you can't do this yourself, don't worry, the friendly folk from the\ncore team update labels as part of their regular reviews.\n\nStyle Guide\n-----------\n\nTo make working with the code easier a set of guidelines have evolved in\nthe past that new contributions should try to follow. While they are not set\nin stone and always up for changes should the need arise they still provide\na first orientation to go by when contributing to this repository.\n\nThe details of the coding style as well as what to take care about with your\ncontributions can be found in our [style guide](docs/styleguide.md).\n\nPull Requests\n-------------\n\nCode contributions are most welcome! Just [fork the\nrepo](https://github.com/htop-dev/htop) and send a [pull\nrequest](https://github.com/htop-dev/htop/pulls). Help is especially\nappreciated for support of platforms other than Linux. If proposing new\nfeatures, please be mindful that htop is a system tool that needs to keep a\nsmall footprint and perform well on systems under stress -- so unfortunately\nwe can't accept every new feature proposed, as we need to keep the tool slim\nand maintainable. Great ideas backed by a PR are always carefully considered\nfor inclusion though! Also, PRs containing bug fixes and portability tweaks\nare always included, please send those in!\n\nUse of AI\n---------\n\nYou are welcome to use whatever tools work best for you, which includes use\nof AI tools to assist with developing htop. AI tools have proven invaluable\nas learning aids for new contributors, helped to identify problematic code,\nand have been used to fill cross-platform knowledge gaps.\n\nWith great power comes great responsibility. Please ensure you review and\nunderstand any changes made by tools you use before opening pull requests.\nBe aware that AI tools may generate content which is an exact copy of code\nfrom another project - only propose code for inclusion in htop that meets\nthe copyright and licensing requirements of the project.\n\nTo assist the maintainers please always acknowledge any use of AI tools as\npart of your work. Use of `Co-authored-by:` clauses is highly recommended.\n"
  },
  {
    "path": "COPYING",
    "content": "                    GNU GENERAL PUBLIC LICENSE\n                       Version 2, June 1991\n\n Copyright (C) 1989, 1991 Free Software Foundation, Inc.,\n 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n Everyone is permitted to copy and distribute verbatim copies\n of this license document, but changing it is not allowed.\n\n                            Preamble\n\n  The licenses for most software are designed to take away your\nfreedom to share and change it.  By contrast, the GNU General Public\nLicense is intended to guarantee your freedom to share and change free\nsoftware--to make sure the software is free for all its users.  This\nGeneral Public License applies to most of the Free Software\nFoundation's software and to any other program whose authors commit to\nusing it.  (Some other Free Software Foundation software is covered by\nthe GNU Lesser General Public License instead.)  You can apply it to\nyour programs, too.\n\n  When we speak of free software, we are referring to freedom, not\nprice.  Our General Public Licenses are designed to make sure that you\nhave the freedom to distribute copies of free software (and charge for\nthis service if you wish), that you receive source code or can get it\nif you want it, that you can change the software or use pieces of it\nin new free programs; and that you know you can do these things.\n\n  To protect your rights, we need to make restrictions that forbid\nanyone to deny you these rights or to ask you to surrender the rights.\nThese restrictions translate to certain responsibilities for you if you\ndistribute copies of the software, or if you modify it.\n\n  For example, if you distribute copies of such a program, whether\ngratis or for a fee, you must give the recipients all the rights that\nyou have.  You must make sure that they, too, receive or can get the\nsource code.  And you must show them these terms so they know their\nrights.\n\n  We protect your rights with two steps: (1) copyright the software, and\n(2) offer you this license which gives you legal permission to copy,\ndistribute and/or modify the software.\n\n  Also, for each author's protection and ours, we want to make certain\nthat everyone understands that there is no warranty for this free\nsoftware.  If the software is modified by someone else and passed on, we\nwant its recipients to know that what they have is not the original, so\nthat any problems introduced by others will not reflect on the original\nauthors' reputations.\n\n  Finally, any free program is threatened constantly by software\npatents.  We wish to avoid the danger that redistributors of a free\nprogram will individually obtain patent licenses, in effect making the\nprogram proprietary.  To prevent this, we have made it clear that any\npatent must be licensed for everyone's free use or not licensed at all.\n\n  The precise terms and conditions for copying, distribution and\nmodification follow.\n\n                    GNU GENERAL PUBLIC LICENSE\n   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION\n\n  0. This License applies to any program or other work which contains\na notice placed by the copyright holder saying it may be distributed\nunder the terms of this General Public License.  The \"Program\", below,\nrefers to any such program or work, and a \"work based on the Program\"\nmeans either the Program or any derivative work under copyright law:\nthat is to say, a work containing the Program or a portion of it,\neither verbatim or with modifications and/or translated into another\nlanguage.  (Hereinafter, translation is included without limitation in\nthe term \"modification\".)  Each licensee is addressed as \"you\".\n\nActivities other than copying, distribution and modification are not\ncovered by this License; they are outside its scope.  The act of\nrunning the Program is not restricted, and the output from the Program\nis covered only if its contents constitute a work based on the\nProgram (independent of having been made by running the Program).\nWhether that is true depends on what the Program does.\n\n  1. You may copy and distribute verbatim copies of the Program's\nsource code as you receive it, in any medium, provided that you\nconspicuously and appropriately publish on each copy an appropriate\ncopyright notice and disclaimer of warranty; keep intact all the\nnotices that refer to this License and to the absence of any warranty;\nand give any other recipients of the Program a copy of this License\nalong with the Program.\n\nYou may charge a fee for the physical act of transferring a copy, and\nyou may at your option offer warranty protection in exchange for a fee.\n\n  2. You may modify your copy or copies of the Program or any portion\nof it, thus forming a work based on the Program, and copy and\ndistribute such modifications or work under the terms of Section 1\nabove, provided that you also meet all of these conditions:\n\n    a) You must cause the modified files to carry prominent notices\n    stating that you changed the files and the date of any change.\n\n    b) You must cause any work that you distribute or publish, that in\n    whole or in part contains or is derived from the Program or any\n    part thereof, to be licensed as a whole at no charge to all third\n    parties under the terms of this License.\n\n    c) If the modified program normally reads commands interactively\n    when run, you must cause it, when started running for such\n    interactive use in the most ordinary way, to print or display an\n    announcement including an appropriate copyright notice and a\n    notice that there is no warranty (or else, saying that you provide\n    a warranty) and that users may redistribute the program under\n    these conditions, and telling the user how to view a copy of this\n    License.  (Exception: if the Program itself is interactive but\n    does not normally print such an announcement, your work based on\n    the Program is not required to print an announcement.)\n\nThese requirements apply to the modified work as a whole.  If\nidentifiable sections of that work are not derived from the Program,\nand can be reasonably considered independent and separate works in\nthemselves, then this License, and its terms, do not apply to those\nsections when you distribute them as separate works.  But when you\ndistribute the same sections as part of a whole which is a work based\non the Program, the distribution of the whole must be on the terms of\nthis License, whose permissions for other licensees extend to the\nentire whole, and thus to each and every part regardless of who wrote it.\n\nThus, it is not the intent of this section to claim rights or contest\nyour rights to work written entirely by you; rather, the intent is to\nexercise the right to control the distribution of derivative or\ncollective works based on the Program.\n\nIn addition, mere aggregation of another work not based on the Program\nwith the Program (or with a work based on the Program) on a volume of\na storage or distribution medium does not bring the other work under\nthe scope of this License.\n\n  3. You may copy and distribute the Program (or a work based on it,\nunder Section 2) in object code or executable form under the terms of\nSections 1 and 2 above provided that you also do one of the following:\n\n    a) Accompany it with the complete corresponding machine-readable\n    source code, which must be distributed under the terms of Sections\n    1 and 2 above on a medium customarily used for software interchange; or,\n\n    b) Accompany it with a written offer, valid for at least three\n    years, to give any third party, for a charge no more than your\n    cost of physically performing source distribution, a complete\n    machine-readable copy of the corresponding source code, to be\n    distributed under the terms of Sections 1 and 2 above on a medium\n    customarily used for software interchange; or,\n\n    c) Accompany it with the information you received as to the offer\n    to distribute corresponding source code.  (This alternative is\n    allowed only for noncommercial distribution and only if you\n    received the program in object code or executable form with such\n    an offer, in accord with Subsection b above.)\n\nThe source code for a work means the preferred form of the work for\nmaking modifications to it.  For an executable work, complete source\ncode means all the source code for all modules it contains, plus any\nassociated interface definition files, plus the scripts used to\ncontrol compilation and installation of the executable.  However, as a\nspecial exception, the source code distributed need not include\nanything that is normally distributed (in either source or binary\nform) with the major components (compiler, kernel, and so on) of the\noperating system on which the executable runs, unless that component\nitself accompanies the executable.\n\nIf distribution of executable or object code is made by offering\naccess to copy from a designated place, then offering equivalent\naccess to copy the source code from the same place counts as\ndistribution of the source code, even though third parties are not\ncompelled to copy the source along with the object code.\n\n  4. You may not copy, modify, sublicense, or distribute the Program\nexcept as expressly provided under this License.  Any attempt\notherwise to copy, modify, sublicense or distribute the Program is\nvoid, and will automatically terminate your rights under this License.\nHowever, parties who have received copies, or rights, from you under\nthis License will not have their licenses terminated so long as such\nparties remain in full compliance.\n\n  5. You are not required to accept this License, since you have not\nsigned it.  However, nothing else grants you permission to modify or\ndistribute the Program or its derivative works.  These actions are\nprohibited by law if you do not accept this License.  Therefore, by\nmodifying or distributing the Program (or any work based on the\nProgram), you indicate your acceptance of this License to do so, and\nall its terms and conditions for copying, distributing or modifying\nthe Program or works based on it.\n\n  6. Each time you redistribute the Program (or any work based on the\nProgram), the recipient automatically receives a license from the\noriginal licensor to copy, distribute or modify the Program subject to\nthese terms and conditions.  You may not impose any further\nrestrictions on the recipients' exercise of the rights granted herein.\nYou are not responsible for enforcing compliance by third parties to\nthis License.\n\n  7. If, as a consequence of a court judgment or allegation of patent\ninfringement or for any other reason (not limited to patent issues),\nconditions are imposed on you (whether by court order, agreement or\notherwise) that contradict the conditions of this License, they do not\nexcuse you from the conditions of this License.  If you cannot\ndistribute so as to satisfy simultaneously your obligations under this\nLicense and any other pertinent obligations, then as a consequence you\nmay not distribute the Program at all.  For example, if a patent\nlicense would not permit royalty-free redistribution of the Program by\nall those who receive copies directly or indirectly through you, then\nthe only way you could satisfy both it and this License would be to\nrefrain entirely from distribution of the Program.\n\nIf any portion of this section is held invalid or unenforceable under\nany particular circumstance, the balance of the section is intended to\napply and the section as a whole is intended to apply in other\ncircumstances.\n\nIt is not the purpose of this section to induce you to infringe any\npatents or other property right claims or to contest validity of any\nsuch claims; this section has the sole purpose of protecting the\nintegrity of the free software distribution system, which is\nimplemented by public license practices.  Many people have made\ngenerous contributions to the wide range of software distributed\nthrough that system in reliance on consistent application of that\nsystem; it is up to the author/donor to decide if he or she is willing\nto distribute software through any other system and a licensee cannot\nimpose that choice.\n\nThis section is intended to make thoroughly clear what is believed to\nbe a consequence of the rest of this License.\n\n  8. If the distribution and/or use of the Program is restricted in\ncertain countries either by patents or by copyrighted interfaces, the\noriginal copyright holder who places the Program under this License\nmay add an explicit geographical distribution limitation excluding\nthose countries, so that distribution is permitted only in or among\ncountries not thus excluded.  In such case, this License incorporates\nthe limitation as if written in the body of this License.\n\n  9. The Free Software Foundation may publish revised and/or new versions\nof the General Public License from time to time.  Such new versions will\nbe similar in spirit to the present version, but may differ in detail to\naddress new problems or concerns.\n\nEach version is given a distinguishing version number.  If the Program\nspecifies a version number of this License which applies to it and \"any\nlater version\", you have the option of following the terms and conditions\neither of that version or of any later version published by the Free\nSoftware Foundation.  If the Program does not specify a version number of\nthis License, you may choose any version ever published by the Free Software\nFoundation.\n\n  10. If you wish to incorporate parts of the Program into other free\nprograms whose distribution conditions are different, write to the author\nto ask for permission.  For software which is copyrighted by the Free\nSoftware Foundation, write to the Free Software Foundation; we sometimes\nmake exceptions for this.  Our decision will be guided by the two goals\nof preserving the free status of all derivatives of our free software and\nof promoting the sharing and reuse of software generally.\n\n                            NO WARRANTY\n\n  11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY\nFOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW.  EXCEPT WHEN\nOTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES\nPROVIDE THE PROGRAM \"AS IS\" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED\nOR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF\nMERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.  THE ENTIRE RISK AS\nTO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU.  SHOULD THE\nPROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,\nREPAIR OR CORRECTION.\n\n  12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING\nWILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR\nREDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,\nINCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING\nOUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED\nTO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY\nYOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER\nPROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE\nPOSSIBILITY OF SUCH DAMAGES.\n\n                     END OF TERMS AND CONDITIONS\n\n            How to Apply These Terms to Your New Programs\n\n  If you develop a new program, and you want it to be of the greatest\npossible use to the public, the best way to achieve this is to make it\nfree software which everyone can redistribute and change under these terms.\n\n  To do so, attach the following notices to the program.  It is safest\nto attach them to the start of each source file to most effectively\nconvey the exclusion of warranty; and each file should have at least\nthe \"copyright\" line and a pointer to where the full notice is found.\n\n    <one line to give the program's name and a brief idea of what it does.>\n    Copyright (C) <year>  <name of author>\n\n    This program is free software; you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation; either version 2 of the License, or\n    (at your option) any later version.\n\n    This program is distributed in the hope that it will be useful,\n    but WITHOUT ANY WARRANTY; without even the implied warranty of\n    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n    GNU General Public License for more details.\n\n    You should have received a copy of the GNU General Public License along\n    with this program; if not, write to the Free Software Foundation, Inc.,\n    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.\n\nAlso add information on how to contact you by electronic and paper mail.\n\nIf the program is interactive, make it output a short notice like this\nwhen it starts in an interactive mode:\n\n    Gnomovision version 69, Copyright (C) year name of author\n    Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.\n    This is free software, and you are welcome to redistribute it\n    under certain conditions; type `show c' for details.\n\nThe hypothetical commands `show w' and `show c' should show the appropriate\nparts of the General Public License.  Of course, the commands you use may\nbe called something other than `show w' and `show c'; they could even be\nmouse-clicks or menu items--whatever suits your program.\n\nYou should also get your employer (if you work as a programmer) or your\nschool, if any, to sign a \"copyright disclaimer\" for the program, if\nnecessary.  Here is a sample; alter the names:\n\n  Yoyodyne, Inc., hereby disclaims all copyright interest in the program\n  `Gnomovision' (which makes passes at compilers) written by James Hacker.\n\n  <signature of Ty Coon>, 1 April 1989\n  Ty Coon, President of Vice\n\nThis General Public License does not permit incorporating your program into\nproprietary programs.  If your program is a subroutine library, you may\nconsider it more useful to permit linking proprietary applications with the\nlibrary.  If this is what you want to do, use the GNU Lesser General\nPublic License instead of this License.\n"
  },
  {
    "path": "CPUMeter.c",
    "content": "/*\nhtop - CPUMeter.c\n(C) 2004-2011 Hisham H. Muhammad\nReleased under the GNU GPLv2+, see the COPYING file\nin the source distribution for its full text.\n*/\n\n#include \"config.h\" // IWYU pragma: keep\n\n#include \"CPUMeter.h\"\n\n#include <assert.h>\n#include <stdbool.h>\n#include <stddef.h>\n#include <stdlib.h>\n#include <string.h>\n\n#include \"CRT.h\"\n#include \"Machine.h\"\n#include \"Macros.h\"\n#include \"Object.h\"\n#include \"Platform.h\"\n#include \"RichString.h\"\n#include \"Settings.h\"\n#include \"XUtils.h\"\n\n\nstatic const int CPUMeter_attributes[] = {\n   CPU_NICE,\n   CPU_NORMAL,\n   CPU_SYSTEM,\n   CPU_IRQ,\n   CPU_SOFTIRQ,\n   CPU_STEAL,\n   CPU_GUEST,\n   CPU_IOWAIT\n};\n\nstatic const int CPUMeter_attributes_summary[] = {\n   CPU_NICE,\n   CPU_NORMAL,\n   CPU_SYSTEM,\n   CPU_GUEST\n};\n\ntypedef struct CPUMeterData_ {\n   unsigned int cpus;\n   Meter** meters;\n} CPUMeterData;\n\nstatic void CPUMeter_init(Meter* this) {\n   unsigned int cpu = this->param;\n   const Machine* host = this->host;\n   if (cpu == 0) {\n      Meter_setCaption(this, \"Avg\");\n   } else if (host->activeCPUs > 1) {\n      char caption[10];\n      xSnprintf(caption, sizeof(caption), \"%3u\", Settings_cpuId(host->settings, cpu - 1));\n      Meter_setCaption(this, caption);\n   }\n}\n\n// Custom uiName runtime logic to include the param (processor)\nstatic void CPUMeter_getUiName(const Meter* this, char* buffer, size_t length) {\n   assert(length > 0);\n\n   if (this->param > 0)\n      xSnprintf(buffer, length, \"%s %u\", Meter_uiName(this), this->param);\n   else\n      xSnprintf(buffer, length, \"%s\", Meter_uiName(this));\n}\n\nstatic void CPUMeter_updateValues(Meter* this) {\n   memset(this->values, 0, sizeof(double) * CPU_METER_ITEMCOUNT);\n\n   const Machine* host = this->host;\n   const Settings* settings = host->settings;\n   if (settings->detailedCPUTime) {\n      this->curAttributes = CPUMeter_attributes;\n   } else {\n      this->curAttributes = CPUMeter_attributes_summary;\n   }\n\n   unsigned int cpu = this->param;\n   if (cpu > host->existingCPUs) {\n      xSnprintf(this->txtBuffer, sizeof(this->txtBuffer), \"absent\");\n      return;\n   }\n\n   double percent = Platform_setCPUValues(this, cpu);\n   if (!isNonnegative(percent)) {\n      xSnprintf(this->txtBuffer, sizeof(this->txtBuffer), \"offline\");\n      return;\n   }\n\n   char cpuUsageBuffer[8] = { 0 };\n   char cpuFrequencyBuffer[16] = { 0 };\n   char cpuTemperatureBuffer[16] = { 0 };\n\n   if (settings->showCPUUsage) {\n      xSnprintf(cpuUsageBuffer, sizeof(cpuUsageBuffer), \"%.1f%%\", percent);\n   }\n\n   if (settings->showCPUFrequency) {\n      double cpuFrequency = this->values[CPU_METER_FREQUENCY];\n      if (isNonnegative(cpuFrequency)) {\n         xSnprintf(cpuFrequencyBuffer, sizeof(cpuFrequencyBuffer), \"%4uMHz\", (unsigned)cpuFrequency);\n      } else {\n         xSnprintf(cpuFrequencyBuffer, sizeof(cpuFrequencyBuffer), \"N/A\");\n      }\n   }\n\n   #ifdef BUILD_WITH_CPU_TEMP\n   if (settings->showCPUTemperature) {\n      double cpuTemperature = this->values[CPU_METER_TEMPERATURE];\n      if (isNaN(cpuTemperature))\n         xSnprintf(cpuTemperatureBuffer, sizeof(cpuTemperatureBuffer), \"N/A\");\n      else if (settings->degreeFahrenheit)\n         xSnprintf(cpuTemperatureBuffer, sizeof(cpuTemperatureBuffer), \"%3d%sF\", (int)(cpuTemperature * 9 / 5 + 32), CRT_degreeSign);\n      else\n         xSnprintf(cpuTemperatureBuffer, sizeof(cpuTemperatureBuffer), \"%d%sC\", (int)cpuTemperature, CRT_degreeSign);\n   }\n   #endif\n\n   xSnprintf(this->txtBuffer, sizeof(this->txtBuffer), \"%s%s%s%s%s\",\n             cpuUsageBuffer,\n             (cpuUsageBuffer[0] && (cpuFrequencyBuffer[0] || cpuTemperatureBuffer[0])) ? \" \" : \"\",\n             cpuFrequencyBuffer,\n             (cpuFrequencyBuffer[0] && cpuTemperatureBuffer[0]) ? \" \" : \"\",\n             cpuTemperatureBuffer);\n}\n\nstatic void CPUMeter_display(const Object* cast, RichString* out) {\n   char buffer[50];\n   int len;\n   const Meter* this = (const Meter*)cast;\n   const Machine* host = this->host;\n   const Settings* settings = host->settings;\n\n   if (this->param > host->existingCPUs) {\n      RichString_appendAscii(out, CRT_colors[METER_SHADOW], \" absent\");\n      return;\n   }\n\n   if (this->curItems == 0) {\n      RichString_appendAscii(out, CRT_colors[METER_SHADOW], \" offline\");\n      return;\n   }\n\n   len = xSnprintf(buffer, sizeof(buffer), \"%5.1f%% \", this->values[CPU_METER_NORMAL]);\n   RichString_appendAscii(out, CRT_colors[METER_TEXT], \":\");\n   RichString_appendnAscii(out, CRT_colors[CPU_NORMAL], buffer, len);\n   if (settings->detailedCPUTime) {\n      len = xSnprintf(buffer, sizeof(buffer), \"%5.1f%% \", this->values[CPU_METER_KERNEL]);\n      RichString_appendAscii(out, CRT_colors[METER_TEXT], \"sy:\");\n      RichString_appendnAscii(out, CRT_colors[CPU_SYSTEM], buffer, len);\n      len = xSnprintf(buffer, sizeof(buffer), \"%5.1f%% \", this->values[CPU_METER_NICE]);\n      RichString_appendAscii(out, CRT_colors[METER_TEXT], \"ni:\");\n      RichString_appendnAscii(out, CRT_colors[CPU_NICE_TEXT], buffer, len);\n      len = xSnprintf(buffer, sizeof(buffer), \"%5.1f%% \", this->values[CPU_METER_IRQ]);\n      RichString_appendAscii(out, CRT_colors[METER_TEXT], \"hi:\");\n      RichString_appendnAscii(out, CRT_colors[CPU_IRQ], buffer, len);\n      len = xSnprintf(buffer, sizeof(buffer), \"%5.1f%% \", this->values[CPU_METER_SOFTIRQ]);\n      RichString_appendAscii(out, CRT_colors[METER_TEXT], \"si:\");\n      RichString_appendnAscii(out, CRT_colors[CPU_SOFTIRQ], buffer, len);\n      if (isNonnegative(this->values[CPU_METER_STEAL])) {\n         len = xSnprintf(buffer, sizeof(buffer), \"%5.1f%% \", this->values[CPU_METER_STEAL]);\n         RichString_appendAscii(out, CRT_colors[METER_TEXT], \"st:\");\n         RichString_appendnAscii(out, CRT_colors[CPU_STEAL], buffer, len);\n      }\n      if (isNonnegative(this->values[CPU_METER_GUEST])) {\n         len = xSnprintf(buffer, sizeof(buffer), \"%5.1f%% \", this->values[CPU_METER_GUEST]);\n         RichString_appendAscii(out, CRT_colors[METER_TEXT], \"gu:\");\n         RichString_appendnAscii(out, CRT_colors[CPU_GUEST], buffer, len);\n      }\n      len = xSnprintf(buffer, sizeof(buffer), \"%5.1f%% \", this->values[CPU_METER_IOWAIT]);\n      RichString_appendAscii(out, CRT_colors[METER_TEXT], \"wa:\");\n      RichString_appendnAscii(out, CRT_colors[CPU_IOWAIT], buffer, len);\n   } else {\n      len = xSnprintf(buffer, sizeof(buffer), \"%5.1f%% \", this->values[CPU_METER_KERNEL]);\n      RichString_appendAscii(out, CRT_colors[METER_TEXT], \"sys:\");\n      RichString_appendnAscii(out, CRT_colors[CPU_SYSTEM], buffer, len);\n      len = xSnprintf(buffer, sizeof(buffer), \"%5.1f%% \", this->values[CPU_METER_NICE]);\n      RichString_appendAscii(out, CRT_colors[METER_TEXT], \"low:\");\n      RichString_appendnAscii(out, CRT_colors[CPU_NICE_TEXT], buffer, len);\n      if (isNonnegative(this->values[CPU_METER_IRQ])) {\n         len = xSnprintf(buffer, sizeof(buffer), \"%5.1f%% \", this->values[CPU_METER_IRQ]);\n         RichString_appendAscii(out, CRT_colors[METER_TEXT], \"vir:\");\n         RichString_appendnAscii(out, CRT_colors[CPU_GUEST], buffer, len);\n      }\n   }\n\n   if (settings->showCPUFrequency) {\n      char cpuFrequencyBuffer[10];\n      double cpuFrequency = this->values[CPU_METER_FREQUENCY];\n      if (isNonnegative(cpuFrequency)) {\n         len = xSnprintf(cpuFrequencyBuffer, sizeof(cpuFrequencyBuffer), \"%4uMHz \", (unsigned)cpuFrequency);\n      } else {\n         len = xSnprintf(cpuFrequencyBuffer, sizeof(cpuFrequencyBuffer), \"N/A     \");\n      }\n      RichString_appendAscii(out, CRT_colors[METER_TEXT], \"freq: \");\n      RichString_appendnWide(out, CRT_colors[METER_VALUE], cpuFrequencyBuffer, len);\n   }\n\n   #ifdef BUILD_WITH_CPU_TEMP\n   if (settings->showCPUTemperature) {\n      char cpuTemperatureBuffer[10];\n      double cpuTemperature = this->values[CPU_METER_TEMPERATURE];\n      if (isNaN(cpuTemperature)) {\n         len = xSnprintf(cpuTemperatureBuffer, sizeof(cpuTemperatureBuffer), \"N/A\");\n      } else if (settings->degreeFahrenheit) {\n         len = xSnprintf(cpuTemperatureBuffer, sizeof(cpuTemperatureBuffer), \"%5.1f%sF\", cpuTemperature * 9 / 5 + 32, CRT_degreeSign);\n      } else {\n         len = xSnprintf(cpuTemperatureBuffer, sizeof(cpuTemperatureBuffer), \"%5.1f%sC\", cpuTemperature, CRT_degreeSign);\n      }\n      RichString_appendAscii(out, CRT_colors[METER_TEXT], \"temp:\");\n      RichString_appendnWide(out, CRT_colors[METER_VALUE], cpuTemperatureBuffer, len);\n   }\n   #endif\n}\n\nstatic void AllCPUsMeter_getRange(const Meter* this, int* start, int* count) {\n   unsigned int cpus = this->host->existingCPUs;\n   switch (Meter_name(this)[0]) {\n      default:\n      case 'A': // All\n         *start = 0;\n         *count = cpus;\n         break;\n      case 'L': // First Half\n         *start = 0;\n         *count = (cpus + 1) / 2;\n         break;\n      case 'R': // Second Half\n         *start = (cpus + 1) / 2;\n         *count = cpus - *start;\n         break;\n   }\n}\n\nstatic void AllCPUsMeter_updateValues(Meter* this) {\n   CPUMeterData* data = this->meterData;\n   Meter** meters = data->meters;\n   int start, count;\n   AllCPUsMeter_getRange(this, &start, &count);\n   for (int i = 0; i < count; i++)\n      Meter_updateValues(meters[i]);\n}\n\nstatic void CPUMeterCommonInit(Meter* this) {\n   int start, count;\n   AllCPUsMeter_getRange(this, &start, &count);\n\n   CPUMeterData* data = this->meterData;\n   if (!data) {\n      data = xCalloc(1, sizeof(CPUMeterData));\n      data->cpus = this->host->existingCPUs;\n      data->meters = count ? xCalloc(count, sizeof(Meter*)) : NULL;\n      this->meterData = data;\n   }\n\n   Meter** meters = data->meters;\n   for (int i = 0; i < count; i++) {\n      if (!meters[i])\n         meters[i] = Meter_new(this->host, start + i + 1, (const MeterClass*) Class(CPUMeter));\n\n      Meter_init(meters[i]);\n   }\n}\n\nstatic void CPUMeterCommonUpdateMode(Meter* this, MeterModeId mode, int ncol) {\n   CPUMeterData* data = this->meterData;\n   Meter** meters = data->meters;\n   this->mode = mode;\n   int start, count;\n   AllCPUsMeter_getRange(this, &start, &count);\n   if (!count) {\n      this->h = 1;\n      return;\n   }\n   for (int i = 0; i < count; i++) {\n      Meter_setMode(meters[i], mode);\n   }\n   int h = meters[0]->h;\n   assert(h > 0);\n   this->h = h * ((count + ncol - 1) / ncol);\n}\n\nstatic void AllCPUsMeter_done(Meter* this) {\n   CPUMeterData* data = this->meterData;\n   Meter** meters = data->meters;\n   int start, count;\n   AllCPUsMeter_getRange(this, &start, &count);\n   for (int i = 0; i < count; i++)\n      Meter_delete((Object*)meters[i]);\n   free(data->meters);\n   free(data);\n}\n\nstatic void SingleColCPUsMeter_updateMode(Meter* this, MeterModeId mode) {\n   CPUMeterCommonUpdateMode(this, mode, 1);\n}\n\nstatic void DualColCPUsMeter_updateMode(Meter* this, MeterModeId mode) {\n   CPUMeterCommonUpdateMode(this, mode, 2);\n}\n\nstatic void QuadColCPUsMeter_updateMode(Meter* this, MeterModeId mode) {\n   CPUMeterCommonUpdateMode(this, mode, 4);\n}\n\nstatic void OctoColCPUsMeter_updateMode(Meter* this, MeterModeId mode) {\n   CPUMeterCommonUpdateMode(this, mode, 8);\n}\n\nstatic void CPUMeterCommonDraw(Meter* this, int x, int y, int w, int ncol) {\n   CPUMeterData* data = this->meterData;\n   Meter** meters = data->meters;\n   int start, count;\n   AllCPUsMeter_getRange(this, &start, &count);\n   int colwidth = w / ncol;\n   int diff = w % ncol;\n   int nrows = (count + ncol - 1) / ncol;\n   for (int i = 0; i < count; i++) {\n      int d = (i / nrows) > diff ? diff : (i / nrows); // dynamic spacer\n      int xpos = x + ((i / nrows) * colwidth) + d;\n      int ypos = y + ((i % nrows) * meters[0]->h);\n      meters[i]->draw(meters[i], xpos, ypos, colwidth);\n   }\n}\n\nstatic void DualColCPUsMeter_draw(Meter* this, int x, int y, int w) {\n   CPUMeterCommonDraw(this, x, y, w, 2);\n}\n\nstatic void QuadColCPUsMeter_draw(Meter* this, int x, int y, int w) {\n   CPUMeterCommonDraw(this, x, y, w, 4);\n}\n\nstatic void OctoColCPUsMeter_draw(Meter* this, int x, int y, int w) {\n   CPUMeterCommonDraw(this, x, y, w, 8);\n}\n\n\nstatic void SingleColCPUsMeter_draw(Meter* this, int x, int y, int w) {\n   CPUMeterData* data = this->meterData;\n   Meter** meters = data->meters;\n   int start, count;\n   AllCPUsMeter_getRange(this, &start, &count);\n   for (int i = 0; i < count; i++) {\n      meters[i]->draw(meters[i], x, y, w);\n      y += meters[i]->h;\n   }\n}\n\n\nconst MeterClass CPUMeter_class = {\n   .super = {\n      .extends = Class(Meter),\n      .delete = Meter_delete,\n      .display = CPUMeter_display\n   },\n   .updateValues = CPUMeter_updateValues,\n   .getUiName = CPUMeter_getUiName,\n   .defaultMode = BAR_METERMODE,\n   .supportedModes = METERMODE_DEFAULT_SUPPORTED,\n   .maxItems = CPU_METER_ITEMCOUNT,\n   .isPercentChart = true,\n   .total = 100.0,\n   .attributes = CPUMeter_attributes,\n   .name = \"CPU\",\n   .uiName = \"CPU\",\n   .caption = \"CPU\",\n   .init = CPUMeter_init\n};\n\nconst MeterClass AllCPUsMeter_class = {\n   .super = {\n      .extends = Class(Meter),\n      .delete = Meter_delete,\n      .display = CPUMeter_display\n   },\n   .updateValues = AllCPUsMeter_updateValues,\n   .defaultMode = BAR_METERMODE,\n   .supportedModes = METERMODE_DEFAULT_SUPPORTED,\n   .total = 100.0,\n   .attributes = CPUMeter_attributes,\n   .name = \"AllCPUs\",\n   .uiName = \"CPUs (1/1)\",\n   .description = \"CPUs (1/1): all CPUs\",\n   .caption = \"CPU\",\n   .draw = SingleColCPUsMeter_draw,\n   .init = CPUMeterCommonInit,\n   .updateMode = SingleColCPUsMeter_updateMode,\n   .done = AllCPUsMeter_done\n};\n\nconst MeterClass AllCPUs2Meter_class = {\n   .super = {\n      .extends = Class(Meter),\n      .delete = Meter_delete,\n      .display = CPUMeter_display\n   },\n   .updateValues = AllCPUsMeter_updateValues,\n   .defaultMode = BAR_METERMODE,\n   .supportedModes = METERMODE_DEFAULT_SUPPORTED,\n   .isMultiColumn = true,\n   .total = 100.0,\n   .attributes = CPUMeter_attributes,\n   .name = \"AllCPUs2\",\n   .uiName = \"CPUs (1&2/2)\",\n   .description = \"CPUs (1&2/2): all CPUs in 2 shorter columns\",\n   .caption = \"CPU\",\n   .draw = DualColCPUsMeter_draw,\n   .init = CPUMeterCommonInit,\n   .updateMode = DualColCPUsMeter_updateMode,\n   .done = AllCPUsMeter_done\n};\n\nconst MeterClass LeftCPUsMeter_class = {\n   .super = {\n      .extends = Class(Meter),\n      .delete = Meter_delete,\n      .display = CPUMeter_display\n   },\n   .updateValues = AllCPUsMeter_updateValues,\n   .defaultMode = BAR_METERMODE,\n   .supportedModes = METERMODE_DEFAULT_SUPPORTED,\n   .isMultiColumn = true,\n   .total = 100.0,\n   .attributes = CPUMeter_attributes,\n   .name = \"LeftCPUs\",\n   .uiName = \"CPUs (1/2)\",\n   .description = \"CPUs (1/2): first half of list\",\n   .caption = \"CPU\",\n   .draw = SingleColCPUsMeter_draw,\n   .init = CPUMeterCommonInit,\n   .updateMode = SingleColCPUsMeter_updateMode,\n   .done = AllCPUsMeter_done\n};\n\nconst MeterClass RightCPUsMeter_class = {\n   .super = {\n      .extends = Class(Meter),\n      .delete = Meter_delete,\n      .display = CPUMeter_display\n   },\n   .updateValues = AllCPUsMeter_updateValues,\n   .defaultMode = BAR_METERMODE,\n   .supportedModes = METERMODE_DEFAULT_SUPPORTED,\n   .isMultiColumn = true,\n   .total = 100.0,\n   .attributes = CPUMeter_attributes,\n   .name = \"RightCPUs\",\n   .uiName = \"CPUs (2/2)\",\n   .description = \"CPUs (2/2): second half of list\",\n   .caption = \"CPU\",\n   .draw = SingleColCPUsMeter_draw,\n   .init = CPUMeterCommonInit,\n   .updateMode = SingleColCPUsMeter_updateMode,\n   .done = AllCPUsMeter_done\n};\n\nconst MeterClass LeftCPUs2Meter_class = {\n   .super = {\n      .extends = Class(Meter),\n      .delete = Meter_delete,\n      .display = CPUMeter_display\n   },\n   .updateValues = AllCPUsMeter_updateValues,\n   .defaultMode = BAR_METERMODE,\n   .supportedModes = METERMODE_DEFAULT_SUPPORTED,\n   .isMultiColumn = true,\n   .total = 100.0,\n   .attributes = CPUMeter_attributes,\n   .name = \"LeftCPUs2\",\n   .uiName = \"CPUs (1&2/4)\",\n   .description = \"CPUs (1&2/4): first half in 2 shorter columns\",\n   .caption = \"CPU\",\n   .draw = DualColCPUsMeter_draw,\n   .init = CPUMeterCommonInit,\n   .updateMode = DualColCPUsMeter_updateMode,\n   .done = AllCPUsMeter_done\n};\n\nconst MeterClass RightCPUs2Meter_class = {\n   .super = {\n      .extends = Class(Meter),\n      .delete = Meter_delete,\n      .display = CPUMeter_display\n   },\n   .updateValues = AllCPUsMeter_updateValues,\n   .defaultMode = BAR_METERMODE,\n   .supportedModes = METERMODE_DEFAULT_SUPPORTED,\n   .isMultiColumn = true,\n   .total = 100.0,\n   .attributes = CPUMeter_attributes,\n   .name = \"RightCPUs2\",\n   .uiName = \"CPUs (3&4/4)\",\n   .description = \"CPUs (3&4/4): second half in 2 shorter columns\",\n   .caption = \"CPU\",\n   .draw = DualColCPUsMeter_draw,\n   .init = CPUMeterCommonInit,\n   .updateMode = DualColCPUsMeter_updateMode,\n   .done = AllCPUsMeter_done\n};\n\nconst MeterClass AllCPUs4Meter_class = {\n   .super = {\n      .extends = Class(Meter),\n      .delete = Meter_delete,\n      .display = CPUMeter_display\n   },\n   .updateValues = AllCPUsMeter_updateValues,\n   .defaultMode = BAR_METERMODE,\n   .supportedModes = METERMODE_DEFAULT_SUPPORTED,\n   .isMultiColumn = true,\n   .total = 100.0,\n   .attributes = CPUMeter_attributes,\n   .name = \"AllCPUs4\",\n   .uiName = \"CPUs (1&2&3&4/4)\",\n   .description = \"CPUs (1&2&3&4/4): all CPUs in 4 shorter columns\",\n   .caption = \"CPU\",\n   .draw = QuadColCPUsMeter_draw,\n   .init = CPUMeterCommonInit,\n   .updateMode = QuadColCPUsMeter_updateMode,\n   .done = AllCPUsMeter_done\n};\n\nconst MeterClass LeftCPUs4Meter_class = {\n   .super = {\n      .extends = Class(Meter),\n      .delete = Meter_delete,\n      .display = CPUMeter_display\n   },\n   .updateValues = AllCPUsMeter_updateValues,\n   .defaultMode = BAR_METERMODE,\n   .supportedModes = METERMODE_DEFAULT_SUPPORTED,\n   .isMultiColumn = true,\n   .total = 100.0,\n   .attributes = CPUMeter_attributes,\n   .name = \"LeftCPUs4\",\n   .uiName = \"CPUs (1-4/8)\",\n   .description = \"CPUs (1-4/8): first half in 4 shorter columns\",\n   .caption = \"CPU\",\n   .draw = QuadColCPUsMeter_draw,\n   .init = CPUMeterCommonInit,\n   .updateMode = QuadColCPUsMeter_updateMode,\n   .done = AllCPUsMeter_done\n};\n\nconst MeterClass RightCPUs4Meter_class = {\n   .super = {\n      .extends = Class(Meter),\n      .delete = Meter_delete,\n      .display = CPUMeter_display\n   },\n   .updateValues = AllCPUsMeter_updateValues,\n   .defaultMode = BAR_METERMODE,\n   .supportedModes = METERMODE_DEFAULT_SUPPORTED,\n   .isMultiColumn = true,\n   .total = 100.0,\n   .attributes = CPUMeter_attributes,\n   .name = \"RightCPUs4\",\n   .uiName = \"CPUs (5-8/8)\",\n   .description = \"CPUs (5-8/8): second half in 4 shorter columns\",\n   .caption = \"CPU\",\n   .draw = QuadColCPUsMeter_draw,\n   .init = CPUMeterCommonInit,\n   .updateMode = QuadColCPUsMeter_updateMode,\n   .done = AllCPUsMeter_done\n};\n\nconst MeterClass AllCPUs8Meter_class = {\n   .super = {\n      .extends = Class(Meter),\n      .delete = Meter_delete,\n      .display = CPUMeter_display\n   },\n   .updateValues = AllCPUsMeter_updateValues,\n   .defaultMode = BAR_METERMODE,\n   .supportedModes = METERMODE_DEFAULT_SUPPORTED,\n   .isMultiColumn = true,\n   .total = 100.0,\n   .attributes = CPUMeter_attributes,\n   .name = \"AllCPUs8\",\n   .uiName = \"CPUs (1-8/8)\",\n   .description = \"CPUs (1-8/8): all CPUs in 8 shorter columns\",\n   .caption = \"CPU\",\n   .draw = OctoColCPUsMeter_draw,\n   .init = CPUMeterCommonInit,\n   .updateMode = OctoColCPUsMeter_updateMode,\n   .done = AllCPUsMeter_done\n};\n\nconst MeterClass LeftCPUs8Meter_class = {\n   .super = {\n      .extends = Class(Meter),\n      .delete = Meter_delete,\n      .display = CPUMeter_display\n   },\n   .updateValues = AllCPUsMeter_updateValues,\n   .defaultMode = BAR_METERMODE,\n   .supportedModes = METERMODE_DEFAULT_SUPPORTED,\n   .isMultiColumn = true,\n   .total = 100.0,\n   .attributes = CPUMeter_attributes,\n   .name = \"LeftCPUs8\",\n   .uiName = \"CPUs (1-8/16)\",\n   .description = \"CPUs (1-8/16): first half in 8 shorter columns\",\n   .caption = \"CPU\",\n   .draw = OctoColCPUsMeter_draw,\n   .init = CPUMeterCommonInit,\n   .updateMode = OctoColCPUsMeter_updateMode,\n   .done = AllCPUsMeter_done\n};\n\nconst MeterClass RightCPUs8Meter_class = {\n   .super = {\n      .extends = Class(Meter),\n      .delete = Meter_delete,\n      .display = CPUMeter_display\n   },\n   .updateValues = AllCPUsMeter_updateValues,\n   .defaultMode = BAR_METERMODE,\n   .supportedModes = METERMODE_DEFAULT_SUPPORTED,\n   .isMultiColumn = true,\n   .total = 100.0,\n   .attributes = CPUMeter_attributes,\n   .name = \"RightCPUs8\",\n   .uiName = \"CPUs (9-16/16)\",\n   .description = \"CPUs (9-16/16): second half in 8 shorter columns\",\n   .caption = \"CPU\",\n   .draw = OctoColCPUsMeter_draw,\n   .init = CPUMeterCommonInit,\n   .updateMode = OctoColCPUsMeter_updateMode,\n   .done = AllCPUsMeter_done\n};\n"
  },
  {
    "path": "CPUMeter.h",
    "content": "#ifndef HEADER_CPUMeter\n#define HEADER_CPUMeter\n/*\nhtop - CPUMeter.h\n(C) 2004-2011 Hisham H. Muhammad\nReleased under the GNU GPLv2+, see the COPYING file\nin the source distribution for its full text.\n*/\n\n#include \"Meter.h\"\n\n\ntypedef enum {\n   CPU_METER_NICE = 0,\n   CPU_METER_NORMAL = 1,\n   CPU_METER_KERNEL = 2,\n   CPU_METER_IRQ = 3,\n   CPU_METER_SOFTIRQ = 4,\n   CPU_METER_STEAL = 5,\n   CPU_METER_GUEST = 6,\n   CPU_METER_IOWAIT = 7,\n   CPU_METER_FREQUENCY = 8,\n   CPU_METER_TEMPERATURE = 9,\n   CPU_METER_ITEMCOUNT = 10, // number of entries in this enum\n} CPUMeterValues;\n\nextern const MeterClass CPUMeter_class;\n\nextern const MeterClass AllCPUsMeter_class;\n\nextern const MeterClass AllCPUs2Meter_class;\n\nextern const MeterClass LeftCPUsMeter_class;\n\nextern const MeterClass RightCPUsMeter_class;\n\nextern const MeterClass LeftCPUs2Meter_class;\n\nextern const MeterClass RightCPUs2Meter_class;\n\nextern const MeterClass AllCPUs4Meter_class;\n\nextern const MeterClass LeftCPUs4Meter_class;\n\nextern const MeterClass RightCPUs4Meter_class;\n\nextern const MeterClass AllCPUs8Meter_class;\n\nextern const MeterClass LeftCPUs8Meter_class;\n\nextern const MeterClass RightCPUs8Meter_class;\n\n#endif\n"
  },
  {
    "path": "CRT.c",
    "content": "/*\nhtop - CRT.c\n(C) 2004-2011 Hisham H. Muhammad\nReleased under the GNU GPLv2+, see the COPYING file\nin the source distribution for its full text.\n*/\n\n#include \"config.h\" // IWYU pragma: keep\n\n#include \"CRT.h\"\n\n#include <errno.h>\n#include <fcntl.h>\n#include <langinfo.h>\n#include <limits.h>\n#include <signal.h>\n#include <stdarg.h>\n#include <stdio.h>\n#include <stdlib.h>\n#include <string.h>\n#include <unistd.h>\n#include <sys/stat.h> // IWYU pragma: keep\n\n#include \"CommandLine.h\"\n#include \"ProvideCurses.h\"\n#include \"ProvideTerm.h\"\n#include \"XUtils.h\"\n\n#if !defined(NDEBUG) && defined(HAVE_MEMFD_CREATE)\n#include <sys/mman.h>\n#endif\n\n#if defined(HAVE_LIBUNWIND_H) && defined(HAVE_LOCAL_UNWIND)\n# define PRINT_BACKTRACE\n# define UNW_LOCAL_ONLY\n# include <libunwind.h>\n# if defined(HAVE_DLADDR)\n#  include <dlfcn.h>\n# endif\n#elif defined(HAVE_EXECINFO_H) && defined(BACKTRACE_RETURN_TYPE)\n# define PRINT_BACKTRACE\n# include <execinfo.h>\n#endif\n\n\n#define ColorIndex(i,j) ((7-(i))*8+(j))\n\n#define ColorPair(i,j) COLOR_PAIR(ColorIndex(i,j))\n\n#define Black   COLOR_BLACK\n#define Red     COLOR_RED\n#define Green   COLOR_GREEN\n#define Yellow  COLOR_YELLOW\n#define Blue    COLOR_BLUE\n#define Magenta COLOR_MAGENTA\n#define Cyan    COLOR_CYAN\n#define White   COLOR_WHITE\n\n#define ColorPairGrayBlack  ColorPair(Magenta,Magenta)\n#define ColorIndexGrayBlack ColorIndex(Magenta,Magenta)\n\n#define ColorPairWhiteDefault  ColorPair(Red, Red)\n#define ColorIndexWhiteDefault ColorIndex(Red, Red)\n\nstatic const char* const CRT_treeStrAscii[LAST_TREE_STR] = {\n   [TREE_STR_VERT] = \"|\",\n   [TREE_STR_RTEE] = \"`\",\n   [TREE_STR_BEND] = \"`\",\n   [TREE_STR_TEND] = \",\",\n   [TREE_STR_OPEN] = \"+\",\n   [TREE_STR_SHUT] = \"-\",\n   [TREE_STR_ASC]  = \"+\",\n   [TREE_STR_DESC] = \"-\",\n};\n\n#ifdef HAVE_LIBNCURSESW\n\nstatic const char* const CRT_treeStrUtf8[LAST_TREE_STR] = {\n   [TREE_STR_VERT] = \"\\xe2\\x94\\x82\", // │\n   [TREE_STR_RTEE] = \"\\xe2\\x94\\x9c\", // ├\n   [TREE_STR_BEND] = \"\\xe2\\x94\\x94\", // └\n   [TREE_STR_TEND] = \"\\xe2\\x94\\x8c\", // ┌\n   [TREE_STR_OPEN] = \"+\",            // +, TODO use 🮯 'BOX DRAWINGS LIGHT HORIZONTAL\n                                     // WITH VERTICAL STROKE' (U+1FBAF, \"\\xf0\\x9f\\xae\\xaf\") when\n                                     // Unicode 13 is common\n   [TREE_STR_SHUT] = \"\\xe2\\x94\\x80\", // ─\n   [TREE_STR_ASC]  = \"\\xe2\\x96\\xb3\", // △\n   [TREE_STR_DESC] = \"\\xe2\\x96\\xbd\", // ▽\n};\n\nbool CRT_utf8 = false;\n\n#endif\n\nconst char* const* CRT_treeStr = CRT_treeStrAscii;\n\nstatic const Settings* CRT_settings;\n\n#ifdef HAVE_LIBNCURSESW\n# if MB_LEN_MAX >= 3 // Minimum required to support UTF-8 BMP subset\nchar CRT_degreeSign[MB_LEN_MAX * 2] = \"\\xc2\\xb0\";\n# else\nchar CRT_degreeSign[MB_LEN_MAX * 2] = \"\";\n# endif\n#else\nchar CRT_degreeSign[] = \"\";\n#endif\n\nstatic void initDegreeSign(void) {\n#ifdef HAVE_LIBNCURSESW\n# if MB_LEN_MAX >= 3\n   if (CRT_utf8)\n      return;\n# endif\n\n   // this might fail if the current locale does not support wide characters\n   int r = snprintf(CRT_degreeSign, sizeof(CRT_degreeSign), \"%lc\", 176);\n   if (r <= 0 || (size_t)r >= sizeof(CRT_degreeSign))\n      CRT_degreeSign[0] = '\\0';\n#endif\n\n   // No-op\n   return;\n}\n\nconst int* CRT_colors;\n\nstatic int CRT_colorSchemes[LAST_COLORSCHEME][LAST_COLORELEMENT] = {\n   [COLORSCHEME_DEFAULT] = {\n      [RESET_COLOR] = ColorPair(White, Black),\n      [DEFAULT_COLOR] = ColorPair(White, Black),\n      [FUNCTION_BAR] = ColorPair(Black, Cyan),\n      [FUNCTION_KEY] = ColorPair(White, Black),\n      [PANEL_HEADER_FOCUS] = ColorPair(Black, Green),\n      [PANEL_HEADER_UNFOCUS] = ColorPair(Black, Green),\n      [PANEL_SELECTION_FOCUS] = ColorPair(Black, Cyan),\n      [PANEL_SELECTION_FOLLOW] = ColorPair(Black, Yellow),\n      [PANEL_SELECTION_UNFOCUS] = ColorPair(Black, White),\n      [FAILED_SEARCH] = ColorPair(Red, Cyan),\n      [FAILED_READ] = A_BOLD | ColorPair(Red, Black),\n      [PAUSED] = A_BOLD | ColorPair(Yellow, Cyan),\n      [UPTIME] = A_BOLD | ColorPair(Cyan, Black),\n      [BATTERY] = A_BOLD | ColorPair(Cyan, Black),\n      [LARGE_NUMBER] = A_BOLD | ColorPair(Red, Black),\n      [METER_SHADOW] = A_BOLD | ColorPairGrayBlack,\n      [METER_TEXT] = ColorPair(Cyan, Black),\n      [METER_VALUE] = A_BOLD | ColorPair(Cyan, Black),\n      [METER_VALUE_ERROR] = A_BOLD | ColorPair(Red, Black),\n      [METER_VALUE_IOREAD] = ColorPair(Green, Black),\n      [METER_VALUE_IOWRITE] = A_BOLD | ColorPair(Blue, Black),\n      [METER_VALUE_NOTICE] = A_BOLD | ColorPair(White, Black),\n      [METER_VALUE_OK] = ColorPair(Green, Black),\n      [METER_VALUE_WARN] = A_BOLD | ColorPair(Yellow, Black),\n      [LED_COLOR] = ColorPair(Green, Black),\n      [TASKS_RUNNING] = A_BOLD | ColorPair(Green, Black),\n      [PROCESS] = A_NORMAL,\n      [PROCESS_SHADOW] = A_BOLD | ColorPairGrayBlack,\n      [PROCESS_TAG] = A_BOLD | ColorPair(Yellow, Black),\n      [PROCESS_MEGABYTES] = ColorPair(Cyan, Black),\n      [PROCESS_GIGABYTES] = ColorPair(Green, Black),\n      [PROCESS_BASENAME] = A_BOLD | ColorPair(Cyan, Black),\n      [PROCESS_TREE] = ColorPair(Cyan, Black),\n      [PROCESS_RUN_STATE] = ColorPair(Green, Black),\n      [PROCESS_D_STATE] = A_BOLD | ColorPair(Red, Black),\n      [PROCESS_HIGH_PRIORITY] = ColorPair(Red, Black),\n      [PROCESS_LOW_PRIORITY] = ColorPair(Green, Black),\n      [PROCESS_NEW] = ColorPair(Black, Green),\n      [PROCESS_TOMB] = ColorPair(Black, Red),\n      [PROCESS_THREAD] = ColorPair(Green, Black),\n      [PROCESS_THREAD_BASENAME] = A_BOLD | ColorPair(Green, Black),\n      [PROCESS_COMM] = ColorPair(Magenta, Black),\n      [PROCESS_THREAD_COMM] = A_BOLD | ColorPair(Blue, Black),\n      [PROCESS_PRIV] = ColorPair(Magenta, Black),\n      [BAR_BORDER] = A_BOLD,\n      [BAR_SHADOW] = A_BOLD | ColorPairGrayBlack,\n      [SWAP] = ColorPair(Red, Black),\n      [SWAP_CACHE] = ColorPair(Yellow, Black),\n      [SWAP_FRONTSWAP] = A_BOLD | ColorPairGrayBlack,\n      [GRAPH_1] = A_BOLD | ColorPair(Cyan, Black),\n      [GRAPH_2] = ColorPair(Cyan, Black),\n      [MEMORY_1] = ColorPair(Green, Black),\n      [MEMORY_2] = ColorPair(Magenta, Black),\n      [MEMORY_3] = A_BOLD | ColorPairGrayBlack,\n      [MEMORY_4] = A_BOLD | ColorPair(Blue, Black),\n      [MEMORY_5] = ColorPair(Yellow, Black),\n      [MEMORY_6] = ColorPair(Cyan, Black),\n      [HUGEPAGE_1] = ColorPair(Green, Black),\n      [HUGEPAGE_2] = ColorPair(Yellow, Black),\n      [HUGEPAGE_3] = ColorPair(Red, Black),\n      [HUGEPAGE_4] = A_BOLD | ColorPair(Blue, Black),\n      [LOAD_AVERAGE_FIFTEEN] = ColorPair(Cyan, Black),\n      [LOAD_AVERAGE_FIVE] = A_BOLD | ColorPair(Cyan, Black),\n      [LOAD_AVERAGE_ONE] = A_BOLD | ColorPair(White, Black),\n      [LOAD] = A_BOLD,\n      [HELP_BOLD] = A_BOLD | ColorPair(Cyan, Black),\n      [HELP_SHADOW] = A_BOLD | ColorPairGrayBlack,\n      [CLOCK] = A_BOLD,\n      [DATE] = A_BOLD,\n      [DATETIME] = A_BOLD,\n      [CHECK_BOX] = ColorPair(Cyan, Black),\n      [CHECK_MARK] = A_BOLD,\n      [CHECK_TEXT] = A_NORMAL,\n      [HOSTNAME] = A_BOLD,\n      [CPU_NICE] = A_BOLD | ColorPair(Blue, Black),\n      [CPU_NICE_TEXT] = A_BOLD | ColorPair(Blue, Black),\n      [CPU_NORMAL] = ColorPair(Green, Black),\n      [CPU_SYSTEM] = ColorPair(Red, Black),\n      [CPU_IOWAIT] = A_BOLD | ColorPairGrayBlack,\n      [CPU_IRQ] = ColorPair(Yellow, Black),\n      [CPU_SOFTIRQ] = ColorPair(Magenta, Black),\n      [CPU_STEAL] = ColorPair(Cyan, Black),\n      [CPU_GUEST] = ColorPair(Cyan, Black),\n      [GPU_ENGINE_1] = ColorPair(Green, Black),\n      [GPU_ENGINE_2] = ColorPair(Yellow, Black),\n      [GPU_ENGINE_3] = ColorPair(Red, Black),\n      [GPU_ENGINE_4] = A_BOLD | ColorPair(Blue, Black),\n      [GPU_RESIDUE] = ColorPair(Magenta, Black),\n      [PANEL_EDIT] = ColorPair(White, Blue),\n      [SCREENS_OTH_BORDER] = ColorPair(Blue, Blue),\n      [SCREENS_OTH_TEXT] = ColorPair(Black, Blue),\n      [SCREENS_CUR_BORDER] = ColorPair(Green, Green),\n      [SCREENS_CUR_TEXT] = ColorPair(Black, Green),\n      [PRESSURE_STALL_THREEHUNDRED] = ColorPair(Cyan, Black),\n      [PRESSURE_STALL_SIXTY] = A_BOLD | ColorPair(Cyan, Black),\n      [PRESSURE_STALL_TEN] = A_BOLD | ColorPair(White, Black),\n      [FILE_DESCRIPTOR_USED] = ColorPair(Green, Black),\n      [FILE_DESCRIPTOR_MAX] = A_BOLD | ColorPair(Blue, Black),\n      [ZFS_MFU] = A_BOLD | ColorPair(Blue, Black),\n      [ZFS_MRU] = ColorPair(Yellow, Black),\n      [ZFS_ANON] = ColorPair(Magenta, Black),\n      [ZFS_HEADER] = ColorPair(Cyan, Black),\n      [ZFS_OTHER] = ColorPair(Magenta, Black),\n      [ZFS_COMPRESSED] = A_BOLD | ColorPair(Blue, Black),\n      [ZFS_RATIO] = ColorPair(Magenta, Black),\n      [ZRAM_COMPRESSED] = A_BOLD | ColorPair(Blue, Black),\n      [ZRAM_UNCOMPRESSED] = ColorPair(Yellow, Black),\n      [DYNAMIC_GRAY] = ColorPairGrayBlack,\n      [DYNAMIC_DARKGRAY] = A_BOLD | ColorPairGrayBlack,\n      [DYNAMIC_RED] = ColorPair(Red, Black),\n      [DYNAMIC_GREEN] = ColorPair(Green, Black),\n      [DYNAMIC_BLUE] = A_BOLD | ColorPair(Blue, Black),\n      [DYNAMIC_CYAN] = ColorPair(Cyan, Black),\n      [DYNAMIC_MAGENTA] = ColorPair(Magenta, Black),\n      [DYNAMIC_YELLOW] = ColorPair(Yellow, Black),\n      [DYNAMIC_WHITE] = ColorPair(White, Black),\n   },\n   [COLORSCHEME_MONOCHROME] = {\n      [RESET_COLOR] = A_NORMAL,\n      [DEFAULT_COLOR] = A_NORMAL,\n      [FUNCTION_BAR] = A_REVERSE,\n      [FUNCTION_KEY] = A_NORMAL,\n      [PANEL_HEADER_FOCUS] = A_REVERSE,\n      [PANEL_HEADER_UNFOCUS] = A_REVERSE,\n      [PANEL_SELECTION_FOCUS] = A_REVERSE,\n      [PANEL_SELECTION_FOLLOW] = A_REVERSE,\n      [PANEL_SELECTION_UNFOCUS] = A_BOLD,\n      [FAILED_SEARCH] = A_REVERSE | A_BOLD,\n      [FAILED_READ] = A_BOLD,\n      [PAUSED] = A_BOLD | A_REVERSE,\n      [UPTIME] = A_BOLD,\n      [BATTERY] = A_BOLD,\n      [LARGE_NUMBER] = A_BOLD,\n      [METER_SHADOW] = A_DIM,\n      [METER_TEXT] = A_NORMAL,\n      [METER_VALUE] = A_BOLD,\n      [METER_VALUE_ERROR] = A_BOLD,\n      [METER_VALUE_IOREAD] = A_NORMAL,\n      [METER_VALUE_IOWRITE] = A_NORMAL,\n      [METER_VALUE_NOTICE] = A_BOLD,\n      [METER_VALUE_OK] = A_NORMAL,\n      [METER_VALUE_WARN] = A_BOLD,\n      [LED_COLOR] = A_NORMAL,\n      [TASKS_RUNNING] = A_BOLD,\n      [PROCESS] = A_NORMAL,\n      [PROCESS_SHADOW] = A_DIM,\n      [PROCESS_TAG] = A_BOLD,\n      [PROCESS_MEGABYTES] = A_BOLD,\n      [PROCESS_GIGABYTES] = A_BOLD,\n      [PROCESS_BASENAME] = A_BOLD,\n      [PROCESS_TREE] = A_BOLD,\n      [PROCESS_RUN_STATE] = A_BOLD,\n      [PROCESS_D_STATE] = A_BOLD,\n      [PROCESS_HIGH_PRIORITY] = A_BOLD,\n      [PROCESS_LOW_PRIORITY] = A_DIM,\n      [PROCESS_NEW] = A_BOLD,\n      [PROCESS_TOMB] = A_DIM,\n      [PROCESS_THREAD] = A_BOLD,\n      [PROCESS_THREAD_BASENAME] = A_REVERSE,\n      [PROCESS_COMM] = A_BOLD,\n      [PROCESS_THREAD_COMM] = A_REVERSE,\n      [PROCESS_PRIV] = A_BOLD,\n      [BAR_BORDER] = A_BOLD,\n      [BAR_SHADOW] = A_DIM,\n      [SWAP] = A_BOLD,\n      [SWAP_CACHE] = A_NORMAL,\n      [SWAP_FRONTSWAP] = A_DIM,\n      [GRAPH_1] = A_BOLD,\n      [GRAPH_2] = A_NORMAL,\n      [MEMORY_1] = A_BOLD,\n      [MEMORY_2] = A_NORMAL,\n      [MEMORY_3] = A_NORMAL,\n      [MEMORY_4] = A_NORMAL,\n      [MEMORY_5] = A_DIM,\n      [MEMORY_6] = A_NORMAL,\n      [HUGEPAGE_1] = A_BOLD,\n      [HUGEPAGE_2] = A_NORMAL,\n      [HUGEPAGE_3] = A_REVERSE | A_BOLD,\n      [HUGEPAGE_4] = A_REVERSE,\n      [LOAD_AVERAGE_FIFTEEN] = A_DIM,\n      [LOAD_AVERAGE_FIVE] = A_NORMAL,\n      [LOAD_AVERAGE_ONE] = A_BOLD,\n      [LOAD] = A_BOLD,\n      [HELP_BOLD] = A_BOLD,\n      [HELP_SHADOW] = A_DIM,\n      [CLOCK] = A_BOLD,\n      [DATE] = A_BOLD,\n      [DATETIME] = A_BOLD,\n      [CHECK_BOX] = A_BOLD,\n      [CHECK_MARK] = A_NORMAL,\n      [CHECK_TEXT] = A_NORMAL,\n      [HOSTNAME] = A_BOLD,\n      [CPU_NICE] = A_NORMAL,\n      [CPU_NICE_TEXT] = A_NORMAL,\n      [CPU_NORMAL] = A_BOLD,\n      [CPU_SYSTEM] = A_BOLD,\n      [CPU_IOWAIT] = A_NORMAL,\n      [CPU_IRQ] = A_BOLD,\n      [CPU_SOFTIRQ] = A_BOLD,\n      [CPU_STEAL] = A_DIM,\n      [CPU_GUEST] = A_DIM,\n      [GPU_ENGINE_1] = A_BOLD,\n      [GPU_ENGINE_2] = A_NORMAL,\n      [GPU_ENGINE_3] = A_REVERSE | A_BOLD,\n      [GPU_ENGINE_4] = A_REVERSE,\n      [GPU_RESIDUE] = A_BOLD,\n      [PANEL_EDIT] = A_BOLD,\n      [SCREENS_OTH_BORDER] = A_DIM,\n      [SCREENS_OTH_TEXT] = A_DIM,\n      [SCREENS_CUR_BORDER] = A_REVERSE,\n      [SCREENS_CUR_TEXT] = A_REVERSE,\n      [PRESSURE_STALL_THREEHUNDRED] = A_DIM,\n      [PRESSURE_STALL_SIXTY] = A_NORMAL,\n      [PRESSURE_STALL_TEN] = A_BOLD,\n      [FILE_DESCRIPTOR_USED] = A_BOLD,\n      [FILE_DESCRIPTOR_MAX] = A_BOLD,\n      [ZFS_MFU] = A_NORMAL,\n      [ZFS_MRU] = A_NORMAL,\n      [ZFS_ANON] = A_DIM,\n      [ZFS_HEADER] = A_BOLD,\n      [ZFS_OTHER] = A_DIM,\n      [ZFS_COMPRESSED] = A_BOLD,\n      [ZFS_RATIO] = A_BOLD,\n      [ZRAM_COMPRESSED] = A_NORMAL,\n      [ZRAM_UNCOMPRESSED] = A_NORMAL,\n      [DYNAMIC_GRAY] = A_DIM,\n      [DYNAMIC_DARKGRAY] = A_DIM,\n      [DYNAMIC_RED] = A_BOLD,\n      [DYNAMIC_GREEN] = A_NORMAL,\n      [DYNAMIC_BLUE] = A_NORMAL,\n      [DYNAMIC_CYAN] = A_BOLD,\n      [DYNAMIC_MAGENTA] = A_NORMAL,\n      [DYNAMIC_YELLOW] = A_NORMAL,\n      [DYNAMIC_WHITE] = A_BOLD,\n   },\n   [COLORSCHEME_BLACKONWHITE] = {\n      [RESET_COLOR] = ColorPair(Black, White),\n      [DEFAULT_COLOR] = ColorPair(Black, White),\n      [FUNCTION_BAR] = ColorPair(Black, Cyan),\n      [FUNCTION_KEY] = ColorPair(Black, White),\n      [PANEL_HEADER_FOCUS] = ColorPair(Black, Green),\n      [PANEL_HEADER_UNFOCUS] = ColorPair(Black, Green),\n      [PANEL_SELECTION_FOCUS] = ColorPair(Black, Cyan),\n      [PANEL_SELECTION_FOLLOW] = ColorPair(Black, Yellow),\n      [PANEL_SELECTION_UNFOCUS] = ColorPair(Blue, White),\n      [FAILED_SEARCH] = ColorPair(Red, Cyan),\n      [FAILED_READ] = ColorPair(Red, White),\n      [PAUSED] = A_BOLD | ColorPair(Yellow, Cyan),\n      [UPTIME] = ColorPair(Yellow, White),\n      [BATTERY] = ColorPair(Yellow, White),\n      [LARGE_NUMBER] = ColorPair(Red, White),\n      [METER_SHADOW] = ColorPair(Blue, White),\n      [METER_TEXT] = ColorPair(Blue, White),\n      [METER_VALUE] = ColorPair(Black, White),\n      [METER_VALUE_ERROR] = A_BOLD | ColorPair(Red, White),\n      [METER_VALUE_IOREAD] = ColorPair(Green, White),\n      [METER_VALUE_IOWRITE] = ColorPair(Yellow, White),\n      [METER_VALUE_NOTICE] = A_BOLD | ColorPair(Yellow, White),\n      [METER_VALUE_OK] = ColorPair(Green, White),\n      [METER_VALUE_WARN] = A_BOLD | ColorPair(Yellow, White),\n      [LED_COLOR] = ColorPair(Green, White),\n      [TASKS_RUNNING] = ColorPair(Green, White),\n      [PROCESS] = ColorPair(Black, White),\n      [PROCESS_SHADOW] = A_BOLD | ColorPair(Black, White),\n      [PROCESS_TAG] = ColorPair(White, Blue),\n      [PROCESS_MEGABYTES] = ColorPair(Blue, White),\n      [PROCESS_GIGABYTES] = ColorPair(Green, White),\n      [PROCESS_BASENAME] = ColorPair(Blue, White),\n      [PROCESS_TREE] = ColorPair(Green, White),\n      [PROCESS_RUN_STATE] = ColorPair(Green, White),\n      [PROCESS_D_STATE] = A_BOLD | ColorPair(Red, White),\n      [PROCESS_HIGH_PRIORITY] = ColorPair(Red, White),\n      [PROCESS_LOW_PRIORITY] = ColorPair(Green, White),\n      [PROCESS_NEW] = ColorPair(White, Green),\n      [PROCESS_TOMB] = ColorPair(White, Red),\n      [PROCESS_THREAD] = ColorPair(Blue, White),\n      [PROCESS_THREAD_BASENAME] = A_BOLD | ColorPair(Blue, White),\n      [PROCESS_COMM] = ColorPair(Magenta, White),\n      [PROCESS_THREAD_COMM] = ColorPair(Green, White),\n      [PROCESS_PRIV] = ColorPair(Magenta, White),\n      [BAR_BORDER] = ColorPair(Blue, White),\n      [BAR_SHADOW] = ColorPair(Black, White),\n      [SWAP] = ColorPair(Red, White),\n      [SWAP_CACHE] = ColorPair(Yellow, White),\n      [SWAP_FRONTSWAP] = A_BOLD | ColorPair(Black, White),\n      [GRAPH_1] = A_BOLD | ColorPair(Blue, White),\n      [GRAPH_2] = ColorPair(Blue, White),\n      [MEMORY_1] = ColorPair(Green, White),\n      [MEMORY_2] = ColorPair(Cyan, White),\n      [MEMORY_3] = ColorPair(Yellow, White),\n      [MEMORY_4] = ColorPair(Magenta, White),\n      [MEMORY_5] = A_BOLD | ColorPair(Black, White),\n      [MEMORY_6] = ColorPair(Red, White),\n      [HUGEPAGE_1] = ColorPair(Green, White),\n      [HUGEPAGE_2] = ColorPair(Yellow, White),\n      [HUGEPAGE_3] = ColorPair(Red, White),\n      [HUGEPAGE_4] = ColorPair(Blue, White),\n      [LOAD_AVERAGE_FIFTEEN] = ColorPair(Black, White),\n      [LOAD_AVERAGE_FIVE] = ColorPair(Black, White),\n      [LOAD_AVERAGE_ONE] = ColorPair(Black, White),\n      [LOAD] = ColorPair(Black, White),\n      [HELP_BOLD] = ColorPair(Blue, White),\n      [HELP_SHADOW] = A_BOLD | ColorPair(Black, White),\n      [CLOCK] = ColorPair(Black, White),\n      [DATE] = ColorPair(Black, White),\n      [DATETIME] = ColorPair(Black, White),\n      [CHECK_BOX] = ColorPair(Blue, White),\n      [CHECK_MARK] = ColorPair(Black, White),\n      [CHECK_TEXT] = ColorPair(Black, White),\n      [HOSTNAME] = ColorPair(Black, White),\n      [CPU_NICE] = ColorPair(Cyan, White),\n      [CPU_NICE_TEXT] = ColorPair(Cyan, White),\n      [CPU_NORMAL] = ColorPair(Green, White),\n      [CPU_SYSTEM] = ColorPair(Red, White),\n      [CPU_IOWAIT] = A_BOLD | ColorPair(Black, White),\n      [CPU_IRQ] = ColorPair(Blue, White),\n      [CPU_SOFTIRQ] = ColorPair(Blue, White),\n      [CPU_STEAL] = ColorPair(Cyan, White),\n      [CPU_GUEST] = ColorPair(Cyan, White),\n      [GPU_ENGINE_1] = ColorPair(Green, White),\n      [GPU_ENGINE_2] = ColorPair(Yellow, White),\n      [GPU_ENGINE_3] = ColorPair(Red, White),\n      [GPU_ENGINE_4] = ColorPair(Blue, White),\n      [GPU_RESIDUE] = ColorPair(Magenta, White),\n      [PANEL_EDIT] = ColorPair(White, Blue),\n      [SCREENS_OTH_BORDER] = A_BOLD | ColorPair(Black, White),\n      [SCREENS_OTH_TEXT] = A_BOLD | ColorPair(Black, White),\n      [SCREENS_CUR_BORDER] = ColorPair(Green, Green),\n      [SCREENS_CUR_TEXT] = ColorPair(Black, Green),\n      [PRESSURE_STALL_THREEHUNDRED] = ColorPair(Black, White),\n      [PRESSURE_STALL_SIXTY] = ColorPair(Black, White),\n      [PRESSURE_STALL_TEN] = ColorPair(Black, White),\n      [FILE_DESCRIPTOR_USED] = ColorPair(Green, White),\n      [FILE_DESCRIPTOR_MAX] = ColorPair(Blue, White),\n      [ZFS_MFU] = ColorPair(Cyan, White),\n      [ZFS_MRU] = ColorPair(Yellow, White),\n      [ZFS_ANON] = ColorPair(Magenta, White),\n      [ZFS_HEADER] = ColorPair(Yellow, White),\n      [ZFS_OTHER] = ColorPair(Magenta, White),\n      [ZFS_COMPRESSED] = ColorPair(Cyan, White),\n      [ZFS_RATIO] = ColorPair(Magenta, White),\n      [ZRAM_COMPRESSED] = ColorPair(Cyan, White),\n      [ZRAM_UNCOMPRESSED] = ColorPair(Yellow, White),\n      [DYNAMIC_GRAY] = ColorPair(Black, White),\n      [DYNAMIC_DARKGRAY] = A_BOLD | ColorPair(Black, White),\n      [DYNAMIC_RED] = ColorPair(Red, White),\n      [DYNAMIC_GREEN] = ColorPair(Green, White),\n      [DYNAMIC_BLUE] = ColorPair(Blue, White),\n      [DYNAMIC_CYAN] = ColorPair(Yellow, White),\n      [DYNAMIC_MAGENTA] = ColorPair(Magenta, White),\n      [DYNAMIC_YELLOW] = ColorPair(Yellow, White),\n      [DYNAMIC_WHITE] = A_BOLD | ColorPair(Black, White),\n   },\n   [COLORSCHEME_LIGHTTERMINAL] = {\n      [RESET_COLOR] = ColorPair(Black, Black),\n      [DEFAULT_COLOR] = ColorPair(Black, Black),\n      [FUNCTION_BAR] = ColorPair(Black, Cyan),\n      [FUNCTION_KEY] = ColorPair(Black, Black),\n      [PANEL_HEADER_FOCUS] = ColorPair(Black, Green),\n      [PANEL_HEADER_UNFOCUS] = ColorPair(Black, Green),\n      [PANEL_SELECTION_FOCUS] = ColorPair(Black, Cyan),\n      [PANEL_SELECTION_FOLLOW] = ColorPair(Black, Yellow),\n      [PANEL_SELECTION_UNFOCUS] = ColorPair(Blue, Black),\n      [FAILED_SEARCH] = ColorPair(Red, Cyan),\n      [FAILED_READ] = ColorPair(Red, Black),\n      [PAUSED] = A_BOLD | ColorPair(Yellow, Cyan),\n      [UPTIME] = ColorPair(Yellow, Black),\n      [BATTERY] = ColorPair(Yellow, Black),\n      [LARGE_NUMBER] = ColorPair(Red, Black),\n      [METER_SHADOW] = A_BOLD | ColorPairGrayBlack,\n      [METER_TEXT] = ColorPair(Blue, Black),\n      [METER_VALUE] = ColorPair(Black, Black),\n      [METER_VALUE_ERROR] = A_BOLD | ColorPair(Red, Black),\n      [METER_VALUE_IOREAD] = ColorPair(Green, Black),\n      [METER_VALUE_IOWRITE] = ColorPair(Yellow, Black),\n      [METER_VALUE_NOTICE] = A_BOLD | ColorPairWhiteDefault,\n      [METER_VALUE_OK] = ColorPair(Green, Black),\n      [METER_VALUE_WARN] = A_BOLD | ColorPair(Yellow, Black),\n      [LED_COLOR] = ColorPair(Green, Black),\n      [TASKS_RUNNING] = ColorPair(Green, Black),\n      [PROCESS] = ColorPair(Black, Black),\n      [PROCESS_SHADOW] = A_BOLD | ColorPairGrayBlack,\n      [PROCESS_TAG] = ColorPair(White, Blue),\n      [PROCESS_MEGABYTES] = ColorPair(Blue, Black),\n      [PROCESS_GIGABYTES] = ColorPair(Green, Black),\n      [PROCESS_BASENAME] = ColorPair(Green, Black),\n      [PROCESS_TREE] = ColorPair(Blue, Black),\n      [PROCESS_RUN_STATE] = ColorPair(Green, Black),\n      [PROCESS_D_STATE] = A_BOLD | ColorPair(Red, Black),\n      [PROCESS_HIGH_PRIORITY] = ColorPair(Red, Black),\n      [PROCESS_LOW_PRIORITY] = ColorPair(Green, Black),\n      [PROCESS_NEW] = ColorPair(Black, Green),\n      [PROCESS_TOMB] = ColorPair(Black, Red),\n      [PROCESS_THREAD] = ColorPair(Blue, Black),\n      [PROCESS_THREAD_BASENAME] = A_BOLD | ColorPair(Blue, Black),\n      [PROCESS_COMM] = ColorPair(Magenta, Black),\n      [PROCESS_THREAD_COMM] = ColorPair(Yellow, Black),\n      [PROCESS_PRIV] = ColorPair(Magenta, Black),\n      [BAR_BORDER] = ColorPair(Blue, Black),\n      [BAR_SHADOW] = ColorPairGrayBlack,\n      [SWAP] = ColorPair(Red, Black),\n      [SWAP_CACHE] = ColorPair(Yellow, Black),\n      [SWAP_FRONTSWAP] = ColorPairGrayBlack,\n      [GRAPH_1] = A_BOLD | ColorPair(Cyan, Black),\n      [GRAPH_2] = ColorPair(Cyan, Black),\n      [MEMORY_1] = ColorPair(Green, Black),\n      [MEMORY_2] = ColorPair(Cyan, Black),\n      [MEMORY_3] = ColorPair(Yellow, Black),\n      [MEMORY_4] = ColorPair(Magenta, Black),\n      [MEMORY_5] = ColorPairGrayBlack,\n      [MEMORY_6] = ColorPair(Blue, Black),\n      [HUGEPAGE_1] = ColorPair(Green, Black),\n      [HUGEPAGE_2] = ColorPair(Yellow, Black),\n      [HUGEPAGE_3] = ColorPair(Red, Black),\n      [HUGEPAGE_4] = ColorPair(Blue, Black),\n      [LOAD_AVERAGE_FIFTEEN] = ColorPair(Black, Black),\n      [LOAD_AVERAGE_FIVE] = ColorPair(Black, Black),\n      [LOAD_AVERAGE_ONE] = ColorPair(Black, Black),\n      [LOAD] = ColorPairWhiteDefault,\n      [HELP_BOLD] = ColorPair(Blue, Black),\n      [HELP_SHADOW] = A_BOLD | ColorPairGrayBlack,\n      [CLOCK] = ColorPairWhiteDefault,\n      [DATE] = ColorPairWhiteDefault,\n      [DATETIME] = ColorPairWhiteDefault,\n      [CHECK_BOX] = ColorPair(Blue, Black),\n      [CHECK_MARK] = ColorPair(Black, Black),\n      [CHECK_TEXT] = ColorPair(Black, Black),\n      [HOSTNAME] = ColorPairWhiteDefault,\n      [CPU_NICE] = ColorPair(Cyan, Black),\n      [CPU_NICE_TEXT] = ColorPair(Cyan, Black),\n      [CPU_NORMAL] = ColorPair(Green, Black),\n      [CPU_SYSTEM] = ColorPair(Red, Black),\n      [CPU_IOWAIT] = A_BOLD | ColorPair(Black, Black),\n      [CPU_IRQ] = A_BOLD | ColorPair(Blue, Black),\n      [CPU_SOFTIRQ] = ColorPair(Blue, Black),\n      [CPU_STEAL] = ColorPair(Black, Black),\n      [CPU_GUEST] = ColorPair(Black, Black),\n      [GPU_ENGINE_1] = ColorPair(Green, Black),\n      [GPU_ENGINE_2] = ColorPair(Yellow, Black),\n      [GPU_ENGINE_3] = ColorPair(Red, Black),\n      [GPU_ENGINE_4] = ColorPair(Blue, Black),\n      [GPU_RESIDUE] = ColorPair(Magenta, Black),\n      [PANEL_EDIT] = ColorPair(White, Blue),\n      [SCREENS_OTH_BORDER] = ColorPair(Blue, Black),\n      [SCREENS_OTH_TEXT] = ColorPair(Blue, Black),\n      [SCREENS_CUR_BORDER] = ColorPair(Green, Green),\n      [SCREENS_CUR_TEXT] = ColorPair(Black, Green),\n      [PRESSURE_STALL_THREEHUNDRED] = ColorPair(Black, Black),\n      [PRESSURE_STALL_SIXTY] = ColorPair(Black, Black),\n      [PRESSURE_STALL_TEN] = ColorPair(Black, Black),\n      [FILE_DESCRIPTOR_USED] = ColorPair(Green, Black),\n      [FILE_DESCRIPTOR_MAX] = A_BOLD | ColorPair(Blue, Black),\n      [ZFS_MFU] = ColorPair(Cyan, Black),\n      [ZFS_MRU] = ColorPair(Yellow, Black),\n      [ZFS_ANON] = A_BOLD | ColorPair(Magenta, Black),\n      [ZFS_HEADER] = ColorPair(Black, Black),\n      [ZFS_OTHER] = A_BOLD | ColorPair(Magenta, Black),\n      [ZFS_COMPRESSED] = ColorPair(Cyan, Black),\n      [ZFS_RATIO] = A_BOLD | ColorPair(Magenta, Black),\n      [ZRAM_COMPRESSED] = ColorPair(Cyan, Black),\n      [ZRAM_UNCOMPRESSED] = ColorPair(Yellow, Black),\n      [DYNAMIC_GRAY] = ColorPairGrayBlack,\n      [DYNAMIC_DARKGRAY] = A_BOLD | ColorPairGrayBlack,\n      [DYNAMIC_RED] = ColorPair(Red, Black),\n      [DYNAMIC_GREEN] = ColorPair(Green, Black),\n      [DYNAMIC_BLUE] = ColorPair(Blue, Black),\n      [DYNAMIC_CYAN] = ColorPair(Cyan, Black),\n      [DYNAMIC_MAGENTA] = ColorPair(Magenta, Black),\n      [DYNAMIC_YELLOW] = ColorPair(Yellow, Black),\n      [DYNAMIC_WHITE] = ColorPairWhiteDefault,\n   },\n   [COLORSCHEME_MIDNIGHT] = {\n      [RESET_COLOR] = ColorPair(White, Blue),\n      [DEFAULT_COLOR] = ColorPair(White, Blue),\n      [FUNCTION_BAR] = ColorPair(Black, Cyan),\n      [FUNCTION_KEY] = A_NORMAL,\n      [PANEL_HEADER_FOCUS] = ColorPair(Black, Cyan),\n      [PANEL_HEADER_UNFOCUS] = ColorPair(Black, Cyan),\n      [PANEL_SELECTION_FOCUS] = ColorPair(Black, White),\n      [PANEL_SELECTION_FOLLOW] = ColorPair(Black, Yellow),\n      [PANEL_SELECTION_UNFOCUS] = A_BOLD | ColorPair(Yellow, Blue),\n      [FAILED_SEARCH] = ColorPair(Red, Cyan),\n      [FAILED_READ] = A_BOLD | ColorPair(Red, Blue),\n      [PAUSED] = A_BOLD | ColorPair(Yellow, Cyan),\n      [UPTIME] = A_BOLD | ColorPair(Yellow, Blue),\n      [BATTERY] = A_BOLD | ColorPair(Yellow, Blue),\n      [LARGE_NUMBER] = A_BOLD | ColorPair(Red, Blue),\n      [METER_SHADOW] = ColorPair(Cyan, Blue),\n      [METER_TEXT] = ColorPair(Cyan, Blue),\n      [METER_VALUE] = A_BOLD | ColorPair(Cyan, Blue),\n      [METER_VALUE_ERROR] = A_BOLD | ColorPair(Red, Blue),\n      [METER_VALUE_IOREAD] = ColorPair(Green, Blue),\n      [METER_VALUE_IOWRITE] = ColorPair(Black, Blue),\n      [METER_VALUE_NOTICE] = A_BOLD | ColorPair(White, Blue),\n      [METER_VALUE_OK] = ColorPair(Green, Blue),\n      [METER_VALUE_WARN] = A_BOLD | ColorPair(Yellow, Black),\n      [LED_COLOR] = ColorPair(Green, Blue),\n      [TASKS_RUNNING] = A_BOLD | ColorPair(Green, Blue),\n      [PROCESS] = ColorPair(White, Blue),\n      [PROCESS_SHADOW] = A_BOLD | ColorPair(Black, Blue),\n      [PROCESS_TAG] = A_BOLD | ColorPair(Yellow, Blue),\n      [PROCESS_MEGABYTES] = ColorPair(Cyan, Blue),\n      [PROCESS_GIGABYTES] = ColorPair(Green, Blue),\n      [PROCESS_BASENAME] = A_BOLD | ColorPair(Cyan, Blue),\n      [PROCESS_TREE] = ColorPair(Cyan, Blue),\n      [PROCESS_RUN_STATE] = ColorPair(Green, Blue),\n      [PROCESS_D_STATE] = A_BOLD | ColorPair(Red, Blue),\n      [PROCESS_HIGH_PRIORITY] = ColorPair(Red, Blue),\n      [PROCESS_LOW_PRIORITY] = ColorPair(Green, Blue),\n      [PROCESS_NEW] = ColorPair(Blue, Green),\n      [PROCESS_TOMB] = ColorPair(Blue, Red),\n      [PROCESS_THREAD] = ColorPair(Green, Blue),\n      [PROCESS_THREAD_BASENAME] = A_BOLD | ColorPair(Green, Blue),\n      [PROCESS_COMM] = ColorPair(Magenta, Blue),\n      [PROCESS_THREAD_COMM] = ColorPair(Black, Blue),\n      [PROCESS_PRIV] = ColorPair(Magenta, Blue),\n      [BAR_BORDER] = A_BOLD | ColorPair(Yellow, Blue),\n      [BAR_SHADOW] = ColorPair(Cyan, Blue),\n      [SWAP] = ColorPair(Red, Blue),\n      [SWAP_CACHE] = A_BOLD | ColorPair(Yellow, Blue),\n      [SWAP_FRONTSWAP] = A_BOLD | ColorPair(Black, Blue),\n      [GRAPH_1] = A_BOLD | ColorPair(Cyan, Blue),\n      [GRAPH_2] = ColorPair(Cyan, Blue),\n      [MEMORY_1] = A_BOLD | ColorPair(Green, Blue),\n      [MEMORY_2] = A_BOLD | ColorPair(Cyan, Blue),\n      [MEMORY_3] = A_BOLD | ColorPair(Yellow, Blue),\n      [MEMORY_4] = A_BOLD | ColorPair(Magenta, Blue),\n      [MEMORY_5] = A_BOLD | ColorPair(Black, Blue),\n      [MEMORY_6] = ColorPair(Cyan, Blue),\n      [HUGEPAGE_1] = A_BOLD | ColorPair(Green, Blue),\n      [HUGEPAGE_2] = A_BOLD | ColorPair(Yellow, Blue),\n      [HUGEPAGE_3] = A_BOLD | ColorPair(Red, Blue),\n      [HUGEPAGE_4] = A_BOLD | ColorPair(White, Blue),\n      [LOAD_AVERAGE_FIFTEEN] = A_BOLD | ColorPair(Black, Blue),\n      [LOAD_AVERAGE_FIVE] = A_NORMAL | ColorPair(White, Blue),\n      [LOAD_AVERAGE_ONE] = A_BOLD | ColorPair(White, Blue),\n      [LOAD] = A_BOLD | ColorPair(White, Blue),\n      [HELP_BOLD] = A_BOLD | ColorPair(Cyan, Blue),\n      [HELP_SHADOW] = A_BOLD | ColorPair(Black, Blue),\n      [CLOCK] = ColorPair(White, Blue),\n      [DATE] = ColorPair(White, Blue),\n      [DATETIME] = ColorPair(White, Blue),\n      [CHECK_BOX] = ColorPair(Cyan, Blue),\n      [CHECK_MARK] = A_BOLD | ColorPair(White, Blue),\n      [CHECK_TEXT] = A_NORMAL | ColorPair(White, Blue),\n      [HOSTNAME] = ColorPair(White, Blue),\n      [CPU_NICE] = A_BOLD | ColorPair(Cyan, Blue),\n      [CPU_NICE_TEXT] = A_BOLD | ColorPair(Cyan, Blue),\n      [CPU_NORMAL] = A_BOLD | ColorPair(Green, Blue),\n      [CPU_SYSTEM] = A_BOLD | ColorPair(Red, Blue),\n      [CPU_IOWAIT] = A_BOLD | ColorPair(Black, Blue),\n      [CPU_IRQ] = A_BOLD | ColorPair(Black, Blue),\n      [CPU_SOFTIRQ] = ColorPair(Black, Blue),\n      [CPU_STEAL] = ColorPair(White, Blue),\n      [CPU_GUEST] = ColorPair(White, Blue),\n      [GPU_ENGINE_1] = A_BOLD | ColorPair(Green, Blue),\n      [GPU_ENGINE_2] = A_BOLD | ColorPair(Yellow, Blue),\n      [GPU_ENGINE_3] = A_BOLD | ColorPair(Red, Blue),\n      [GPU_ENGINE_4] = A_BOLD | ColorPair(White, Blue),\n      [GPU_RESIDUE] = A_BOLD | ColorPair(Magenta, Blue),\n      [PANEL_EDIT] = ColorPair(White, Blue),\n      [SCREENS_OTH_BORDER] = A_BOLD | ColorPair(Yellow, Blue),\n      [SCREENS_OTH_TEXT] = ColorPair(Cyan, Blue),\n      [SCREENS_CUR_BORDER] = ColorPair(Cyan, Cyan),\n      [SCREENS_CUR_TEXT] = ColorPair(Black, Cyan),\n      [PRESSURE_STALL_THREEHUNDRED] = A_BOLD | ColorPair(Black, Blue),\n      [PRESSURE_STALL_SIXTY] = A_NORMAL | ColorPair(White, Blue),\n      [PRESSURE_STALL_TEN] = A_BOLD | ColorPair(White, Blue),\n      [FILE_DESCRIPTOR_USED] = A_BOLD | ColorPair(Green, Blue),\n      [FILE_DESCRIPTOR_MAX] = A_BOLD | ColorPair(Red, Blue),\n      [ZFS_MFU] = A_BOLD | ColorPair(White, Blue),\n      [ZFS_MRU] = A_BOLD | ColorPair(Yellow, Blue),\n      [ZFS_ANON] = A_BOLD | ColorPair(Magenta, Blue),\n      [ZFS_HEADER] = A_BOLD | ColorPair(Yellow, Blue),\n      [ZFS_OTHER] = A_BOLD | ColorPair(Magenta, Blue),\n      [ZFS_COMPRESSED] = A_BOLD | ColorPair(White, Blue),\n      [ZFS_RATIO] = A_BOLD | ColorPair(Magenta, Blue),\n      [ZRAM_COMPRESSED] = ColorPair(Cyan, Blue),\n      [ZRAM_UNCOMPRESSED] = ColorPair(Yellow, Blue),\n      [DYNAMIC_GRAY] = ColorPairGrayBlack,\n      [DYNAMIC_DARKGRAY] = A_BOLD | ColorPairGrayBlack,\n      [DYNAMIC_RED] = ColorPair(Red, Blue),\n      [DYNAMIC_GREEN] = ColorPair(Green, Blue),\n      [DYNAMIC_BLUE] = ColorPair(Black, Blue),\n      [DYNAMIC_CYAN] = ColorPair(Cyan, Blue),\n      [DYNAMIC_MAGENTA] = ColorPair(Magenta, Blue),\n      [DYNAMIC_YELLOW] = ColorPair(Yellow, Blue),\n      [DYNAMIC_WHITE] = ColorPair(White, Blue),\n   },\n   [COLORSCHEME_BLACKNIGHT] = {\n      [RESET_COLOR] = ColorPair(Cyan, Black),\n      [DEFAULT_COLOR] = ColorPair(Cyan, Black),\n      [FUNCTION_BAR] = ColorPair(Black, Green),\n      [FUNCTION_KEY] = ColorPair(Cyan, Black),\n      [PANEL_HEADER_FOCUS] = ColorPair(Black, Green),\n      [PANEL_HEADER_UNFOCUS] = ColorPair(Black, Green),\n      [PANEL_SELECTION_FOCUS] = ColorPair(Black, Cyan),\n      [PANEL_SELECTION_FOLLOW] = ColorPair(Black, Yellow),\n      [PANEL_SELECTION_UNFOCUS] = ColorPair(Black, White),\n      [FAILED_SEARCH] = ColorPair(Red, Green),\n      [FAILED_READ] = A_BOLD | ColorPair(Red, Black),\n      [PAUSED] = A_BOLD | ColorPair(Yellow, Green),\n      [UPTIME] = ColorPair(Green, Black),\n      [BATTERY] = ColorPair(Green, Black),\n      [LARGE_NUMBER] = A_BOLD | ColorPair(Red, Black),\n      [METER_SHADOW] = A_BOLD | ColorPairGrayBlack,\n      [METER_TEXT] = ColorPair(Cyan, Black),\n      [METER_VALUE] = ColorPair(Green, Black),\n      [METER_VALUE_ERROR] = A_BOLD | ColorPair(Red, Black),\n      [METER_VALUE_IOREAD] = ColorPair(Green, Black),\n      [METER_VALUE_IOWRITE] = ColorPair(Blue, Black),\n      [METER_VALUE_NOTICE] = A_BOLD | ColorPair(White, Black),\n      [METER_VALUE_OK] = ColorPair(Green, Black),\n      [METER_VALUE_WARN] = A_BOLD | ColorPair(Yellow, Black),\n      [LED_COLOR] = ColorPair(Green, Black),\n      [TASKS_RUNNING] = A_BOLD | ColorPair(Green, Black),\n      [PROCESS] = ColorPair(Cyan, Black),\n      [PROCESS_SHADOW] = A_BOLD | ColorPairGrayBlack,\n      [PROCESS_TAG] = A_BOLD | ColorPair(Yellow, Black),\n      [PROCESS_MEGABYTES] = A_BOLD | ColorPair(Green, Black),\n      [PROCESS_GIGABYTES] = A_BOLD | ColorPair(Yellow, Black),\n      [PROCESS_BASENAME] = A_BOLD | ColorPair(Green, Black),\n      [PROCESS_TREE] = ColorPair(Cyan, Black),\n      [PROCESS_THREAD] = ColorPair(Green, Black),\n      [PROCESS_THREAD_BASENAME] = A_BOLD | ColorPair(Blue, Black),\n      [PROCESS_COMM] = ColorPair(Magenta, Black),\n      [PROCESS_THREAD_COMM] = ColorPair(Yellow, Black),\n      [PROCESS_RUN_STATE] = ColorPair(Green, Black),\n      [PROCESS_D_STATE] = A_BOLD | ColorPair(Red, Black),\n      [PROCESS_HIGH_PRIORITY] = ColorPair(Red, Black),\n      [PROCESS_LOW_PRIORITY] = ColorPair(Green, Black),\n      [PROCESS_NEW] = ColorPair(Black, Green),\n      [PROCESS_TOMB] = ColorPair(Black, Red),\n      [PROCESS_PRIV] = ColorPair(Magenta, Black),\n      [BAR_BORDER] = A_BOLD | ColorPair(Green, Black),\n      [BAR_SHADOW] = ColorPair(Cyan, Black),\n      [SWAP] = ColorPair(Red, Black),\n      [SWAP_CACHE] = ColorPair(Yellow, Black),\n      [SWAP_FRONTSWAP] = ColorPair(Yellow, Black),\n      [GRAPH_1] = A_BOLD | ColorPair(Green, Black),\n      [GRAPH_2] = ColorPair(Green, Black),\n      [MEMORY_1] = ColorPair(Green, Black),\n      [MEMORY_2] = ColorPair(Blue, Black),\n      [MEMORY_3] = ColorPair(Yellow, Black),\n      [MEMORY_4] = ColorPair(Magenta, Black),\n      [MEMORY_5] = ColorPair(Yellow, Black),\n      [MEMORY_6] = ColorPair(Cyan, Black),\n      [HUGEPAGE_1] = ColorPair(Green, Black),\n      [HUGEPAGE_2] = ColorPair(Yellow, Black),\n      [HUGEPAGE_3] = ColorPair(Red, Black),\n      [HUGEPAGE_4] = ColorPair(Blue, Black),\n      [LOAD_AVERAGE_FIFTEEN] = ColorPair(Green, Black),\n      [LOAD_AVERAGE_FIVE] = ColorPair(Green, Black),\n      [LOAD_AVERAGE_ONE] = A_BOLD | ColorPair(Green, Black),\n      [LOAD] = A_BOLD,\n      [HELP_BOLD] = A_BOLD | ColorPair(Cyan, Black),\n      [HELP_SHADOW] = A_BOLD | ColorPairGrayBlack,\n      [CLOCK] = ColorPair(Green, Black),\n      [CHECK_BOX] = ColorPair(Green, Black),\n      [CHECK_MARK] = A_BOLD | ColorPair(Green, Black),\n      [CHECK_TEXT] = ColorPair(Cyan, Black),\n      [HOSTNAME] = ColorPair(Green, Black),\n      [CPU_NICE] = ColorPair(Blue, Black),\n      [CPU_NICE_TEXT] = A_BOLD | ColorPair(Blue, Black),\n      [CPU_NORMAL] = ColorPair(Green, Black),\n      [CPU_SYSTEM] = ColorPair(Red, Black),\n      [CPU_IOWAIT] = ColorPair(Yellow, Black),\n      [CPU_IRQ] = A_BOLD | ColorPair(Blue, Black),\n      [CPU_SOFTIRQ] = ColorPair(Blue, Black),\n      [CPU_STEAL] = ColorPair(Cyan, Black),\n      [CPU_GUEST] = ColorPair(Cyan, Black),\n      [GPU_ENGINE_1] = ColorPair(Green, Black),\n      [GPU_ENGINE_2] = ColorPair(Yellow, Black),\n      [GPU_ENGINE_3] = ColorPair(Red, Black),\n      [GPU_ENGINE_4] = ColorPair(Blue, Black),\n      [GPU_RESIDUE] = ColorPair(Magenta, Black),\n      [PANEL_EDIT] = ColorPair(White, Cyan),\n      [SCREENS_OTH_BORDER] = ColorPair(White, Black),\n      [SCREENS_OTH_TEXT] = ColorPair(Cyan, Black),\n      [SCREENS_CUR_BORDER] = A_BOLD | ColorPair(White, Black),\n      [SCREENS_CUR_TEXT] = A_BOLD | ColorPair(Green, Black),\n      [PRESSURE_STALL_THREEHUNDRED] = ColorPair(Green, Black),\n      [PRESSURE_STALL_SIXTY] = ColorPair(Green, Black),\n      [PRESSURE_STALL_TEN] = A_BOLD | ColorPair(Green, Black),\n      [FILE_DESCRIPTOR_USED] = ColorPair(Green, Black),\n      [FILE_DESCRIPTOR_MAX] = A_BOLD | ColorPair(Blue, Black),\n      [ZFS_MFU] = ColorPair(Blue, Black),\n      [ZFS_MRU] = ColorPair(Yellow, Black),\n      [ZFS_ANON] = ColorPair(Magenta, Black),\n      [ZFS_HEADER] = ColorPair(Yellow, Black),\n      [ZFS_OTHER] = ColorPair(Magenta, Black),\n      [ZFS_COMPRESSED] = ColorPair(Blue, Black),\n      [ZFS_RATIO] = ColorPair(Magenta, Black),\n      [ZRAM_COMPRESSED] = ColorPair(Blue, Black),\n      [ZRAM_UNCOMPRESSED] = ColorPair(Yellow, Black),\n      [DYNAMIC_GRAY] = ColorPairGrayBlack,\n      [DYNAMIC_DARKGRAY] = A_BOLD | ColorPairGrayBlack,\n      [DYNAMIC_RED] = ColorPair(Red, Black),\n      [DYNAMIC_GREEN] = ColorPair(Green, Black),\n      [DYNAMIC_BLUE] = ColorPair(Blue, Black),\n      [DYNAMIC_CYAN] = ColorPair(Cyan, Black),\n      [DYNAMIC_MAGENTA] = ColorPair(Magenta, Black),\n      [DYNAMIC_YELLOW] = ColorPair(Yellow, Black),\n      [DYNAMIC_WHITE] = ColorPair(White, Black),\n   },\n   [COLORSCHEME_BROKENGRAY] = { 0 }, // dynamically generated.\n   [COLORSCHEME_NORD] = {\n      [RESET_COLOR] = A_NORMAL,\n      [DEFAULT_COLOR] = A_NORMAL,\n      [FUNCTION_BAR] = ColorPair(Black, Cyan),\n      [FUNCTION_KEY] = A_NORMAL,\n      [PANEL_HEADER_FOCUS] = ColorPair(Black, Cyan),\n      [PANEL_HEADER_UNFOCUS] = ColorPair(Black, Cyan),\n      [PANEL_SELECTION_FOCUS] = ColorPair(Black, Cyan),\n      [PANEL_SELECTION_FOLLOW] = A_REVERSE,\n      [PANEL_SELECTION_UNFOCUS] = A_BOLD,\n      [FAILED_SEARCH] = A_REVERSE | A_BOLD | ColorPair(Yellow, Black),\n      [FAILED_READ] = A_BOLD | ColorPair(Yellow, Black),\n      [PAUSED] = A_BOLD | ColorPair(Black, Cyan),\n      [UPTIME] = A_BOLD,\n      [BATTERY] = A_BOLD,\n      [LARGE_NUMBER] = A_BOLD | ColorPair(Yellow, Black),\n      [METER_SHADOW] = A_BOLD | ColorPairGrayBlack,\n      [METER_TEXT] = A_NORMAL,\n      [METER_VALUE] = A_BOLD,\n      [METER_VALUE_ERROR] = A_BOLD,\n      [METER_VALUE_IOREAD] = A_NORMAL,\n      [METER_VALUE_IOWRITE] = A_NORMAL,\n      [METER_VALUE_NOTICE] = A_BOLD | ColorPair(Cyan, Black),\n      [METER_VALUE_OK] = A_NORMAL,\n      [METER_VALUE_WARN] = A_BOLD,\n      [LED_COLOR] = A_NORMAL,\n      [TASKS_RUNNING] = A_BOLD,\n      [PROCESS] = A_NORMAL,\n      [PROCESS_SHADOW] = A_BOLD | ColorPairGrayBlack,\n      [PROCESS_TAG] = A_BOLD | ColorPair(Cyan, Black),\n      [PROCESS_MEGABYTES] = A_BOLD | ColorPair(White, Black),\n      [PROCESS_GIGABYTES] = A_BOLD | ColorPair(Cyan, Black),\n      [PROCESS_BASENAME] = A_BOLD,\n      [PROCESS_TREE] = A_BOLD,\n      [PROCESS_RUN_STATE] = A_BOLD,\n      [PROCESS_D_STATE]  = A_BOLD | ColorPair(Yellow, Black),\n      [PROCESS_HIGH_PRIORITY] = A_BOLD,\n      [PROCESS_LOW_PRIORITY] = A_BOLD | ColorPairGrayBlack,\n      [PROCESS_NEW] = A_BOLD,\n      [PROCESS_TOMB] = A_BOLD | ColorPairGrayBlack,\n      [PROCESS_PRIV] = A_BOLD | ColorPair(Cyan, Black),\n      [BAR_BORDER] = A_BOLD,\n      [BAR_SHADOW] = A_BOLD | ColorPairGrayBlack,\n      [SWAP] = A_BOLD,\n      [SWAP_CACHE] = A_NORMAL,\n      [SWAP_FRONTSWAP] = A_BOLD | ColorPairGrayBlack,\n      [GRAPH_1]  = A_BOLD,\n      [GRAPH_2] = A_NORMAL,\n      [MEMORY_1] = A_BOLD | ColorPair(Yellow, Black),\n      [MEMORY_2] = A_NORMAL,\n      [MEMORY_3] = A_NORMAL,\n      [MEMORY_4] = A_NORMAL,\n      [MEMORY_5] = A_BOLD | ColorPairGrayBlack,\n      [MEMORY_6] = A_NORMAL,\n      [HUGEPAGE_1] = A_BOLD,\n      [HUGEPAGE_2] = A_NORMAL,\n      [HUGEPAGE_3] = A_BOLD | ColorPair(Cyan, Black),\n      [HUGEPAGE_4] = A_BOLD | ColorPair(Cyan, Black),\n      [LOAD_AVERAGE_FIFTEEN] = A_BOLD | ColorPairGrayBlack,\n      [LOAD_AVERAGE_FIVE] = A_NORMAL,\n      [LOAD_AVERAGE_ONE] = A_BOLD,\n      [LOAD] = A_BOLD,\n      [HELP_BOLD] = A_BOLD,\n      [HELP_SHADOW] = A_BOLD | ColorPairGrayBlack,\n      [CLOCK] = A_BOLD,\n      [DATE] = A_BOLD,\n      [DATETIME] = A_BOLD,\n      [CHECK_BOX] = A_BOLD,\n      [CHECK_MARK] = A_NORMAL,\n      [CHECK_TEXT] = A_NORMAL,\n      [HOSTNAME] = A_BOLD | ColorPair(Cyan, Black),\n      [CPU_NICE] = A_NORMAL,\n      [CPU_NICE_TEXT] = A_NORMAL,\n      [CPU_NORMAL] = A_BOLD,\n      [CPU_SYSTEM] = A_BOLD | ColorPair(Yellow, Black),\n      [CPU_IOWAIT] = A_NORMAL,\n      [CPU_IRQ] = A_BOLD,\n      [CPU_SOFTIRQ] = A_BOLD,\n      [CPU_STEAL] = A_BOLD | ColorPairGrayBlack,\n      [CPU_GUEST] = A_BOLD | ColorPairGrayBlack,\n      [GPU_ENGINE_1] = A_BOLD,\n      [GPU_ENGINE_2] = A_NORMAL,\n      [GPU_ENGINE_3] = A_BOLD | ColorPair(Cyan, Black),\n      [GPU_ENGINE_4] = A_BOLD | ColorPair(Cyan, Black),\n      [GPU_RESIDUE] = A_BOLD,\n      [PANEL_EDIT] = A_BOLD,\n      [SCREENS_OTH_BORDER] = A_BOLD | ColorPairGrayBlack,\n      [SCREENS_OTH_TEXT]  = A_BOLD | ColorPairGrayBlack,\n      [SCREENS_CUR_BORDER] = ColorPair(Black, Cyan),\n      [SCREENS_CUR_TEXT] = ColorPair(Black, Cyan),\n      [PRESSURE_STALL_THREEHUNDRED] = A_BOLD | ColorPairGrayBlack,\n      [PRESSURE_STALL_SIXTY] = A_NORMAL,\n      [PRESSURE_STALL_TEN] = A_BOLD,\n      [FILE_DESCRIPTOR_USED] = A_BOLD,\n      [FILE_DESCRIPTOR_MAX] = A_BOLD | ColorPair(Yellow, Black),\n      [ZFS_MFU] = A_NORMAL,\n      [ZFS_MRU] = A_NORMAL,\n      [ZFS_ANON] = A_BOLD | ColorPairGrayBlack,\n      [ZFS_HEADER] = A_BOLD,\n      [ZFS_OTHER] = A_BOLD | ColorPairGrayBlack,\n      [ZFS_COMPRESSED] = A_BOLD,\n      [ZFS_RATIO] = A_BOLD,\n      [ZRAM_COMPRESSED] = A_NORMAL,\n      [ZRAM_UNCOMPRESSED] = A_NORMAL,\n      [DYNAMIC_GRAY] = A_BOLD | ColorPairGrayBlack,\n      [DYNAMIC_DARKGRAY] = A_BOLD | ColorPairGrayBlack,\n      [DYNAMIC_RED] = A_BOLD | ColorPair(Yellow, Black),\n      [DYNAMIC_GREEN] = A_BOLD,\n      [DYNAMIC_BLUE] = A_BOLD | ColorPair(Cyan, Black),\n      [DYNAMIC_CYAN] = A_BOLD | ColorPair(Cyan, Black),\n      [DYNAMIC_MAGENTA] = A_BOLD,\n      [DYNAMIC_YELLOW] = A_BOLD | ColorPair(Yellow, Black),\n      [DYNAMIC_WHITE] = A_BOLD,\n   },\n};\n\nstatic bool CRT_retainScreenOnExit = false;\n\nint CRT_scrollHAmount = 5;\n\nint CRT_scrollWheelVAmount = 10;\n\nColorScheme CRT_colorScheme = COLORSCHEME_DEFAULT;\n\nATTR_NORETURN\nstatic void CRT_handleSIGTERM(int sgn) {\n   CRT_done();\n\n   if (!CRT_settings->changed)\n      _exit(0);\n\n   const char* signal_str = strsignal(sgn);\n   if (!signal_str)\n      signal_str = \"unknown reason\";\n\n   char err_buf[512];\n   snprintf(err_buf, sizeof(err_buf),\n           \"A signal %d (%s) was received, exiting without persisting settings to htoprc.\\n\",\n           sgn, signal_str);\n   full_write_str(STDERR_FILENO, err_buf);\n   _exit(0);\n}\n\n#ifndef NDEBUG\n\nstatic int stderrRedirectNewFd = -1;\nstatic int stderrRedirectBackupFd = -1;\n\nstatic int createStderrCacheFile(void) {\n#if defined(HAVE_MEMFD_CREATE)\n   return memfd_create(\"htop.stderr-redirect\", 0);\n#elif defined(O_TMPFILE)\n   return open(\"/tmp\", O_TMPFILE | O_CREAT | O_EXCL | O_RDWR, S_IRUSR | S_IWUSR);\n#else\n   char tmpName[] = \"htop.stderr-redirectXXXXXX\";\n   mode_t curUmask = umask(S_IXUSR | S_IRWXG | S_IRWXO);\n   int r = mkstemp(tmpName);\n   umask(curUmask);\n   if (r < 0)\n      return r;\n\n   (void) unlink(tmpName);\n\n   return r;\n#endif /* HAVE_MEMFD_CREATE */\n}\n\nstatic void redirectStderr(void) {\n   stderrRedirectNewFd = createStderrCacheFile();\n   if (stderrRedirectNewFd < 0) {\n      /* ignore failure */\n      return;\n   }\n\n   stderrRedirectBackupFd = dup(STDERR_FILENO);\n   dup2(stderrRedirectNewFd, STDERR_FILENO);\n}\n\nstatic void dumpStderr(void) {\n   if (stderrRedirectNewFd < 0)\n      return;\n\n   fsync(STDERR_FILENO);\n   dup2(stderrRedirectBackupFd, STDERR_FILENO);\n   close(stderrRedirectBackupFd);\n   stderrRedirectBackupFd = -1;\n   lseek(stderrRedirectNewFd, 0, SEEK_SET);\n\n   bool header = false;\n   char buffer[8192];\n   for (;;) {\n      errno = 0;\n      ssize_t res = read(stderrRedirectNewFd, buffer, sizeof(buffer));\n      if (res < 0) {\n         if (errno == EINTR)\n            continue;\n\n         break;\n      }\n\n      if (res == 0) {\n         break;\n      }\n\n      if (res > 0) {\n         if (!header) {\n            full_write_str(STDERR_FILENO, \">>>>>>>>>> stderr output >>>>>>>>>>\\n\");\n            header = true;\n         }\n         full_write(STDERR_FILENO, buffer, res);\n      }\n   }\n\n   if (header)\n      full_write_str(STDERR_FILENO, \"\\n<<<<<<<<<< stderr output <<<<<<<<<<\\n\");\n\n   close(stderrRedirectNewFd);\n   stderrRedirectNewFd = -1;\n}\n\nvoid CRT_debug_impl(const char* file, size_t lineno, const char* func, const char* fmt, ...)  {\n   va_list args;\n\n   fprintf(stderr, \"[%s:%zu (%s)]: \", file, lineno, func);\n   va_start(args, fmt);\n   vfprintf(stderr, fmt, args);\n   va_end(args);\n   fprintf(stderr, \"\\n\");\n}\n\n#else /* !NDEBUG */\n\nstatic void redirectStderr(void) {\n}\n\nstatic void dumpStderr(void) {\n}\n\n#endif /* !NDEBUG */\n\nstatic struct sigaction old_sig_handler[32];\n\nstatic void CRT_installSignalHandlers(void) {\n   struct sigaction act;\n   sigemptyset(&act.sa_mask);\n   act.sa_flags = (int)SA_RESETHAND | SA_NODEFER;\n   act.sa_handler = CRT_handleSIGSEGV;\n   sigaction(SIGSEGV, &act, &old_sig_handler[SIGSEGV]);\n   sigaction(SIGFPE, &act, &old_sig_handler[SIGFPE]);\n   sigaction(SIGILL, &act, &old_sig_handler[SIGILL]);\n   sigaction(SIGBUS, &act, &old_sig_handler[SIGBUS]);\n#ifndef HTOP_PCP\n   sigaction(SIGPIPE, &act, &old_sig_handler[SIGPIPE]);\n#else\n   signal(SIGPIPE, SIG_IGN);\n#endif\n   sigaction(SIGSYS, &act, &old_sig_handler[SIGSYS]);\n   sigaction(SIGABRT, &act, &old_sig_handler[SIGABRT]);\n\n   signal(SIGCHLD, SIG_DFL);\n   signal(SIGINT, CRT_handleSIGTERM);\n   signal(SIGTERM, CRT_handleSIGTERM);\n   signal(SIGQUIT, CRT_handleSIGTERM);\n   signal(SIGUSR1, SIG_IGN);\n   signal(SIGUSR2, SIG_IGN);\n}\n\nvoid CRT_resetSignalHandlers(void) {\n   sigaction(SIGSEGV, &old_sig_handler[SIGSEGV], NULL);\n   sigaction(SIGFPE, &old_sig_handler[SIGFPE], NULL);\n   sigaction(SIGILL, &old_sig_handler[SIGILL], NULL);\n   sigaction(SIGBUS, &old_sig_handler[SIGBUS], NULL);\n   sigaction(SIGPIPE, &old_sig_handler[SIGPIPE], NULL);\n   sigaction(SIGSYS, &old_sig_handler[SIGSYS], NULL);\n   sigaction(SIGABRT, &old_sig_handler[SIGABRT], NULL);\n\n   signal(SIGINT, SIG_DFL);\n   signal(SIGTERM, SIG_DFL);\n   signal(SIGQUIT, SIG_DFL);\n   signal(SIGUSR1, SIG_DFL);\n   signal(SIGUSR2, SIG_DFL);\n}\n\n#ifdef HAVE_GETMOUSE\nvoid CRT_setMouse(bool enabled) {\n   if (enabled) {\n#if NCURSES_MOUSE_VERSION > 1\n      mousemask(BUTTON1_RELEASED | BUTTON3_RELEASED | BUTTON4_PRESSED | BUTTON5_PRESSED, NULL);\n#else\n      mousemask(BUTTON1_RELEASED | BUTTON3_RELEASED, NULL);\n#endif\n   } else {\n      mousemask(0, NULL);\n   }\n}\n#endif\n\nstatic bool terminalSupportsDefinedKeys(const char* termType) {\n   if (!termType) {\n      return false;\n   }\n\n   #define IS_END_OR_DASH(ch) ((ch) == '-' || (ch) == '\\0')\n\n   switch (termType[0]) {\n   case 'a':\n      if (String_eq(termType, \"alacritty\")) {\n         return true;\n      }\n      break;\n   case 'f':\n      if (String_eq(termType, \"foot\")) {\n         return true;\n      }\n      break;\n   case 's':\n      if (termType[1] == 't' && IS_END_OR_DASH(termType[2])) {\n         return true;\n      }\n      if (String_startsWith(termType, \"screen\") && IS_END_OR_DASH(termType[6])) {\n         return true;\n      }\n      break;\n   case 't':\n      if (String_startsWith(termType, \"tmux\") && IS_END_OR_DASH(termType[4])) {\n         return true;\n      }\n      break;\n   case 'v':\n      if (String_eq(termType, \"vt220\")) {\n         return true;\n      }\n      break;\n   case 'x':\n      if (String_startsWith(termType, \"xterm\") && IS_END_OR_DASH(termType[5])) {\n         return true;\n      }\n      break;\n   }\n\n   return false;\n}\n\nvoid CRT_init(const Settings* settings, bool allowUnicode, bool retainScreenOnExit) {\n   initscr();\n\n   if (retainScreenOnExit) {\n      CRT_retainScreenOnExit = true;\n      refresh();\n      tputs(exit_ca_mode, 0, putchar);\n      tputs(clear_screen, 0, putchar);\n      fflush(stdout);\n      enter_ca_mode = 0;\n      exit_ca_mode = 0;\n   }\n\n   redirectStderr();\n   noecho();\n   CRT_settings = settings;\n\n   for (int i = 0; i < LAST_COLORELEMENT; i++) {\n      unsigned int color = CRT_colorSchemes[COLORSCHEME_DEFAULT][i];\n      CRT_colorSchemes[COLORSCHEME_BROKENGRAY][i] = color == (A_BOLD | ColorPairGrayBlack) ? ColorPair(White, Black) : color;\n   }\n\n   halfdelay(settings->delay);\n   nonl();\n   intrflush(stdscr, false);\n   keypad(stdscr, true);\n#ifdef HAVE_GETMOUSE\n   mouseinterval(0);\n#endif\n   curs_set(0);\n\n   if (has_colors()) {\n      start_color();\n   }\n\n   const char* termType = getenv(\"TERM\");\n   if (termType && String_eq(termType, \"linux\")) {\n      CRT_scrollHAmount = 20;\n   } else {\n      CRT_scrollHAmount = 5;\n   }\n\n   if (terminalSupportsDefinedKeys(termType)) {\n#ifdef HTOP_NETBSD\n#define define_key(s_, k_) define_key((char*)s_, k_)\nIGNORE_WCASTQUAL_BEGIN\n#endif\n      define_key(\"\\033[H\", KEY_HOME);\n      define_key(\"\\033[F\", KEY_END);\n      define_key(\"\\033[7~\", KEY_HOME);\n      define_key(\"\\033[8~\", KEY_END);\n      define_key(\"\\033OP\", KEY_F(1));\n      define_key(\"\\033OQ\", KEY_F(2));\n      define_key(\"\\033OR\", KEY_F(3));\n      define_key(\"\\033OS\", KEY_F(4));\n      define_key(\"\\033O2R\", KEY_F(15));\n      define_key(\"\\033[11~\", KEY_F(1));\n      define_key(\"\\033[12~\", KEY_F(2));\n      define_key(\"\\033[13~\", KEY_F(3));\n      define_key(\"\\033[14~\", KEY_F(4));\n      define_key(\"\\033[14;2~\", KEY_F(15));\n      define_key(\"\\033[17;2~\", KEY_F(18));\n      define_key(\"\\033[Z\", KEY_SHIFT_TAB);\n      char sequence[3] = \"\\033a\";\n      for (char c = 'a'; c <= 'z'; c++) {\n         sequence[1] = c;\n         define_key(sequence, KEY_ALT('A' + (c - 'a')));\n      }\n      define_key(\"\\033[I\", KEY_FOCUS_IN);\n      define_key(\"\\033[O\", KEY_FOCUS_OUT);\n#ifdef HTOP_NETBSD\nIGNORE_WCASTQUAL_END\n#undef define_key\n#endif\n   }\n   if (termType && (String_startsWith(termType, \"rxvt\"))) {\n      define_key(\"\\033[Z\", KEY_SHIFT_TAB);\n   }\n\n   CRT_installSignalHandlers();\n\n   use_default_colors();\n\n   CRT_setColors(has_colors() ? settings->colorScheme : COLORSCHEME_MONOCHROME);\n\n#ifdef HAVE_LIBNCURSESW\n   if (allowUnicode && String_eq(nl_langinfo(CODESET), \"UTF-8\")) {\n      CRT_utf8 = true;\n   } else {\n      CRT_utf8 = false;\n   }\n#else\n   (void) allowUnicode;\n#endif\n\n   CRT_treeStr =\n#ifdef HAVE_LIBNCURSESW\n      CRT_utf8 ? CRT_treeStrUtf8 :\n#endif\n      CRT_treeStrAscii;\n\n   CRT_setMouse(settings->enableMouse);\n\n   initDegreeSign();\n}\n\nvoid CRT_done(void) {\n   int resetColor = CRT_colors ? CRT_colors[RESET_COLOR] : CRT_colorSchemes[COLORSCHEME_DEFAULT][RESET_COLOR];\n\n   attron(resetColor);\n   mvhline(LINES - 1, 0, ' ', COLS);\n   attroff(resetColor);\n   refresh();\n\n   if (CRT_retainScreenOnExit) {\n      mvcur(-1, -1, LINES - 1, 0);\n   }\n\n   curs_set(1);\n   endwin();\n\n   dumpStderr();\n}\n\nvoid CRT_fatalError(const char* note) {\n   const char* sysMsg = strerror(errno);\n   CRT_done();\n   fprintf(stderr, \"%s: %s\\n\", note, sysMsg);\n   exit(2);\n}\n\nint CRT_readKey(void) {\n   nocbreak();\n   cbreak();\n   nodelay(stdscr, FALSE);\n   int ret = getch();\n   halfdelay(CRT_settings->delay);\n   return ret;\n}\n\nvoid CRT_disableDelay(void) {\n   nocbreak();\n   cbreak();\n   nodelay(stdscr, TRUE);\n}\n\nvoid CRT_enableDelay(void) {\n   halfdelay(CRT_settings->delay);\n}\n\nvoid CRT_setColors(int colorScheme) {\n   if (colorScheme >= LAST_COLORSCHEME || colorScheme < 0) {\n      colorScheme = COLORSCHEME_DEFAULT;\n   }\n\n   CRT_colorScheme = colorScheme;\n\n   for (short int i = 0; i < 8; i++) {\n      for (short int j = 0; j < 8; j++) {\n         if (ColorIndex(i, j) != ColorIndexGrayBlack && ColorIndex(i, j) != ColorIndexWhiteDefault) {\n            short int bg = (colorScheme != COLORSCHEME_BLACKNIGHT) && (j == 0) ? -1 : j;\n            init_pair(ColorIndex(i, j), i, bg);\n         }\n      }\n   }\n\n   short int grayBlackFg = COLORS > 8 ? 8 : 0;\n   short int grayBlackBg = (colorScheme != COLORSCHEME_BLACKNIGHT) ? -1 : 0;\n   init_pair(ColorIndexGrayBlack, grayBlackFg, grayBlackBg);\n\n   init_pair(ColorIndexWhiteDefault, White, -1);\n\n   CRT_colors = CRT_colorSchemes[colorScheme];\n}\n\n#ifdef PRINT_BACKTRACE\nstatic void print_backtrace(void) {\n#if defined(HAVE_LIBUNWIND_H) && defined(HAVE_LOCAL_UNWIND)\n   unw_context_t context;\n   unw_getcontext(&context);\n\n   unw_cursor_t cursor;\n   unw_init_local(&cursor, &context);\n\n   unsigned int item = 0;\n\n   char err_buf[1024];\n\n   while (unw_step(&cursor) > 0) {\n      unw_word_t pc;\n      unw_get_reg(&cursor, UNW_REG_IP, &pc);\n      if (pc == 0)\n         break;\n\n      char symbolName[256] = \"?\";\n      unw_word_t offset = 0;\n      unw_get_proc_name(&cursor, symbolName, sizeof(symbolName), &offset);\n\n      unw_proc_info_t pip;\n      pip.unwind_info = 0;\n\n      const char* fname = \"?\";\n      const void* ptr = 0;\n      if (unw_get_proc_info(&cursor, &pip) == 0) {\n         ptr = (const void*)(pip.start_ip + offset);\n\n         #ifdef HAVE_DLADDR\n         Dl_info dlinfo;\n         if (dladdr(ptr, &dlinfo) && dlinfo.dli_fname && *dlinfo.dli_fname)\n            fname = dlinfo.dli_fname;\n         #endif\n      }\n\n      const bool is_signal_frame = unw_is_signal_frame(&cursor) > 0;\n      const char* frame = is_signal_frame ? \"  {signal frame}\" : \"\";\n\n      snprintf(err_buf, sizeof(err_buf), \"%2u: %#14lx  %s  (%s+%#lx)  [%p]%s\\n\", item++, pc, fname, symbolName, offset, ptr, frame);\n      full_write_str(STDERR_FILENO, err_buf);\n   }\n#elif defined(HAVE_EXECINFO_H) && defined(BACKTRACE_RETURN_TYPE)\n   void* backtraceArray[256];\n\n   BACKTRACE_RETURN_TYPE nptrs = backtrace(backtraceArray, ARRAYSIZE(backtraceArray));\n   if (nptrs > 0) {\n      backtrace_symbols_fd(backtraceArray, nptrs, STDERR_FILENO);\n   } else {\n      full_write_str(STDERR_FILENO,\n         \"[No backtrace information available from libc]\\n\"\n      );\n   }\n#else\n#error No implementation for print_backtrace()!\n#endif\n}\n#endif\n\nvoid CRT_handleSIGSEGV(int signal) {\n   CRT_done();\n\n   char err_buf[512];\n\n   snprintf(err_buf, sizeof(err_buf), \"\\n\\n\"\n      \"FATAL PROGRAM ERROR DETECTED\\n\"\n      \"============================\\n\"\n      \"Please check at https://htop.dev/issues whether this issue has already been reported.\\n\"\n      \"If no similar issue has been reported before, please create a new issue with the following information:\\n\"\n      \"  - Your %s version: '\"VERSION\"'\\n\"\n      \"  - Your OS and kernel version (uname -a)\\n\"\n      \"  - Your distribution and release (lsb_release -a)\\n\"\n      \"  - Likely steps to reproduce (How did it happen?)\\n\",\n      program\n   );\n   full_write_str(STDERR_FILENO, err_buf);\n\n#ifdef PRINT_BACKTRACE\n   full_write_str(STDERR_FILENO, \"  - Backtrace of the issue (see below)\\n\");\n#endif\n\n   full_write_str(STDERR_FILENO,\n      \"\\n\"\n   );\n\n   const char* signal_str = strsignal(signal);\n   if (!signal_str) {\n      signal_str = \"unknown reason\";\n   }\n   snprintf(err_buf, sizeof(err_buf),\n      \"Error information:\\n\"\n      \"------------------\\n\"\n      \"A signal %d (%s) was received.\\n\"\n      \"\\n\",\n      signal, signal_str\n   );\n   full_write_str(STDERR_FILENO, err_buf);\n\n   full_write_str(STDERR_FILENO,\n      \"Setting information:\\n\"\n      \"--------------------\\n\");\n   Settings_write(CRT_settings, true);\n   full_write_str(STDERR_FILENO, \"\\n\\n\");\n\n#ifdef PRINT_BACKTRACE\n   full_write_str(STDERR_FILENO,\n      \"Backtrace information:\\n\"\n      \"----------------------\\n\"\n   );\n\n   print_backtrace();\n\n   snprintf(err_buf, sizeof(err_buf),\n      \"\\n\"\n      \"To make the above information more practical to work with, \"\n      \"please also provide a disassembly of your %s binary. \"\n      \"This can usually be done by running the following command:\\n\"\n      \"\\n\",\n      program\n   );\n   full_write_str(STDERR_FILENO, err_buf);\n\n#ifdef HTOP_DARWIN\n   snprintf(err_buf, sizeof(err_buf), \"   otool -tvV `which %s` > ~/%s.otool\\n\", program, program);\n#else\n   snprintf(err_buf, sizeof(err_buf), \"   objdump -d -S -w `which %s` > ~/%s.objdump\\n\", program, program);\n#endif\n   full_write_str(STDERR_FILENO, err_buf);\n\n   full_write_str(STDERR_FILENO,\n      \"\\n\"\n      \"Please include the generated file in your report.\\n\"\n   );\n#endif\n\n   snprintf(err_buf, sizeof(err_buf),\n      \"Running this program with debug symbols or inside a debugger may provide further insights.\\n\"\n      \"\\n\"\n      \"Thank you for helping to improve %s!\\n\"\n      \"\\n\",\n      program\n   );\n   full_write_str(STDERR_FILENO, err_buf);\n\n   /* Call old sigsegv handler; may be default exit or third party one (e.g. ASAN) */\n   if (sigaction(signal, &old_sig_handler[signal], NULL) < 0) {\n      /* This avoids an infinite loop in case the handler could not be reset. */\n      full_write_str(STDERR_FILENO,\n         \"!!! Chained handler could not be restored. Forcing exit.\\n\"\n      );\n      _exit(1);\n   }\n\n   /* Trigger the previous signal handler. */\n   raise(signal);\n\n   // Always terminate, even if installed handler returns\n   full_write_str(STDERR_FILENO,\n      \"!!! Chained handler did not exit. Forcing exit.\\n\"\n   );\n   _exit(1);\n}\n"
  },
  {
    "path": "CRT.h",
    "content": "#ifndef HEADER_CRT\n#define HEADER_CRT\n/*\nhtop - CRT.h\n(C) 2004-2011 Hisham H. Muhammad\nReleased under the GNU GPLv2+, see the COPYING file\nin the source distribution for its full text.\n*/\n\n#include <stdbool.h>\n\n#include \"Macros.h\"\n#include \"ProvideCurses.h\"\n#include \"Settings.h\"\n\n\n#define SCREEN_TAB_MARGIN_LEFT 2\n#define SCREEN_TAB_COLUMN_GAP  1\n\ntypedef enum TreeStr_ {\n   TREE_STR_VERT,\n   TREE_STR_RTEE,\n   TREE_STR_BEND,\n   TREE_STR_TEND,\n   TREE_STR_OPEN,\n   TREE_STR_SHUT,\n   TREE_STR_ASC,\n   TREE_STR_DESC,\n   LAST_TREE_STR\n} TreeStr;\n\ntypedef enum ColorScheme_ {\n   COLORSCHEME_DEFAULT,\n   COLORSCHEME_MONOCHROME,\n   COLORSCHEME_BLACKONWHITE,\n   COLORSCHEME_LIGHTTERMINAL,\n   COLORSCHEME_MIDNIGHT,\n   COLORSCHEME_BLACKNIGHT,\n   COLORSCHEME_BROKENGRAY,\n   COLORSCHEME_NORD,\n   LAST_COLORSCHEME\n} ColorScheme;\n\ntypedef enum ColorElements_ {\n   RESET_COLOR,\n   DEFAULT_COLOR,\n   FUNCTION_BAR,\n   FUNCTION_KEY,\n   FAILED_SEARCH,\n   FAILED_READ,\n   PAUSED,\n   PANEL_HEADER_FOCUS,\n   PANEL_HEADER_UNFOCUS,\n   PANEL_SELECTION_FOCUS,\n   PANEL_SELECTION_FOLLOW,\n   PANEL_SELECTION_UNFOCUS,\n   LARGE_NUMBER,\n   METER_SHADOW,\n   METER_TEXT,\n   METER_VALUE,\n   METER_VALUE_ERROR,\n   METER_VALUE_IOREAD,\n   METER_VALUE_IOWRITE,\n   METER_VALUE_NOTICE,\n   METER_VALUE_OK,\n   METER_VALUE_WARN,\n   LED_COLOR,\n   UPTIME,\n   BATTERY,\n   TASKS_RUNNING,\n   SWAP,\n   SWAP_CACHE,\n   SWAP_FRONTSWAP,\n   PROCESS,\n   PROCESS_SHADOW,\n   PROCESS_TAG,\n   PROCESS_MEGABYTES,\n   PROCESS_GIGABYTES,\n   PROCESS_TREE,\n   PROCESS_RUN_STATE,\n   PROCESS_D_STATE,\n   PROCESS_BASENAME,\n   PROCESS_HIGH_PRIORITY,\n   PROCESS_LOW_PRIORITY,\n   PROCESS_NEW,\n   PROCESS_TOMB,\n   PROCESS_THREAD,\n   PROCESS_THREAD_BASENAME,\n   PROCESS_COMM,\n   PROCESS_THREAD_COMM,\n   PROCESS_PRIV,\n   BAR_BORDER,\n   BAR_SHADOW,\n   GRAPH_1,\n   GRAPH_2,\n   MEMORY_1,\n   MEMORY_2,\n   MEMORY_3,\n   MEMORY_4,\n   MEMORY_5,\n   MEMORY_6,\n   HUGEPAGE_1,\n   HUGEPAGE_2,\n   HUGEPAGE_3,\n   HUGEPAGE_4,\n   LOAD,\n   LOAD_AVERAGE_FIFTEEN,\n   LOAD_AVERAGE_FIVE,\n   LOAD_AVERAGE_ONE,\n   CHECK_BOX,\n   CHECK_MARK,\n   CHECK_TEXT,\n   CLOCK,\n   DATE,\n   DATETIME,\n   HELP_BOLD,\n   HELP_SHADOW,\n   HOSTNAME,\n   CPU_NICE,\n   CPU_NICE_TEXT,\n   CPU_NORMAL,\n   CPU_SYSTEM,\n   CPU_IOWAIT,\n   CPU_IRQ,\n   CPU_SOFTIRQ,\n   CPU_STEAL,\n   CPU_GUEST,\n   GPU_ENGINE_1,\n   GPU_ENGINE_2,\n   GPU_ENGINE_3,\n   GPU_ENGINE_4,\n   GPU_RESIDUE,\n   PANEL_EDIT,\n   SCREENS_OTH_BORDER,\n   SCREENS_OTH_TEXT,\n   SCREENS_CUR_BORDER,\n   SCREENS_CUR_TEXT,\n   PRESSURE_STALL_TEN,\n   PRESSURE_STALL_SIXTY,\n   PRESSURE_STALL_THREEHUNDRED,\n   FILE_DESCRIPTOR_USED,\n   FILE_DESCRIPTOR_MAX,\n   ZFS_MFU,\n   ZFS_MRU,\n   ZFS_ANON,\n   ZFS_HEADER,\n   ZFS_OTHER,\n   ZFS_COMPRESSED,\n   ZFS_RATIO,\n   ZRAM_COMPRESSED,\n   ZRAM_UNCOMPRESSED,\n   DYNAMIC_GRAY,\n   DYNAMIC_DARKGRAY,\n   DYNAMIC_RED,\n   DYNAMIC_GREEN,\n   DYNAMIC_BLUE,\n   DYNAMIC_CYAN,\n   DYNAMIC_MAGENTA,\n   DYNAMIC_YELLOW,\n   DYNAMIC_WHITE,\n   LAST_COLORELEMENT\n} ColorElements;\n\nvoid CRT_fatalError(const char* note) ATTR_NORETURN;\n\n#ifdef NDEBUG\n# define CRT_debug(...)\n#else\nvoid CRT_debug_impl(const char* file, size_t lineno, const char* func, const char* fmt, ...) ATTR_FORMAT(printf, 4, 5);\n# define CRT_debug(...) CRT_debug_impl(__FILE__, __LINE__, __func__, __VA_ARGS__)\n#endif\n\nvoid CRT_handleSIGSEGV(int signal) ATTR_NORETURN;\n\n#define KEY_WHEELUP    KEY_F(30)\n#define KEY_WHEELDOWN  KEY_F(31)\n#define KEY_RECLICK    KEY_F(32)\n#define KEY_RIGHTCLICK KEY_F(33)\n#define KEY_SHIFT_TAB  KEY_F(34)\n#define KEY_ALT(x)     (KEY_F(64 - 26) + ((x) - 'A'))\n#define KEY_FOCUS_IN   (KEY_MAX + 'I')\n#define KEY_FOCUS_OUT  (KEY_MAX + 'O')\n#define KEY_DEL_MAC    127\n\nextern char CRT_degreeSign[];\n\n#ifdef HAVE_LIBNCURSESW\n\nextern bool CRT_utf8;\n\n#endif\n\nextern const char* const* CRT_treeStr;\n\nextern const int* CRT_colors;\n\nextern int CRT_cursorX;\n\nextern int CRT_scrollHAmount;\n\nextern int CRT_scrollWheelVAmount;\n\nextern ColorScheme CRT_colorScheme;\n\n#ifdef HAVE_GETMOUSE\nvoid CRT_setMouse(bool enabled);\n#else\n#define CRT_setMouse(enabled)\n#endif\n\nvoid CRT_init(const Settings* settings, bool allowUnicode, bool retainScreenOnExit);\n\nvoid CRT_done(void);\n\nvoid CRT_resetSignalHandlers(void);\n\nint CRT_readKey(void);\n\nvoid CRT_disableDelay(void);\n\nvoid CRT_enableDelay(void);\n\nstatic inline void CRT_updateDelay(void) {\n   CRT_enableDelay(); // pushes new delay setting into halfdelay(3X)\n}\n\nvoid CRT_setColors(int colorScheme);\n\n#endif\n"
  },
  {
    "path": "CategoriesPanel.c",
    "content": "/*\nhtop - CategoriesPanel.c\n(C) 2004-2011 Hisham H. Muhammad\nReleased under the GNU GPLv2+, see the COPYING file\nin the source distribution for its full text.\n*/\n\n#include \"config.h\" // IWYU pragma: keep\n\n#include \"CategoriesPanel.h\"\n\n#include <ctype.h>\n#include <stdbool.h>\n#include <stdlib.h>\n\n#include \"AvailableMetersPanel.h\"\n#include \"ColorsPanel.h\"\n#include \"DisplayOptionsPanel.h\"\n#include \"FunctionBar.h\"\n#include \"Header.h\"\n#include \"HeaderLayout.h\"\n#include \"HeaderOptionsPanel.h\"\n#include \"ListItem.h\"\n#include \"Macros.h\"\n#include \"MetersPanel.h\"\n#include \"Object.h\"\n#include \"ProvideCurses.h\"\n#include \"ScreensPanel.h\"\n#include \"ScreenTabsPanel.h\"\n#include \"Settings.h\"\n#include \"Vector.h\"\n#include \"XUtils.h\"\n\n\nstatic const char* const CategoriesFunctions[] = {\"      \", \"      \", \"      \", \"      \", \"      \", \"      \", \"      \", \"      \", \"      \", \"Done  \", NULL};\n\nstatic void CategoriesPanel_delete(Object* object) {\n   CategoriesPanel* this = (CategoriesPanel*) object;\n   Panel_done(&this->super);\n   free(this);\n}\n\nstatic void CategoriesPanel_makeMetersPage(CategoriesPanel* this) {\n   size_t columns = HeaderLayout_getColumns(this->scr->header->headerLayout);\n   MetersPanel** meterPanels = xMallocArray(columns, sizeof(MetersPanel*));\n   Settings* settings = this->host->settings;\n\n   for (size_t i = 0; i < columns; i++) {\n      char titleBuffer[32];\n      xSnprintf(titleBuffer, sizeof(titleBuffer), \"Column %zu\", i + 1);\n      meterPanels[i] = MetersPanel_new(settings, titleBuffer, this->header->columns[i], this->scr);\n\n      if (i != 0) {\n         meterPanels[i]->leftNeighbor = meterPanels[i - 1];\n         meterPanels[i - 1]->rightNeighbor = meterPanels[i];\n      }\n\n      ScreenManager_add(this->scr, (Panel*) meterPanels[i], 20);\n   }\n\n   Panel* availableMeters = (Panel*) AvailableMetersPanel_new(this->host, this->header, columns, meterPanels, this->scr);\n   ScreenManager_add(this->scr, availableMeters, -1);\n}\n\nstatic void CategoriesPanel_makeDisplayOptionsPage(CategoriesPanel* this) {\n   Settings* settings = this->host->settings;\n   Panel* displayOptions = (Panel*) DisplayOptionsPanel_new(settings, this->scr);\n   ScreenManager_add(this->scr, displayOptions, -1);\n}\n\nstatic void CategoriesPanel_makeColorsPage(CategoriesPanel* this) {\n   Settings* settings = this->host->settings;\n   Panel* colors = (Panel*) ColorsPanel_new(settings);\n   ScreenManager_add(this->scr, colors, -1);\n}\n\n#if defined(HTOP_PCP)   /* all platforms supporting dynamic screens */\nstatic void CategoriesPanel_makeScreenTabsPage(CategoriesPanel* this) {\n   Settings* settings = this->host->settings;\n   Panel* screenTabs = (Panel*) ScreenTabsPanel_new(settings);\n   Panel* screenNames = (Panel*) ((ScreenTabsPanel*)screenTabs)->names;\n   ScreenManager_add(this->scr, screenTabs, 20);\n   ScreenManager_add(this->scr, screenNames, -1);\n}\n#endif\n\nstatic void CategoriesPanel_makeScreensPage(CategoriesPanel* this) {\n   Settings* settings = this->host->settings;\n   Panel* screens = (Panel*) ScreensPanel_new(settings);\n   Panel* columns = (Panel*) ((ScreensPanel*)screens)->columns;\n   Panel* availableColumns = (Panel*) ((ScreensPanel*)screens)->availableColumns;\n   ScreenManager_add(this->scr, screens, 20);\n   ScreenManager_add(this->scr, columns, 20);\n   ScreenManager_add(this->scr, availableColumns, -1);\n}\n\nstatic void CategoriesPanel_makeHeaderOptionsPage(CategoriesPanel* this) {\n   Settings* settings = this->host->settings;\n   Panel* colors = (Panel*) HeaderOptionsPanel_new(settings, this->scr);\n   ScreenManager_add(this->scr, colors, -1);\n}\n\ntypedef void (* CategoriesPanel_makePageFunc)(CategoriesPanel* ref);\ntypedef struct CategoriesPanelPage_ {\n   const char* name;\n   CategoriesPanel_makePageFunc ctor;\n} CategoriesPanelPage;\n\nstatic CategoriesPanelPage categoriesPanelPages[] = {\n   { .name = \"Display options\", .ctor = CategoriesPanel_makeDisplayOptionsPage },\n   { .name = \"Header layout\", .ctor = CategoriesPanel_makeHeaderOptionsPage },\n   { .name = \"Meters\", .ctor = CategoriesPanel_makeMetersPage },\n#if defined(HTOP_PCP)   /* all platforms supporting dynamic screens */\n   { .name = \"Screen tabs\", .ctor = CategoriesPanel_makeScreenTabsPage },\n#endif\n   { .name = \"Screens\", .ctor = CategoriesPanel_makeScreensPage },\n   { .name = \"Colors\", .ctor = CategoriesPanel_makeColorsPage },\n};\n\nstatic HandlerResult CategoriesPanel_eventHandler(Panel* super, int ch) {\n   CategoriesPanel* this = (CategoriesPanel*) super;\n\n   HandlerResult result = IGNORED;\n\n   int selected = Panel_getSelectedIndex(super);\n   switch (ch) {\n      case EVENT_SET_SELECTED:\n         result = HANDLED;\n         break;\n      case KEY_UP:\n      case KEY_CTRL('P'):\n      case KEY_DOWN:\n      case KEY_CTRL('N'):\n      case KEY_NPAGE:\n      case KEY_PPAGE:\n      case KEY_HOME:\n      case KEY_END: {\n         int previous = selected;\n         Panel_onKey(super, ch);\n         selected = Panel_getSelectedIndex(super);\n         if (previous != selected)\n            result = HANDLED;\n         break;\n      }\n      default:\n         if (0 < ch && ch < 255 && isgraph((unsigned char)ch))\n            result = Panel_selectByTyping(super, ch);\n         if (result == BREAK_LOOP)\n            result = IGNORED;\n         break;\n   }\n   if (result == HANDLED) {\n      int size = ScreenManager_size(this->scr);\n      for (int i = 1; i < size; i++)\n         ScreenManager_remove(this->scr, 1);\n\n      if (selected >= 0 && (size_t)selected < ARRAYSIZE(categoriesPanelPages)) {\n         categoriesPanelPages[selected].ctor(this);\n      }\n   }\n   return result;\n}\n\nconst PanelClass CategoriesPanel_class = {\n   .super = {\n      .extends = Class(Panel),\n      .delete = CategoriesPanel_delete\n   },\n   .eventHandler = CategoriesPanel_eventHandler\n};\n\nCategoriesPanel* CategoriesPanel_new(ScreenManager* scr, Header* header, Machine* host) {\n   CategoriesPanel* this = AllocThis(CategoriesPanel);\n   Panel* super = &this->super;\n\n   FunctionBar* fuBar = FunctionBar_new(CategoriesFunctions, NULL, NULL);\n   Panel_init(super, 1, 1, 1, 1, Class(ListItem), true, fuBar);\n\n   this->scr = scr;\n   this->host = host;\n   this->header = header;\n   Panel_setHeader(super, \"Categories\");\n   for (size_t i = 0; i < ARRAYSIZE(categoriesPanelPages); i++)\n      Panel_add(super, (Object*) ListItem_new(categoriesPanelPages[i].name, 0));\n\n   ScreenManager_add(scr, super, 16);\n   categoriesPanelPages[0].ctor(this);\n   return this;\n}\n"
  },
  {
    "path": "CategoriesPanel.h",
    "content": "#ifndef HEADER_CategoriesPanel\n#define HEADER_CategoriesPanel\n/*\nhtop - CategoriesPanel.h\n(C) 2004-2011 Hisham H. Muhammad\nReleased under the GNU GPLv2+, see the COPYING file\nin the source distribution for its full text.\n*/\n\n#include \"Header.h\"\n#include \"Machine.h\"\n#include \"Panel.h\"\n#include \"ScreenManager.h\"\n\n\ntypedef struct CategoriesPanel_ {\n   Panel super;\n   ScreenManager* scr;\n   Machine* host;\n   Header* header;\n} CategoriesPanel;\n\nextern const PanelClass CategoriesPanel_class;\n\nCategoriesPanel* CategoriesPanel_new(ScreenManager* scr, Header* header, Machine* host);\n\n#endif\n"
  },
  {
    "path": "ChangeLog",
    "content": "What's new in version 3.4.1\n\n* Support for PMAPI v3 for PCP\n* PCP code cleanups\n* Proper checks for strchrnul\n* Code cleanup in the NetworkIOMeter\n* Improved documentation for the --user option\n* Display stuck processes on Darwin\n* Handle issues when the monotonic clock runs backwards\n* Fix builds using native curses on NetBSD\n\nWhat's new in version 3.4.0\n\n* More expressive version tag generated for development versions (htop --version, help screen)\n* Improve Darwin support for ARM-based systems\n* Fix static linking with libsystemd\n* Various build fixes for DragonFlyBSD, Darwin, NetBSD, OpenBSD & Solaris\n* Fix running task display (count)\n* Fix sort order handling in tree mode\n* Add warning when exiting with a signal (not saving .htoprc)\n* Add Disk I/O and Network I/O meter for DragonFlyBSD\n* Improve handling of invalid Unicode strings\n* Disable basename checking for kernel tasks\n* Updated documentation for pcp-htop\n* Disable FOCUS_IN/FOCUS_OUT event handling\n* Add GPU meter for Linux and PCP\n* Add column for GPU time per process on Linux and PCP\n* Avoid glibc FILE API voodoo\n* Ignore previously unhandled signals USR1 and USR2\n* Force locating the config file to only use absolute paths\n* Prefer reading htoprc from ~/.config/htop/htoprc over legacy ~/.htoprc\n* Force writing the configuration to a regular file\n* Use distinct config files for htop and pcp-htop\n* Link libnl3 at runtime\n* Gather permitted capabilities via capget(2)\n* Avoid fetching certain process information for each thread on Linux (speed up)\n* Improved handling for invalid data in /proc/tty/drivers on Linux\n* Various changes to avoid memory allocations inside signal handlers\n* Add single column header layout\n* Fix DivByZero bug on startup on Darwin\n* Include thread information on Darwin\n* Show process state on Darwin\n* Update compat check for C23 compilers\n* Improved detail in help screen\n* Unicode support for CGROUP, CCGROUP, CONTAINER and SECATTR columns\n* Mark newline characters in the process command line display\n* Resolve nested derived metrics for PCP\n* Make supported modes/styles specific to each meter\n* Refined checks for terminals supporting to redefine keys\n* Fix handling of the NICE value on FreeBSD\n* Fix display of CPU utilization on FreeBSD\n* Honour update interval adjustments properly without restart\n* Force rebuild of display table after item removals\n* Reworked handling for various temperature sensors\n* Fix high CPU load when the strace'd process exits prematurely\n* Document --drop-capabilities to require a compile time support\n* Always call PKG_PROG_PKG_CONFIG in configure\n* Make configure warn when pkg.m4 is absent\n* Rewrite curses/terminfo detection code in configure\n* Keep following a process when resuming process updates (Z key)\n* Normalize Disk I/O usage and allow utilization above 100%\n* Plug several memory leaks and improve performance for information parsing\n* Allow to show or hide cache and buffers in memory usage meter\n* Visibility hint and UX improvements in status bar of display options panel\n* Remove IOKit / IOMainPort / IOMMasterPort logic for Darwin builds\n* Replace BCC with metrics from BPF for pcp-htop\n\nWhat's new in version 3.3.0\n\n* Multiple refactorings and code improvements\n* Shorten docker container IDs to 12 characters\n* Settings: preserve empty header\n* Fix execlp() argument without pointer cast\n* OpenFilesScreen: Make column sizing dynamic for file size, offset and inode\n* Add support for \"truss\" (FreeBSD equivalent of \"strace\")\n* Darwin: add NetworkIOMeter support\n* HeaderLayout: add \"3 columns - 40/30/30\", \"... 30/40/30\" & \"... 30/30/40\"\n* Meter: use correct unicode characters for digit '9'\n* Note in manual re default memory units of KiB\n* Add column for process container name\n* Add logic to filter the container name (+type) from the CGroup name\n* Change NetworkIOMeter value unit from KiB/s to bytes/second\n* Cap DiskIOMeter \"utilisation\" percentage at 100%\n* PCP platform implementation of frontswap and zswap accounting\n* Shorten podman/libpod container IDs to 12 characters\n* Write configuration to temporary file first\n* Incorporate shared memory in bar text\n* Move shared memory next to used memory\n* Correct order of memory meter in help\n* Add recalculate to Ctrl-L refresh\n* Update process list on thread visibility toggling\n* Support dynamic screens with 'top-most' entities beyond processes\n* Introduce Row and Table classes for screens beyond top-processes\n* Rework ZramMeter and remove MeterClass.comprisedValues\n* More robust logic for CPU process percentages (Linux & PCP)\n* Show year as start time for processes older than a year\n* Short-term fix for docker container detection\n* default color preset: use bold blue for better visibility\n* Document 'O' keyboard shortcut\n* Implement logic for '--max-iterations'\n* Update F5 key label on tab switch (Tree <-> List)\n* Force re-sorting of the process list view after switching between list/treeview mode\n* Linux: (hack) work around the fact that Zswapped pages may be SwapCached\n* Linux: implement zswap support\n* {Memory,Swap}Meter: add \"compressed memory\" metrics\n* Darwin: add DiskIOMeter support\n* Fix scroll relative to followed process\n* ZramMeter: update bar mode\n* Use shared real memory on FreeBSD\n* Increase Search and Filter max string length to 128\n* Improve CPU computation code\n* Remove LXC special handling for the CPU count\n* Create new File Descriptor meter\n* PCP: add IRQ PSI meter\n* Linux: add IRQ PSI meter\n* Linux: highlight username if process has elevated privileges\n* Add support for scheduling policies\n* Add a systemd user meter to monitor user units.\n* FreeBSD: remove duplicate zfs ARC size subtraction\n\nWhat's new in version 3.2.2\n\n* CPUMeter now can show frequency in text mode\n* Add option to render distribution path prefixes shadowed\n* DiskIOMeter converts to bytes per second (not per interval)\n* DiskIOMeter uses complete units, including missing \"iB/s\"\n* DiskIOMeter indicates read and write in meter mode\n* NetworkIOMeter converts to packets per second, shows packet rate\n* Allow continued process following when changing display settings\n* Update the panel header when changing to another tab\n* Drop margin around the header if there are no meters\n* Use Unicode replacement character for non-printable characters\n* Default color preset uses bold blue for better visibility\n* Update the Panel header on sort order inversions ('I')\n* Toggle the header meters with pound key\n* Fix ScreenPanel to handle quitting the panel while renaming\n* Add fallback for HOME environment variable using passwd database\n* Replace meaningless ID column with FD column in lock screen\n* Use device format in the lock screen matching the files screen\n* On Linux, improvements to file-descriptor lock detection\n* On Linux, further distinguish systemd states in the SystemdMeter\n* On Linux, improvements to cgroup and container identification\n* On Linux, support openat(2) without readlinkat(2) platforms\n* On Darwin, fix current process buffer handling for busy systems\n* On DragonFly BSD, fix incorrect processor time of processes\n* On FreeBSD, fix an issue with the memory graph not showing correctly\n* On FreeBSD, add support for displaying shared memory usage\n* On PCP, use pmLookupDescs(3) if available for efficiency\n* On PCP, normalize generic columns values for consistent display\n* On PCP, changes preparing for configurable, dynamic screens\n* Handle invalid process columns from the configuration file\n* Avoid undefined behaviour with deeply nested processes\n* Fix crash when removing the currently active screen\n* Prevent possible crash on a very early error path\n* Include automake for Debian/Ubuntu\n* Restore non-mouse support\n* Reject unsupported command line arguments\n* Document idle process state\n* Clarify M_TRS/M_DRS columns\n\nWhat's new in version 3.2.1\n\n* Fix setting to show all branches collapsed by default\n* Restore functionality of stripExeFromCmdline setting\n* Fix some command line display settings not being honored without restart\n* Display single digit precision for CPU% greater than 99.9%\n* On Linux, FreeBSD and PCP consider only shrinkable ZFS ARC as cache\n* On Linux, increase field width of CPUD% and SWAPD% columns\n* Colorize process state characters in help screen\n* Use mousemask(3X) to enable and disable mouse control\n* Fix heap buffer overflow in Vector_compact\n* On Solaris, fix a process time scaling error\n* On Solaris, fix the build\n* On NetBSD, OpenBSD and Solaris ensure env buffer size is sufficient\n* On Linux, resolve processes exiting interfering with sampling\n* Fix ProcessTable quadratic removal when scanning processes\n* Under LXC, limit CPU count to that given by /proc/cpuinfo\n* Improve container detection for LXC\n* Some minor documentation fixes\n\nWhat's new in version 3.2.0\n\n* Support for displaying multiple tabs in the user interface\n* Allow multiple filter and search terms (logical OR, separate by \"|\")\n* Set correct default sorting direction (defaultSortDesc)\n* Improve performance for process lookup and update\n* Rework the IOMeters initial display\n* Removed duplicate sections on COMM and EXE\n* Highlight process UNINTERRUPTIBLE_WAIT state (D)\n* Show only integer value when CPU% more than 99.9%\n* Handle rounding ambiguity between 99.9 and 100.0%\n* No longer leaves empty the last column in header\n* Fix header layout and meters reset if a header column is empty\n* Fix PID and UID column widths off-by-one error\n* On Linux, read generic sysfs batteries\n* On Linux, do not collect LRS per thread (it is process-wide)\n* On Linux, dynamically adjust the SECATTR and CGROUP column widths\n* On Linux, fix a crash in LXD\n* On FreeBSD, add support for showing process emulation\n* On Darwin, lazily set process TTY name\n* Always set SIGCHLD to default handling\n* Avoid zombie processes on signal races\n* Ensure last line is cleared when SIGINT is received\n* Instead of SIGTERM, pre-select the last sent signal\n* Internal Hashtable performance and sizing improvements\n* Add heuristics for guessing LXC or Docker from /proc/1/mounts\n* Force elapsed time display to zero if process started in the future\n* Avoid extremely large year values when printing time\n* Fix division by zero when calculating IO rates\n* Fix out of boundary writes in XUtils\n* Fix custom thread name display issue\n* Use AC_CANONICAL_HOST, not AC_CANONICAL_TARGET in configure.ac\n* Support libunwind of LLVM\n\nWhat's new in version 3.1.2\n\n* Bugfix for crash when storing modified settings at exit\n* Generate xz-compressed source tarball (with configure) using github actions\n* Allow -u UID with numerical value as argument\n* Added documentation for obsolete/state libraries/program files highlighting\n* Some obsolete/stale library highlighting refinements\n* Column width issues resolved\n* Dynamic UID column sizing improved\n* Discard stale information from Disk and Network I/O meters\n* Refined Linux kernel thread detection\n* Reworked process state handling\n* New CCGROUP column showing abbreviated cgroup name\n* New OFFSET column in the list of open files screen\n\nWhat's new in version 3.1.1\n\n* Update license headers to explicitly say GPLv2+\n* Document minimum version for libcap (thanks to James Brown)\n* Fix mouse wheel collision with autogroups nice adjustment\n* Adjust Makefile.am macro definitions for older automake versions\n* Ensure consistent reporting of MemoryMeter 'used' memory\n* Report hugepage memory as real and used memory (as before)\n* Handle procExeDeleted, usesDeletedLib without mergedCommandline mode\n* Validate meter configuration before proceeding beyond htoprc parsing\n* Properly release memory on partially read configuration\n* Handle interrupted sampling from within libpcp PDU transfers\n* On Linux, provide O_PATH value if not defined\n* On Linux, always compute procExeDeleted if already set\n* Workaround for Rosetta 2 on Darwin (thanks to Alexander Momchilov)\n* Fix FreeBSD cmdline memory leak in Process_updateCmdline, and\n* Plug a Disk I/O meter memory leak on FreeBSD (thanks to Ximalas)\n\nWhat's new in version 3.1.0\n\n* Updated COPYING file to remove the PLPA exemption (appendix 2)\n  With this change the license is now GPLv2 without any additional wording.\n* Improved default sort ordering\n  Note for users: This may lead to an inverted sort order on startup of\n  htop 3.1.0 compared to previous versions.\n  This is due to what is stored in your htoprc file. Solution: Press I\n  (to invert sort order).\n  This changed setting will be saved by htop on exit as long as it can\n  write to your htoprc file.\n* The compile-time option to cater specifically for running htop as\n  setuid has been removed\n* Add read-only option\n  This allows htop to be run in an non-intrusive fashion where it acts only\n  as a process viewer disabling all functions to manipulate system state.\n  Note: This is not a security feature!\n* Move the code for handling the command line formatting related tasks\n  to be shared across all platforms\n  This means important features like stale binary/library highlighting\n  can now be available on all supported platforms.\n* Make the EXE and COMM columns available on all platforms\n  All supported platforms have the name of the executable (EXE) and a\n  self-chosen thread/command name (COMM) available one way or the other.\n  Moving this column to be handled as a platform-independently available\n  information simplifies the markup of the command line.\n* Introduce configuration file versioning and config_reader_min_version\n  Starting with this version the configuration file contains a version\n  identifying the minimum version of the configuration parser needed to\n  fully understand the configuration file format.\n  Old configuration file formats are automatically upgraded when\n  saving the config file (htoprc).\n* Make the configuration parser friendlier to users (thanks to Bart Bakker)\n  With this change only settings that cannot be parsed properly are\n  reset to their defaults.\n* Improve default display for systems with many CPUs\n* Add the process ELAPSED time column\n* Improve the process STATE column sorting\n* Reworked handling resize and redrawing of the UI\n* Fixed an issue where the LED meter mode could overflow allotted space\n* Allow text mode Meters to span empty neighbors to the right\n* Rescale graph meters when value of total changes\n  (thanks to Michael Schönitzer)\n* Update generic process field display\n  Usually \"uninteresting\" values in columns like 1 thread, nice value\n  of 0, CPU and memory of 0%, idle/sleeping state, etc. are shown with\n  reduced intensity (dark grey)\n* Option and key (\"*\") to collapse / expand all branches under PID 1\n  (and PID 2 if kernel threads are shown) (thanks to Krishna Chaitanya)\n* Keep following a process when inverting the sort order, displaying\n  the help screen or hiding/unhiding userland threads.\n  If a thread is currently selected the selection is updated to point\n  to the thread's parent process. (thanks to Gonzalo, et.al.)\n* Reorder process scanning to be performed before updating the display\n  of the meters in the header\n* Always check the user for a process for any changes.\n  This affects multiple platforms that previously didn't correctly handle\n  the user field for a process to change at runtime (e.g. due to seteuid\n  or similar syscalls).\n* Disable mouse option when support is unavailable\n* Support curses libraries without ncurses mouse support\n  (thanks to Santhosh Raju)\n* Support offline and hot-swapping of CPUs on all platforms\n* Fix the CPU Meter for machines with more than 256 CPUs\n* Supplemented the \"show updated/deleted executables\" feature (red basename)\n  to indicate when linked libraries were updated (yellow basename)\n* Apply the stale binary highlighting for the EXE column in addition to\n  the command line field\n* Add new combined Memory and Swap meter\n* Implement bar and graph mode for NetworkIO Meter\n  (thanks to Michael F. Schönitzer)\n* Rework TTY column to be more consistent across platforms\n* Make the CWD column generally available on all platforms\n  (thanks to Santhosh Raju et. al.)\n* Add Performance Co-Pilot (PCP) platform support\n  This is added via a separate pcp-htop(1) binary which provides remote host\n  analysis, new Meters for any PCP metric and new Columns for any PCP process\n  metric - see the pcp-htop(5) man page for further details.\n  (thanks to Sohaib Mohamed)\n* Add Linux columns and key bindings for process autogroup identifier\n  and nice value\n* Change available and used memory reporting on Linux to be based on\n  MemAvailable (Kernel 3.14+) (thanks to Chris Cheney and Tomas Wido)\n* Add a new SysArchMeter showing kernel and platform information\n  (thanks to ahgamut)\n* Linux memory usage explicitly treats tmpfs memory usage as shared memory\n  This is to make memory used by tmpfs visible as this cannot be freed\n  unlike normal filesystem cache data.\n* Exclude zram devices when calculating DiskIO on Linux\n* Use PATH lookup for systemctl in systemd meter (thanks to Scott Olson)\n* Add native platform support for NetBSD\n  This allows htop to run on NetBSD without the need for active Linux\n  emulation of the procfs filesystem.\n  (thanks to Santhosh Raju and Nia Alarie)\n* Add NetworkIO, DiskIO, CPU frequency, and battery meter support on NetBSD\n  (thanks to Nia Alarie)\n* Fix NetBSD display of in-use and cached memory (thanks to Nia Alarie)\n* Rework NetBSD CPU and memory accounting (thanks to Santhosh Raju)\n* Fix NetBSD accounting of user and kernel threads (thanks to Santhosh Raju)\n* Initial work to allow building with default libcurses on NetBSD\n  (thanks to Santhosh Raju)\n* FreeBSD updates - implement process majflt and processor column values\n* Add FreeBSD support for CPU frequency and temperature\n* Fixes and cleanups for ZFS Meters and metrics\n* Correctly color the ZFS ARC ratio (thanks to Ross Williams)\n* Bugfixes related to CPU time display/calculations for darwin on M1 systems\n  (thanks to Alexander Momchilov)\n* Harmonize the handling of multiple batteries across different platforms.\n  The system is now considered to run on AC if at least one power supply\n  marked as AC is found in the system.\n  Battery capacity is summed up over all batteries found.\n  This also changes the old behavior that batteries reported by the\n  system after the first AC adapter where sometimes ignored.\n* Correctly handle multiple batteries on Darwin.\n  Resolves a possible memory leak on systems with multiple batteries.\n* Handle Linux Shmem being part of Cached in the MemoryMeter\n* Add SwapCached to the Linux swap meter (thanks to David Zarzycki)\n* Convert process time to days if applicable (thanks to David Zarzycki)\n* Always show the number of threads in the TaskMeter, even when threads\n  are not shown in the process list\n* Fix Linux --drop-capabilities option handling\n* Correctly detect failure to initialize Linux boottime\n* Overhaul the Linux memory fields to partition them like free(1) now does\n* Improve the Linux process I/O column values\n* Rework the libsensors parsing on Linux\n* Update the MemoryMeter to display shared memory\n* Update OpenBSD platform - implement additional columns, scan LWP,\n  proper markup for STATE, show CPU frequency\n* Fix the tree view on OpenBSD when hiding kernel threads\n* Remove old InfoScreen lines before re-scanning (thanks to Øystein Hiåsen)\n* Document historic naming of Light-Weight Processes column aka threads\n* Improve user interaction when the last process entry is selected\n* Draw the panel header on the TraceScreen (thanks to Youngjae Lee)\n* Add mouse wheel scroll and fix mouse selection on the InfoScreen\n  (thanks to Youngjae Lee)\n* Add a HugepageMeter and subtract hugepages from normal memory\n* Display wide characters in LED meters and restore non-wide ncurses support\n* Add command line option to drop Linux capabilities\n* Support scheduler affinity on platforms beyond Linux\n* Report on any failure to write the configuration file\n* Cache stderr to be able to print assert messages.\n  These messages are shown in case htop terminates unexpectedly.\n* Print current settings on crash\n* Reset signal handlers on program exit\n* Add configure script option to create a static htop binary\n* Resolved longer-standing compilation issues on Solaris/Illumos\n* Check for availability of set_escdelay in configure\n  (thanks to Stefan Polluks)\n* Build system updates for autotools 2.70\n\nWhat's new in version 3.0.5\n\n* BUGFIX / SECURITY: InfoScreen: fix uncontrolled format string\n* BUGFIX: Improve white text in the Light Terminal colour scheme\n  (both of the above thanks to V)\n* Enable the function bar on the main screen to be hidden (see Setup -> Display options)\n* BUGFIX: Reduce layout issues esp. around printing wide characters (not complete yet)\n* BUGFIX: Make the follow function exit cleanly after followed process died\n* Solaris: make Process callbacks static\n* Update help and man page for improved -t / -s options\n* Drop usage of formatted error messages from <err.h>\n* Show arrow indicating order of sorted process column\n* Lots of plumbing around the internal Hashtable, hardening and code cleanups\n* LibSensors: add support for Ryzen CPUs\n  (thanks to Matej Dian)\n* BUGFIX: Fix CPU percentage on M1 silicon Macs\n  (thanks to Luke Groeninger)\n* LoadMeter: dynamically adjust color and total of bar\n* Find libsensors.so.4 for Fedora and friends\n* Add support to display CPU frequencies on Solarish platforms\n  (thanks to Dominik Hassler)\n* Enable going back to previous search matches (Shift-F3)\n* Added keybind 'N' for sorting by PID (drops 'n'/'N' as not used before much)\n  (thanks to Jake Mannens)\n\nWhat's new in version 3.0.4\n\n* Separate tree and list sort orders\n* Invert Process_compare so that superclass matches run first\n  (thanks to Hisham Muhammad)\n* Unhardcode Mac OS tick-to-milliseconds conversion\n  (thanks to Alexander Momchilov)\n* Check if clock_gettime needs linking of librt\n* Define O_PATH if not already defined\n  (thanks to Chris Burr)\n* Add column on Mac for processes running under translation\n  (thanks to Dániel Bakai)\n* Configure check for additional linker flags for keypad(3)\n* PSI Meter: constant width and only print ten-duration as bar\n* Sort in paused mode after inverting sort order\n* Handle absence of package CPU temperature\n* Meter: restore non-wide-character build\n* LibSensors: restore temperature for Raspberry Pi\n* MainPanel: do not reset hideProcessSelection on KEY_SHUFFLE\n* BarMeter: rework text padding\n* Panel: rework drawing of FunctionBar\n* Meter: fix artifacts with very tiny width\n* DragonFlyBSD updates\n* BUGFIX: Fix dlopen issue for libsensors5 for some platforms\n* BUGFIX: Fix broken tree display on inverted sort order\n* BUGFIX: Fix pause mode (\"Z\") in tree view\n* BUGFIX: Correct timebase for non-x86 CPUs on Darwin\n* BUGFIX: Avoid NULL dereference on zombie processes\n* Document dynamic bindings and assumed external configuration\n* Update key mapping documentation for sorting\n\nWhat's new in version 3.0.3\n\n* Process sorting in 'tree' mode\n  (thanks to Maxim Zhiburt)\n* Improved command display/sort functionality\n  (thanks to Narendran Gopalakrishnan)\n* Add screen for active file locks\n  (thanks to Fynn J. Wulf)\n* Calculate library size (M_LRS column) from maps file\n  (thanks to Fynn J. Wulf)\n* Add a Zram meter\n  (thanks to Murloc Knight)\n* Add Linux cwd process column\n* Dynamically load libsensors at runtime\n* Improve PressureStall Meter display strings\n* Hide process selection on ESC\n* Fully support non-ascii characters in Meter-Bar\n* Add support to change numeric options in settings screen\n* Rename virtual memory column from M_SIZE to M_VIRT\n* Add process column for normalized CPU usage\n* Show CPU temperature in CPU meter\n* Drop hideThreads Setting\n* Add a systemd meter\n* Add a network IO meter\n* Add a SELinux meter\n* Compress size of default FunctionBar\n* Updates to the OpenFiles screen\n* Continue updating header data in paused mode\n* BUGFIX: Handle data wraparounds in IO meters\n* BUGFIX: Update InfoScreen content on resize\n* Add security attribute process column\n* Add DiskIOMeter for IO read/write usage\n* Read CPU frequency from sysfs by default\n* Add Linux process column for context switches\n* Several FreeBSD and Mac OS X platform updates\n  (thanks to Christian Göttsche)\n* Add process environment for FreeBSD\n  (thanks to Ross Williams)\n* Parse POWER_SUPPLY_CAPACITY for Linux Battery meter\n  (thanks to Jan Palus)\n* Add octuple-column CPU meters.\n* BUGFIX: On Linux consider ZFS ARC to be cache\n  (thanks to @multi)\n* BUGFIX: Limit screen title length to window width\n* Show selected command wrapped in a separate window\n  (thanks to @ryenus)\n* Allow to pass '/' for item search\n* Document implicit incremental search\n* Handle 'q' as quit if first character\n* Avoid expensive build of process tree when not using it\n* Include documentation for COMM and EXE\n* Distinguish display of no permissions for reading M_LRS\n* Only calculate M_LRS size every 2 seconds\n* Improvements to comm / cmdline display functionality\n* Merged view for COMM, EXE and cmdline\n  (thanks to Narendran Gopalakrishnan and Benny Baumann)\n* Consistent kernel thread display for COMM/EXE columns\n* Central fault handling for all platforms\n* Handle parsing envID & VPid from process status file\n* Use threshold for display of guest/steal/irq meters\n* Enhance highlighting of semi-large and large numbers\n* Documentation on the repository style guide\n  (thanks to Benny Baumann)\n* Align processor identifier to the right\n  (thanks to Christian Hesse)\n* Document M_PSS, M_PSSWP, M_SWAP in man page\n* Add Date and DateTime meters\n  (thanks to Michael F. Schönitzer)\n* BUGFIX: Fix Solaris 11.4 due to missing ZFS ARC kstats\n  (thanks to @senjan)\n* Code hardening, speedups, fd and memory leak fixes\n  (thanks to Christian Göttsche and Benny Baumann)\n* Number CPUs from zero by default\n  (thanks to Zev Weiss)\n* Remove residual python checks during the build process\n  (thanks to Stephen Gregoratto)\n\nWhat's new in version 3.0.2\n\n* BUGFIX: Drop 'vim_mode' - several issues, needs rethink\n* BUGFIX: fix regression in -u optional-argument handling\n* Build system rework to remove python, header generation\n  (thanks to Zev Weiss and Hugo Musso Gualandi)\n* BUGFIX: report nice level correctly on Solaris\n  (thanks to Dominik Hassler)\n* CI, code quality improvements\n  (thanks to Tobias Kortkamp, Christian Hesse, Benny Baumann)\n\nWhat's new in version 3.0.1\n\n* Coverity fixes, CI improvements, documentation updates\n* BUGFIX: Fix early exit with longer sysfs battery paths\n* BUGFIX: Improve OOM output, fix sorting\n  (thanks to Christian Göttsche)\n* Rework check buttons and tree open/closed\n  (thanks to Bert Wesarg)\n* Add -U/--no-unicode option to disable unicode\n  (thanks to Christian Hesse)\n* Improvements to the affinity panel\n  (thanks to Bert Wesarg)\n\nWhat's new in version 3.0.0\n\n* New maintainers - after a prolonged period of inactivity\n  from Hisham, the creator and original maintainer, a team\n  of community maintainers have volunteered to take over a\n  fork at https://htop.dev and https://github.com/htop-dev\n  to keep the project going.\n* Support ZFS ARC statistics\n  (thanks to Ross Williams)\n* Support more than 2 smaller CPU meter columns\n  (thanks to Christoph Budziszewski)\n* Support Linux proportional set size metrics\n  (thanks to @linvinus, @ntninja and @himikof)\n* Support Linux pressure stall information metrics\n  (thanks to Ran Benita)\n* New display option to show CPU frequency in CPU meters\n  (thanks to Arnav Singh)\n* Update Linux sysfs battery discovery for recent kernels\n  (thanks to @smattie)\n* Add hardware topology information in the affinity panel\n  (thanks to Bert Wesarg)\n* Add timestamp reporting to the strace screen\n  (thanks to Mario Harjac)\n* Add simple, optional vim key mapping mode\n  (thanks to Daniel Flanagan)\n* Added an option to disable the mouse\n  (thanks to MartinJM)\n* Add Solaris11 compatibility\n  (thanks to Jan Senolt)\n* Without an argument -u uses $USER value automatically\n  (thanks to @solanav)\n* Support less(1) search navigation shortcuts\n  (thanks to @syrrim)\n* Update the FreeBSD maximum PID to match FreeBSD change\n  (thanks to @multiplexd)\n* Report values larger than 100 terabytes\n  (thanks to @adrien1018)\n* Widen ST_UID (UID) column to allow for UIDs > 9999\n  (thanks to DLange)\n* BUGFIX: fix makefiles for building with clang\n  (thanks to Jorge Pereira)\n* BUGFIX: fix <sys/sysmacros.h> major() usage\n  (thanks to @wataash and Kang-Che Sung)\n* BUGFIX: fix the STARTTIME column on FreeBSD\n  (thanks to Rob Crowston)\n* BUGFIX: truncate overwide jail names on FreeBSD\n  (thanks to Rob Crowston)\n* BUGFIX: fix reported memory values on FreeBSD\n  (thanks to Tobias Kortkamp)\n* BUGFIX: fix reported CPU meter values on OpenBSD\n  (thanks to @motet-a)\n* BUGFIX: correctly identify other types of zombie process\n  (thanks to @joder)\n* BUGFIX: improve follow-process handling in some situations\n  (thanks to @wangqr)\n* BUGFIX: fix custom meters reverting to unexpected setting\n  (thanks to @wangqr)\n* BUGFIX: close pipe after running lsof(1)\n  (thanks to Jesin)\n* BUGFIX: meters honour setting of counting CPUs from 0/1\n  (thanks to @rnsanchez)\n\nWhat's new in version 2.2.0\n\n* Solaris/Illumos/OpenIndiana support\n  (thanks to Guy M. Broome)\n* -t/--tree flag for starting in tree-view mode\n  (thanks to Daniel Flanagan)\n* macOS: detects High Sierra version to avoid OS bug\n  (thanks to Pierre Malhaire)\n* OpenBSD: read battery data\n  (thanks to @nerd972)\n* Various automake and build improvements\n  (thanks to Kang-Che Sung)\n* Check for pkg-config when building with --enable-delayacct\n  (thanks to @florian2833z for the report)\n* Avoid some bashisms in configure script\n  (thanks to Jesin)\n* Use CFLAGS from ncurses*-config if present\n  (thanks to Michael Klein)\n* Header generator supports non-UTF-8 environments\n  (thanks to @volkov-am)\n* Linux: changed detection of kernel threads\n* Collapse current subtree pressing Backspace\n* BUGFIX: fix behavior of SYSCR column\n  (thanks to Marc Kleine-Budde)\n* BUGFIX: obtain exit code of lsof correctly\n  (thanks to @wangqr)\n* BUGFIX: fix crash with particular keycodes\n  (thanks to Wellington Torrejais da Silva for the report)\n* BUGFIX: fix issue with small terminals\n  (thanks to Daniel Elf for the report)\n* BUGFIX: fix terminal color issues\n  (thanks to Kang-Che Sung for the report)\n* BUGFIX: preserve LDFLAGS when building\n  (thanks to Lance Frederickson for the report)\n* BUGFIX: fixed overflow for systems with >= 100 signals\n\nWhat's new in version 2.1.0\n\n* Linux: Delay accounting metrics\n  (thanks to André Carvalho)\n* DragonFlyBSD support\n  (thanks to Diederik de Groot)\n* Support for real-time signals\n  (thanks to Kang-Che Sung)\n* 'c' key now works with threads as well\n* Session column renamed from SESN to SID\n  (thanks to Kamyar Rasta)\n* Improved UI for meter style selection\n  (thanks to Kang-Che Sung)\n* Improved code for constructing process tree\n  (thanks to wangqr)\n* Compile-time option to disable setuid\n* Error checking of various standard library operations\n* Replacement of sprintf with snprintf\n  (thanks to Tomasz Kramkowski)\n* Linux: performance improvements in battery meter\n* Linux: update process TTY device\n* Linux: add support for sorting TASK_IDLE\n  (thanks to Vladimir Panteleev)\n* Linux: add upper-bound to running process counter\n  (thanks to Lucas Correia Villa Real)\n* BUGFIX: avoid crash when battery is removed\n  (thanks to Jan Chren)\n* BUGFIX: macOS: fix infinite loop in tree view\n  (thanks to Wataru Ashihara)\n\nWhat's new in version 2.0.2\n\n* Mac OS X: stop trying when task_for_pid fails for a process,\n  stops spamming logs with errors.\n* Add Ctrl+A and Ctrl+E to go to beginning and end of line\n* FreeBSD: fixes for CPU calculation\n  (thanks to Tim Creech, Andy Pilate)\n* Usability: auto-follow process after a search.\n* Use Linux backend on GNU Hurd\n* Improvement for reproducible builds.\n* BUGFIX: Fix behavior of Alt-key combinations\n  (thanks to Kang-Che Sung)\n* Various code tweaks and cleanups\n  (thanks to Kang-Che Sung)\n\nWhat's new in version 2.0.1\n\n* OpenBSD: Various fixes and improvements\n  (thanks to Michael McConville and Juan Francisco Cantero Hurtado)\n* FreeBSD: fix CPU and memory readings\n  (thanks to Tim Creech, Hung-Yi Chen, Bernard Spil, Greg V)\n* FreeBSD: add battery support\n  (thanks to Greg V)\n* Linux: Retain last-obtained name of a zombie process\n* Mac OS X: Improve portability for OS X versions\n  (thanks to Michael Klein)\n* Mac OS X: Fix reading command-line arguments and basename\n* Mac OS X: Fix process state information\n* Mac OS X: Fix tree view collapsing/expanding\n* Mac OS X: Fix tree organization\n* Mac OS X: Fix memory accounting\n* Fix crash when emptying a column of meters\n* Make Esc key more responsive\n\nWhat's new in version 2.0.0\n\n* Platform abstraction layer\n* Initial FreeBSD support\n* Initial Mac OS X support\n  (thanks to David Hunt)\n* Swap meter for Mac OSX\n  (thanks to Ștefan Rusu)\n* OpenBSD port\n  (thanks to Michael McConville)\n* FreeBSD support improvements\n  (thanks to Martin Misuth)\n* Support for NCurses 6 ABI, including mouse wheel support\n* Much improved mouse responsiveness\n* Process environment variables screen\n  (thanks to Michael Klein)\n* Higher-resolution UTF-8 based Graph mode\n  (Thanks to James Hall from vtop for the idea!)\n* Show program path settings\n  (thanks to Tobias Geerinckx-Rice)\n* BUGFIX: Fix crash when scrolling an empty filtered list.\n* Use dynamic units for text display, and several fixes\n  (thanks to Christian Hesse)\n* BUGFIX: fix error caused by overflow in usertime calculation.\n  (thanks to Patrick Marlier)\n* Catch all memory allocation errors\n  (thanks to Michael McConville for the push)\n* Several tweaks and bugfixes\n  (See the Git log for details and contributors!)\n\nWhat's new in version 1.0.3\n\n* Tag all children ('c' key)\n* Fixes in accounting of guest time when using virtualization\n  (thanks to Patrick Marlier)\n* Performance improvements\n  (thanks to Jann Horn)\n* Further performance improvements due to conditional parsing\n  of IO data depending on selected fields.\n* Better consistency in coloring.\n* Increase limit of buffer when tracing a deep nested process tree.\n* Display pagefault stats.\n* BUGFIX: Fix crash when adding meters and toggling detailed CPU time.\n  (thanks to Dawid Gajownik)\n* Add column to track the OOM-killer score of processes\n  (thanks to Leigh Simpson)\n\nWhat's new in version 1.0.2\n\n* Add IO priority support ('i' key)\n* Avoid deleting .htoprc if it is a symlink\n* Fail gracefully when /proc is not mounted\n  (thanks to Philipp Hagemeister)\n* Option to update process names on every refresh\n  (thanks to Rob Hoelz)\n* BUGFIX: Fix crashes when process list is empty\n\nWhat's new in version 1.0.1\n\n* Move .htoprc to XDG-compliant path ~/.config/htop/htoprc,\n  respecting $XDG_CONFIG_HOME\n  (thanks to Hadzhimurad Ustarkhan for the suggestion.)\n* Safer behavior on the kill screen, to make it harder to kill the wrong process.\n* Fix for building in FreeBSD 8.2\n  (thanks to Trond Endrestol)\n* BUGFIX: behavior of 'F' (follow) key was broken, also affecting the\n  persistence of mouse selections.\n* BUGFIX: keep main panel up-to-date when running the screen manager,\n  to fix crash when processes die while on the F9/Kill screen.\n\nWhat's new in version 1.0\n\n* Performance improvements\n* Support for splitting CPU meters into two or four columns\n  (thanks to Wim Heirman)\n* Switch from PLPA, which is now deprecated, to HWLOC.\n* Bring back support for native Linux sched_setaffinity,\n  so we don't have to use HWLOC where we don't need to.\n* Support for typing in user names and column fields in selection panels.\n* Support for UTF-8 tree drawing\n  (thanks to Bin Guo)\n* Option for counting CPUs from zero\n  (thanks to Sean Noonan)\n* Meters update in every screen (no longer halting while on Setup, etc.)\n* Stricter checks for command-line options\n  (thanks to Sebastian Pipping)\n* Incremental filtering\n  (thanks to Seth Heeren for the idea and initial implementation)\n* Try harder to find the ncurses header\n  (thanks to Moritz Barsnick)\n* Man page updates\n  (thanks to Vincent Launchbury)\n* BUGFIX: Support larger numbers for process times.\n  (thanks to Tristan Nakagawa for the report.)\n* BUGFIX: Segfault in BarMeterMode_draw() for small terminal widths\n  (patch by Sebastian Pipping)\n\nWhat's new in version 0.9\n\n* Add support for \"steal\"/guest CPU time measurement\n  in virtualization environments\n* Expand and collapse subtrees using '+' and '-' when in tree-view\n* Support for cgroups\n  (thanks to Guillaume Zitta and Daniel Lezcano)\n* Show custom thread names\n  (thanks to Anders Torger)\n* Add support for STARTTIME field\n* Upgrade PLPA to version 1.3.2\n* Fix license terms with regard to PLPA\n  (thanks to Tom Callaway)\n* getopt-based long options and --no-color\n  (thanks to Vincent Launchbury)\n* BUGFIX: Fix display of nan% in CPU meters\n  (thanks to Steven Hampson)\n* BUGFIX: Fix memory leak\n  (thanks to Pavol Rusnak)\n* Add Bash/emacs style navigation keys\n  (thanks to Daniel Schuler)\n* Improve battery meter support\n  (thanks to Richard W.)\n* BUGFIX: Fix IO-wait color in \"Black on White\" scheme\n* BUGFIX: Fix search by process name when list is filtered by user.\n  (thanks to Sergej Pupykin for the report.)\n* BUGFIX: Fix alignment for display of memory values above 100G (sign of the times!)\n  (thanks to Jan van Haarst for the report.)\n\nWhat's new in version 0.8.3\n\n* BUGFIX: Fix crash on F6 key\n  (thanks to Rainer Suhm)\n* BUGFIX: Fix a minor bug which affected the build process.\n\nWhat's new in version 0.8.2\n\n* Integrated lsof (press 'l')\n* Fix display of gigabyte-sized values\n  (thanks to Andika Triwidada)\n* Option to display hostname in the meters area\n* Rename VEID to CTID in OpenVZ systems\n  (thanks to Thorsten Schifferdecker)\n* Corrections to the desktop entry file\n  (thanks by Samuli Suominen)\n* BUGFIX: Correct page size calculation for FreeBSD systems\n  (thanks to Andrew Paulsen)\n* Allow compilation without PLPA on systems that don't support it\n  (thanks to Timothy Redaelli)\n* BUGFIX: Fix missing tree view when userland threads are hidden\n  (thanks to Josh Stone)\n* BUGFIX: Fix for VPID on OpenVZ systems\n  (thanks to Wolfgang Frisch)\n\nWhat's new in version 0.8.1\n\n* Linux-VServer support\n  (thanks to Jonathan Sambrook and Benedikt Bohm)\n* Battery meter\n  (thanks to Ian Page Hands)\n* BUGFIX: Fix collection of IO stats in multithreaded processes\n  (thanks to Gerhard Heift)\n* Remove assertion that fails on hardened kernels\n  (thanks to Wolfram Schlich for the report)\n\nWhat's new in version 0.8\n\n* Ability to change sort column with the mouse by\n  clicking column titles (click again to invert order)\n* Add support for Linux per-process IO statistics,\n  enabled with the --enable-taskstats flag, which\n  requires a kernel compiled with taskstats support.\n  (thanks to Tobias Oetiker)\n* Add Unicode support, enabled with the --enable-unicode\n  flag, which requires libncursesw.\n  (thanks to Sergej Pupykin)\n* BUGFIX: Fix display of CPU count for threaded processes.\n  When user threads are hidden, process now shows the\n  sum of processor usage for all processors. When user\n  threads are displayed, each thread shows its own\n  processor usage, including the root thread.\n  (thanks to Bert Wesarg for the report)\n* BUGFIX: avoid crashing when using many meters\n  (thanks to David Cho for the report)\n\nWhat's new in version 0.7\n\n* CPU affinity configuration ('a' key)\n* Improve display of tree view, properly nesting\n  threads of the same app based on TGID.\n* IO-wait time now counts as idle time, which is a more\n  accurate description. It is still available in\n  split time, now called detailed CPU time.\n  (thanks to Samuel Thibault for the report)\n* BUGFIX: Correct display of TPGID field\n* Add TGID field\n* BUGFIX: Don't crash with invalid command-line flags\n  (thanks to Nico Golde for the report)\n* Fix GCC 4.3 compilation issues\n  (thanks to Martin Michlmayr for the report)\n* OpenVZ support, enabled at compile-time with\n  the --enable-openvz flag.\n  (thanks to Sergey Lychko)\n\nWhat's new in version 0.6.6\n\n* Add support of NLWP field\n  (thanks to Bert Wesarg)\n* BUGFIX: Fix use of configurable /proc location\n  (thanks to Florent Thoumie)\n* Fix memory percentage calculation and make it saner\n  (thanks to Olev Kartau for the report)\n* Added display of DRS, DT, LRS and TRS\n  (thanks to Matthias Lederhofer)\n* BUGFIX: LRS and DRS memory values were flipped\n  (thanks to Matthias Lederhofer)\n* BUGFIX: Don't crash on very high UIDs\n  (thanks to Egmont Koblinger)\n\nWhat's new in version 0.6.5\n\n* Add hardened-debug flags for debugging with Hardened GCC\n* BUGFIX: Handle error condition when a directory vanishes\n  from /proc\n* BUGFIX: Fix leak of process command line\n* BUGFIX: Collect orphaned items when arranging the tree view.\n  (thanks to Wolfram Schlich for assistance with debugging)\n* Separate proc and memory debugging into separate #defines.\n* BUGFIX: Fix message when configure fails due to\n  missing libraries\n  (thanks to Jon)\n* BUGFIX: Don't truncate value when displaying a very large\n  process\n  (thanks to Bo Liu)\n\nWhat's new in version 0.6.4\n\n* Add an option to split the display of kernel time\n  in the CPU meter into system, IO-wait, IRQ and soft-IRQ.\n  (thanks to Philipp Richter)\n* --sort-key flag in the command-line, overriding the\n  saved setting in .htoprc for the session.\n  (thanks to Rodolfo Borges)\n* BUGFIX: Fixed string overflow on uptime display.\n  (thanks to Marc Cahalan)\n\nWhat's new in version 0.6.3\n\n* Performance improvements: uses much less CPU than the\n  previous release with the default setup.\n* Use 64-bit values when storing processor times to\n  avoid overflow.\n* Memory consumption improvements, compensating storage\n  of 64-bit values.\n* Internal change: rename TypedVector to Vector and\n  ListBox (and related classes) to Panel.\n* Have configure actually fail when needed libraries or\n  headers are not found.\n* Horizontally scroll in larger increments when on the\n  Linux console because of slow update of unaccelerated fb\n* No longer untag processes after sending a signal\n  (useful for when SIGTERM fails and one wants to try again\n  with SIGKILL). All processes can be untagged at once with 'U'.\n  (thanks to A. Costa for the suggestion)\n\nWhat's new in version 0.6.2\n\n* BUGFIX: Fixed crash when using some .htoprc files from 0.6\n  (thanks to Wolfram Schlich and John Thomas for the reports)\n* BUGFIX: Ensure changes to color scheme are saved\n* BUGFIX: Make configure behave correctly with --with-proc\n* Minor addition to .desktop file.\n\nWhat's new in version 0.6.1\n\n* New meter type: \"All CPUs\", which dynamically adjusts\n  to the number of CPUs present in the machine. Note that\n  because of this, older versions of htop may crash when\n  using an .htoprc file modified my the newer version.\n* Accept --with-proc=<dir> in configure, to specify\n  alternative procfs locations (making htop friendlier\n  to the Linux compatibility layer in FreeBSD)\n* Included icon .desktop and desktop entry\n  (thanks to Peter Hyman)\n* Added a check to make sure that a root-user htop closes\n  when its parent non-root terminal is closed.\n  (thanks to Ilya Evseev for the report)\n* BUGFIX: does not crash anymore when $HOME is not set\n  (thanks to Henning Schild for the report)\n* Wait for strace child process to die properly.\n  (thanks to Marcus Fritzsch)\n* Support $HTOPRC\n  (thanks to Luis Limon)\n\nWhat's new in version 0.6\n\n* Configuration of columns merged into the Setup screen\n* Integrated strace (press 's')\n  (thanks to Marinho Barcellos for the help)\n* BUGFIX: some fixes, aided by Valgrind\n  (thanks to Wolfram Schlich for the report)\n* BUGFIX: fixed bug when switching meter modes\n  (thanks to Eduardo Righes for the report)\n* Show processes of a single user\n* \"SortBy\" function now menu-based\n* Improved mouse handling\n* ...and on top of that reduced memory consumption!\n\nWhat's new in version 0.5.4\n\n* Color schemes\n* -d flag, to configure delay between updates.\n  Note that the delay value is saved in ~/.htoprc.\n* BUGFIX: Update of meters was halting after help screen.\n  (thanks to Matt Moore)\n* BUGFIX: No longer display incorrect information\n  in first frame.\n* BUGFIX: Fix auto-detection of /proc/stat,\n  correcting CPU usage information on multiprocessor\n  systems.\n\nWhat's new in version 0.5.3\n\n* Read new field \"steal\" on newer /proc/stat files\n* Auto-detects format of /proc/stat, to cope\n  with patched 2.4 kernels which display 2.6-style\n  information (most notably those on RHEL 3)\n  (thanks to Fernando Dotta for the report)\n* Support $HOME_ETC initiative\n  (see http://www.pld-linux.org/Docs/home-etc)\n  (thanks to Roman Barczynski for the tip)\n* The configure script now tests for /proc, so\n  that it fails early on unsupported platforms\n  instead of during compilation/execution.\n* Made presentation of the function keys in the\n  status bar consistent across views\n  (thanks to David Mathog for the report)\n* Minor changes to make the codebase more friendly\n  to possible future ports\n  (thanks to Jari Aalto and David Mathog for the reports)\n\nWhat's new in version 0.5.2\n\n* BUGFIX: Correct display of user field\n  (thanks to Marcin Miroslaw for the report)\n* Keyboard support improvements\n  (thanks to Aury Fink Filho for the report)\n\nWhat's new in version 0.5.1\n\n* BUGFIX: Correctly displays NPTL threads from\n  /proc/<pid>/task subdirectories\n  (thanks to Mike Pot for the report)\n* BUGFIX: Fixes key handling on Signals listbox\n  (thanks to Ondrej Vlach)\n* Renicing no longer displays temporary illegal values\n  (thanks to Ondrej Vlach)\n* 'Hide userland threads' feature for NPTL threads\n\nWhat's new in version 0.5\n\n* Tree view\n* New column, TIME (user + system time,\n  like in top, 'T' switches to \"sort by time\")\n* Major reorganization of the underlying code of the\n  setup screen, to manage setup pages\n* New setup page: Display options\n* Hide kernel threads ('K' key)\n* Colorized memory numbers\n* Vastly improved support for monochromatic terminals\n* Shadow processes that do not belong to user ('U' key)\n* Header margin configuration accessible via setup screen\n* Visual feedback on failing incremental search\n* BUGFIX: fixed keyboard input issues on 64-bit machines\n* BUGFIX: hopefully fixed the incorrect values\n  that show on status bars in some systems\n* BUGFIX: doesn't mess with fields list anymore when\n  canceling after changing the number of items\n* Uptime meter no longer says \"1 days\" ;)\n\nWhat's new in version 0.4.1\n\n* BUGFIX: compiles on 64-bit architectures again\n  (thanks to Bartosz Fenski for the report)\n* BUGFIX: multi-processor support fixed on kernels 2.6\n  (thanks to Wolfram Schlich for the report)\n\nWhat's new in version 0.4\n\n* Support for multiple processors!\n* Basic mouse support\n* Modular header based on configurable meters;\n  supports 4 view modes: bar, text, LED, graph\n* Uptime, load average meters\n  (thanks to Marc Calahan)\n* Meters setup screen; should eventually evolve into a\n  general setup screen, with column setup, keybindings, etc.\n* Thread hiding toggleable\n  (press 'T' to hide the nonstandard dotfiles in /proc)\n* BUGFIX: Do not flicker screen on column configuration screen\n* Clock and load average meters\n  (thanks to Marc Calahan)\n* BUGFIX: numeric swap indicator was printing bogus value\n* BUGFIX: internal fixes on Panel widget\n* Clear the bottom line when exiting\n* Press \"F3\" during search to walk through the results\n* Improved navigation on column configuration screen\n* BUGFIX: fix segfault on kernels with restricted /proc\n  enabled\n* BUGFIX: a few last-minute bugfixes in the setup UI\n  (thanks to Gaspare Bruno for the reports)\n\n\nWhat's new in version 0.3.3\n\n* Saves column and sorting configuration in ~/.htoprc\n* Displays \"hidden\" threads on RedHat 9\n  (Thanks to Leonardo Godinho)\n* BUGFIX: supports process names with spaces\n  (Thanks to Marc Calahan)\n* BUGFIX: ...and parentheses :)\n* BUGFIX: long process names overflowed RichString\n  (Thanks to Marc Calahan)\n\nWhat's new in version 0.3.2\n\n* Performance and memory usage improvements, aided by gprof\n* BUGFIX: quite a few fixes, aided by Valgrind\n* Header preview on column configuration screen\n  (Thanks to Marc Calahan)\n\nWhat's new in version 0.3.1\n\n* BUGFIX: crash fixes related to process list handling\n  (thanks to Marc Calahan)\n* Man page\n  (thanks to Bartosz Fenski)\n* Tag processes with the space bar\n* Kill multiple process based on tag\n* BUGFIX: corrected processing order of updates in list\n* Screen refresh function on Ctrl-L\n* Large numbers are shown in MB/GB notation in order to fit screen\n  (thanks to Marc Calahan)\n* Realtime priority is correctly displayed\n  (thanks to Marc Calahan)\n* Preliminary support for configurable columns, with 'C'\n  (thanks to Marc Calahan)\n  -- not all columns display properly yet\n\nWhat's new in version 0.3\n\n* BUGFIX: no dirt left on screen on horizontal scrolling\n* Signal selection on \"kill\" command\n* Color-coding for users, nice and process status\n* \"Follow\" function\n* Fully selectable sort order\n* Function bar on last line\n* Build system now uses autotools\n\nWhat's new in version 0.2.1\n\n* Sorting by process or memory usage ('P' and 'M', like top)\n* Quicker default update (1.5 second, not yet configurable)\n* Now the order of the elements in the process list stay\n  'locked' for a while after you move the cursor to ease\n  selecting a process\n* Corrected the installation instructions in README\n  (Thanks to Jeremy Eglen)\n* Should now compile cleanly on Conectiva 9 and similar systems\n  (Thanks to Adriano Frare for the report)\n* Friendlier Makefile\n* Help screen ('h')\n\nWhat's new in version 0.2\n\n* Memory indicators in header now show used and total, in MB\n* Preliminary support for sorting (CPU% only)\n* Memory percentage field (resident memory / used memory)\n* BUGFIX: identified source of spurious crashes\n* Can search names containing numbers\n  (Thanks to Rafael Jeffman)\n* Correctly calculates memory page size\n  (Thanks to Rafael Jeffman)\n\nWhat's new in version 0.13\n\n* Handles terminal resize\n* Display all user names (not only those in /etc/passwd)\n  (Thanks to Julio Biason)\n\nWhat's new in version 0.12\n\n* Support for 2.6 kernels\n* Uses terminal default colors as a background\n\nWhat's new in version 0.11\n\n* BUGFIX: does not crash when UID is not in /etc/passwd\n\nWhat's new in version 0.1\n\n* Everything!\n"
  },
  {
    "path": "ClockMeter.c",
    "content": "/*\nhtop - ClockMeter.c\n(C) 2004-2011 Hisham H. Muhammad\nReleased under the GNU GPLv2+, see the COPYING file\nin the source distribution for its full text.\n*/\n\n#include \"config.h\" // IWYU pragma: keep\n\n#include \"ClockMeter.h\"\n\n#include <time.h>\n#include <sys/time.h>\n\n#include \"CRT.h\"\n#include \"Machine.h\"\n#include \"Object.h\"\n\n\nstatic const int ClockMeter_attributes[] = {\n   CLOCK\n};\n\nstatic void ClockMeter_updateValues(Meter* this) {\n   const Machine* host = this->host;\n\n   struct tm result;\n   const struct tm* lt = localtime_r(&host->realtime.tv_sec, &result);\n   strftime(this->txtBuffer, sizeof(this->txtBuffer), \"%H:%M:%S\", lt);\n}\n\nconst MeterClass ClockMeter_class = {\n   .super = {\n      .extends = Class(Meter),\n      .delete = Meter_delete\n   },\n   .updateValues = ClockMeter_updateValues,\n   .defaultMode = TEXT_METERMODE,\n   .supportedModes = (1 << TEXT_METERMODE) | (1 << LED_METERMODE),\n   .maxItems = 0,\n   .total = 0.0,\n   .attributes = ClockMeter_attributes,\n   .name = \"Clock\",\n   .uiName = \"Clock\",\n   .caption = \"Time: \",\n};\n"
  },
  {
    "path": "ClockMeter.h",
    "content": "#ifndef HEADER_ClockMeter\n#define HEADER_ClockMeter\n/*\nhtop - ClockMeter.h\n(C) 2004-2011 Hisham H. Muhammad\nReleased under the GNU GPLv2+, see the COPYING file\nin the source distribution for its full text.\n*/\n\n#include \"Meter.h\"\n\n\nextern const MeterClass ClockMeter_class;\n\n#endif\n"
  },
  {
    "path": "ColorsPanel.c",
    "content": "/*\nhtop - ColorsPanel.c\n(C) 2004-2011 Hisham H. Muhammad\nReleased under the GNU GPLv2+, see the COPYING file\nin the source distribution for its full text.\n*/\n\n#include \"config.h\" // IWYU pragma: keep\n\n#include \"ColorsPanel.h\"\n\n#include <assert.h>\n#include <stdbool.h>\n#include <stdlib.h>\n\n#include \"CRT.h\"\n#include \"FunctionBar.h\"\n#include \"Object.h\"\n#include \"OptionItem.h\"\n#include \"ProvideCurses.h\"\n\n\n// TO ADD A NEW SCHEME:\n// * Increment the size of bool check in ColorsPanel.h\n// * Add the entry in the ColorSchemeNames array below in the file\n// * Add a define in CRT.h that matches the order of the array\n// * Add the colors in CRT_setColors\n\n\nstatic const char* const ColorsFunctions[] = {\"      \", \"      \", \"      \", \"      \", \"      \", \"      \", \"      \", \"      \", \"      \", \"Done  \", NULL};\n\nstatic const char* const ColorSchemeNames[] = {\n   \"Default\",\n   \"Monochromatic\",\n   \"Black on White\",\n   \"Light Terminal\",\n   \"MC\",\n   \"Black Night\",\n   \"Broken Gray\",\n   \"Nord\",\n   NULL\n};\n\nstatic void ColorsPanel_delete(Object* object) {\n   ColorsPanel* this = (ColorsPanel*) object;\n   Panel_done(&this->super);\n   free(this);\n}\n\nstatic HandlerResult ColorsPanel_eventHandler(Panel* super, int ch) {\n   ColorsPanel* this = (ColorsPanel*) super;\n\n   HandlerResult result = IGNORED;\n\n   switch (ch) {\n      case 0x0a:\n      case 0x0d:\n      case KEY_ENTER:\n      case KEY_MOUSE:\n      case KEY_RECLICK:\n      case ' ': {\n         int mark = Panel_getSelectedIndex(super);\n         assert(mark >= 0);\n         assert(mark < LAST_COLORSCHEME);\n\n         for (int i = 0; ColorSchemeNames[i] != NULL; i++)\n            CheckItem_set((CheckItem*)Panel_get(super, i), false);\n         CheckItem_set((CheckItem*)Panel_get(super, mark), true);\n\n         this->settings->colorScheme = mark;\n         this->settings->changed = true;\n         this->settings->lastUpdate++;\n\n         CRT_setColors(mark);\n         clear();\n\n         result = HANDLED | REDRAW;\n      }\n   }\n\n   return result;\n}\n\nconst PanelClass ColorsPanel_class = {\n   .super = {\n      .extends = Class(Panel),\n      .delete = ColorsPanel_delete\n   },\n   .eventHandler = ColorsPanel_eventHandler\n};\n\nColorsPanel* ColorsPanel_new(Settings* settings) {\n   ColorsPanel* this = AllocThis(ColorsPanel);\n   Panel* super = &this->super;\n\n   FunctionBar* fuBar = FunctionBar_new(ColorsFunctions, NULL, NULL);\n   Panel_init(super, 1, 1, 1, 1, Class(CheckItem), true, fuBar);\n\n   this->settings = settings;\n\n   assert(ARRAYSIZE(ColorSchemeNames) == LAST_COLORSCHEME + 1);\n\n   Panel_setHeader(super, \"Colors\");\n   for (int i = 0; ColorSchemeNames[i] != NULL; i++) {\n      Panel_add(super, (Object*) CheckItem_newByVal(ColorSchemeNames[i], false));\n   }\n   CheckItem_set((CheckItem*)Panel_get(super, (int)CRT_colorScheme), true);\n   return this;\n}\n"
  },
  {
    "path": "ColorsPanel.h",
    "content": "#ifndef HEADER_ColorsPanel\n#define HEADER_ColorsPanel\n/*\nhtop - ColorsPanel.h\n(C) 2004-2011 Hisham H. Muhammad\nReleased under the GNU GPLv2+, see the COPYING file\nin the source distribution for its full text.\n*/\n\n#include \"Panel.h\"\n#include \"Settings.h\"\n\n\ntypedef struct ColorsPanel_ {\n   Panel super;\n\n   Settings* settings;\n} ColorsPanel;\n\nextern const PanelClass ColorsPanel_class;\n\nColorsPanel* ColorsPanel_new(Settings* settings);\n\n#endif\n"
  },
  {
    "path": "ColumnsPanel.c",
    "content": "/*\nhtop - ColumnsPanel.c\n(C) 2004-2011 Hisham H. Muhammad\nReleased under the GNU GPLv2+, see the COPYING file\nin the source distribution for its full text.\n*/\n\n#include \"config.h\" // IWYU pragma: keep\n\n#include \"ColumnsPanel.h\"\n\n#include <assert.h>\n#include <ctype.h>\n#include <stdlib.h>\n\n#include \"CRT.h\"\n#include \"DynamicColumn.h\"\n#include \"FunctionBar.h\"\n#include \"Hashtable.h\"\n#include \"ListItem.h\"\n#include \"Object.h\"\n#include \"Process.h\"\n#include \"ProvideCurses.h\"\n#include \"RowField.h\"\n#include \"XUtils.h\"\n\n\nstatic const char* const ColumnsFunctions[] = {\"      \", \"      \", \"      \", \"      \", \"      \", \"      \", \"MoveUp\", \"MoveDn\", \"Remove\", \"Done  \", NULL};\n\nstatic void ColumnsPanel_delete(Object* object) {\n   ColumnsPanel* this = (ColumnsPanel*) object;\n   Panel_done(&this->super);\n   free(this);\n}\n\nstatic HandlerResult ColumnsPanel_eventHandler(Panel* super, int ch) {\n   ColumnsPanel* const this = (ColumnsPanel*) super;\n\n   int selected = Panel_getSelectedIndex(super);\n   HandlerResult result = IGNORED;\n   int size = Panel_size(super);\n\n   switch (ch) {\n      case 0x0a:\n      case 0x0d:\n      case KEY_ENTER:\n      case KEY_MOUSE:\n      case KEY_RECLICK:\n         if (selected < size) {\n            this->moving = !(this->moving);\n            Panel_setSelectionColor(super, this->moving ? PANEL_SELECTION_FOLLOW : PANEL_SELECTION_FOCUS);\n            ListItem* selectedItem = (ListItem*) Panel_getSelected(super);\n            if (selectedItem)\n               selectedItem->moving = this->moving;\n            result = HANDLED;\n         }\n         break;\n      case KEY_UP:\n         if (!this->moving)\n            break;\n         /* else fallthrough */\n      case KEY_F(7):\n      case '[':\n      case '-':\n         if (selected < size)\n            Panel_moveSelectedUp(super);\n         result = HANDLED;\n         break;\n      case KEY_DOWN:\n         if (!this->moving)\n            break;\n         /* else fallthrough */\n      case KEY_F(8):\n      case ']':\n      case '+':\n         if (selected < size - 1)\n            Panel_moveSelectedDown(super);\n         result = HANDLED;\n         break;\n      case KEY_F(9):\n      case KEY_DC:\n      case KEY_DEL_MAC:\n         if (size > 1 && selected < size)\n            Panel_remove(super, selected);\n         result = HANDLED;\n         break;\n      default:\n         if (0 < ch && ch < 255 && isgraph((unsigned char)ch))\n            result = Panel_selectByTyping(super, ch);\n         if (result == BREAK_LOOP)\n            result = IGNORED;\n         break;\n   }\n\n   if (result == HANDLED)\n      ColumnsPanel_update(super);\n\n   return result;\n}\n\nconst PanelClass ColumnsPanel_class = {\n   .super = {\n      .extends = Class(Panel),\n      .delete = ColumnsPanel_delete\n   },\n   .eventHandler = ColumnsPanel_eventHandler\n};\n\nstatic void ColumnsPanel_add(Panel* super, unsigned int key, Hashtable* columns) {\n   const char* name;\n   if (key < LAST_PROCESSFIELD) {\n      name = Process_fields[key].name;\n   } else {\n      const DynamicColumn* column = Hashtable_get(columns, key);\n      assert(column);\n      if (!column) {\n         name = NULL;\n      } else {\n         /* heading preferred here but name is always available */\n         name = column->heading ? column->heading : column->name;\n      }\n   }\n   if (name == NULL)\n      name = \"- \";\n   Panel_add(super, (Object*) ListItem_new(name, key));\n}\n\nvoid ColumnsPanel_fill(ColumnsPanel* this, ScreenSettings* ss, Hashtable* columns) {\n   Panel* super = &this->super;\n   Panel_prune(super);\n   for (const RowField* fields = ss->fields; *fields; fields++)\n      ColumnsPanel_add(super, *fields, columns);\n   this->ss = ss;\n}\n\nColumnsPanel* ColumnsPanel_new(ScreenSettings* ss, Hashtable* columns, bool* changed) {\n   ColumnsPanel* this = AllocThis(ColumnsPanel);\n   Panel* super = &this->super;\n\n   FunctionBar* fuBar = FunctionBar_new(ColumnsFunctions, NULL, NULL);\n   Panel_init(super, 1, 1, 1, 1, Class(ListItem), true, fuBar);\n\n   this->ss = ss;\n   this->changed = changed;\n   this->moving = false;\n   Panel_setHeader(super, \"Active Columns\");\n\n   ColumnsPanel_fill(this, ss, columns);\n\n   return this;\n}\n\nvoid ColumnsPanel_update(Panel* super) {\n   ColumnsPanel* this = (ColumnsPanel*) super;\n   int size = Panel_size(super);\n   *(this->changed) = true;\n   this->ss->fields = xRealloc(this->ss->fields, sizeof(ProcessField) * (size + 1));\n   this->ss->flags = 0;\n   for (int i = 0; i < size; i++) {\n      int key = ((ListItem*) Panel_get(super, i))->key;\n      this->ss->fields[i] = key;\n      if (key < LAST_PROCESSFIELD)\n         this->ss->flags |= Process_fields[key].flags;\n   }\n   this->ss->fields[size] = 0;\n}\n"
  },
  {
    "path": "ColumnsPanel.h",
    "content": "#ifndef HEADER_ColumnsPanel\n#define HEADER_ColumnsPanel\n/*\nhtop - ColumnsPanel.h\n(C) 2004-2011 Hisham H. Muhammad\nReleased under the GNU GPLv2+, see the COPYING file\nin the source distribution for its full text.\n*/\n\n#include <stdbool.h>\n\n#include \"Hashtable.h\"\n#include \"Panel.h\"\n#include \"Settings.h\"\n\n\ntypedef struct ColumnsPanel_ {\n   Panel super;\n   ScreenSettings* ss;\n   bool* changed;\n\n   bool moving;\n} ColumnsPanel;\n\nextern const PanelClass ColumnsPanel_class;\n\nColumnsPanel* ColumnsPanel_new(ScreenSettings* ss, Hashtable* columns, bool* changed);\n\nvoid ColumnsPanel_fill(ColumnsPanel* this, ScreenSettings* ss, Hashtable* columns);\n\nvoid ColumnsPanel_update(Panel* super);\n\n#endif\n"
  },
  {
    "path": "CommandLine.c",
    "content": "/*\nhtop - CommandLine.c\n(C) 2004-2011 Hisham H. Muhammad\n(C) 2020-2021 htop dev team\nReleased under the GNU GPLv2+, see the COPYING file\nin the source distribution for its full text.\n*/\n\n#include \"config.h\" // IWYU pragma: keep\n\n#include \"CommandLine.h\"\n\n#include <assert.h>\n#include <ctype.h>\n#include <getopt.h>\n#include <limits.h>\n#include <locale.h>\n#include <stdbool.h>\n#include <stdio.h>\n#include <stdlib.h>\n#include <string.h>\n#include <time.h>\n#include <unistd.h>\n\n#include \"Action.h\"\n#include \"CRT.h\"\n#include \"DynamicColumn.h\"\n#include \"DynamicMeter.h\"\n#include \"DynamicScreen.h\"\n#include \"Hashtable.h\"\n#include \"Header.h\"\n#include \"IncSet.h\"\n#include \"Machine.h\"\n#include \"MainPanel.h\"\n#include \"MetersPanel.h\"\n#include \"Panel.h\"\n#include \"Platform.h\"\n#include \"Process.h\"\n#include \"ProcessTable.h\"\n#include \"ScreenManager.h\"\n#include \"ScreensPanel.h\"\n#include \"ScreenTabsPanel.h\"\n#include \"Settings.h\"\n#include \"Table.h\"\n#include \"UsersTable.h\"\n#include \"XUtils.h\"\n\n\nstatic void printVersionFlag(const char* name) {\n   printf(\"%s \" VERSION \"\\n\", name);\n}\n\nstatic void printHelpFlag(const char* name) {\n   printf(\"%s \" VERSION \"\\n\"\n          COPYRIGHT \"\\n\"\n          \"Released under the GNU GPLv2+.\\n\\n\"\n          \"-C --no-color                   Use a monochrome color scheme\\n\"\n          \"-d --delay=DELAY                Set the delay between updates, in tenths of seconds\\n\"\n          \"-F --filter=FILTER              Show only the commands matching the given filter\\n\"\n          \"   --no-function-bar             Hide the function bar\\n\"\n          \"-h --help                       Print this help screen\\n\"\n          \"-H --highlight-changes[=DELAY]  Highlight new and old processes\\n\", name);\n#ifdef HAVE_GETMOUSE\n   printf(\"-M --no-mouse                   Disable the mouse\\n\");\n#endif\n   printf(\"   --no-meters                  Hide meters\\n\"\n          \"-n --max-iterations=NUMBER      Exit htop after NUMBER iterations/frame updates\\n\"\n          \"-p --pid=PID[,PID,PID...]       Show only the given PIDs\\n\"\n          \"   --readonly                   Disable all system and process changing features\\n\"\n          \"-s --sort-key=COLUMN            Sort by COLUMN in list view (try --sort-key=help for a list)\\n\"\n          \"-t --tree                       Show the tree view (can be combined with -s)\\n\"\n          \"-u --user[=USERNAME]            Show only processes for a given user (or $USER)\\n\"\n          \"-U --no-unicode                 Do not use unicode but plain ASCII\\n\"\n          \"-V --version                    Print version info\\n\");\n   Platform_longOptionsUsage(name);\n   printf(\"\\n\"\n          \"Press F1 inside %s for online help.\\n\"\n          \"See 'man %s' for more information.\\n\", name, name);\n}\n\n// ----------------------------------------\n\ntypedef struct CommandLineSettings_ {\n   Hashtable* pidMatchList;\n   char* commFilter;\n   uid_t userId;\n   int sortKey;\n   int delay;\n   int iterationsRemaining;\n   bool useColors;\n#ifdef HAVE_GETMOUSE\n   bool enableMouse;\n#endif\n   bool treeView;\n   bool allowUnicode;\n   bool highlightChanges;\n   int highlightDelaySecs;\n   bool readonly;\n   bool hideMeters;\n   bool hideFunctionBar;\n} CommandLineSettings;\n\nstatic CommandLineStatus parseArguments(int argc, char** argv, CommandLineSettings* flags) {\n\n   *flags = (CommandLineSettings) {\n      .pidMatchList = NULL,\n      .commFilter = NULL,\n      .userId = (uid_t)-1, // -1 is guaranteed to be an invalid uid_t (see setreuid(2))\n      .sortKey = 0,\n      .delay = -1,\n      .iterationsRemaining = -1,\n      .useColors = true,\n#ifdef HAVE_GETMOUSE\n      .enableMouse = true,\n#endif\n      .treeView = false,\n      .allowUnicode = true,\n      .highlightChanges = false,\n      .highlightDelaySecs = -1,\n      .readonly = false,\n      .hideMeters = false,\n      .hideFunctionBar = false,\n   };\n\n   {\n      // Implement NO_COLOR env support, cf. https://no-color.org/\n      const char* no_color = getenv(\"NO_COLOR\");\n      if (no_color && no_color[0] != '\\0') {\n         flags->useColors = false;\n      }\n   }\n\n   const struct option long_opts[] =\n   {\n      {\"help\",       no_argument,         0, 'h'},\n      {\"version\",    no_argument,         0, 'V'},\n      {\"delay\",      required_argument,   0, 'd'},\n      {\"max-iterations\", required_argument, 0, 'n'},\n      {\"sort-key\",   required_argument,   0, 's'},\n      {\"user\",       optional_argument,   0, 'u'},\n      {\"no-color\",   no_argument,         0, 'C'},\n      {\"no-colour\",  no_argument,         0, 'C'},\n      {\"no-mouse\",   no_argument,         0, 'M'},\n      {\"no-unicode\", no_argument,         0, 'U'},\n      {\"no-meters\",  no_argument,         0, 129},\n      {\"tree\",       no_argument,         0, 't'},\n      {\"pid\",        required_argument,   0, 'p'},\n      {\"filter\",     required_argument,   0, 'F'},\n      {\"no-functionbar\", no_argument,     0, 130},\n      {\"no-function-bar\", no_argument,    0, 130},\n      {\"highlight-changes\", optional_argument, 0, 'H'},\n      {\"readonly\",   no_argument,         0, 128},\n      PLATFORM_LONG_OPTIONS\n      {0, 0, 0, 0}\n   };\n\n   int opt, opti = 0;\n   /* Parse arguments */\n   while ((opt = getopt_long(argc, argv, \"hVMCs:td:n:u::Up:F:H::\", long_opts, &opti))) {\n      if (opt == EOF)\n         break;\n\n      switch (opt) {\n         case 'h':\n            printHelpFlag(program);\n            return STATUS_OK_EXIT;\n         case 'V':\n            printVersionFlag(program);\n            return STATUS_OK_EXIT;\n         case 's':\n            assert(optarg); /* please clang analyzer, cause optarg can be NULL in the 'u' case */\n            if (String_eq(optarg, \"help\")) {\n               for (int j = 1; j < LAST_PROCESSFIELD; j++) {\n                  const char* name = Process_fields[j].name;\n                  const char* description = Process_fields[j].description;\n                  if (name)\n                     printf(\"%19s %s\\n\", name, description);\n               }\n               return STATUS_OK_EXIT;\n            }\n            flags->sortKey = 0;\n            for (int j = 1; j < LAST_PROCESSFIELD; j++) {\n               if (Process_fields[j].name == NULL)\n                  continue;\n               if (String_eq(optarg, Process_fields[j].name)) {\n                  flags->sortKey = j;\n                  break;\n               }\n            }\n            if (flags->sortKey == 0) {\n               fprintf(stderr, \"Error: invalid column \\\"%s\\\".\\n\", optarg);\n               return STATUS_ERROR_EXIT;\n            }\n            break;\n         case 'd':\n            if (sscanf(optarg, \"%16d\", &(flags->delay)) == 1) {\n               if (flags->delay < 1)\n                  flags->delay = 1;\n               if (flags->delay > 100)\n                  flags->delay = 100;\n            } else {\n               fprintf(stderr, \"Error: invalid delay value \\\"%s\\\".\\n\", optarg);\n               return STATUS_ERROR_EXIT;\n            }\n            break;\n         case 'n':\n            if (sscanf(optarg, \"%16d\", &flags->iterationsRemaining) == 1) {\n               if (flags->iterationsRemaining <= 0) {\n                  fprintf(stderr, \"Error: maximum iteration count must be positive.\\n\");\n                  return STATUS_ERROR_EXIT;\n               }\n            } else {\n               fprintf(stderr, \"Error: invalid maximum iteration count \\\"%s\\\".\\n\", optarg);\n               return STATUS_ERROR_EXIT;\n            }\n            break;\n         case 'u': {\n            const char* username = optarg;\n            if (!username && optind < argc && argv[optind] != NULL &&\n                (argv[optind][0] != '\\0' && argv[optind][0] != '-')) {\n               username = argv[optind++];\n            }\n\n            if (!username) {\n               flags->userId = geteuid();\n            } else if (!Action_setUserOnly(username, &(flags->userId))) {\n               char* endptr;\n               /* using strtoll as strtoul negative value handling is not what we want */\n               long long val = strtoll(username, &endptr, 10);\n               if (*endptr != '\\0' || username == endptr || val < 0 || val >= UINT_MAX) {\n                  fprintf(stderr, \"Error: invalid user \\\"%s\\\".\\n\", username);\n                  return STATUS_ERROR_EXIT;\n               }\n               flags->userId = (uid_t)val;\n            }\n            break;\n         }\n         case 'C':\n            flags->useColors = false;\n            break;\n         case 'M':\n#ifdef HAVE_GETMOUSE\n            flags->enableMouse = false;\n#endif\n            break;\n         case 'U':\n            flags->allowUnicode = false;\n            break;\n         case 129:\n            flags->hideMeters = true;\n            break;\n         case 't':\n            flags->treeView = true;\n            break;\n         case 'p': {\n            assert(optarg); /* please clang analyzer, cause optarg can be NULL in the 'u' case */\n            char* argCopy = xStrdup(optarg);\n            char* saveptr;\n            const char* pid = strtok_r(argCopy, \",\", &saveptr);\n\n            if (!flags->pidMatchList) {\n               flags->pidMatchList = Hashtable_new(8, false);\n            }\n\n            while (pid) {\n               unsigned int num_pid = atoi(pid);\n               //  deepcode ignore CastIntegerToAddress: we just want a non-NULL pointer here\n               Hashtable_put(flags->pidMatchList, num_pid, (void*) 1);\n               pid = strtok_r(NULL, \",\", &saveptr);\n            }\n            free(argCopy);\n\n            break;\n         }\n         case 'F':\n            assert(optarg);\n            if (optarg[0] == '\\0' || optarg[0] == '|') {\n               fprintf(stderr, \"Error: invalid filter value \\\"%s\\\".\\n\", optarg);\n               return STATUS_ERROR_EXIT;\n            }\n            free_and_xStrdup(&flags->commFilter, optarg);\n            break;\n         case 130:\n            flags->hideFunctionBar = true;\n            break;\n         case 'H': {\n            const char* delay = optarg;\n            if (!delay && optind < argc && argv[optind] != NULL &&\n                (argv[optind][0] != '\\0' && argv[optind][0] != '-')) {\n               delay = argv[optind++];\n            }\n            if (delay) {\n               if (sscanf(delay, \"%16d\", &(flags->highlightDelaySecs)) == 1) {\n                  if (flags->highlightDelaySecs < 1)\n                     flags->highlightDelaySecs = 1;\n               } else {\n                  fprintf(stderr, \"Error: invalid highlight delay value \\\"%s\\\".\\n\", delay);\n                  return STATUS_ERROR_EXIT;\n               }\n            }\n            flags->highlightChanges = true;\n            break;\n         }\n         case 128:\n            flags->readonly = true;\n            break;\n\n         default: {\n            CommandLineStatus status;\n            if ((status = Platform_getLongOption(opt, argc, argv)) != STATUS_OK)\n               return status;\n            break;\n         }\n      }\n   }\n\n   if (optind < argc) {\n      fprintf(stderr, \"Error: unsupported non-option ARGV-elements:\");\n      while (optind < argc)\n         fprintf(stderr, \" %s\", argv[optind++]);\n      fprintf(stderr, \"\\n\");\n      return STATUS_ERROR_EXIT;\n   }\n\n   return STATUS_OK;\n}\n\nstatic void setCommFilter(State* state, char** commFilter) {\n   Table* table = state->host->activeTable;\n   IncSet* inc = state->mainPanel->inc;\n\n   IncSet_setFilter(inc, *commFilter);\n   table->incFilter = IncSet_filter(inc);\n\n   free(*commFilter);\n   *commFilter = NULL;\n}\n\nint CommandLine_run(int argc, char** argv) {\n\n   /* initialize locale */\n   const char* lc_ctype;\n   if ((lc_ctype = getenv(\"LC_CTYPE\")) || (lc_ctype = getenv(\"LC_ALL\")))\n      setlocale(LC_CTYPE, lc_ctype);\n   else\n      setlocale(LC_CTYPE, \"\");\n\n   CommandLineStatus status = STATUS_OK;\n   CommandLineSettings flags = { 0 };\n\n   if ((status = parseArguments(argc, argv, &flags)) != STATUS_OK)\n      return status != STATUS_OK_EXIT ? 1 : 0;\n\n   if (flags.readonly)\n      Settings_enableReadonly();\n\n   if (!Platform_init())\n      return 1;\n\n   UsersTable* ut = UsersTable_new();\n   Hashtable* dm = DynamicMeters_new();\n   Hashtable* dc = DynamicColumns_new();\n   Hashtable* ds = DynamicScreens_new();\n\n   Machine* host = Machine_new(ut, flags.userId);\n   ProcessTable* pt = ProcessTable_new(host, flags.pidMatchList);\n   Settings* settings = Settings_new(host, dm, dc, ds);\n   Machine_populateTablesFromSettings(host, settings, &pt->super);\n\n   Header* header = Header_new(host, 2);\n   Header_populateFromSettings(header);\n\n   int colorSchemeFromConfig = settings->colorScheme;\n\n   if (flags.delay != -1)\n      settings->delay = flags.delay;\n   if (!flags.useColors)\n      settings->colorScheme = COLORSCHEME_MONOCHROME;\n#ifdef HAVE_GETMOUSE\n   if (!flags.enableMouse)\n      settings->enableMouse = false;\n#endif\n   if (flags.treeView)\n      settings->ss->treeView = true;\n   if (flags.highlightChanges)\n      settings->highlightChanges = true;\n   if (flags.highlightDelaySecs != -1)\n      settings->highlightDelaySecs = flags.highlightDelaySecs;\n   if (flags.sortKey > 0) {\n      // -t -s <key> means \"tree sorted by key\"\n      // -s <key> means \"list sorted by key\" (previous existing behavior)\n      if (!flags.treeView) {\n         settings->ss->treeView = false;\n      }\n      ScreenSettings_setSortKey(settings->ss, flags.sortKey);\n   }\n   if (flags.hideFunctionBar)\n      settings->hideFunctionBar = 2;\n\n   host->iterationsRemaining = flags.iterationsRemaining;\n   CRT_init(settings, flags.allowUnicode, flags.iterationsRemaining != -1);\n\n   // Do not save the color scheme override to 'htoprc'.\n   // 'settings' will keep the original color scheme until the user\n   // changes it in the Setup.\n   // ('CRT_colorScheme' holds the current, active color scheme.)\n   settings->colorScheme = colorSchemeFromConfig;\n\n   MainPanel* panel = MainPanel_new();\n   Machine_setTablesPanel(host, (Panel*) panel);\n\n   MainPanel_updateLabels(panel, settings->ss->treeView, flags.commFilter);\n\n   State state = {\n      .host = host,\n      .mainPanel = panel,\n      .header = header,\n      .failedUpdate = NULL,\n      .pauseUpdate = false,\n      .hideSelection = false,\n      .hideMeters = flags.hideMeters,\n   };\n\n   MainPanel_setState(panel, &state);\n   if (flags.commFilter)\n      setCommFilter(&state, &(flags.commFilter));\n\n   ScreenManager* scr = ScreenManager_new(header, host, &state, true);\n   ScreenManager_add(scr, (Panel*) panel, -1);\n\n   Machine_scan(host);\n   Machine_scanTables(host);\n\n   if (settings->ss->allBranchesCollapsed)\n      Table_collapseAllBranches(&pt->super);\n\n   ScreenManager_run(scr, NULL, NULL, NULL);\n\n   Platform_done();\n\n   CRT_done();\n\n   if (settings->changed) {\n#ifndef NDEBUG\n      if (!String_eq(settings->initialFilename, settings->filename))\n         fprintf(stderr, \"Configuration %s was resolved to %s\\n\", settings->initialFilename, settings->filename);\n#endif /* NDEBUG */\n      int r = Settings_write(settings, false);\n      if (r < 0)\n         fprintf(stderr, \"Cannot save configuration to %s: %s\\n\", settings->filename, strerror(-r));\n   }\n\n   Header_delete(header);\n   Machine_delete(host);\n\n   ScreenManager_delete(scr);\n   MetersPanel_cleanup();\n   ScreensPanel_cleanup();\n   ScreenTabsPanel_cleanup();\n\n   UsersTable_delete(ut);\n\n   if (flags.pidMatchList)\n      Hashtable_delete(flags.pidMatchList);\n\n   CRT_resetSignalHandlers();\n\n   /* Delete these last, since they can get accessed in the crash handler */\n   Settings_delete(settings);\n   DynamicColumns_delete(dc);\n   DynamicMeters_delete(dm);\n   DynamicScreens_delete(ds);\n\n   return 0;\n}\n"
  },
  {
    "path": "CommandLine.h",
    "content": "#ifndef HEADER_CommandLine\n#define HEADER_CommandLine\n/*\nhtop - CommandLine.h\n(C) 2004-2011 Hisham H. Muhammad\n(C) 2020-2021 htop dev team\nReleased under the GNU GPLv2+, see the COPYING file\nin the source distribution for its full text.\n*/\n\ntypedef enum {\n   STATUS_OK,\n   STATUS_ERROR_EXIT,\n   STATUS_OK_EXIT\n} CommandLineStatus;\n\nextern const char* program;\n\nint CommandLine_run(int argc, char** argv);\n\n#endif\n"
  },
  {
    "path": "CommandScreen.c",
    "content": "/*\nhtop - CommandScreen.c\n(C) 2017,2020 ryenus\n(C) 2020,2021 htop dev team\nReleased under the GNU GPLv2+, see the COPYING file\nin the source distribution for its full text.\n*/\n\n#include \"config.h\" // IWYU pragma: keep\n\n#include \"CommandScreen.h\"\n\n#include <assert.h>\n#include <stdlib.h>\n#include <string.h>\n\n#include \"Macros.h\"\n#include \"Panel.h\"\n#include \"ProvideCurses.h\"\n\n\nstatic void CommandScreen_scan(InfoScreen* this) {\n   Panel* panel = this->display;\n   int idx = MAXIMUM(Panel_getSelectedIndex(panel), 0);\n   Panel_prune(panel);\n\n   const char* p = Process_getCommand(this->process);\n\n   size_t line_maxlen = COLS < 40 ? 40 : COLS;\n   size_t line_offset = 0;\n   size_t last_space = 0;\n   char* line = xCalloc(line_maxlen + 1, sizeof(char));\n\n   for (; *p != '\\0'; p++) {\n      if (line_offset >= line_maxlen) {\n         assert(line_offset <= line_maxlen);\n         assert(last_space <= line_maxlen);\n\n         size_t line_len = last_space <= 0 ? line_offset : last_space;\n         char tmp = line[line_len];\n         line[line_len] = '\\0';\n         InfoScreen_addLine(this, line);\n         line[line_len] = tmp;\n\n         assert(line_len <= line_offset);\n         line_offset -= line_len;\n         memmove(line, line + line_len, line_offset);\n\n         last_space = 0;\n      }\n\n      line[line_offset++] = *p;\n      if (*p == ' ') {\n         last_space = line_offset;\n      }\n   }\n\n   if (line_offset > 0) {\n      line[line_offset] = '\\0';\n      InfoScreen_addLine(this, line);\n   }\n\n   free(line);\n\n   Panel_setSelected(panel, idx);\n}\n\nstatic void CommandScreen_draw(InfoScreen* this) {\n   InfoScreen_drawTitled(this, \"Command of process %d - %s\", Process_getPid(this->process), Process_getCommand(this->process));\n}\n\nconst InfoScreenClass CommandScreen_class = {\n   .super = {\n      .extends = Class(Object),\n      .delete = CommandScreen_delete\n   },\n   .scan = CommandScreen_scan,\n   .draw = CommandScreen_draw\n};\n\nCommandScreen* CommandScreen_new(Process* process) {\n   CommandScreen* this = AllocThis(CommandScreen);\n   return (CommandScreen*) InfoScreen_init(&this->super, process, NULL, LINES - 2, \" \");\n}\n\nvoid CommandScreen_delete(Object* this) {\n   free(InfoScreen_done((InfoScreen*)this));\n}\n"
  },
  {
    "path": "CommandScreen.h",
    "content": "#ifndef HEADER_CommandScreen\n#define HEADER_CommandScreen\n/*\nhtop - CommandScreen.h\n(C) 2017,2020 ryenus\n(C) 2020,2021 htop dev team\nReleased under the GNU GPLv2+, see the COPYING file\nin the source distribution for its full text.\n*/\n\n#include \"InfoScreen.h\"\n#include \"Object.h\"\n#include \"Process.h\"\n\n\ntypedef struct CommandScreen_ {\n   InfoScreen super;\n} CommandScreen;\n\nextern const InfoScreenClass CommandScreen_class;\n\nCommandScreen* CommandScreen_new(Process* process);\n\nvoid CommandScreen_delete(Object* this);\n\n#endif\n"
  },
  {
    "path": "DateTimeMeter.c",
    "content": "/*\nhtop - DateTimeMeter.c\n(C) 2004-2020 Hisham H. Muhammad, Michael Schönitzer\nReleased under the GNU GPLv2+, see the COPYING file\nin the source distribution for its full text.\n*/\n\n#include \"config.h\" // IWYU pragma: keep\n\n#include \"DateTimeMeter.h\"\n\n#include <time.h>\n#include <sys/time.h>\n\n#include \"CRT.h\"\n#include \"Machine.h\"\n#include \"Object.h\"\n\n\nstatic const int DateMeter_attributes[] = {\n   DATE\n};\n\nstatic const int DateTimeMeter_attributes[] = {\n   DATETIME\n};\n\nstatic void DateMeter_updateValues(Meter* this) {\n   const Machine* host = this->host;\n\n   struct tm result;\n   const struct tm* lt = localtime_r(&host->realtime.tv_sec, &result);\n   strftime(this->txtBuffer, sizeof(this->txtBuffer), \"%F\", lt);\n}\n\nstatic void DateTimeMeter_updateValues(Meter* this) {\n   const Machine* host = this->host;\n\n   struct tm result;\n   const struct tm* lt = localtime_r(&host->realtime.tv_sec, &result);\n   strftime(this->txtBuffer, sizeof(this->txtBuffer), \"%F %H:%M:%S\", lt);\n}\n\nconst MeterClass DateMeter_class = {\n   .super = {\n      .extends = Class(Meter),\n      .delete = Meter_delete\n   },\n   .updateValues = DateMeter_updateValues,\n   .defaultMode = TEXT_METERMODE,\n   .supportedModes = (1 << TEXT_METERMODE) | (1 << LED_METERMODE),\n   .maxItems = 0,\n   .total = 0.0,\n   .attributes = DateMeter_attributes,\n   .name = \"Date\",\n   .uiName = \"Date\",\n   .caption = \"Date: \",\n};\n\nconst MeterClass DateTimeMeter_class = {\n   .super = {\n      .extends = Class(Meter),\n      .delete = Meter_delete\n   },\n   .updateValues = DateTimeMeter_updateValues,\n   .defaultMode = TEXT_METERMODE,\n   .supportedModes = (1 << TEXT_METERMODE) | (1 << LED_METERMODE),\n   .maxItems = 0,\n   .total = 0.0,\n   .attributes = DateTimeMeter_attributes,\n   .name = \"DateTime\",\n   .uiName = \"Date and Time\",\n   .caption = \"Date & Time: \",\n};\n"
  },
  {
    "path": "DateTimeMeter.h",
    "content": "#ifndef HEADER_DateTimeMeter\n#define HEADER_DateTimeMeter\n/*\nhtop - DateTimeMeter.h\n(C) 2004-2011 Hisham H. Muhammad\nReleased under the GNU GPLv2+, see the COPYING file\nin the source distribution for its full text.\n*/\n\n#include \"Meter.h\"\n\n\nextern const MeterClass DateMeter_class;\n\nextern const MeterClass DateTimeMeter_class;\n\n#endif\n"
  },
  {
    "path": "Debug.h",
    "content": "#ifndef HEADER_Debug\n#define HEADER_Debug\n/*\nhtop - Debug.h\n(C) 2020 htop dev team\nReleased under the GNU GPLv2+, see the COPYING file\nin the source distribution for its full text.\n*/\n\n#include <assert.h> // IWYU pragma: keep\n\n\n/*\n * static_assert() hack for pre-C11\n * TODO: drop after moving to -std=c11 or newer\n */\n\n/* C11 guarantees _Static_assert is a keyword */\n#if (defined __STDC_VERSION__ ? __STDC_VERSION__ : 0) < 201112L\n# if !defined(_Static_assert)\n#  define _Static_assert(expr, msg)                                    \\\n   extern int (*__Static_assert_function (void))                       \\\n      [!!sizeof (struct { int __error_if_negative: (expr) ? 2 : -1; })]\n# endif\n#endif\n\n/* C23 guarantees static_assert is a keyword or a macro */\n#if (defined __STDC_VERSION__ ? __STDC_VERSION__ : 0) < 202311L\n# if !defined(static_assert)\n#  define static_assert(expr, msg) _Static_assert(expr, msg)\n# endif\n#endif\n\n#endif /* HEADER_Debug */\n"
  },
  {
    "path": "DiskIOMeter.c",
    "content": "/*\nhtop - DiskIOMeter.c\n(C) 2020 htop dev team\nReleased under the GNU GPLv2+, see the COPYING file\nin the source distribution for its full text.\n*/\n\n#include \"config.h\" // IWYU pragma: keep\n\n#include \"DiskIOMeter.h\"\n\n#include <stdbool.h>\n\n#include \"CRT.h\"\n#include \"Machine.h\"\n#include \"Macros.h\"\n#include \"Object.h\"\n#include \"Platform.h\"\n#include \"RichString.h\"\n#include \"Row.h\"\n#include \"XUtils.h\"\n\n\ntypedef struct DiskIOMeterData_ {\n   Meter* diskIORateMeter;\n   Meter* diskIOTimeMeter;\n} DiskIOMeterData;\n\nstatic const int DiskIORateMeter_attributes[] = {\n   METER_VALUE_IOREAD,\n   METER_VALUE_IOWRITE,\n};\n\nstatic const int DiskIOTimeMeter_attributes[] = {\n   METER_VALUE_NOTICE,\n};\n\nstatic MeterRateStatus status = RATESTATUS_INIT;\nstatic double cached_read_diff;\nstatic char cached_read_diff_str[6];\nstatic double cached_write_diff;\nstatic char cached_write_diff_str[6];\nstatic uint64_t cached_num_disks;\nstatic double cached_utilisation_diff;\nstatic double cached_utilisation_norm;\n\nstatic void DiskIOUpdateCache(const Machine* host) {\n   static uint64_t cached_last_update;\n\n   uint64_t passedTimeInMs = host->realtimeMs - cached_last_update;\n\n   /* update only every 500ms to have a sane span for rate calculation */\n   if (passedTimeInMs <= 500)\n      return;\n\n   DiskIOData data;\n   bool hasNewData = Platform_getDiskIO(&data);\n   if (!hasNewData) {\n      status = RATESTATUS_NODATA;\n   } else if (cached_last_update == 0) {\n      status = RATESTATUS_INIT;\n   } else if (passedTimeInMs > 30000) {\n      status = RATESTATUS_STALE;\n   } else {\n      status = RATESTATUS_DATA;\n   }\n\n   cached_last_update = host->realtimeMs;\n\n   if (!hasNewData)\n      return;\n\n   static uint64_t cached_read_total;\n   static uint64_t cached_write_total;\n   static uint64_t cached_msTimeSpend_total;\n\n   if (status != RATESTATUS_INIT) {\n      uint64_t diff;\n\n      if (data.totalBytesRead > cached_read_total) {\n         diff = data.totalBytesRead - cached_read_total;\n         diff = (1000 * diff) / passedTimeInMs; /* convert to B/s */\n      } else {\n         diff = 0;\n      }\n      cached_read_diff = diff;\n      Meter_humanUnit(cached_read_diff_str, cached_read_diff / ONE_K, sizeof(cached_read_diff_str));\n\n      if (data.totalBytesWritten > cached_write_total) {\n         diff = data.totalBytesWritten - cached_write_total;\n         diff = (1000 * diff) / passedTimeInMs; /* convert to B/s */\n      } else {\n         diff = 0;\n      }\n      cached_write_diff = diff;\n      Meter_humanUnit(cached_write_diff_str, cached_write_diff / ONE_K, sizeof(cached_write_diff_str));\n\n      cached_num_disks = data.numDisks;\n      cached_utilisation_diff = 0.0;\n      cached_utilisation_norm = 0.0;\n      if (data.totalMsTimeSpend > cached_msTimeSpend_total) {\n         diff = data.totalMsTimeSpend - cached_msTimeSpend_total;\n         cached_utilisation_diff = 100.0 * (double)diff / passedTimeInMs;\n         if (data.numDisks > 0) {\n            cached_utilisation_norm = (double)diff / (passedTimeInMs * data.numDisks);\n            cached_utilisation_norm = MINIMUM(cached_utilisation_norm, 1.0);\n         }\n      }\n   }\n\n   cached_read_total = data.totalBytesRead;\n   cached_write_total = data.totalBytesWritten;\n   cached_msTimeSpend_total = data.totalMsTimeSpend;\n}\n\nstatic void DiskIORateMeter_updateValues(Meter* this) {\n   DiskIOUpdateCache(this->host);\n\n   this->values[0] = cached_read_diff;\n   this->values[1] = cached_write_diff;\n\n   switch (status) {\n      case RATESTATUS_NODATA:\n         xSnprintf(this->txtBuffer, sizeof(this->txtBuffer), \"no data\");\n         return;\n      case RATESTATUS_INIT:\n         xSnprintf(this->txtBuffer, sizeof(this->txtBuffer), \"init\");\n         return;\n      case RATESTATUS_STALE:\n         xSnprintf(this->txtBuffer, sizeof(this->txtBuffer), \"stale\");\n         return;\n      case RATESTATUS_DATA:\n         break;\n   }\n\n   xSnprintf(this->txtBuffer, sizeof(this->txtBuffer), \"r:%siB/s w:%siB/s\", cached_read_diff_str, cached_write_diff_str);\n}\n\nstatic void DiskIORateMeter_display(ATTR_UNUSED const Object* cast, RichString* out) {\n   switch (status) {\n      case RATESTATUS_NODATA:\n         RichString_writeAscii(out, CRT_colors[METER_VALUE_ERROR], \"no data\");\n         return;\n      case RATESTATUS_INIT:\n         RichString_writeAscii(out, CRT_colors[METER_VALUE], \"initializing...\");\n         return;\n      case RATESTATUS_STALE:\n         RichString_writeAscii(out, CRT_colors[METER_VALUE_WARN], \"stale data\");\n         return;\n      case RATESTATUS_DATA:\n         break;\n   }\n\n   RichString_appendAscii(out, CRT_colors[METER_TEXT], \"read: \");\n   RichString_appendAscii(out, CRT_colors[METER_VALUE_IOREAD], cached_read_diff_str);\n   RichString_appendAscii(out, CRT_colors[METER_VALUE_IOREAD], \"iB/s\");\n\n   RichString_appendAscii(out, CRT_colors[METER_TEXT], \" write: \");\n   RichString_appendAscii(out, CRT_colors[METER_VALUE_IOWRITE], cached_write_diff_str);\n   RichString_appendAscii(out, CRT_colors[METER_VALUE_IOWRITE], \"iB/s\");\n}\n\nstatic void DiskIOTimeMeter_updateValues(Meter* this) {\n   DiskIOUpdateCache(this->host);\n\n   this->values[0] = cached_utilisation_norm;\n\n   switch (status) {\n      case RATESTATUS_NODATA:\n         xSnprintf(this->txtBuffer, sizeof(this->txtBuffer), \"no data\");\n         return;\n      case RATESTATUS_INIT:\n         xSnprintf(this->txtBuffer, sizeof(this->txtBuffer), \"init\");\n         return;\n      case RATESTATUS_STALE:\n         xSnprintf(this->txtBuffer, sizeof(this->txtBuffer), \"stale\");\n         return;\n      case RATESTATUS_DATA:\n         break;\n   }\n\n   char numDisksStr[12];\n   numDisksStr[0] = '\\0';\n   if (cached_num_disks > 1 && cached_num_disks < 1000) {\n      xSnprintf(numDisksStr, sizeof(numDisksStr), \" (%udisks)\", (unsigned int)cached_num_disks);\n   }\n\n   xSnprintf(this->txtBuffer, sizeof(this->txtBuffer), \"%.1f%%%s\", cached_utilisation_diff, numDisksStr);\n}\n\nstatic void DiskIOTimeMeter_display(ATTR_UNUSED const Object* cast, RichString* out) {\n   switch (status) {\n      case RATESTATUS_NODATA:\n         RichString_writeAscii(out, CRT_colors[METER_VALUE_ERROR], \"no data\");\n         return;\n      case RATESTATUS_INIT:\n         RichString_writeAscii(out, CRT_colors[METER_VALUE], \"initializing...\");\n         return;\n      case RATESTATUS_STALE:\n         RichString_writeAscii(out, CRT_colors[METER_VALUE_WARN], \"stale data\");\n         return;\n      case RATESTATUS_DATA:\n         break;\n   }\n\n   char buffer[16];\n\n   int color = cached_utilisation_diff > 40.0 ? METER_VALUE_NOTICE : METER_VALUE;\n   int len = xSnprintf(buffer, sizeof(buffer), \"%.1f%%\", cached_utilisation_diff);\n   RichString_appendnAscii(out, CRT_colors[color], buffer, len);\n   RichString_appendAscii(out, CRT_colors[METER_TEXT], \" busy\");\n\n   if (cached_num_disks > 1 && cached_num_disks < 1000) {\n      RichString_appendAscii(out, CRT_colors[METER_TEXT], \" (\");\n      len = xSnprintf(buffer, sizeof(buffer), \"%u\", (unsigned int)cached_num_disks);\n      RichString_appendnAscii(out, CRT_colors[METER_VALUE], buffer, len);\n      RichString_appendAscii(out, CRT_colors[METER_TEXT], \" disks)\");\n   }\n}\n\nstatic void DiskIOMeter_display(const Object* cast, RichString* out) {\n   DiskIORateMeter_display(cast, out);\n\n   switch (status) {\n      case RATESTATUS_NODATA:\n      case RATESTATUS_INIT:\n      case RATESTATUS_STALE:\n         return;\n      case RATESTATUS_DATA:\n         break;\n   }\n\n   RichString_appendAscii(out, CRT_colors[METER_TEXT], \"; \");\n   DiskIOTimeMeter_display(cast, out);\n}\n\nstatic void DiskIOMeter_updateValues(Meter* this) {\n   DiskIOMeterData* data = this->meterData;\n\n   Meter_updateValues(data->diskIORateMeter);\n   Meter_updateValues(data->diskIOTimeMeter);\n}\n\nstatic void DiskIOMeter_draw(Meter* this, int x, int y, int w) {\n   DiskIOMeterData* data = this->meterData;\n\n   assert(data->diskIORateMeter->draw);\n   assert(data->diskIOTimeMeter->draw);\n\n   switch (this->mode) {\n   case TEXT_METERMODE:\n   case LED_METERMODE:\n      data->diskIORateMeter->draw(this, x, y, w);\n      return;\n   }\n\n   /* Use the same width for each sub meter to align with CPU meter */\n   const int colwidth = w / 2;\n   const int diff = w % 2;\n\n   data->diskIORateMeter->draw(data->diskIORateMeter, x, y, colwidth);\n   data->diskIOTimeMeter->draw(data->diskIOTimeMeter, x + colwidth + diff, y, colwidth);\n}\n\nstatic void DiskIOMeter_init(Meter* this) {\n   if (!this->meterData) {\n      this->meterData = xCalloc(1, sizeof(DiskIOMeterData));\n   }\n\n   DiskIOMeterData* data = this->meterData;\n\n   if (!data->diskIORateMeter)\n      data->diskIORateMeter = Meter_new(this->host, 0, (const MeterClass*) Class(DiskIORateMeter));\n   if (!data->diskIOTimeMeter)\n      data->diskIOTimeMeter = Meter_new(this->host, 0, (const MeterClass*) Class(DiskIOTimeMeter));\n\n   if (Meter_initFn(data->diskIORateMeter)) {\n      Meter_init(data->diskIORateMeter);\n   }\n   if (Meter_initFn(data->diskIOTimeMeter)) {\n      Meter_init(data->diskIOTimeMeter);\n   }\n}\n\nstatic void DiskIOMeter_updateMode(Meter* this, MeterModeId mode) {\n   DiskIOMeterData* data = this->meterData;\n\n   this->mode = mode;\n\n   Meter_setMode(data->diskIORateMeter, mode);\n   Meter_setMode(data->diskIOTimeMeter, mode);\n\n   this->h = MAXIMUM(data->diskIORateMeter->h, data->diskIOTimeMeter->h);\n}\n\nstatic void DiskIOMeter_done(Meter* this) {\n   DiskIOMeterData* data = this->meterData;\n\n   Meter_delete((Object*)data->diskIORateMeter);\n   Meter_delete((Object*)data->diskIOTimeMeter);\n\n   free(data);\n}\n\nconst MeterClass DiskIORateMeter_class = {\n   .super = {\n      .extends = Class(Meter),\n      .delete = Meter_delete,\n      .display = DiskIORateMeter_display\n   },\n   .updateValues = DiskIORateMeter_updateValues,\n   .defaultMode = TEXT_METERMODE,\n   .supportedModes = METERMODE_DEFAULT_SUPPORTED,\n   .maxItems = 2,\n   .isPercentChart = false,\n   .total = 1.0,\n   .attributes = DiskIORateMeter_attributes,\n   .name = \"DiskIORate\",\n   .uiName = \"Disk IO Rate\",\n   .description = \"Disk IO read & write bytes per second\",\n   .caption = \"Dsk: \"\n};\n\nconst MeterClass DiskIOTimeMeter_class = {\n   .super = {\n      .extends = Class(Meter),\n      .delete = Meter_delete,\n      .display = DiskIOTimeMeter_display\n   },\n   .updateValues = DiskIOTimeMeter_updateValues,\n   .defaultMode = TEXT_METERMODE,\n   .supportedModes = METERMODE_DEFAULT_SUPPORTED,\n   .maxItems = 1,\n   .isPercentChart = true,\n   .total = 1.0,\n   .attributes = DiskIOTimeMeter_attributes,\n   .name = \"DiskIOTime\",\n   .uiName = \"Disk IO Time\",\n   .description = \"Disk percent time busy\",\n   .caption = \"Dsk: \"\n};\n\nconst MeterClass DiskIOMeter_class = {\n   .super = {\n      .extends = Class(Meter),\n      .delete = Meter_delete,\n      .display = DiskIOMeter_display\n   },\n   .updateValues = DiskIOMeter_updateValues,\n   .defaultMode = TEXT_METERMODE,\n   .supportedModes = METERMODE_DEFAULT_SUPPORTED,\n   .isMultiColumn = true,\n   .name = \"DiskIO\",\n   .uiName = \"Disk IO\",\n   .description = \"Disk IO rate & time combined display\",\n   .caption = \"Dsk: \",\n   .draw = DiskIOMeter_draw,\n   .init = DiskIOMeter_init,\n   .updateMode = DiskIOMeter_updateMode,\n   .done = DiskIOMeter_done\n};\n"
  },
  {
    "path": "DiskIOMeter.h",
    "content": "#ifndef HEADER_DiskIOMeter\n#define HEADER_DiskIOMeter\n/*\nhtop - DiskIOMeter.h\n(C) 2020 htop dev team\nReleased under the GNU GPLv2+, see the COPYING file\nin the source distribution for its full text.\n*/\n\n#include <stdint.h>\n\n#include \"Meter.h\"\n\n\ntypedef struct DiskIOData_ {\n   uint64_t totalBytesRead;\n   uint64_t totalBytesWritten;\n   uint64_t totalMsTimeSpend;\n   uint64_t numDisks;\n} DiskIOData;\n\nextern const MeterClass DiskIORateMeter_class;\n\nextern const MeterClass DiskIOTimeMeter_class;\n\nextern const MeterClass DiskIOMeter_class;\n\n#endif /* HEADER_DiskIOMeter */\n"
  },
  {
    "path": "DisplayOptionsPanel.c",
    "content": "/*\nhtop - DisplayOptionsPanel.c\n(C) 2004-2011 Hisham H. Muhammad\nReleased under the GNU GPLv2+, see the COPYING file\nin the source distribution for its full text.\n*/\n\n#include \"config.h\" // IWYU pragma: keep\n\n#include \"DisplayOptionsPanel.h\"\n\n#include <stdbool.h>\n#include <stdlib.h>\n#include <string.h>\n\n#include \"CRT.h\"\n#include \"FunctionBar.h\"\n#include \"Header.h\"\n#include \"Object.h\"\n#include \"OptionItem.h\"\n#include \"ProvideCurses.h\"\n#include \"ScreensPanel.h\"\n\n\nstatic const char* const DisplayOptionsFunctions[] =       {\"      \", \"      \", \"      \", \"      \", \"      \", \"      \", \"      \", \"      \", \"      \", \"Done  \", NULL};\n\nstatic const char* const DisplayOptionsDecIncFunctions[] = {\"      \", \"      \", \"      \", \"      \", \"      \", \"      \", \"Dec   \", \"Inc   \", \"      \", \"Done  \", NULL};\n\nstatic void DisplayOptionsPanel_delete(Object* object) {\n   DisplayOptionsPanel* this = (DisplayOptionsPanel*) object;\n   FunctionBar_delete(this->decIncBar);\n   Panel_done(&this->super);\n   free(this);\n}\n\nstatic HandlerResult DisplayOptionsPanel_eventHandler(Panel* super, int ch) {\n   DisplayOptionsPanel* this = (DisplayOptionsPanel*) super;\n\n   HandlerResult result = IGNORED;\n   OptionItem* selected = (OptionItem*) Panel_getSelected(super);\n\n   if (!selected) {\n      return result;\n   }\n\n   switch (ch) {\n      case '\\n':\n      case '\\r':\n      case KEY_ENTER:\n      case ' ':\n         switch (OptionItem_kind(selected)) {\n            case OPTION_ITEM_TEXT:\n               break;\n            case OPTION_ITEM_CHECK:\n               CheckItem_toggle((CheckItem*)selected);\n               result = HANDLED;\n               break;\n            case OPTION_ITEM_NUMBER:\n               NumberItem_toggle((NumberItem*)selected);\n               result = HANDLED;\n               break;\n         }\n         break;\n      case '-':\n      case KEY_F(7):\n      case KEY_RIGHTCLICK:\n         if (OptionItem_kind(selected) == OPTION_ITEM_NUMBER) {\n            NumberItem_decrease((NumberItem*)selected);\n            result = HANDLED;\n         } else if (OptionItem_kind(selected) == OPTION_ITEM_CHECK) {\n            CheckItem_set((CheckItem*)selected, false);\n            result = HANDLED;\n         }\n         break;\n      case '+':\n      case KEY_F(8):\n         if (OptionItem_kind(selected) == OPTION_ITEM_NUMBER) {\n            NumberItem_increase((NumberItem*)selected);\n            result = HANDLED;\n         } else if (OptionItem_kind(selected) == OPTION_ITEM_CHECK) {\n            CheckItem_set((CheckItem*)selected, true);\n            result = HANDLED;\n         }\n         break;\n      case KEY_RECLICK:\n         if (OptionItem_kind(selected) == OPTION_ITEM_NUMBER) {\n            NumberItem_increase((NumberItem*)selected);\n            result = HANDLED;\n         } else if (OptionItem_kind(selected) == OPTION_ITEM_CHECK) {\n            CheckItem_toggle((CheckItem*)selected);\n            result = HANDLED;\n         }\n         break;\n      case KEY_UP:\n      case KEY_DOWN:\n      case KEY_NPAGE:\n      case KEY_PPAGE:\n      case KEY_HOME:\n      case KEY_END:\n         {\n            OptionItem* previous = selected;\n            Panel_onKey(super, ch);\n            selected = (OptionItem*) Panel_getSelected(super);\n            if (previous != selected) {\n               result = HANDLED;\n            }\n         }\n         /* fallthrough */\n      case EVENT_SET_SELECTED:\n         if (selected && OptionItem_kind(selected) == OPTION_ITEM_NUMBER) {\n            super->currentBar = this->decIncBar;\n         } else {\n            Panel_setDefaultBar(super);\n         }\n         break;\n   }\n\n   if (result == HANDLED) {\n      this->settings->changed = true;\n      this->settings->lastUpdate++;\n      CRT_updateDelay();\n      Header* header = this->scr->header;\n      Header_calculateHeight(header);\n      Header_reinit(header);\n      Header_updateData(header);\n      Header_draw(header);\n      ScreenManager_resize(this->scr);\n   }\n\n   return result;\n}\n\nconst PanelClass DisplayOptionsPanel_class = {\n   .super = {\n      .extends = Class(Panel),\n      .delete = DisplayOptionsPanel_delete\n   },\n   .eventHandler = DisplayOptionsPanel_eventHandler\n};\n\nDisplayOptionsPanel* DisplayOptionsPanel_new(Settings* settings, ScreenManager* scr) {\n   DisplayOptionsPanel* this = AllocThis(DisplayOptionsPanel);\n   Panel* super = &this->super;\n\n   FunctionBar* fuBar = FunctionBar_new(DisplayOptionsFunctions, NULL, NULL);\n   Panel_init(super, 1, 1, 1, 1, Class(OptionItem), true, fuBar);\n\n   this->decIncBar = FunctionBar_new(DisplayOptionsDecIncFunctions, NULL, NULL);\n   this->settings = settings;\n   this->scr = scr;\n\n   Panel_setHeader(super, \"Display options\");\n\n   #define TABMSG \"For current screen tab: \\0\"\n   char tabheader[sizeof(TABMSG) + SCREEN_NAME_LEN + 1] = TABMSG;\n   strncat(tabheader, settings->ss->heading, SCREEN_NAME_LEN);\n   Panel_add(super, (Object*) TextItem_new(tabheader));\n   #undef TABMSG\n\n   Panel_add(super, (Object*) CheckItem_newByRef(\"Tree view\", &(settings->ss->treeView)));\n   Panel_add(super, (Object*) CheckItem_newByRef(\"- Tree view is always sorted by PID (htop 2 behavior)\", &(settings->ss->treeViewAlwaysByPID)));\n   Panel_add(super, (Object*) CheckItem_newByRef(\"- Tree view is collapsed by default\", &(settings->ss->allBranchesCollapsed)));\n   Panel_add(super, (Object*) TextItem_new(\"Global options:\"));\n   Panel_add(super, (Object*) CheckItem_newByRef(\"Show tabs for screens\", &(settings->screenTabs)));\n   Panel_add(super, (Object*) CheckItem_newByRef(\"Shadow other users' processes\", &(settings->shadowOtherUsers)));\n   Panel_add(super, (Object*) CheckItem_newByRef(\"Hide kernel threads\", &(settings->hideKernelThreads)));\n   Panel_add(super, (Object*) CheckItem_newByRef(\"Hide userland process threads\", &(settings->hideUserlandThreads)));\n   Panel_add(super, (Object*) CheckItem_newByRef(\"Hide processes running in containers\", &(settings->hideRunningInContainer)));\n   Panel_add(super, (Object*) CheckItem_newByRef(\"Display threads in a different color\", &(settings->highlightThreads)));\n   Panel_add(super, (Object*) CheckItem_newByRef(\"Show custom thread names\", &(settings->showThreadNames)));\n   Panel_add(super, (Object*) CheckItem_newByRef(\"Show program path\", &(settings->showProgramPath)));\n   Panel_add(super, (Object*) CheckItem_newByRef(\"Highlight program \\\"basename\\\"\", &(settings->highlightBaseName)));\n   Panel_add(super, (Object*) CheckItem_newByRef(\"Highlight out-dated/removed programs (red) / libraries (yellow)\", &(settings->highlightDeletedExe)));\n   Panel_add(super, (Object*) CheckItem_newByRef(\"Shadow distribution path prefixes\", &(settings->shadowDistPathPrefix)));\n   Panel_add(super, (Object*) CheckItem_newByRef(\"Merge exe, comm and cmdline in Command\", &(settings->showMergedCommand)));\n   Panel_add(super, (Object*) CheckItem_newByRef(\"- Try to find comm in cmdline (when Command is merged)\", &(settings->findCommInCmdline)));\n   Panel_add(super, (Object*) CheckItem_newByRef(\"- Try to strip exe from cmdline (when Command is merged)\", &(settings->stripExeFromCmdline)));\n   Panel_add(super, (Object*) CheckItem_newByRef(\"Highlight large numbers in memory counters\", &(settings->highlightMegabytes)));\n   Panel_add(super, (Object*) CheckItem_newByRef(\"Leave a margin around header\", &(settings->headerMargin)));\n   Panel_add(super, (Object*) CheckItem_newByRef(\"Detailed CPU time (System/IO-Wait/Hard-IRQ/Soft-IRQ/Steal/Guest)\", &(settings->detailedCPUTime)));\n   Panel_add(super, (Object*) CheckItem_newByRef(\"Count CPUs from 1 instead of 0\", &(settings->countCPUsFromOne)));\n   Panel_add(super, (Object*) CheckItem_newByRef(\"Update process names on every refresh\", &(settings->updateProcessNames)));\n   Panel_add(super, (Object*) CheckItem_newByRef(\"Add guest time in CPU meter percentage\", &(settings->accountGuestInCPUMeter)));\n   Panel_add(super, (Object*) CheckItem_newByRef(\"Also show CPU percentage numerically\", &(settings->showCPUUsage)));\n   Panel_add(super, (Object*) CheckItem_newByRef(\"Also show CPU frequency\", &(settings->showCPUFrequency)));\n   #ifdef BUILD_WITH_CPU_TEMP\n   Panel_add(super, (Object*) CheckItem_newByRef(\n   #if defined(HTOP_LINUX)\n                                                 \"Also show CPU temperature (requires libsensors)\",\n   #elif defined(HTOP_FREEBSD)\n                                                 \"Also show CPU temperature\",\n   #else\n   #error Unknown temperature implementation!\n   #endif\n                                                 &(settings->showCPUTemperature)));\n   Panel_add(super, (Object*) CheckItem_newByRef(\"- Show temperature in degree Fahrenheit instead of Celsius\", &(settings->degreeFahrenheit)));\n   #endif\n   Panel_add(super, (Object*) CheckItem_newByRef(\"Show cached memory in graph and bar modes\", &(settings->showCachedMemory)));\n   #ifdef HAVE_GETMOUSE\n   Panel_add(super, (Object*) CheckItem_newByRef(\"Enable the mouse\", &(settings->enableMouse)));\n   #endif\n   Panel_add(super, (Object*) NumberItem_newByRef(\"Update interval (in seconds)\", &(settings->delay), -1, 1, 255));\n   Panel_add(super, (Object*) CheckItem_newByRef(\"Highlight new and old processes\", &(settings->highlightChanges)));\n   Panel_add(super, (Object*) NumberItem_newByRef(\"- Highlight time (in seconds)\", &(settings->highlightDelaySecs), 0, 1, 24 * 60 * 60));\n   Panel_add(super, (Object*) NumberItem_newByRef(\"Hide main function bar (0 - off, 1 - on ESC until next input, 2 - permanently)\", &(settings->hideFunctionBar), 0, 0, 2));\n   #ifdef HAVE_LIBHWLOC\n   Panel_add(super, (Object*) CheckItem_newByRef(\"Show topology when selecting affinity by default\", &(settings->topologyAffinity)));\n   #endif\n\n   return this;\n}\n"
  },
  {
    "path": "DisplayOptionsPanel.h",
    "content": "#ifndef HEADER_DisplayOptionsPanel\n#define HEADER_DisplayOptionsPanel\n/*\nhtop - DisplayOptionsPanel.h\n(C) 2004-2011 Hisham H. Muhammad\nReleased under the GNU GPLv2+, see the COPYING file\nin the source distribution for its full text.\n*/\n\n#include \"FunctionBar.h\"\n#include \"Panel.h\"\n#include \"ScreenManager.h\"\n#include \"Settings.h\"\n\n\ntypedef struct DisplayOptionsPanel_ {\n   Panel super;\n\n   Settings* settings;\n   ScreenManager* scr;\n   FunctionBar* decIncBar;\n} DisplayOptionsPanel;\n\nextern const PanelClass DisplayOptionsPanel_class;\n\nDisplayOptionsPanel* DisplayOptionsPanel_new(Settings* settings, ScreenManager* scr);\n\n#endif\n"
  },
  {
    "path": "DynamicColumn.c",
    "content": "/*\nhtop - DynamicColumn.c\n(C) 2021 Sohaib Mohammed\n(C) 2021 htop dev team\n(C) 2021 Red Hat, Inc.  All Rights Reserved.\nReleased under the GNU GPLv2+, see the COPYING file\nin the source distribution for its full text.\n*/\n\n#include \"config.h\" // IWYU pragma: keep\n\n#include \"DynamicColumn.h\"\n\n#include <stddef.h>\n#include <stdlib.h>\n\n#include \"Platform.h\"\n#include \"RichString.h\"\n#include \"XUtils.h\"\n\n\nHashtable* DynamicColumns_new(void) {\n   Hashtable* dynamics = Platform_dynamicColumns();\n   if (!dynamics)\n      dynamics = Hashtable_new(0, true);\n   return dynamics;\n}\n\nvoid DynamicColumns_delete(Hashtable* dynamics) {\n   if (dynamics) {\n      Platform_dynamicColumnsDone(dynamics);\n      Hashtable_delete(dynamics);\n   }\n}\n\nconst char* DynamicColumn_name(unsigned int key) {\n   return Platform_dynamicColumnName(key);\n}\n\nvoid DynamicColumn_done(DynamicColumn* this) {\n   free(this->heading);\n   free(this->caption);\n   free(this->description);\n}\n\ntypedef struct {\n   const char* name;\n   const DynamicColumn* data;\n   unsigned int key;\n} DynamicIterator;\n\nstatic void DynamicColumn_compare(ht_key_t key, void* value, void* data) {\n   const DynamicColumn* column = (const DynamicColumn*)value;\n   DynamicIterator* iter = (DynamicIterator*)data;\n   if (String_eq(iter->name, column->name)) {\n      iter->data = column;\n      iter->key = key;\n   }\n}\n\nconst DynamicColumn* DynamicColumn_search(Hashtable* dynamics, const char* name, unsigned int* key) {\n   DynamicIterator iter = { .key = 0, .data = NULL, .name = name };\n   if (dynamics)\n      Hashtable_foreach(dynamics, DynamicColumn_compare, &iter);\n   if (key)\n      *key = iter.key;\n   return iter.data;\n}\n\nconst DynamicColumn* DynamicColumn_lookup(Hashtable* dynamics, unsigned int key) {\n   return (const DynamicColumn*) Hashtable_get(dynamics, key);\n}\n\nbool DynamicColumn_writeField(const Process* proc, RichString* str, unsigned int key) {\n   return Platform_dynamicColumnWriteField(proc, str, key);\n}\n"
  },
  {
    "path": "DynamicColumn.h",
    "content": "#ifndef HEADER_DynamicColumn\n#define HEADER_DynamicColumn\n/*\nhtop - DynamicColumn.h\n(C) 2023 htop dev team\nReleased under the GNU GPLv2+, see the COPYING file\nin the source distribution for its full text.\n*/\n\n#include <stdbool.h>\n\n#include \"Hashtable.h\"\n#include \"Process.h\"\n#include \"RichString.h\"\n#include \"Table.h\"\n\n\n#define DYNAMIC_MAX_COLUMN_WIDTH 64\n#define DYNAMIC_DEFAULT_COLUMN_WIDTH -5\n\ntypedef struct DynamicColumn_ {\n   char name[32];           /* unique, internal-only name */\n   char* heading;           /* displayed in main screen */\n   char* caption;           /* displayed in setup menu (short name) */\n   char* description;       /* displayed in setup menu (detail) */\n   int width;               /* display width +/- for value alignment */\n   bool enabled;            /* false == ignore this column (until enabled) */\n   Table* table;            /* pointer to DynamicScreen or ProcessTable */\n} DynamicColumn;\n\nHashtable* DynamicColumns_new(void);\n\nvoid DynamicColumns_delete(Hashtable* dynamics);\n\nconst char* DynamicColumn_name(unsigned int key);\n\nvoid DynamicColumn_done(DynamicColumn* this);\n\nconst DynamicColumn* DynamicColumn_lookup(Hashtable* dynamics, unsigned int key);\n\nconst DynamicColumn* DynamicColumn_search(Hashtable* dynamics, const char* name, unsigned int* key);\n\nbool DynamicColumn_writeField(const Process* proc, RichString* str, unsigned int key);\n\n#endif\n"
  },
  {
    "path": "DynamicMeter.c",
    "content": "/*\nhtop - DynamicMeter.c\n(C) 2021 htop dev team\n(C) 2021 Red Hat, Inc.  All Rights Reserved.\nReleased under the GNU GPLv2+, see the COPYING file\nin the source distribution for its full text.\n*/\n\n#include \"config.h\" // IWYU pragma: keep\n\n#include \"DynamicMeter.h\"\n\n#include <assert.h>\n#include <stdbool.h>\n#include <stddef.h>\n#include <string.h>\n\n#include \"CRT.h\"\n#include \"Machine.h\"\n#include \"Object.h\"\n#include \"Platform.h\"\n#include \"RichString.h\"\n#include \"Settings.h\"\n#include \"XUtils.h\"\n\n\nstatic const int DynamicMeter_attributes[] = {\n   DYNAMIC_GRAY,\n   DYNAMIC_DARKGRAY,\n   DYNAMIC_RED,\n   DYNAMIC_GREEN,\n   DYNAMIC_BLUE,\n   DYNAMIC_CYAN,\n   DYNAMIC_MAGENTA,\n   DYNAMIC_YELLOW,\n   DYNAMIC_WHITE\n};\n\nHashtable* DynamicMeters_new(void) {\n   return Platform_dynamicMeters();\n}\n\nvoid DynamicMeters_delete(Hashtable* dynamics) {\n   if (dynamics) {\n      Platform_dynamicMetersDone(dynamics);\n      Hashtable_delete(dynamics);\n   }\n}\n\ntypedef struct {\n   const char* name;\n   ht_key_t key;\n   bool found;\n} DynamicIterator;\n\nstatic void DynamicMeter_compare(ht_key_t key, void* value, void* data) {\n   const DynamicMeter* meter = (const DynamicMeter*)value;\n   DynamicIterator* iter = (DynamicIterator*)data;\n   if (String_eq(iter->name, meter->name)) {\n      iter->found = true;\n      iter->key = key;\n   }\n}\n\nbool DynamicMeter_search(Hashtable* dynamics, const char* name, ht_key_t* key) {\n   DynamicIterator iter = { .key = 0, .name = name, .found = false };\n   if (dynamics)\n      Hashtable_foreach(dynamics, DynamicMeter_compare, &iter);\n   if (key)\n      *key = iter.key;\n   return iter.found;\n}\n\nconst char* DynamicMeter_lookup(Hashtable* dynamics, ht_key_t key) {\n   const DynamicMeter* meter = Hashtable_get(dynamics, key);\n   return meter ? meter->name : NULL;\n}\n\nstatic void DynamicMeter_init(Meter* meter) {\n   Platform_dynamicMeterInit(meter);\n}\n\nstatic void DynamicMeter_updateValues(Meter* meter) {\n   Platform_dynamicMeterUpdateValues(meter);\n}\n\nstatic void DynamicMeter_display(const Object* cast, RichString* out) {\n   const Meter* meter = (const Meter*)cast;\n   Platform_dynamicMeterDisplay(meter, out);\n}\n\nstatic const char* DynamicMeter_getCaption(const Meter* this) {\n   const Settings* settings = this->host->settings;\n   const DynamicMeter* meter = Hashtable_get(settings->dynamicMeters, this->param);\n   if (meter)\n      return meter->caption ? meter->caption : meter->name;\n   return this->caption;\n}\n\nstatic void DynamicMeter_getUiName(const Meter* this, char* name, size_t length) {\n   assert(length > 0);\n\n   const Settings* settings = this->host->settings;\n   const DynamicMeter* meter = Hashtable_get(settings->dynamicMeters, this->param);\n   if (meter) {\n      const char* uiName = meter->caption;\n      if (uiName) {\n         size_t uiNameLen = strlen(uiName);\n         if (uiNameLen > 2 && uiName[uiNameLen - 2] == ':')\n            uiNameLen -= 2;\n\n         String_safeStrncpy(name, uiName, MINIMUM(length, uiNameLen + 1));\n      } else {\n         String_safeStrncpy(name, meter->name, length);\n      }\n   }\n}\n\nconst MeterClass DynamicMeter_class = {\n   .super = {\n      .extends = Class(Meter),\n      .delete = Meter_delete,\n      .display = DynamicMeter_display\n   },\n   .init = DynamicMeter_init,\n   .updateValues = DynamicMeter_updateValues,\n   .getCaption = DynamicMeter_getCaption,\n   .getUiName = DynamicMeter_getUiName,\n   .defaultMode = TEXT_METERMODE,\n   .supportedModes = METERMODE_DEFAULT_SUPPORTED,\n   .maxItems = 0,\n   .total = 100.0,\n   .attributes = DynamicMeter_attributes,\n   .name = \"Dynamic\",\n   .uiName = \"Dynamic\",\n   .caption = \"\",\n};\n"
  },
  {
    "path": "DynamicMeter.h",
    "content": "#ifndef HEADER_DynamicMeter\n#define HEADER_DynamicMeter\n/*\nhtop - DynamicMeter.h\n(C) 2021 htop dev team\n(C) 2021 Red Hat, Inc.  All Rights Reserved.\nReleased under the GNU GPLv2+, see the COPYING file\nin the source distribution for its full text.\n*/\n\n#include <stdbool.h>\n\n#include \"Hashtable.h\"\n#include \"Meter.h\"\n\n\ntypedef struct DynamicMeter_ {\n   char name[32];  /* unique name, cannot contain spaces */\n   char* caption;\n   char* description;\n   unsigned int type;\n   double maximum;\n} DynamicMeter;\n\nHashtable* DynamicMeters_new(void);\n\nvoid DynamicMeters_delete(Hashtable* dynamics);\n\nconst char* DynamicMeter_lookup(Hashtable* dynamics, ht_key_t key);\n\nbool DynamicMeter_search(Hashtable* dynamics, const char* name, ht_key_t* key);\n\nextern const MeterClass DynamicMeter_class;\n\n#endif\n"
  },
  {
    "path": "DynamicScreen.c",
    "content": "/*\nhtop - DynamicScreen.c\n(C) 2022 Sohaib Mohammed\n(C) 2022-2023 htop dev team\nReleased under the GNU GPLv2+, see the COPYING file\nin the source distribution for its full text.\n*/\n\n#include \"config.h\" // IWYU pragma: keep\n\n#include \"DynamicScreen.h\"\n\n#include <stdbool.h>\n#include <stddef.h>\n#include <stdlib.h>\n\n#include \"Hashtable.h\"\n#include \"Platform.h\"\n#include \"XUtils.h\"\n\n\nHashtable* DynamicScreens_new(void) {\n   return Platform_dynamicScreens();\n}\n\nvoid DynamicScreens_delete(Hashtable* screens) {\n   if (screens) {\n      Platform_dynamicScreensDone(screens);\n      Hashtable_delete(screens);\n   }\n}\n\nvoid DynamicScreen_done(DynamicScreen* this) {\n   free(this->caption);\n   free(this->fields);\n   free(this->heading);\n   free(this->sortKey);\n   free(this->columnKeys);\n}\n\ntypedef struct {\n   const char* name;\n   ht_key_t key;\n   bool found;\n} DynamicIterator;\n\nstatic void DynamicScreen_compare(ht_key_t key, void* value, void* data) {\n   const DynamicScreen* screen = (const DynamicScreen*)value;\n   DynamicIterator* iter = (DynamicIterator*)data;\n   if (String_eq(iter->name, screen->name)) {\n      iter->found = true;\n      iter->key = key;\n   }\n}\n\nbool DynamicScreen_search(Hashtable* screens, const char* name, ht_key_t* key) {\n   DynamicIterator iter = { .key = 0, .name = name, .found = false };\n   if (screens)\n      Hashtable_foreach(screens, DynamicScreen_compare, &iter);\n   if (key)\n      *key = iter.key;\n   return iter.found;\n}\n\nconst char* DynamicScreen_lookup(Hashtable* screens, ht_key_t key) {\n   const DynamicScreen* screen = Hashtable_get(screens, key);\n   return screen ? screen->name : NULL;\n}\n"
  },
  {
    "path": "DynamicScreen.h",
    "content": "#ifndef HEADER_DynamicScreen\n#define HEADER_DynamicScreen\n/*\nhtop - DynamicColumn.h\n(C) 2023 htop dev team\nReleased under the GNU GPLv2+, see the COPYING file\nin the source distribution for its full text.\n*/\n\n#include <stdbool.h>\n\n#include \"Hashtable.h\"\n#include \"Panel.h\"\n\n\ntypedef struct DynamicScreen_ {\n   char name[32];  /* unique name cannot contain any spaces */\n   char* heading;  /* user-settable more readable name */\n   char* caption;  /* explanatory text for screen */\n   char* fields;\n   char* sortKey;\n   char* columnKeys;\n   int direction;\n} DynamicScreen;\n\nHashtable* DynamicScreens_new(void);\n\nvoid DynamicScreens_delete(Hashtable* screens);\n\nvoid DynamicScreen_done(DynamicScreen* this);\n\nvoid DynamicScreens_addAvailableColumns(Panel* availableColumns, char* screen);\n\nconst char* DynamicScreen_lookup(Hashtable* screens, unsigned int key);\n\nbool DynamicScreen_search(Hashtable* screens, const char* name, unsigned int* key);\n\n#endif\n"
  },
  {
    "path": "EnvScreen.c",
    "content": "/*\nhtop - EnvScreen.c\n(C) 2015,2016 Michael Klein\n(C) 2016,2017 Hisham H. Muhammad\n(C) 2020,2021 htop dev team\nReleased under the GNU GPLv2+, see the COPYING file\nin the source distribution for its full text.\n*/\n\n#include \"config.h\" // IWYU pragma: keep\n\n#include \"EnvScreen.h\"\n\n#include <stdlib.h>\n#include <string.h>\n\n#include \"Macros.h\"\n#include \"Panel.h\"\n#include \"Platform.h\"\n#include \"ProvideCurses.h\"\n#include \"Vector.h\"\n#include \"XUtils.h\"\n\n\nEnvScreen* EnvScreen_new(Process* process) {\n   EnvScreen* this = xMalloc(sizeof(EnvScreen));\n   Object_setClass(this, Class(EnvScreen));\n   return (EnvScreen*) InfoScreen_init(&this->super, process, NULL, LINES - 2, \" \");\n}\n\nvoid EnvScreen_delete(Object* this) {\n   free(InfoScreen_done((InfoScreen*)this));\n}\n\nstatic void EnvScreen_draw(InfoScreen* this) {\n   InfoScreen_drawTitled(this, \"Environment of process %d - %s\", Process_getPid(this->process), Process_getCommand(this->process));\n}\n\nstatic void EnvScreen_scan(InfoScreen* this) {\n   Panel* panel = this->display;\n   int idx = MAXIMUM(Panel_getSelectedIndex(panel), 0);\n\n   Panel_prune(panel);\n\n   char* env = Platform_getProcessEnv(Process_getPid(this->process));\n   if (env) {\n      for (const char* p = env; *p; p = strrchr(p, 0) + 1)\n         InfoScreen_addLine(this, p);\n      free(env);\n   }\n   else {\n      InfoScreen_addLine(this, \"Could not read process environment.\");\n   }\n\n   Vector_insertionSort(this->lines);\n   Vector_insertionSort(panel->items);\n   Panel_setSelected(panel, idx);\n}\n\nconst InfoScreenClass EnvScreen_class = {\n   .super = {\n      .extends = Class(Object),\n      .delete = EnvScreen_delete\n   },\n   .scan = EnvScreen_scan,\n   .draw = EnvScreen_draw\n};\n"
  },
  {
    "path": "EnvScreen.h",
    "content": "#ifndef HEADER_EnvScreen\n#define HEADER_EnvScreen\n/*\nhtop - EnvScreen.h\n(C) 2015,2016 Michael Klein\n(C) 2016,2017 Hisham H. Muhammad\n(C) 2020,2021 htop dev team\nReleased under the GNU GPLv2+, see the COPYING file\nin the source distribution for its full text.\n*/\n\n#include \"InfoScreen.h\"\n#include \"Object.h\"\n#include \"Process.h\"\n\n\ntypedef struct EnvScreen_ {\n   InfoScreen super;\n} EnvScreen;\n\nextern const InfoScreenClass EnvScreen_class;\n\nEnvScreen* EnvScreen_new(Process* process);\n\nvoid EnvScreen_delete(Object* this);\n\n#endif\n"
  },
  {
    "path": "FileDescriptorMeter.c",
    "content": "/*\nhtop - FileDescriptorMeter.c\n(C) 2022 htop dev team\nReleased under the GNU GPLv2+, see the COPYING file\nin the source distribution for its full text.\n*/\n\n#include \"config.h\" // IWYU pragma: keep\n\n#include \"FileDescriptorMeter.h\"\n\n#include <math.h>\n\n#include \"CRT.h\"\n#include \"Macros.h\"\n#include \"Meter.h\"\n#include \"Object.h\"\n#include \"Platform.h\"\n#include \"RichString.h\"\n#include \"XUtils.h\"\n\n\n#define FD_EFFECTIVE_UNLIMITED(x) (!isgreaterequal((double)(1<<30), (x)))\n\nstatic const int FileDescriptorMeter_attributes[] = {\n   FILE_DESCRIPTOR_USED,\n   FILE_DESCRIPTOR_MAX\n};\n\nstatic void FileDescriptorMeter_updateValues(Meter* this) {\n   this->values[0] = 0;\n   this->values[1] = 1;\n\n   Platform_getFileDescriptors(&this->values[0], &this->values[1]);\n\n   /* only print bar for first value */\n   this->curItems = 1;\n\n   /* Use maximum value for scaling of bar mode\n    *\n    * As the plain total value can be very large compared to\n    * the actually used value, this is capped in the following way:\n    *\n    * 1. If the maximum value is below (or equal to) 1<<16, use it directly\n    * 2. If the maximum value is above, use powers of 2 starting at 1<<16 and\n    *    double it until it's larger than 16 times the used file handles\n    *    (capped at the maximum number of files)\n    * 3. If the maximum is effectively unlimited (AKA > 1<<30),\n    *    Do the same as for 2, but cap at 1<<30.\n    */\n   if (this->values[1] <= 1 << 16) {\n      this->total = this->values[1];\n   } else {\n      if (this->total < 16 * this->values[0]) {\n         for (this->total = 1 << 16; this->total < 16 * this->values[0]; this->total *= 2) {\n            if (this->total >= 1 << 30) {\n               break;\n            }\n         }\n      }\n\n      if (this->total > this->values[1]) {\n         this->total = this->values[1];\n      }\n\n      if (this->total > 1 << 30) {\n         this->total = 1 << 30;\n      }\n   }\n\n   if (!isNonnegative(this->values[0])) {\n      xSnprintf(this->txtBuffer, sizeof(this->txtBuffer), \"unknown/unknown\");\n   } else if (FD_EFFECTIVE_UNLIMITED(this->values[1])) {\n      xSnprintf(this->txtBuffer, sizeof(this->txtBuffer), \"%.0lf/unlimited\", this->values[0]);\n   } else {\n      xSnprintf(this->txtBuffer, sizeof(this->txtBuffer), \"%.0lf/%.0lf\", this->values[0], this->values[1]);\n   }\n}\n\nstatic void FileDescriptorMeter_display(const Object* cast, RichString* out) {\n   const Meter* this = (const Meter*)cast;\n   char buffer[50];\n   int len;\n\n   if (!isNonnegative(this->values[0])) {\n      RichString_appendAscii(out, CRT_colors[METER_TEXT], \"unknown\");\n      return;\n   }\n\n   RichString_appendAscii(out, CRT_colors[METER_TEXT], \"used: \");\n   len = xSnprintf(buffer, sizeof(buffer), \"%.0lf\", this->values[0]);\n   RichString_appendnAscii(out, CRT_colors[FILE_DESCRIPTOR_USED], buffer, len);\n\n   RichString_appendAscii(out, CRT_colors[METER_TEXT], \" max: \");\n   if (FD_EFFECTIVE_UNLIMITED(this->values[1])) {\n      RichString_appendAscii(out, CRT_colors[FILE_DESCRIPTOR_MAX], \"unlimited\");\n   } else {\n      len = xSnprintf(buffer, sizeof(buffer), \"%.0lf\", this->values[1]);\n      RichString_appendnAscii(out, CRT_colors[FILE_DESCRIPTOR_MAX], buffer, len);\n   }\n}\n\nconst MeterClass FileDescriptorMeter_class = {\n   .super = {\n      .extends = Class(Meter),\n      .delete = Meter_delete,\n      .display = FileDescriptorMeter_display,\n   },\n   .updateValues = FileDescriptorMeter_updateValues,\n   .defaultMode = TEXT_METERMODE,\n   .supportedModes = METERMODE_DEFAULT_SUPPORTED,\n   .maxItems = 2,\n   .isPercentChart = false,\n   .total = 65536.0,\n   .attributes = FileDescriptorMeter_attributes,\n   .name = \"FileDescriptors\",\n   .uiName = \"File Descriptors\",\n   .caption = \"FDs: \",\n   .description = \"Number of allocated/available file descriptors\"\n};\n"
  },
  {
    "path": "FileDescriptorMeter.h",
    "content": "#ifndef HEADER_FileDescriptorMeter\n#define HEADER_FileDescriptorMeter\n/*\nhtop - FileDescriptorMeter.h\n(C) 2022 htop dev team\nReleased under the GNU GPLv2+, see the COPYING file\nin the source distribution for its full text.\n*/\n\n#include \"Meter.h\"\n\n\nextern const MeterClass FileDescriptorMeter_class;\n\n#endif\n"
  },
  {
    "path": "FunctionBar.c",
    "content": "/*\nhtop - FunctionBar.c\n(C) 2004-2011 Hisham H. Muhammad\nReleased under the GNU GPLv2+, see the COPYING file\nin the source distribution for its full text.\n*/\n\n#include \"config.h\" // IWYU pragma: keep\n\n#include \"FunctionBar.h\"\n\n#include <assert.h>\n#include <stdlib.h>\n#include <string.h>\n\n#include \"CRT.h\"\n#include \"Macros.h\"\n#include \"ProvideCurses.h\"\n#include \"XUtils.h\"\n\n\n#define FUNCTIONBAR_MAXEVENTS 11 /* sufficient for all cases, includes NULL */\n\nstatic const char* const FunctionBar_FKeys[] = {\"F1\", \"F2\", \"F3\", \"F4\", \"F5\", \"F6\", \"F7\", \"F8\", \"F9\", \"F10\", NULL};\n\nstatic const char* const FunctionBar_FLabels[] = {\"      \", \"      \", \"      \", \"      \", \"      \", \"      \", \"      \", \"      \", \"      \", \"      \", NULL};\n\nstatic int FunctionBar_FEvents[] = {KEY_F(1), KEY_F(2), KEY_F(3), KEY_F(4), KEY_F(5), KEY_F(6), KEY_F(7), KEY_F(8), KEY_F(9), KEY_F(10)};\n\nstatic const char* const FunctionBar_EnterEscKeys[] = {\"Enter\", \"Esc\", NULL};\nstatic const int FunctionBar_EnterEscEvents[] = {13, 27};\n\nstatic int currentLen = 0;\n\nFunctionBar* FunctionBar_newEnterEsc(const char* enter, const char* esc) {\n   const char* functions[FUNCTIONBAR_MAXEVENTS] = {enter, esc, NULL};\n   return FunctionBar_new(functions, FunctionBar_EnterEscKeys, FunctionBar_EnterEscEvents);\n}\n\nFunctionBar* FunctionBar_new(const char* const* functions, const char* const* keys, const int* events) {\n   FunctionBar* this = xCalloc(1, sizeof(FunctionBar));\n   this->functions = xCalloc(FUNCTIONBAR_MAXEVENTS, sizeof(char*));\n   if (!functions) {\n      functions = FunctionBar_FLabels;\n   }\n   for (size_t i = 0; functions[i]; i++) {\n      assert(i < FUNCTIONBAR_MAXEVENTS);\n      this->functions[i] = xStrdup(functions[i]);\n   }\n   if (keys && events) {\n      this->staticData = false;\n      this->keys.keys = xCalloc(FUNCTIONBAR_MAXEVENTS, sizeof(char*));\n      this->events = xCalloc(FUNCTIONBAR_MAXEVENTS, sizeof(int));\n      for (size_t i = 0; functions[i]; i++) {\n         assert(i < FUNCTIONBAR_MAXEVENTS);\n         this->keys.keys[i] = xStrdup(keys[i]);\n         this->events[i] = events[i];\n      }\n   } else {\n      this->staticData = true;\n      this->keys.constKeys = FunctionBar_FKeys;\n      this->events = FunctionBar_FEvents;\n   }\n   return this;\n}\n\nvoid FunctionBar_delete(FunctionBar* this) {\n   for (size_t i = 0; this->functions[i]; i++) {\n      assert(i < FUNCTIONBAR_MAXEVENTS);\n      free(this->functions[i]);\n      if (!this->staticData) {\n         free(this->keys.keys[i]);\n      }\n   }\n   free(this->functions);\n   if (!this->staticData) {\n      free(this->keys.keys);\n      free(this->events);\n   }\n   free(this);\n}\n\nvoid FunctionBar_setLabel(FunctionBar* this, int event, const char* text) {\n   for (size_t i = 0; this->functions[i]; i++) {\n      assert(i < FUNCTIONBAR_MAXEVENTS);\n      if (this->events[i] == event) {\n         free(this->functions[i]);\n         this->functions[i] = xStrdup(text);\n         break;\n      }\n   }\n}\n\nint FunctionBar_draw(const FunctionBar* this) {\n   return FunctionBar_drawExtra(this, NULL, -1, false);\n}\n\nint FunctionBar_drawExtra(const FunctionBar* this, const char* buffer, int attr, bool setCursor) {\n   int cursorX = 0;\n   attrset(CRT_colors[FUNCTION_BAR]);\n   mvhline(LINES - 1, 0, ' ', COLS);\n   int x = 0;\n   for (size_t i = 0; this->functions[i]; i++) {\n      assert(i < FUNCTIONBAR_MAXEVENTS);\n      attrset(CRT_colors[FUNCTION_KEY]);\n      mvaddstr(LINES - 1, x, this->keys.constKeys[i]);\n      x += strlen(this->keys.constKeys[i]);\n      attrset(CRT_colors[FUNCTION_BAR]);\n      mvaddstr(LINES - 1, x, this->functions[i]);\n      x += strlen(this->functions[i]);\n   }\n\n   if (buffer) {\n      if (attr == -1) {\n         attrset(CRT_colors[FUNCTION_BAR]);\n      } else {\n         attrset(attr);\n      }\n      mvaddstr(LINES - 1, x, buffer);\n      x += strlen(buffer);\n      cursorX = x;\n   }\n\n   attrset(CRT_colors[RESET_COLOR]);\n\n   if (setCursor) {\n      curs_set(1);\n   } else {\n      curs_set(0);\n   }\n\n   currentLen = x;\n\n   return cursorX;\n}\n\nvoid FunctionBar_append(const char* buffer, int attr) {\n   if (attr == -1) {\n      attrset(CRT_colors[FUNCTION_BAR]);\n   } else {\n      attrset(attr);\n   }\n   mvaddstr(LINES - 1, currentLen + 1, buffer);\n   attrset(CRT_colors[RESET_COLOR]);\n\n   currentLen += strlen(buffer) + 1;\n}\n\nint FunctionBar_synthesizeEvent(const FunctionBar* this, int pos) {\n   int x = 0;\n   for (size_t i = 0; this->functions[i]; i++) {\n      assert(i < FUNCTIONBAR_MAXEVENTS);\n      x += strlen(this->keys.constKeys[i]);\n      x += strlen(this->functions[i]);\n      if (pos < x) {\n         return this->events[i];\n      }\n   }\n   return ERR;\n}\n"
  },
  {
    "path": "FunctionBar.h",
    "content": "#ifndef HEADER_FunctionBar\n#define HEADER_FunctionBar\n/*\nhtop - FunctionBar.h\n(C) 2004-2011 Hisham H. Muhammad\nReleased under the GNU GPLv2+, see the COPYING file\nin the source distribution for its full text.\n*/\n\n#include <stdbool.h>\n#include <stdint.h>\n\n\ntypedef struct FunctionBar_ {\n   char** functions;\n   union {\n      char** keys;\n      const char* const* constKeys;\n   } keys;\n   int* events;\n   bool staticData;\n} FunctionBar;\n\nFunctionBar* FunctionBar_newEnterEsc(const char* enter, const char* esc);\n\nFunctionBar* FunctionBar_new(const char* const* functions, const char* const* keys, const int* events);\n\nvoid FunctionBar_delete(FunctionBar* this);\n\nvoid FunctionBar_setLabel(FunctionBar* this, int event, const char* text);\n\nint FunctionBar_draw(const FunctionBar* this);\n\nint FunctionBar_drawExtra(const FunctionBar* this, const char* buffer, int attr, bool setCursor);\n\nvoid FunctionBar_append(const char* buffer, int attr);\n\nint FunctionBar_synthesizeEvent(const FunctionBar* this, int pos);\n\n#endif\n"
  },
  {
    "path": "GPUMeter.c",
    "content": "/*\nhtop - GPUMeter.c\n(C) 2023 htop dev team\nReleased under the GNU GPLv2+, see the COPYING file\nin the source distribution for its full text.\n*/\n\n#include \"config.h\" // IWYU pragma: keep\n\n#include \"GPUMeter.h\"\n\n#include <math.h>\n\n#include \"CRT.h\"\n#include \"Platform.h\"\n#include \"RichString.h\"\n#include \"XUtils.h\"\n\n\nstruct GPUMeterEngineData GPUMeter_engineData[4];\nstatic double totalUsage = NAN;\nstatic unsigned long long int totalGPUTimeDiff = -1ULL;\n\nstatic const int GPUMeter_attributes[] = {\n   GPU_ENGINE_1,\n   GPU_ENGINE_2,\n   GPU_ENGINE_3,\n   GPU_ENGINE_4,\n   GPU_RESIDUE,\n};\n\nstatic size_t activeMeters;\n\nbool GPUMeter_active(void) {\n   return activeMeters > 0;\n}\n\nstatic int humanTimeUnit(char* buffer, size_t size, unsigned long long int value) {\n\n   if (value < 1000)\n      return xSnprintf(buffer, size, \"%3lluns\", value);\n\n   if (value < 10000)\n      return xSnprintf(buffer, size, \"%1llu.%1lluus\", value / 1000, (value % 1000) / 100);\n\n   value /= 1000;\n\n   if (value < 1000)\n      return xSnprintf(buffer, size, \"%3lluus\", value);\n\n   if (value < 10000)\n      return xSnprintf(buffer, size, \"%1llu.%1llums\", value / 1000, (value % 1000) / 100);\n\n   value /= 1000;\n\n   if (value < 1000)\n      return xSnprintf(buffer, size, \"%3llums\", value);\n\n   if (value < 10000)\n      return xSnprintf(buffer, size, \"%1llu.%1llus\", value / 1000, (value % 1000) / 100);\n\n   value /= 1000;\n\n   if (value < 600)\n      return xSnprintf(buffer, size, \"%3llus\", value);\n\n   value /= 60;\n\n   if (value < 600)\n      return xSnprintf(buffer, size, \"%3llum\", value);\n\n   value /= 60;\n\n   if (value < 96)\n      return xSnprintf(buffer, size, \"%3lluh\", value);\n\n   value /= 24;\n\n   return xSnprintf(buffer, size, \"%3llud\", value);\n}\n\nstatic void GPUMeter_updateValues(Meter* this) {\n   assert(ARRAYSIZE(GPUMeter_engineData) <= ARRAYSIZE(GPUMeter_attributes) - 1);\n\n   Platform_setGPUValues(this, &totalUsage, &totalGPUTimeDiff);\n\n   if (!isNonnegative(totalUsage)) {\n      xSnprintf(this->txtBuffer, sizeof(this->txtBuffer), \"N/A\");\n      return;\n   }\n   xSnprintf(this->txtBuffer, sizeof(this->txtBuffer), \"%.1f%%\", totalUsage);\n}\n\nstatic void GPUMeter_display(const Object* cast, RichString* out) {\n   const Meter* this = (const Meter*)cast;\n\n   char buffer[50];\n   int written;\n\n   RichString_writeAscii(out, CRT_colors[METER_TEXT], \":\");\n   if (!isNonnegative(totalUsage)) {\n      RichString_appendAscii(out, CRT_colors[METER_VALUE], \" N/A\");\n      return;\n   }\n\n   written = xSnprintf(buffer, sizeof(buffer), \"%5.1f%%\", totalUsage);\n   RichString_appendnAscii(out, CRT_colors[METER_VALUE], buffer, written);\n   if (totalGPUTimeDiff != -1ULL) {\n      RichString_appendAscii(out, CRT_colors[METER_TEXT], \"(\");\n      written = humanTimeUnit(buffer, sizeof(buffer), totalGPUTimeDiff);\n      RichString_appendnAscii(out, CRT_colors[METER_VALUE], buffer, written);\n      RichString_appendAscii(out, CRT_colors[METER_TEXT], \")\");\n   }\n\n   for (size_t i = 0; i < ARRAYSIZE(GPUMeter_engineData); i++) {\n      if (!GPUMeter_engineData[i].key)\n         break;\n\n      RichString_appendAscii(out, CRT_colors[METER_TEXT], \" \");\n      RichString_appendAscii(out, CRT_colors[METER_TEXT], GPUMeter_engineData[i].key);\n      RichString_appendAscii(out, CRT_colors[METER_TEXT], \":\");\n      if (isNonnegative(this->values[i])) {\n         written = xSnprintf(buffer, sizeof(buffer), \"%5.1f%%\", this->values[i]);\n         RichString_appendnAscii(out, CRT_colors[METER_VALUE], buffer, written);\n      } else {\n         RichString_appendAscii(out, CRT_colors[METER_VALUE], \" N/A\");\n      }\n      if (GPUMeter_engineData[i].timeDiff != -1ULL) {\n         RichString_appendAscii(out, CRT_colors[METER_TEXT], \"(\");\n         written = humanTimeUnit(buffer, sizeof(buffer), GPUMeter_engineData[i].timeDiff);\n         RichString_appendnAscii(out, CRT_colors[METER_VALUE], buffer, written);\n         RichString_appendAscii(out, CRT_colors[METER_TEXT], \")\");\n      }\n   }\n}\n\nstatic void GPUMeter_init(Meter* this ATTR_UNUSED) {\n   activeMeters++;\n}\n\nstatic void GPUMeter_done(Meter* this ATTR_UNUSED) {\n   assert(activeMeters > 0);\n   activeMeters--;\n}\n\nconst MeterClass GPUMeter_class = {\n   .super = {\n      .extends = Class(Meter),\n      .delete = Meter_delete,\n      .display = GPUMeter_display,\n   },\n   .init = GPUMeter_init,\n   .done = GPUMeter_done,\n   .updateValues = GPUMeter_updateValues,\n   .defaultMode = BAR_METERMODE,\n   .supportedModes = METERMODE_DEFAULT_SUPPORTED,\n   .maxItems = ARRAYSIZE(GPUMeter_attributes),\n   .isPercentChart = true,\n   .total = 100.0,\n   .attributes = GPUMeter_attributes,\n   .name = \"GPU\",\n   .uiName = \"GPU usage\",\n   .caption = \"GPU\"\n};\n"
  },
  {
    "path": "GPUMeter.h",
    "content": "#ifndef HEADER_GPUMeter\n#define HEADER_GPUMeter\n/*\nhtop - GPUMeter.h\n(C) 2023 htop dev team\nReleased under the GNU GPLv2+, see the COPYING file\nin the source distribution for its full text.\n*/\n\n#include <stdbool.h>\n\n#include \"Meter.h\"\n\n\nstruct GPUMeterEngineData {\n   const char* key;  /* owned by LinuxMachine */\n   unsigned long long int timeDiff;\n   double percentage;\n};\n\nextern struct GPUMeterEngineData GPUMeter_engineData[4];\n\nextern const MeterClass GPUMeter_class;\n\nbool GPUMeter_active(void);\n\n#endif /* HEADER_GPUMeter */\n"
  },
  {
    "path": "Hashtable.c",
    "content": "/*\nhtop - Hashtable.c\n(C) 2004-2011 Hisham H. Muhammad\nReleased under the GNU GPLv2+, see the COPYING file\nin the source distribution for its full text.\n*/\n\n#include \"config.h\" // IWYU pragma: keep\n\n#include \"Hashtable.h\"\n\n#include <assert.h>\n#include <stdint.h>\n#include <stdlib.h>\n#include <string.h>\n\n#include \"CRT.h\"\n#include \"Macros.h\"\n#include \"XUtils.h\"\n\n#ifndef NDEBUG\n#include <stdio.h>\n#endif\n\n\ntypedef struct HashtableItem_ {\n   ht_key_t key;\n   size_t probe;\n   void* value;\n} HashtableItem;\n\nstruct Hashtable_ {\n   size_t size;\n   HashtableItem* buckets;\n   size_t items;\n   bool owner;\n};\n\n\n#ifndef NDEBUG\n\nstatic void Hashtable_dump(const Hashtable* this) {\n   fprintf(stderr, \"Hashtable %p: size=%zu items=%zu owner=%s\\n\",\n           (const void*)this,\n           this->size,\n           this->items,\n           this->owner ? \"yes\" : \"no\");\n\n   size_t items = 0;\n   for (size_t i = 0; i < this->size; i++) {\n      fprintf(stderr, \"  item %5zu: key = %5u probe = %2zu value = %p\\n\",\n              i,\n              this->buckets[i].key,\n              this->buckets[i].probe,\n              this->buckets[i].value);\n\n      if (this->buckets[i].value)\n         items++;\n   }\n\n   fprintf(stderr, \"Hashtable %p: items=%zu counted=%zu\\n\",\n           (const void*)this,\n           this->items,\n           items);\n}\n\nstatic bool Hashtable_isConsistent(const Hashtable* this) {\n   size_t items = 0;\n   for (size_t i = 0; i < this->size; i++) {\n      if (this->buckets[i].value)\n         items++;\n   }\n   bool res = items == this->items;\n   if (!res)\n      Hashtable_dump(this);\n   return res;\n}\n\nsize_t Hashtable_count(const Hashtable* this) {\n   size_t items = 0;\n   for (size_t i = 0; i < this->size; i++) {\n      if (this->buckets[i].value)\n         items++;\n   }\n   assert(items == this->items);\n   return items;\n}\n\n#endif /* NDEBUG */\n\n/* https://oeis.org/A014234 */\nstatic const uint64_t OEISprimes[] = {\n   7, 13, 31, 61, 127, 251, 509, 1021, 2039, 4093, 8191,\n   16381, 32749, 65521,\n#if SIZE_MAX > UINT16_MAX\n   131071, 262139, 524287, 1048573,\n   2097143, 4194301, 8388593, 16777213, 33554393,\n   67108859, 134217689, 268435399, 536870909, 1073741789,\n   2147483647, 4294967291,\n#endif\n#if SIZE_MAX > UINT32_MAX\n   8589934583, 17179869143, 34359738337, 68719476731, 137438953447,\n#endif\n};\n\nstatic size_t nextPrime(size_t n) {\n   /* on 32-bit make sure we do not return primes not fitting in size_t */\n   for (size_t i = 0; i < ARRAYSIZE(OEISprimes); i++) {\n      if (n <= OEISprimes[i]) {\n         return OEISprimes[i];\n      }\n   }\n\n   CRT_fatalError(\"Hashtable: no prime found\");\n}\n\nHashtable* Hashtable_new(size_t size, bool owner) {\n   size = size ? nextPrime(size) : 13;\n\n   Hashtable* this = xMalloc(sizeof(Hashtable));\n   *this = (Hashtable) {\n      .items = 0,\n      .size = size,\n      .buckets = xCalloc(size, sizeof(HashtableItem)),\n      .owner = owner,\n   };\n\n   assert(Hashtable_isConsistent(this));\n   return this;\n}\n\nvoid Hashtable_delete(Hashtable* this) {\n   Hashtable_clear(this);\n\n   free(this->buckets);\n   free(this);\n}\n\nvoid Hashtable_clear(Hashtable* this) {\n   assert(Hashtable_isConsistent(this));\n\n   if (this->owner)\n      for (size_t i = 0; i < this->size; i++)\n         free(this->buckets[i].value);\n\n   memset(this->buckets, 0, this->size * sizeof(HashtableItem));\n   this->items = 0;\n\n   assert(Hashtable_isConsistent(this));\n}\n\nstatic void insert(Hashtable* this, ht_key_t key, void* value) {\n   size_t index = key % this->size;\n   size_t probe = 0;\n#ifndef NDEBUG\n   size_t origIndex = index;\n#endif\n\n   for (;;) {\n      if (!this->buckets[index].value) {\n         this->items++;\n         this->buckets[index].key = key;\n         this->buckets[index].probe = probe;\n         this->buckets[index].value = value;\n         return;\n      }\n\n      if (this->buckets[index].key == key) {\n         if (this->owner && this->buckets[index].value != value)\n            free(this->buckets[index].value);\n         this->buckets[index].value = value;\n         return;\n      }\n\n      /* Robin Hood swap */\n      if (probe > this->buckets[index].probe) {\n         HashtableItem tmp = this->buckets[index];\n\n         this->buckets[index].key = key;\n         this->buckets[index].probe = probe;\n         this->buckets[index].value = value;\n\n         key = tmp.key;\n         probe = tmp.probe;\n         value = tmp.value;\n      }\n\n      index = (index + 1) % this->size;\n      probe++;\n\n      assert(index != origIndex);\n   }\n}\n\nvoid Hashtable_setSize(Hashtable* this, size_t size) {\n\n   assert(Hashtable_isConsistent(this));\n\n   if (size <= this->items)\n      return;\n\n   size_t newSize = nextPrime(size);\n   if (newSize == this->size)\n      return;\n\n   HashtableItem* oldBuckets = this->buckets;\n   size_t oldSize = this->size;\n\n   this->size = newSize;\n   this->buckets = (HashtableItem*) xCalloc(this->size, sizeof(HashtableItem));\n   this->items = 0;\n\n   /* rehash */\n   for (size_t i = 0; i < oldSize; i++) {\n      if (!oldBuckets[i].value)\n         continue;\n\n      insert(this, oldBuckets[i].key, oldBuckets[i].value);\n   }\n\n   free(oldBuckets);\n\n   assert(Hashtable_isConsistent(this));\n}\n\nvoid Hashtable_put(Hashtable* this, ht_key_t key, void* value) {\n\n   assert(Hashtable_isConsistent(this));\n   assert(this->size > 0);\n   assert(value);\n\n   /* grow on load-factor > 0.7 */\n   if (10 * this->items > 7 * this->size) {\n      if (SIZE_MAX / 2 < this->size)\n         CRT_fatalError(\"Hashtable: size overflow\");\n\n      Hashtable_setSize(this, 2 * this->size);\n   }\n\n   insert(this, key, value);\n\n   assert(Hashtable_isConsistent(this));\n   assert(Hashtable_get(this, key) != NULL);\n   assert(this->size > this->items);\n}\n\nvoid* Hashtable_remove(Hashtable* this, ht_key_t key) {\n   size_t index = key % this->size;\n   size_t probe = 0;\n#ifndef NDEBUG\n   size_t origIndex = index;\n#endif\n\n   assert(Hashtable_isConsistent(this));\n\n   void* res = NULL;\n\n   while (this->buckets[index].value) {\n      if (this->buckets[index].key == key) {\n         if (this->owner) {\n            free(this->buckets[index].value);\n         } else {\n            res = this->buckets[index].value;\n         }\n\n         size_t next = (index + 1) % this->size;\n\n         while (this->buckets[next].value && this->buckets[next].probe > 0) {\n            this->buckets[index] = this->buckets[next];\n            this->buckets[index].probe -= 1;\n\n            index = next;\n            next = (index + 1) % this->size;\n         }\n\n         /* set empty after backward shifting */\n         this->buckets[index].value = NULL;\n         this->items--;\n\n         break;\n      }\n\n      if (this->buckets[index].probe < probe)\n         break;\n\n      index = (index + 1) % this->size;\n      probe++;\n\n      assert(index != origIndex);\n   }\n\n   assert(Hashtable_isConsistent(this));\n   assert(Hashtable_get(this, key) == NULL);\n\n   /* shrink on load-factor < 0.125 */\n   if (8 * this->items < this->size)\n      Hashtable_setSize(this, this->size / 3); /* account for nextPrime rounding up */\n\n   return res;\n}\n\nvoid* Hashtable_get(Hashtable* this, ht_key_t key) {\n   size_t index = key % this->size;\n   size_t probe = 0;\n   void* res = NULL;\n#ifndef NDEBUG\n   size_t origIndex = index;\n#endif\n\n   assert(Hashtable_isConsistent(this));\n\n   while (this->buckets[index].value) {\n      if (this->buckets[index].key == key) {\n         res = this->buckets[index].value;\n         break;\n      }\n\n      if (this->buckets[index].probe < probe)\n         break;\n\n      index = (index + 1) != this->size ? (index + 1) : 0;\n      probe++;\n\n      assert(index != origIndex);\n   }\n\n   return res;\n}\n\nvoid Hashtable_foreach(Hashtable* this, Hashtable_PairFunction f, void* userData) {\n   assert(Hashtable_isConsistent(this));\n   for (size_t i = 0; i < this->size; i++) {\n      HashtableItem* walk = &this->buckets[i];\n      if (walk->value)\n         f(walk->key, walk->value, userData);\n   }\n   assert(Hashtable_isConsistent(this));\n}\n"
  },
  {
    "path": "Hashtable.h",
    "content": "#ifndef HEADER_Hashtable\n#define HEADER_Hashtable\n/*\nhtop - Hashtable.h\n(C) 2004-2011 Hisham H. Muhammad\nReleased under the GNU GPLv2+, see the COPYING file\nin the source distribution for its full text.\n*/\n\n#include <stdbool.h>\n#include <stddef.h>\n\n\ntypedef unsigned int ht_key_t;\n\ntypedef void(*Hashtable_PairFunction)(ht_key_t key, void* value, void* userdata);\n\ntypedef struct Hashtable_ Hashtable;\n\n#ifndef NDEBUG\n\nsize_t Hashtable_count(const Hashtable* this);\n\n#endif /* NDEBUG */\n\nHashtable* Hashtable_new(size_t size, bool owner);\n\nvoid Hashtable_delete(Hashtable* this);\n\nvoid Hashtable_clear(Hashtable* this);\n\nvoid Hashtable_setSize(Hashtable* this, size_t size);\n\nvoid Hashtable_put(Hashtable* this, ht_key_t key, void* value);\n\nvoid* Hashtable_remove(Hashtable* this, ht_key_t key);\n\nvoid* Hashtable_get(Hashtable* this, ht_key_t key);\n\nvoid Hashtable_foreach(Hashtable* this, Hashtable_PairFunction f, void* userData);\n\n#endif\n"
  },
  {
    "path": "Header.c",
    "content": "/*\nhtop - Header.c\n(C) 2004-2011 Hisham H. Muhammad\nReleased under the GNU GPLv2+, see the COPYING file\nin the source distribution for its full text.\n*/\n\n#include \"config.h\" // IWYU pragma: keep\n\n#include \"Header.h\"\n\n#include <assert.h>\n#include <math.h>\n#include <stdbool.h>\n#include <stdint.h>\n#include <stdio.h>\n#include <stdlib.h>\n#include <string.h>\n\n#include \"CRT.h\"\n#include \"CPUMeter.h\"\n#include \"DynamicMeter.h\"\n#include \"Macros.h\"\n#include \"Object.h\"\n#include \"Platform.h\"\n#include \"ProvideCurses.h\"\n#include \"Settings.h\"\n#include \"XUtils.h\"\n\n\nHeader* Header_new(Machine* host, HeaderLayout hLayout) {\n   Header* this = xCalloc(1, sizeof(Header));\n   this->columns = xMallocArray(HeaderLayout_getColumns(hLayout), sizeof(Vector*));\n   this->headerLayout = hLayout;\n   this->host = host;\n\n   Header_forEachColumn(this, i) {\n      this->columns[i] = Vector_new(Class(Meter), true, VECTOR_DEFAULT_SIZE);\n   }\n\n   return this;\n}\n\nvoid Header_delete(Header* this) {\n   Header_forEachColumn(this, i) {\n      Vector_delete(this->columns[i]);\n   }\n\n   free(this->columns);\n   free(this);\n}\n\nvoid Header_setLayout(Header* this, HeaderLayout hLayout) {\n   size_t oldColumns = HeaderLayout_getColumns(this->headerLayout);\n   size_t newColumns = HeaderLayout_getColumns(hLayout);\n\n   this->headerLayout = hLayout;\n\n   if (newColumns == oldColumns)\n      return;\n\n   if (newColumns > oldColumns) {\n      this->columns = xReallocArray(this->columns, newColumns, sizeof(Vector*));\n      for (size_t i = oldColumns; i < newColumns; i++)\n         this->columns[i] = Vector_new(Class(Meter), true, VECTOR_DEFAULT_SIZE);\n   } else {\n      // move meters from to-be-deleted columns into last one\n      for (size_t i = newColumns; i < oldColumns; i++) {\n         for (int j = this->columns[i]->items - 1; j >= 0; j--) {\n            Vector_add(this->columns[newColumns - 1], Vector_take(this->columns[i], j));\n         }\n         Vector_delete(this->columns[i]);\n      }\n      this->columns = xReallocArray(this->columns, newColumns, sizeof(Vector*));\n   }\n\n   Header_calculateHeight(this);\n}\n\nstatic void Header_addMeterByName(Header* this, const char* name, MeterModeId mode, size_t column) {\n   assert(column < HeaderLayout_getColumns(this->headerLayout));\n\n   Vector* meters = this->columns[column];\n\n   const char* paren = strchr(name, '(');\n   unsigned int param = 0;\n   size_t nameLen;\n   if (paren) {\n      if (sscanf(paren, \"(%10u)\", &param) != 1) { // not CPUMeter\n         char dynamic[32] = {0};\n         if (sscanf(paren, \"(%30s)\", dynamic) == 1) { // DynamicMeter\n            char* end;\n            if ((end = strrchr(dynamic, ')')) == NULL)\n               return;    // htoprc parse failure\n            *end = '\\0';\n            const Settings* settings = this->host->settings;\n            if (!DynamicMeter_search(settings->dynamicMeters, dynamic, &param))\n               return;    // name lookup failure\n         } else {\n            param = 0;\n         }\n      }\n      nameLen = paren - name;\n   } else {\n      nameLen = strlen(name);\n   }\n\n   for (const MeterClass* const* type = Platform_meterTypes; *type; type++) {\n      if (0 == strncmp(name, (*type)->name, nameLen) && (*type)->name[nameLen] == '\\0') {\n         Meter* meter = Meter_new(this->host, param, *type);\n         if (mode != 0) {\n            Meter_setMode(meter, mode);\n         }\n         Vector_add(meters, meter);\n         break;\n      }\n   }\n}\n\nvoid Header_populateFromSettings(Header* this) {\n   const Settings* settings = this->host->settings;\n   Header_setLayout(this, settings->hLayout);\n\n   Header_forEachColumn(this, col) {\n      const MeterColumnSetting* colSettings = &settings->hColumns[col];\n      Vector_prune(this->columns[col]);\n      for (size_t i = 0; i < colSettings->len; i++) {\n         Header_addMeterByName(this, colSettings->names[i], colSettings->modes[i], col);\n      }\n   }\n\n   Header_calculateHeight(this);\n}\n\nvoid Header_writeBackToSettings(const Header* this) {\n   Settings* settings = this->host->settings;\n   Settings_setHeaderLayout(settings, this->headerLayout);\n\n   Header_forEachColumn(this, col) {\n      MeterColumnSetting* colSettings = &settings->hColumns[col];\n\n      if (colSettings->names) {\n         for (size_t j = 0; j < colSettings->len; j++)\n            free(colSettings->names[j]);\n         free(colSettings->names);\n      }\n      free(colSettings->modes);\n\n      const Vector* vec = this->columns[col];\n      int len = Vector_size(vec);\n\n      colSettings->names = len ? xCalloc(len + 1, sizeof(*colSettings->names)) : NULL;\n      colSettings->modes = len ? xCalloc(len, sizeof(*colSettings->modes)) : NULL;\n      colSettings->len = len;\n\n      for (int i = 0; i < len; i++) {\n         const Meter* meter = (Meter*) Vector_get(vec, i);\n         char* name = NULL;\n         if (meter->param && As_Meter(meter) == &DynamicMeter_class) {\n            const char* dynamic = DynamicMeter_lookup(settings->dynamicMeters, meter->param);\n            xAsprintf(&name, \"%s(%s)\", As_Meter(meter)->name, dynamic);\n         } else if (meter->param && As_Meter(meter) == &CPUMeter_class) {\n            xAsprintf(&name, \"%s(%u)\", As_Meter(meter)->name, meter->param);\n         } else {\n            xAsprintf(&name, \"%s\", As_Meter(meter)->name);\n         }\n         colSettings->names[i] = name;\n         colSettings->modes[i] = meter->mode;\n      }\n   }\n}\n\nMeter* Header_addMeterByClass(Header* this, const MeterClass* type, unsigned int param, size_t column) {\n   assert(column < HeaderLayout_getColumns(this->headerLayout));\n\n   Vector* meters = this->columns[column];\n\n   Meter* meter = Meter_new(this->host, param, type);\n   Vector_add(meters, meter);\n   return meter;\n}\n\nvoid Header_reinit(Header* this) {\n   Header_forEachColumn(this, col) {\n      for (int i = 0; i < Vector_size(this->columns[col]); i++) {\n         Meter* meter = (Meter*) Vector_get(this->columns[col], i);\n         if (Meter_initFn(meter)) {\n            Meter_init(meter);\n         }\n      }\n   }\n}\n\nvoid Header_draw(const Header* this) {\n   const int height = this->height;\n   const int pad = this->pad;\n   attrset(CRT_colors[RESET_COLOR]);\n   for (int y = 0; y < height; y++) {\n      mvhline(y, 0, ' ', COLS);\n   }\n   const size_t numCols = HeaderLayout_getColumns(this->headerLayout);\n   const int width = COLS - 2 * pad - ((int)numCols - 1);\n   int x = pad;\n   float roundingLoss = 0.0F;\n\n   Header_forEachColumn(this, col) {\n      Vector* meters = this->columns[col];\n      float colWidth = (float)width * HeaderLayout_layouts[this->headerLayout].widths[col] / 100.0F;\n\n      roundingLoss += colWidth - floorf(colWidth);\n      if (roundingLoss >= 1.0F) {\n         colWidth += 1.0F;\n         roundingLoss -= 1.0F;\n      }\n\n      for (int y = (pad / 2), i = 0; i < Vector_size(meters); i++) {\n         Meter* meter = (Meter*) Vector_get(meters, i);\n\n         float actualWidth = colWidth;\n\n         /* Let meters in text mode expand to the right on empty neighbors;\n            except for multi column meters. */\n         if (meter->mode == TEXT_METERMODE && !Meter_isMultiColumn(meter)) {\n            for (int j = 1; j < meter->columnWidthCount; j++) {\n               actualWidth++; /* separator column */\n               actualWidth += (float)width * HeaderLayout_layouts[this->headerLayout].widths[col + j] / 100.0F;\n            }\n         }\n\n         assert(meter->draw);\n         meter->draw(meter, x, y, floorf(actualWidth));\n         y += meter->h;\n      }\n\n      x += floorf(colWidth);\n      x++; /* separator column */\n   }\n}\n\nvoid Header_updateData(Header* this) {\n   Header_forEachColumn(this, col) {\n      Vector* meters = this->columns[col];\n      int items = Vector_size(meters);\n      for (int i = 0; i < items; i++) {\n         Meter* meter = (Meter*) Vector_get(meters, i);\n         Meter_updateValues(meter);\n      }\n   }\n}\n\n/*\n * Calculate how many columns the current meter is allowed to span,\n * by counting how many columns to the right are empty or contain a BlankMeter.\n * Returns the number of columns to span, i.e. if the direct neighbor is occupied 1.\n */\nstatic int calcColumnWidthCount(const Header* this, const Meter* curMeter, const int pad, const size_t curColumn, const int curHeight) {\n   for (size_t i = curColumn + 1; i < HeaderLayout_getColumns(this->headerLayout); i++) {\n      const Vector* meters = this->columns[i];\n\n      int height = pad;\n      for (int j = 0; j < Vector_size(meters); j++) {\n         const Meter* meter = (const Meter*) Vector_get(meters, j);\n\n         if (height >= curHeight + curMeter->h)\n            break;\n\n         height += meter->h;\n         if (height <= curHeight)\n            continue;\n\n         if (!Object_isA((const Object*) meter, (const ObjectClass*) &BlankMeter_class))\n            return (int)(i - curColumn);\n      }\n   }\n\n   return (int)(HeaderLayout_getColumns(this->headerLayout) - curColumn);\n}\n\nint Header_calculateHeight(Header* this) {\n   const Settings* settings = this->host->settings;\n   const int pad = settings->headerMargin ? 2 : 0;\n   int maxHeight = pad;\n\n   Header_forEachColumn(this, col) {\n      const Vector* meters = this->columns[col];\n      int height = pad;\n      for (int i = 0; i < Vector_size(meters); i++) {\n         Meter* meter = (Meter*) Vector_get(meters, i);\n         meter->columnWidthCount = calcColumnWidthCount(this, meter, pad, col, height);\n         height += meter->h;\n      }\n      maxHeight = MAXIMUM(maxHeight, height);\n   }\n\n   if (maxHeight == pad) {\n      maxHeight = 0;\n      this->pad = 0;\n   } else {\n      this->pad = pad;\n   }\n\n   if (settings->screenTabs) {\n      maxHeight++;\n   }\n\n   this->height = maxHeight;\n\n   return maxHeight;\n}\n"
  },
  {
    "path": "Header.h",
    "content": "#ifndef HEADER_Header\n#define HEADER_Header\n/*\nhtop - Header.h\n(C) 2004-2011 Hisham H. Muhammad\nReleased under the GNU GPLv2+, see the COPYING file\nin the source distribution for its full text.\n*/\n\n#include <stddef.h>\n\n#include \"HeaderLayout.h\"\n#include \"Machine.h\"\n#include \"Meter.h\"\n#include \"Vector.h\"\n\n\ntypedef struct Header_ {\n   Vector** columns;\n   Machine* host;\n   HeaderLayout headerLayout;\n   int pad;\n   int height;\n} Header;\n\n#define Header_forEachColumn(this_, i_) for (size_t (i_)=0, H_fEC_numColumns_ = HeaderLayout_getColumns((this_)->headerLayout); (i_) < H_fEC_numColumns_; ++(i_))\n\nHeader* Header_new(Machine* host, HeaderLayout hLayout);\n\nvoid Header_delete(Header* this);\n\nvoid Header_setLayout(Header* this, HeaderLayout hLayout);\n\nvoid Header_populateFromSettings(Header* this);\n\nvoid Header_writeBackToSettings(const Header* this);\n\nMeter* Header_addMeterByClass(Header* this, const MeterClass* type, unsigned int param, size_t column);\n\nvoid Header_reinit(Header* this);\n\nvoid Header_draw(const Header* this);\n\nvoid Header_updateData(Header* this);\n\nint Header_calculateHeight(Header* this);\n\n#endif\n"
  },
  {
    "path": "HeaderLayout.h",
    "content": "#ifndef HEADER_HeaderLayout\n#define HEADER_HeaderLayout\n/*\nhtop - HeaderLayout.h\n(C) 2021 htop dev team\nReleased under the GNU GPLv2+, see the COPYING file\nin the source distribution for its full text.\n*/\n\n#include <assert.h>\n#include <stddef.h>\n#include <stdint.h>\n\n#include \"Macros.h\"\n#include \"XUtils.h\"\n\n\ntypedef enum HeaderLayout_ {\n   HF_INVALID = -1,\n   HF_ONE_100,\n   HF_TWO_50_50,\n   HF_TWO_33_67,\n   HF_TWO_67_33,\n   HF_THREE_33_34_33,\n   HF_THREE_25_25_50,\n   HF_THREE_25_50_25,\n   HF_THREE_50_25_25,\n   HF_THREE_40_30_30,\n   HF_THREE_30_40_30,\n   HF_THREE_30_30_40,\n   HF_THREE_40_20_40,\n   HF_FOUR_25_25_25_25,\n   LAST_HEADER_LAYOUT\n} HeaderLayout;\n\nstatic const struct {\n   uint8_t columns;\n   const uint8_t widths[4];\n   const char* name;\n   const char* description;\n} HeaderLayout_layouts[LAST_HEADER_LAYOUT] = {\n   [HF_ONE_100]          = { 1, { 100, 0,  0,  0 }, \"one_100\",          \"1 column  - full width\",      },\n   [HF_TWO_50_50]        = { 2, { 50, 50,  0,  0 }, \"two_50_50\",        \"2 columns - 50/50 (default)\", },\n   [HF_TWO_33_67]        = { 2, { 33, 67,  0,  0 }, \"two_33_67\",        \"2 columns - 33/67\",           },\n   [HF_TWO_67_33]        = { 2, { 67, 33,  0,  0 }, \"two_67_33\",        \"2 columns - 67/33\",           },\n   [HF_THREE_33_34_33]   = { 3, { 33, 34, 33,  0 }, \"three_33_34_33\",   \"3 columns - 33/34/33\",        },\n   [HF_THREE_25_25_50]   = { 3, { 25, 25, 50,  0 }, \"three_25_25_50\",   \"3 columns - 25/25/50\",        },\n   [HF_THREE_25_50_25]   = { 3, { 25, 50, 25,  0 }, \"three_25_50_25\",   \"3 columns - 25/50/25\",        },\n   [HF_THREE_50_25_25]   = { 3, { 50, 25, 25,  0 }, \"three_50_25_25\",   \"3 columns - 50/25/25\",        },\n   [HF_THREE_40_30_30]   = { 3, { 40, 30, 30,  0 }, \"three_40_30_30\",   \"3 columns - 40/30/30\",        },\n   [HF_THREE_30_40_30]   = { 3, { 30, 40, 30,  0 }, \"three_30_40_30\",   \"3 columns - 30/40/30\",        },\n   [HF_THREE_30_30_40]   = { 3, { 30, 30, 40,  0 }, \"three_30_30_40\",   \"3 columns - 30/30/40\",        },\n   [HF_THREE_40_20_40]   = { 3, { 40, 20, 40,  0 }, \"three_40_20_40\",   \"3 columns - 40/20/40\",        },\n   [HF_FOUR_25_25_25_25] = { 4, { 25, 25, 25, 25 }, \"four_25_25_25_25\", \"4 columns - 25/25/25/25\",     },\n};\n\nstatic inline size_t HeaderLayout_getColumns(HeaderLayout hLayout) {\n   /* assert the layout is initialized */\n   assert(0 <= hLayout);\n   assert(hLayout < LAST_HEADER_LAYOUT);\n   assert(HeaderLayout_layouts[hLayout].name[0]);\n   assert(HeaderLayout_layouts[hLayout].description[0]);\n   return HeaderLayout_layouts[hLayout].columns;\n}\n\nstatic inline const char* HeaderLayout_getName(HeaderLayout hLayout) {\n   /* assert the layout is initialized */\n   assert(0 <= hLayout);\n   assert(hLayout < LAST_HEADER_LAYOUT);\n   assert(HeaderLayout_layouts[hLayout].name[0]);\n   assert(HeaderLayout_layouts[hLayout].description[0]);\n   return HeaderLayout_layouts[hLayout].name;\n}\n\nstatic inline HeaderLayout HeaderLayout_fromName(const char* name) {\n   for (size_t i = 0; i < LAST_HEADER_LAYOUT; i++) {\n      if (String_eq(HeaderLayout_layouts[i].name, name))\n         return (HeaderLayout) i;\n   }\n\n   return LAST_HEADER_LAYOUT;\n}\n\n#endif /* HEADER_HeaderLayout */\n"
  },
  {
    "path": "HeaderOptionsPanel.c",
    "content": "/*\nhtop - HeaderOptionsPanel.c\n(C) 2021 htop dev team\nReleased under the GNU GPLv2+, see the COPYING file\nin the source distribution for its full text.\n*/\n\n#include \"config.h\" // IWYU pragma: keep\n\n#include \"HeaderOptionsPanel.h\"\n\n#include <assert.h>\n#include <stdbool.h>\n#include <stdlib.h>\n\n#include \"CRT.h\"\n#include \"FunctionBar.h\"\n#include \"Header.h\"\n#include \"HeaderLayout.h\"\n#include \"Object.h\"\n#include \"OptionItem.h\"\n#include \"ProvideCurses.h\"\n\n\nstatic const char* const HeaderOptionsFunctions[] = {\"      \", \"      \", \"      \", \"      \", \"      \", \"      \", \"      \", \"      \", \"      \", \"Done  \", NULL};\n\nstatic void HeaderOptionsPanel_delete(Object* object) {\n   HeaderOptionsPanel* this = (HeaderOptionsPanel*) object;\n   Panel_done(&this->super);\n   free(this);\n}\n\nstatic HandlerResult HeaderOptionsPanel_eventHandler(Panel* super, int ch) {\n   HeaderOptionsPanel* this = (HeaderOptionsPanel*) super;\n\n   HandlerResult result = IGNORED;\n\n   switch (ch) {\n      case 0x0a:\n      case 0x0d:\n      case KEY_ENTER:\n      case KEY_MOUSE:\n      case KEY_RECLICK:\n      case ' ': {\n         int mark = Panel_getSelectedIndex(super);\n         assert(mark >= 0);\n         assert(mark < LAST_HEADER_LAYOUT);\n\n         for (int i = 0; i < LAST_HEADER_LAYOUT; i++)\n            CheckItem_set((CheckItem*)Panel_get(super, i), false);\n         CheckItem_set((CheckItem*)Panel_get(super, mark), true);\n\n         Header_setLayout(this->scr->header, mark);\n         this->settings->changed = true;\n         this->settings->lastUpdate++;\n\n         ScreenManager_resize(this->scr);\n\n         result = HANDLED;\n      }\n   }\n\n   return result;\n}\n\nconst PanelClass HeaderOptionsPanel_class = {\n   .super = {\n      .extends = Class(Panel),\n      .delete = HeaderOptionsPanel_delete\n   },\n   .eventHandler = HeaderOptionsPanel_eventHandler\n};\n\nHeaderOptionsPanel* HeaderOptionsPanel_new(Settings* settings, ScreenManager* scr) {\n   HeaderOptionsPanel* this = AllocThis(HeaderOptionsPanel);\n   Panel* super = &this->super;\n\n   FunctionBar* fuBar = FunctionBar_new(HeaderOptionsFunctions, NULL, NULL);\n   Panel_init(super, 1, 1, 1, 1, Class(CheckItem), true, fuBar);\n\n   this->scr = scr;\n   this->settings = settings;\n\n   Panel_setHeader(super, \"Header Layout\");\n   for (int i = 0; i < LAST_HEADER_LAYOUT; i++) {\n      Panel_add(super, (Object*) CheckItem_newByVal(HeaderLayout_layouts[i].description, false));\n   }\n   CheckItem_set((CheckItem*)Panel_get(super, scr->header->headerLayout), true);\n   return this;\n}\n"
  },
  {
    "path": "HeaderOptionsPanel.h",
    "content": "#ifndef HEADER_HeaderOptionsPanel\n#define HEADER_HeaderOptionsPanel\n/*\nhtop - ColorsPanel.h\n(C) 2021 htop dev team\nReleased under the GNU GPLv2+, see the COPYING file\nin the source distribution for its full text.\n*/\n\n#include \"Panel.h\"\n#include \"ScreenManager.h\"\n#include \"Settings.h\"\n\n\ntypedef struct HeaderOptionsPanel_ {\n   Panel super;\n\n   ScreenManager* scr;\n   Settings* settings;\n} HeaderOptionsPanel;\n\nextern const PanelClass HeaderOptionsPanel_class;\n\nHeaderOptionsPanel* HeaderOptionsPanel_new(Settings* settings, ScreenManager* scr);\n\n#endif /* HEADER_HeaderOptionsPanel */\n"
  },
  {
    "path": "HostnameMeter.c",
    "content": "/*\nhtop - HostnameMeter.c\n(C) 2004-2011 Hisham H. Muhammad\nReleased under the GNU GPLv2+, see the COPYING file\nin the source distribution for its full text.\n*/\n\n#include \"config.h\" // IWYU pragma: keep\n\n#include \"HostnameMeter.h\"\n\n#include \"CRT.h\"\n#include \"Object.h\"\n#include \"Platform.h\"\n\n\nstatic const int HostnameMeter_attributes[] = {\n   HOSTNAME\n};\n\nstatic void HostnameMeter_updateValues(Meter* this) {\n   Platform_getHostname(this->txtBuffer, sizeof(this->txtBuffer));\n}\n\nconst MeterClass HostnameMeter_class = {\n   .super = {\n      .extends = Class(Meter),\n      .delete = Meter_delete\n   },\n   .updateValues = HostnameMeter_updateValues,\n   .defaultMode = TEXT_METERMODE,\n   .supportedModes = (1 << TEXT_METERMODE),\n   .maxItems = 0,\n   .total = 0.0,\n   .attributes = HostnameMeter_attributes,\n   .name = \"Hostname\",\n   .uiName = \"Hostname\",\n   .caption = \"Hostname: \",\n};\n"
  },
  {
    "path": "HostnameMeter.h",
    "content": "#ifndef HEADER_HostnameMeter\n#define HEADER_HostnameMeter\n/*\nhtop - HostnameMeter.h\n(C) 2004-2011 Hisham H. Muhammad\nReleased under the GNU GPLv2+, see the COPYING file\nin the source distribution for its full text.\n*/\n\n#include \"Meter.h\"\n\n\nextern const MeterClass HostnameMeter_class;\n\n#endif\n"
  },
  {
    "path": "IncSet.c",
    "content": "/*\nhtop - IncSet.c\n(C) 2005-2012 Hisham H. Muhammad\nReleased under the GNU GPLv2+, see the COPYING file\nin the source distribution for its full text.\n*/\n\n#include \"config.h\" // IWYU pragma: keep\n\n#include \"IncSet.h\"\n\n#include <ctype.h>\n#include <string.h>\n#include <stdlib.h>\n\n#include \"CRT.h\"\n#include \"ListItem.h\"\n#include \"Object.h\"\n#include \"ProvideCurses.h\"\n#include \"XUtils.h\"\n\n\nstatic void IncMode_reset(IncMode* mode) {\n   mode->index = 0;\n   mode->buffer[0] = 0;\n}\n\nvoid IncSet_reset(IncSet* this, IncType type) {\n   IncMode_reset(&this->modes[type]);\n}\n\nvoid IncSet_setFilter(IncSet* this, const char* filter) {\n   IncMode* mode = &this->modes[INC_FILTER];\n   size_t len = String_safeStrncpy(mode->buffer, filter, sizeof(mode->buffer));\n   mode->index = len;\n   this->filtering = true;\n}\n\nstatic const char* const searchFunctions[] = {\"Next  \", \"Prev   \", \"Cancel \", \" Search: \", NULL};\nstatic const char* const searchKeys[] = {\"F3\", \"S-F3\", \"Esc\", \"  \"};\nstatic const int searchEvents[] = {KEY_F(3), KEY_F(15), 27, ERR};\n\nstatic inline void IncMode_initSearch(IncMode* search) {\n   memset(search, 0, sizeof(IncMode));\n   search->bar = FunctionBar_new(searchFunctions, searchKeys, searchEvents);\n   search->isFilter = false;\n}\n\nstatic const char* const filterFunctions[] = {\"Done  \", \"Clear \", \" Filter: \", NULL};\nstatic const char* const filterKeys[] = {\"Enter\", \"Esc\", \"  \"};\nstatic const int filterEvents[] = {13, 27, ERR};\n\nstatic inline void IncMode_initFilter(IncMode* filter) {\n   memset(filter, 0, sizeof(IncMode));\n   filter->bar = FunctionBar_new(filterFunctions, filterKeys, filterEvents);\n   filter->isFilter = true;\n}\n\nstatic inline void IncMode_done(IncMode* mode) {\n   FunctionBar_delete(mode->bar);\n}\n\nIncSet* IncSet_new(FunctionBar* bar) {\n   IncSet* this = xMalloc(sizeof(IncSet));\n   IncMode_initSearch(&(this->modes[INC_SEARCH]));\n   IncMode_initFilter(&(this->modes[INC_FILTER]));\n   this->active = NULL;\n   this->defaultBar = bar;\n   this->filtering = false;\n   this->found = false;\n   return this;\n}\n\nvoid IncSet_delete(IncSet* this) {\n   IncMode_done(&(this->modes[0]));\n   IncMode_done(&(this->modes[1]));\n   free(this);\n}\n\nstatic void updateWeakPanel(const IncSet* this, Panel* panel, Vector* lines) {\n   const Object* selected = Panel_getSelected(panel);\n   Panel_prune(panel);\n   if (this->filtering) {\n      int n = 0;\n      const char* incFilter = this->modes[INC_FILTER].buffer;\n      for (int i = 0; i < Vector_size(lines); i++) {\n         ListItem* line = (ListItem*)Vector_get(lines, i);\n         if (String_contains_i(line->value, incFilter, true)) {\n            Panel_add(panel, (Object*)line);\n            if (selected == (Object*)line) {\n               Panel_setSelected(panel, n);\n            }\n\n            n++;\n         }\n      }\n   } else {\n      for (int i = 0; i < Vector_size(lines); i++) {\n         Object* line = Vector_get(lines, i);\n         Panel_add(panel, line);\n         if (selected == line) {\n            Panel_setSelected(panel, i);\n         }\n      }\n   }\n}\n\nstatic bool search(const IncSet* this, Panel* panel, IncMode_GetPanelValue getPanelValue) {\n   int size = Panel_size(panel);\n   for (int i = 0; i < size; i++) {\n      if (String_contains_i(getPanelValue(panel, i), this->active->buffer, true)) {\n         Panel_setSelected(panel, i);\n         return true;\n      }\n   }\n\n   return false;\n}\n\nvoid IncSet_activate(IncSet* this, IncType type, Panel* panel) {\n   this->active = &(this->modes[type]);\n   panel->currentBar = this->active->bar;\n   panel->cursorOn = true;\n   this->panel = panel;\n   IncSet_drawBar(this, CRT_colors[FUNCTION_BAR]);\n}\n\nstatic void IncSet_deactivate(IncSet* this, Panel* panel) {\n   this->active = NULL;\n   Panel_setDefaultBar(panel);\n   panel->cursorOn = false;\n   FunctionBar_draw(this->defaultBar);\n}\n\nstatic bool IncMode_find(const IncMode* mode, Panel* panel, IncMode_GetPanelValue getPanelValue, int step) {\n   int size = Panel_size(panel);\n   int here = Panel_getSelectedIndex(panel);\n   int i = here;\n   for (;;) {\n      i += step;\n      if (i == size) {\n         i = 0;\n      }\n      if (i == -1) {\n         i = size - 1;\n      }\n      if (i == here) {\n         return false;\n      }\n\n      if (String_contains_i(getPanelValue(panel, i), mode->buffer, true)) {\n         Panel_setSelected(panel, i);\n         return true;\n      }\n   }\n}\n\nbool IncSet_handleKey(IncSet* this, int ch, Panel* panel, IncMode_GetPanelValue getPanelValue, Vector* lines) {\n   if (ch == ERR)\n      return true;\n\n   IncMode* mode = this->active;\n   int size = Panel_size(panel);\n   bool filterChanged = false;\n   bool doSearch = true;\n   if (ch == KEY_F(3) || ch == KEY_F(15)) {\n      if (size == 0)\n         return true;\n\n      IncMode_find(mode, panel, getPanelValue, ch == KEY_F(3) ? 1 : -1);\n      doSearch = false;\n   } else if (0 < ch && ch < 255 && isprint((unsigned char)ch)) {\n      if (mode->index < INCMODE_MAX) {\n         mode->buffer[mode->index] = (char) ch;\n         mode->index++;\n         mode->buffer[mode->index] = 0;\n         if (mode->isFilter) {\n            filterChanged = true;\n            if (mode->index == 1) {\n               this->filtering = true;\n            }\n         }\n      }\n   } else if (ch == KEY_CTRL('U')) {\n      mode->index = 0;\n      mode->buffer[mode->index] = 0;\n      if (mode->isFilter) {\n         filterChanged = true;\n         this->filtering = false;\n      }\n   } else if (ch == KEY_BACKSPACE || ch == 127) {\n      if (mode->index > 0) {\n         mode->index--;\n         mode->buffer[mode->index] = 0;\n         if (mode->isFilter) {\n            filterChanged = true;\n            if (mode->index == 0) {\n               this->filtering = false;\n               IncMode_reset(mode);\n            }\n         }\n      } else {\n         doSearch = false;\n      }\n   } else if (ch == KEY_RESIZE) {\n      doSearch = (mode->index > 0);\n   } else {\n      if (mode->isFilter) {\n         filterChanged = true;\n         if (ch == 27) {\n            this->filtering = false;\n            IncMode_reset(mode);\n         }\n      } else {\n         if (ch == 27) {\n            IncMode_reset(mode);\n         }\n      }\n      IncSet_deactivate(this, panel);\n      doSearch = false;\n   }\n   if (doSearch) {\n      this->found = search(this, panel, getPanelValue);\n   }\n   if (filterChanged && lines) {\n      updateWeakPanel(this, panel, lines);\n   }\n   return filterChanged;\n}\n\nconst char* IncSet_getListItemValue(Panel* panel, int i) {\n   const ListItem* l = (const ListItem*) Panel_get(panel, i);\n   return l ? l->value : \"\";\n}\n\nvoid IncSet_drawBar(const IncSet* this, int attr) {\n   if (this->active) {\n      if (!this->active->isFilter && !this->found)\n         attr = CRT_colors[FAILED_SEARCH];\n      int cursorX = FunctionBar_drawExtra(this->active->bar, this->active->buffer, attr, true);\n      this->panel->cursorY = LINES - 1;\n      this->panel->cursorX = cursorX;\n   } else {\n      FunctionBar_draw(this->defaultBar);\n   }\n}\n\nint IncSet_synthesizeEvent(IncSet* this, int x) {\n   if (this->active) {\n      return FunctionBar_synthesizeEvent(this->active->bar, x);\n   } else {\n      return FunctionBar_synthesizeEvent(this->defaultBar, x);\n   }\n}\n"
  },
  {
    "path": "IncSet.h",
    "content": "#ifndef HEADER_IncSet\n#define HEADER_IncSet\n/*\nhtop - IncSet.h\n(C) 2005-2012 Hisham H. Muhammad\nReleased under the GNU GPLv2+, see the COPYING file\nin the source distribution for its full text.\n*/\n\n#include <stdbool.h>\n#include <stddef.h>\n\n#include \"FunctionBar.h\"\n#include \"Panel.h\"\n#include \"Vector.h\"\n\n\n#define INCMODE_MAX 128\n\ntypedef enum {\n   INC_SEARCH = 0,\n   INC_FILTER = 1\n} IncType;\n\ntypedef struct IncMode_ {\n   char buffer[INCMODE_MAX + 1];\n   FunctionBar* bar;\n   size_t index;\n   bool isFilter;\n} IncMode;\n\ntypedef struct IncSet_ {\n   IncMode modes[2];\n   IncMode* active;\n   Panel* panel;\n   FunctionBar* defaultBar;\n   bool filtering;\n   bool found;\n} IncSet;\n\nstatic inline const char* IncSet_filter(const IncSet* this) {\n   return this->filtering ? this->modes[INC_FILTER].buffer : NULL;\n}\n\nvoid IncSet_setFilter(IncSet* this, const char* filter);\n\ntypedef const char* (*IncMode_GetPanelValue)(Panel*, int);\n\nvoid IncSet_reset(IncSet* this, IncType type);\n\nIncSet* IncSet_new(FunctionBar* bar);\n\nvoid IncSet_delete(IncSet* this);\n\nbool IncSet_handleKey(IncSet* this, int ch, Panel* panel, IncMode_GetPanelValue getPanelValue, Vector* lines);\n\nconst char* IncSet_getListItemValue(Panel* panel, int i);\n\nvoid IncSet_activate(IncSet* this, IncType type, Panel* panel);\n\nvoid IncSet_drawBar(const IncSet* this, int attr);\n\nint IncSet_synthesizeEvent(IncSet* this, int x);\n\n#endif\n"
  },
  {
    "path": "InfoScreen.c",
    "content": "/*\nhtop - InfoScreen.c\n(C) 2016 Hisham H. Muhammad\n(C) 2020,2022 htop dev team\nReleased under the GNU GPLv2+, see the COPYING file\nin the source distribution for its full text.\n*/\n\n#include \"config.h\" // IWYU pragma: keep\n\n#include \"InfoScreen.h\"\n\n#include <stdarg.h>\n#include <stdio.h>\n#include <string.h>\n\n#include \"CRT.h\"\n#include \"IncSet.h\"\n#include \"ListItem.h\"\n#include \"Object.h\"\n#include \"ProvideCurses.h\"\n#include \"XUtils.h\"\n\n\nstatic const char* const InfoScreenFunctions[] = {\"Search \", \"Filter \", \"Refresh\", \"Done   \", NULL};\n\nstatic const char* const InfoScreenKeys[] = {\"F3\", \"F4\", \"F5\", \"Esc\"};\n\nstatic const int InfoScreenEvents[] = {KEY_F(3), KEY_F(4), KEY_F(5), 27};\n\nInfoScreen* InfoScreen_init(InfoScreen* this, const Process* process, FunctionBar* bar, int height, const char* panelHeader) {\n   this->process = process;\n   if (!bar) {\n      bar = FunctionBar_new(InfoScreenFunctions, InfoScreenKeys, InfoScreenEvents);\n   }\n   this->display = Panel_new(0, 1, COLS, height, Class(ListItem), false, bar);\n   this->inc = IncSet_new(bar);\n   this->lines = Vector_new(Vector_type(this->display->items), true, VECTOR_DEFAULT_SIZE);\n   Panel_setHeader(this->display, panelHeader);\n   return this;\n}\n\nInfoScreen* InfoScreen_done(InfoScreen* this) {\n   Panel_delete((Object*)this->display);\n   IncSet_delete(this->inc);\n   Vector_delete(this->lines);\n   return this;\n}\n\nvoid InfoScreen_drawTitled(InfoScreen* this, const char* fmt, ...) {\n   va_list ap;\n   va_start(ap, fmt);\n\n   char title[COLS + 1];\n   int len = vsnprintf(title, sizeof(title), fmt, ap);\n   va_end(ap);\n\n   if (len > COLS) {\n      memset(&title[COLS - 3], '.', 3);\n   }\n\n   attrset(CRT_colors[METER_TEXT]);\n   mvhline(0, 0, ' ', COLS);\n   mvaddstr(0, 0, title);\n   attrset(CRT_colors[DEFAULT_COLOR]);\n   Panel_draw(this->display, true, true, true, false);\n\n   IncSet_drawBar(this->inc, CRT_colors[FUNCTION_BAR]);\n}\n\nvoid InfoScreen_addLine(InfoScreen* this, const char* line) {\n   Vector_add(this->lines, (Object*) ListItem_new(line, 0));\n   const char* incFilter = IncSet_filter(this->inc);\n   if (!incFilter || String_contains_i(line, incFilter, true)) {\n      Panel_add(this->display, Vector_get(this->lines, Vector_size(this->lines) - 1));\n   }\n}\n\nvoid InfoScreen_appendLine(InfoScreen* this, const char* line) {\n   if (!Vector_size(this->lines)) {\n      InfoScreen_addLine(this, line);\n      return;\n   }\n\n   Object* last = Vector_get(this->lines, Vector_size(this->lines) - 1);\n   ListItem_append((ListItem*)last, line);\n   const char* incFilter = IncSet_filter(this->inc);\n   Object* displayLast = Panel_size(this->display) ? Panel_get(this->display, Panel_size(this->display) - 1) : NULL;\n   if (incFilter && displayLast != last && String_contains_i(line, incFilter, true)) {\n      Panel_add(this->display, last);\n   }\n}\n\nvoid InfoScreen_run(InfoScreen* this) {\n   Panel* panel = this->display;\n\n   if (As_InfoScreen(this)->scan)\n      InfoScreen_scan(this);\n\n   InfoScreen_draw(this);\n\n   bool looping = true;\n   while (looping) {\n\n      Panel_draw(panel, false, true, true, false);\n      IncSet_drawBar(this->inc, CRT_colors[FUNCTION_BAR]);\n      FunctionBar_setLabel(this->display->defaultBar, KEY_F(4), this->inc->filtering ? \"FILTER \" : \"Filter \");\n\n      int ch = Panel_getCh(panel);\n\n      if (ch == ERR) {\n         if (As_InfoScreen(this)->onErr) {\n            InfoScreen_onErr(this);\n            continue;\n         }\n      }\n\n#ifdef HAVE_GETMOUSE\n      if (ch == KEY_MOUSE) {\n         MEVENT mevent;\n         int ok = getmouse(&mevent);\n         if (ok == OK) {\n            if (mevent.bstate & BUTTON1_RELEASED) {\n               if (mevent.y >= panel->y && mevent.y < LINES - 1) {\n                  Panel_setSelected(panel, mevent.y - panel->y + panel->scrollV - 1);\n                  ch = 0;\n               } else if (mevent.y == LINES - 1) {\n                  ch = IncSet_synthesizeEvent(this->inc, mevent.x);\n               }\n            }\n            #if NCURSES_MOUSE_VERSION > 1\n            else if (mevent.bstate & BUTTON4_PRESSED) {\n               ch = KEY_WHEELUP;\n            } else if (mevent.bstate & BUTTON5_PRESSED) {\n               ch = KEY_WHEELDOWN;\n            }\n            #endif\n         }\n      }\n#endif\n\n      if (this->inc->active) {\n         IncSet_handleKey(this->inc, ch, panel, IncSet_getListItemValue, this->lines);\n         continue;\n      }\n\n      switch (ch) {\n         case ERR:\n            continue;\n         case KEY_F(3):\n         case '/':\n            IncSet_activate(this->inc, INC_SEARCH, panel);\n            break;\n         case KEY_F(4):\n         case '\\\\':\n            IncSet_activate(this->inc, INC_FILTER, panel);\n            break;\n         case KEY_F(5):\n            clear();\n            if (As_InfoScreen(this)->scan) {\n               Vector_prune(this->lines);\n               InfoScreen_scan(this);\n            }\n\n            InfoScreen_draw(this);\n            break;\n         case '\\014': // Ctrl+L\n            clear();\n            InfoScreen_draw(this);\n            break;\n         case 27:\n         case 'q':\n         case KEY_F(10):\n            looping = false;\n            break;\n         case KEY_RESIZE:\n            Panel_resize(panel, COLS, LINES - 2);\n            if (As_InfoScreen(this)->scan) {\n               Vector_prune(this->lines);\n               InfoScreen_scan(this);\n            }\n\n            InfoScreen_draw(this);\n            break;\n         default:\n            if (As_InfoScreen(this)->onKey && InfoScreen_onKey(this, ch)) {\n               continue;\n            }\n            Panel_onKey(panel, ch);\n      }\n   }\n}\n"
  },
  {
    "path": "InfoScreen.h",
    "content": "#ifndef HEADER_InfoScreen\n#define HEADER_InfoScreen\n/*\nhtop - InfoScreen.h\n(C) 2016 Hisham H. Muhammad\n(C) 2020,2022 htop dev team\nReleased under the GNU GPLv2+, see the COPYING file\nin the source distribution for its full text.\n*/\n\n#include <stdbool.h>\n\n#include \"FunctionBar.h\"\n#include \"IncSet.h\"\n#include \"Macros.h\"\n#include \"Object.h\"\n#include \"Panel.h\"\n#include \"Process.h\"\n#include \"Vector.h\"\n\n\ntypedef struct InfoScreen_ {\n   Object super;\n   const Process* process;\n   Panel* display;\n   IncSet* inc;\n   Vector* lines;\n} InfoScreen;\n\ntypedef void(*InfoScreen_Scan)(InfoScreen*);\ntypedef void(*InfoScreen_Draw)(InfoScreen*);\ntypedef void(*InfoScreen_OnErr)(InfoScreen*);\ntypedef bool(*InfoScreen_OnKey)(InfoScreen*, int);\n\ntypedef struct InfoScreenClass_ {\n   const ObjectClass super;\n   const InfoScreen_Scan scan;\n   const InfoScreen_Draw draw;\n   const InfoScreen_OnErr onErr;\n   const InfoScreen_OnKey onKey;\n} InfoScreenClass;\n\n#define As_InfoScreen(this_)          ((const InfoScreenClass*)(((InfoScreen*)(this_))->super.klass))\n#define InfoScreen_scan(this_)        As_InfoScreen(this_)->scan((InfoScreen*)(this_))\n#define InfoScreen_draw(this_)        As_InfoScreen(this_)->draw((InfoScreen*)(this_))\n#define InfoScreen_onErr(this_)       As_InfoScreen(this_)->onErr((InfoScreen*)(this_))\n#define InfoScreen_onKey(this_, ch_)  As_InfoScreen(this_)->onKey((InfoScreen*)(this_), ch_)\n\nInfoScreen* InfoScreen_init(InfoScreen* this, const Process* process, FunctionBar* bar, int height, const char* panelHeader);\n\nInfoScreen* InfoScreen_done(InfoScreen* this);\n\nATTR_FORMAT(printf, 2, 3)\nvoid InfoScreen_drawTitled(InfoScreen* this, const char* fmt, ...);\n\nvoid InfoScreen_addLine(InfoScreen* this, const char* line);\n\nvoid InfoScreen_appendLine(InfoScreen* this, const char* line);\n\nvoid InfoScreen_run(InfoScreen* this);\n\n#endif\n"
  },
  {
    "path": "ListItem.c",
    "content": "/*\nhtop - ListItem.c\n(C) 2004-2011 Hisham H. Muhammad\nReleased under the GNU GPLv2+, see the COPYING file\nin the source distribution for its full text.\n*/\n\n#include \"config.h\" // IWYU pragma: keep\n\n#include \"ListItem.h\"\n\n#include <assert.h>\n#include <stdlib.h>\n#include <string.h>\n\n#include \"CRT.h\"\n#include \"RichString.h\"\n#include \"XUtils.h\"\n\n\nvoid ListItem_delete(Object* cast) {\n   ListItem* this = (ListItem*)cast;\n   free(this->value);\n   free(this);\n}\n\nvoid ListItem_display(const Object* cast, RichString* out) {\n   const ListItem* const this = (const ListItem*)cast;\n   assert (this != NULL);\n\n   if (this->moving) {\n      RichString_writeWide(out, CRT_colors[DEFAULT_COLOR],\n#ifdef HAVE_LIBNCURSESW\n                           CRT_utf8 ? \"↕ \" :\n#endif\n                           \"+ \");\n   }\n   RichString_appendWide(out, CRT_colors[DEFAULT_COLOR], this->value);\n}\n\nvoid ListItem_init(ListItem* this, const char* value, int key) {\n   this->value = xStrdup(value);\n   this->key = key;\n   this->moving = false;\n}\n\nListItem* ListItem_new(const char* value, int key) {\n   ListItem* this = AllocThis(ListItem);\n   ListItem_init(this, value, key);\n   return this;\n}\n\nvoid ListItem_append(ListItem* this, const char* text) {\n   size_t oldLen = strlen(this->value);\n   size_t textLen = strlen(text);\n   size_t newLen = oldLen + textLen;\n   this->value = xRealloc(this->value, newLen + 1);\n   memcpy(this->value + oldLen, text, textLen);\n   this->value[newLen] = '\\0';\n}\n\nint ListItem_compare(const void* cast1, const void* cast2) {\n   const ListItem* obj1 = (const ListItem*) cast1;\n   const ListItem* obj2 = (const ListItem*) cast2;\n   return strcmp(obj1->value, obj2->value);\n}\n\nconst ObjectClass ListItem_class = {\n   .display = ListItem_display,\n   .delete = ListItem_delete,\n   .compare = ListItem_compare\n};\n"
  },
  {
    "path": "ListItem.h",
    "content": "#ifndef HEADER_ListItem\n#define HEADER_ListItem\n/*\nhtop - ListItem.h\n(C) 2004-2011 Hisham H. Muhammad\nReleased under the GNU GPLv2+, see the COPYING file\nin the source distribution for its full text.\n*/\n\n#include <stdbool.h>\n\n#include \"Object.h\"\n#include \"RichString.h\"\n\n\ntypedef struct ListItem_ {\n   Object super;\n   char* value;\n   int key;\n   bool moving;\n} ListItem;\n\nextern const ObjectClass ListItem_class;\n\nvoid ListItem_delete(Object* cast);\n\nvoid ListItem_display(const Object* cast, RichString* out);\n\nvoid ListItem_init(ListItem* this, const char* value, int key);\n\nListItem* ListItem_new(const char* value, int key);\n\nvoid ListItem_append(ListItem* this, const char* text);\n\nint ListItem_compare(const void* cast1, const void* cast2);\n\nstatic inline const char* ListItem_getRef(const ListItem* this) {\n   return this->value;\n}\n\n#endif\n"
  },
  {
    "path": "LoadAverageMeter.c",
    "content": "/*\nhtop - LoadAverageMeter.c\n(C) 2004-2011 Hisham H. Muhammad\nReleased under the GNU GPLv2+, see the COPYING file\nin the source distribution for its full text.\n*/\n\n#include \"config.h\" // IWYU pragma: keep\n\n#include \"LoadAverageMeter.h\"\n\n#include \"CRT.h\"\n#include \"Machine.h\"\n#include \"Object.h\"\n#include \"Platform.h\"\n#include \"RichString.h\"\n#include \"XUtils.h\"\n\n\nstatic const int LoadAverageMeter_attributes[] = {\n   LOAD_AVERAGE_ONE,\n   LOAD_AVERAGE_FIVE,\n   LOAD_AVERAGE_FIFTEEN\n};\n\nstatic const int LoadMeter_attributes[] = {\n   LOAD\n};\n\nstatic const int OK_attributes[] = {\n   METER_VALUE_OK\n};\n\nstatic const int Medium_attributes[] = {\n   METER_VALUE_WARN\n};\n\nstatic const int High_attributes[] = {\n   METER_VALUE_ERROR\n};\n\nstatic void LoadAverageMeter_updateValues(Meter* this) {\n   Platform_getLoadAverage(&this->values[0], &this->values[1], &this->values[2]);\n\n   // only show bar for 1min value\n   this->curItems = 1;\n\n   // change bar color and total based on value\n   if (this->total < this->host->activeCPUs) {\n      this->total = this->host->activeCPUs;\n   }\n   if (this->values[0] < 1.0) {\n      this->curAttributes = OK_attributes;\n   } else if (this->values[0] < this->host->activeCPUs) {\n      this->curAttributes = Medium_attributes;\n   } else {\n      this->curAttributes = High_attributes;\n   }\n\n   xSnprintf(this->txtBuffer, sizeof(this->txtBuffer), \"%.2f/%.2f/%.2f\", this->values[0], this->values[1], this->values[2]);\n}\n\nstatic void LoadAverageMeter_display(const Object* cast, RichString* out) {\n   const Meter* this = (const Meter*)cast;\n   char buffer[20];\n   int len;\n\n   len = xSnprintf(buffer, sizeof(buffer), \"%.2f \", this->values[0]);\n   RichString_appendnAscii(out, CRT_colors[LOAD_AVERAGE_ONE], buffer, len);\n   len = xSnprintf(buffer, sizeof(buffer), \"%.2f \", this->values[1]);\n   RichString_appendnAscii(out, CRT_colors[LOAD_AVERAGE_FIVE], buffer, len);\n   len = xSnprintf(buffer, sizeof(buffer), \"%.2f \", this->values[2]);\n   RichString_appendnAscii(out, CRT_colors[LOAD_AVERAGE_FIFTEEN], buffer, len);\n}\n\nstatic void LoadMeter_updateValues(Meter* this) {\n   double five, fifteen;\n   Platform_getLoadAverage(&this->values[0], &five, &fifteen);\n\n   // change bar color and total based on value\n   if (this->total < this->host->activeCPUs) {\n      this->total = this->host->activeCPUs;\n   }\n   if (this->values[0] < 1.0) {\n      this->curAttributes = OK_attributes;\n   } else if (this->values[0] < this->host->activeCPUs) {\n      this->curAttributes = Medium_attributes;\n   } else {\n      this->curAttributes = High_attributes;\n   }\n\n   xSnprintf(this->txtBuffer, sizeof(this->txtBuffer), \"%.2f\", this->values[0]);\n}\n\nstatic void LoadMeter_display(const Object* cast, RichString* out) {\n   const Meter* this = (const Meter*)cast;\n   char buffer[20];\n   int len;\n\n   len = xSnprintf(buffer, sizeof(buffer), \"%.2f \", this->values[0]);\n   RichString_appendnAscii(out, CRT_colors[LOAD], buffer, len);\n}\n\nconst MeterClass LoadAverageMeter_class = {\n   .super = {\n      .extends = Class(Meter),\n      .delete = Meter_delete,\n      .display = LoadAverageMeter_display,\n   },\n   .updateValues = LoadAverageMeter_updateValues,\n   .defaultMode = TEXT_METERMODE,\n   .supportedModes = METERMODE_DEFAULT_SUPPORTED,\n   .maxItems = 3,\n   .isPercentChart = false,\n   .total = 1.0,\n   .attributes = LoadAverageMeter_attributes,\n   .name = \"LoadAverage\",\n   .uiName = \"Load average\",\n   .description = \"Load averages: 1 minute, 5 minutes, 15 minutes\",\n   .caption = \"Load average: \"\n};\n\nconst MeterClass LoadMeter_class = {\n   .super = {\n      .extends = Class(Meter),\n      .delete = Meter_delete,\n      .display = LoadMeter_display,\n   },\n   .updateValues = LoadMeter_updateValues,\n   .defaultMode = TEXT_METERMODE,\n   .supportedModes = METERMODE_DEFAULT_SUPPORTED,\n   .maxItems = 1,\n   .isPercentChart = false,\n   .total = 1.0,\n   .attributes = LoadMeter_attributes,\n   .name = \"Load\",\n   .uiName = \"Load\",\n   .description = \"Load: average of ready processes in the last minute\",\n   .caption = \"Load: \"\n};\n"
  },
  {
    "path": "LoadAverageMeter.h",
    "content": "#ifndef HEADER_LoadAverageMeter\n#define HEADER_LoadAverageMeter\n/*\nhtop - LoadAverageMeter.h\n(C) 2004-2011 Hisham H. Muhammad\nReleased under the GNU GPLv2+, see the COPYING file\nin the source distribution for its full text.\n*/\n\n#include \"Meter.h\"\n\n\nextern const MeterClass LoadAverageMeter_class;\n\nextern const MeterClass LoadMeter_class;\n\n#endif\n"
  },
  {
    "path": "Machine.c",
    "content": "/*\nhtop - Machine.c\n(C) 2023 Red Hat, Inc.\n(C) 2004,2005 Hisham H. Muhammad\nReleased under the GNU GPLv2+, see the COPYING file\nin the source distribution for its full text.\n*/\n\n#include \"config.h\" // IWYU pragma: keep\n\n#include \"Machine.h\"\n\n#include <stdlib.h>\n#include <unistd.h>\n\n#include \"Object.h\"\n#include \"Platform.h\"\n#include \"Row.h\"\n#include \"XUtils.h\"\n\n\nvoid Machine_init(Machine* this, UsersTable* usersTable, uid_t userId) {\n   this->usersTable = usersTable;\n   this->userId = userId;\n\n   this->htopUserId = getuid();\n\n   // discover fixed column width limits\n   Row_setPidColumnWidth(Platform_getMaxPid());\n\n   // always maintain valid realtime timestamps\n   Platform_gettime_realtime(&this->realtime, &this->realtimeMs);\n\n#ifdef HAVE_LIBHWLOC\n   this->topologyOk = false;\n   if (hwloc_topology_init(&this->topology) == 0) {\n      this->topologyOk =\n         #if HWLOC_API_VERSION < 0x00020000\n         /* try to ignore the top-level machine object type */\n         0 == hwloc_topology_ignore_type_keep_structure(this->topology, HWLOC_OBJ_MACHINE) &&\n         /* ignore caches, which don't add structure */\n         0 == hwloc_topology_ignore_type_keep_structure(this->topology, HWLOC_OBJ_CORE) &&\n         0 == hwloc_topology_ignore_type_keep_structure(this->topology, HWLOC_OBJ_CACHE) &&\n         0 == hwloc_topology_set_flags(this->topology, HWLOC_TOPOLOGY_FLAG_WHOLE_SYSTEM) &&\n         #else\n         0 == hwloc_topology_set_all_types_filter(this->topology, HWLOC_TYPE_FILTER_KEEP_STRUCTURE) &&\n         #endif\n         0 == hwloc_topology_load(this->topology);\n   }\n#endif\n}\n\nvoid Machine_done(Machine* this) {\n#ifdef HAVE_LIBHWLOC\n   if (this->topologyOk) {\n      hwloc_topology_destroy(this->topology);\n   }\n#endif\n   Object_delete(this->processTable);\n   free(this->tables);\n}\n\nstatic void Machine_addTable(Machine* this, Table* table) {\n   /* check that this table has not been seen previously */\n   for (size_t i = 0; i < this->tableCount; i++)\n      if (this->tables[i] == table)\n         return;\n\n   size_t nmemb = this->tableCount + 1;\n   Table** tables = xReallocArray(this->tables, nmemb, sizeof(Table*));\n   tables[nmemb - 1] = table;\n   this->tables = tables;\n   this->tableCount++;\n}\n\nvoid Machine_populateTablesFromSettings(Machine* this, Settings* settings, Table* processTable) {\n   this->settings = settings;\n   this->processTable = processTable;\n\n   for (size_t i = 0; i < settings->nScreens; i++) {\n      ScreenSettings* ss = settings->screens[i];\n\n      if (!ss->table)\n         ss->table = processTable;\n\n      Table* table = ss->table;\n      if (i == 0)\n         this->activeTable = table;\n\n      Machine_addTable(this, table);\n   }\n}\n\nvoid Machine_setTablesPanel(Machine* this, Panel* panel) {\n   for (size_t i = 0; i < this->tableCount; i++) {\n      Table_setPanel(this->tables[i], panel);\n   }\n}\n\nvoid Machine_scanTables(Machine* this) {\n   // set scan timestamp\n   static bool firstScanDone = false;\n\n   if (firstScanDone) {\n      this->prevMonotonicMs = this->monotonicMs;\n      Platform_gettime_monotonic(&this->monotonicMs);\n   } else {\n      this->prevMonotonicMs = 0;\n      this->monotonicMs = 1;\n      firstScanDone = true;\n   }\n   if (this->monotonicMs <= this->prevMonotonicMs) {\n      return;\n   }\n\n   this->maxUserId = 0;\n   Row_resetFieldWidths();\n\n   for (size_t i = 0; i < this->tableCount; i++) {\n      Table* table = this->tables[i];\n\n      // pre-processing of each row\n      Table_scanPrepare(table);\n\n      // scan values for this table\n      Table_scanIterate(table);\n\n      // post-process after scanning\n      Table_scanCleanup(table);\n   }\n\n   Row_setUidColumnWidth(this->maxUserId);\n   Row_setPidColumnWidth(this->maxProcessId);\n}\n"
  },
  {
    "path": "Machine.h",
    "content": "#ifndef HEADER_Machine\n#define HEADER_Machine\n/*\nhtop - Machine.h\n(C) 2023 Red Hat, Inc.\n(C) 2004,2005 Hisham H. Muhammad\nReleased under the GNU GPLv2+, see the COPYING file\nin the source distribution for its full text.\n*/\n\n#include <limits.h>\n#include <stdbool.h>\n#include <stdint.h>\n#include <sys/time.h>\n#include <sys/types.h>\n\n#include \"Panel.h\"\n#include \"Settings.h\"\n#include \"Table.h\"\n#include \"UsersTable.h\"\n\n#ifdef HAVE_LIBHWLOC\n#include <hwloc.h>\n#endif\n\n\n#ifndef MAX_NAME\n#define MAX_NAME 128\n#endif\n\n#ifndef MAX_READ\n#define MAX_READ 2048\n#endif\n\ntypedef unsigned long long int memory_t;\n#define MEMORY_MAX ULLONG_MAX\n\ntypedef struct Machine_ {\n   struct Settings_* settings;\n\n   struct timeval realtime;   /* time of the current sample */\n   uint64_t realtimeMs;       /* current time in milliseconds */\n   uint64_t monotonicMs;      /* same, but from monotonic clock */\n   uint64_t prevMonotonicMs;  /* time in milliseconds from monotonic clock of previous scan */\n\n   int64_t iterationsRemaining;\n\n   #ifdef HAVE_LIBHWLOC\n   hwloc_topology_t topology;\n   bool topologyOk;\n   #endif\n\n   memory_t totalMem;\n\n   memory_t totalSwap;\n   memory_t usedSwap;\n   memory_t cachedSwap;\n\n   unsigned int activeCPUs;\n   unsigned int existingCPUs;\n\n   UsersTable* usersTable;\n   uid_t htopUserId;\n   uid_t maxUserId;  /* recently observed */\n   uid_t userId;  /* selected row user ID */\n\n   pid_t maxProcessId; /* largest PID seen at runtime */\n\n   size_t tableCount;\n   Table **tables;\n   Table *activeTable;\n   Table *processTable;\n} Machine;\n\n\nMachine* Machine_new(UsersTable* usersTable, uid_t userId);\n\nvoid Machine_init(Machine* this, UsersTable* usersTable, uid_t userId);\n\nvoid Machine_delete(Machine* this);\n\nvoid Machine_done(Machine* this);\n\nbool Machine_isCPUonline(const Machine* this, unsigned int id);\n\nvoid Machine_populateTablesFromSettings(Machine* this, Settings* settings, Table* processTable);\n\nvoid Machine_setTablesPanel(Machine* this, Panel* panel);\n\nvoid Machine_scan(Machine* this);\n\nvoid Machine_scanTables(Machine* this);\n\n#endif\n"
  },
  {
    "path": "Macros.h",
    "content": "#ifndef HEADER_Macros\n#define HEADER_Macros\n/*\nhtop - Macros.h\n(C) 2020-2023 htop dev team\nReleased under the GNU GPLv2+, see the COPYING file\nin the source distribution for its full text.\n*/\n\n#include <assert.h> // IWYU pragma: keep\n#include <math.h>\n#include <stdbool.h>\n#include <string.h> // IWYU pragma: keep\n\n\n#ifndef MINIMUM\n#define MINIMUM(a, b)                  ((a) < (b) ? (a) : (b))\n#endif\n\n#ifndef MAXIMUM\n#define MAXIMUM(a, b)                  ((a) > (b) ? (a) : (b))\n#endif\n\n#ifndef CLAMP\n#define CLAMP(x, low, high)            (assert((low) <= (high)), ((x) > (high)) ? (high) : MAXIMUM(x, low))\n#endif\n\n#ifndef ARRAYSIZE\n#define ARRAYSIZE(x)                   (sizeof(x) / sizeof((x)[0]))\n#endif\n\n#ifndef SPACESHIP_NUMBER\n#define SPACESHIP_NUMBER(a, b)         (((a) > (b)) - ((a) < (b)))\n#endif\n\n#ifndef SPACESHIP_NULLSTR\n#define SPACESHIP_NULLSTR(a, b)        strcmp((a) ? (a) : \"\", (b) ? (b) : \"\")\n#endif\n\n#ifndef SPACESHIP_DEFAULTSTR\n#define SPACESHIP_DEFAULTSTR(a, b, s)  strcmp((a) ? (a) : (s), (b) ? (b) : (s))\n#endif\n\n#ifdef  __GNUC__  // defined by GCC and Clang\n\n#define ATTR_FORMAT(type, index, check) __attribute__((format (type, index, check)))\n#define ATTR_NORETURN                   __attribute__((noreturn))\n#define ATTR_UNUSED                     __attribute__((unused))\n#define ATTR_MALLOC                     __attribute__((malloc))\n\n#else /* __GNUC__ */\n\n#define ATTR_FORMAT(type, index, check)\n#define ATTR_NORETURN\n#define ATTR_UNUSED\n#define ATTR_MALLOC\n\n#endif /* __GNUC__ */\n\n#ifdef HAVE_ATTR_NONNULL\n\n#define ATTR_NONNULL                    __attribute__((nonnull))\n#define ATTR_NONNULL_N(...)             __attribute__((nonnull(__VA_ARGS__)))\n\n#else\n\n#define ATTR_NONNULL\n#define ATTR_NONNULL_N(...)\n\n#endif /* HAVE_ATTR_NONNULL */\n\n#ifdef HAVE_ATTR_RETNONNULL\n\n#define ATTR_RETNONNULL                 __attribute__((returns_nonnull))\n\n#else\n\n#define ATTR_RETNONNULL\n\n#endif /* HAVE_ATTR_RETNONNULL */\n\n#ifdef HAVE_ATTR_ALLOC_SIZE\n\n#define ATTR_ALLOC_SIZE1(a)             __attribute__((alloc_size (a)))\n#define ATTR_ALLOC_SIZE2(a, b)          __attribute__((alloc_size (a, b)))\n\n#else\n\n#define ATTR_ALLOC_SIZE1(a)\n#define ATTR_ALLOC_SIZE2(a, b)\n\n#endif /* HAVE_ATTR_ALLOC_SIZE */\n\n#ifdef HAVE_ATTR_ACCESS\n\n#define ATTR_ACCESS2(mode, ref)         __attribute__((access (mode, ref)))\n#define ATTR_ACCESS3(mode, ref, size)   __attribute__((access (mode, ref, size)))\n\n#else\n\n#define ATTR_ACCESS2(mode, ref)\n#define ATTR_ACCESS3(mode, ref, size)\n\n#endif /* HAVE_ATTR_ACCESS */\n\n#define ATTR_ACCESS2_R(ref)              ATTR_ACCESS2(read_only, ref)\n#define ATTR_ACCESS3_R(ref, size)        ATTR_ACCESS3(read_only, ref, size)\n\n#define ATTR_ACCESS2_RW(ref)             ATTR_ACCESS2(read_write, ref)\n#define ATTR_ACCESS3_RW(ref, size)       ATTR_ACCESS3(read_write, ref, size)\n\n#define ATTR_ACCESS2_W(ref)              ATTR_ACCESS2(write_only, ref)\n#define ATTR_ACCESS3_W(ref, size)        ATTR_ACCESS3(write_only, ref, size)\n\n// ignore casts discarding const specifier, e.g.\n//     const char []     ->  char * / void *\n//     const char *[2]'  ->  char *const *\n#if defined(__clang__)\n#define IGNORE_WCASTQUAL_BEGIN  _Pragma(\"clang diagnostic push\") \\\n                                _Pragma(\"clang diagnostic ignored \\\"-Wcast-qual\\\"\")\n#define IGNORE_WCASTQUAL_END    _Pragma(\"clang diagnostic pop\")\n#elif defined(__GNUC__)\n#define IGNORE_WCASTQUAL_BEGIN  _Pragma(\"GCC diagnostic push\") \\\n                                _Pragma(\"GCC diagnostic ignored \\\"-Wcast-qual\\\"\")\n#define IGNORE_WCASTQUAL_END    _Pragma(\"GCC diagnostic pop\")\n#else\n#define IGNORE_WCASTQUAL_BEGIN\n#define IGNORE_WCASTQUAL_END\n#endif\n\n/* Cheaper function for checking NaNs. Unlike the standard isnan(), this may\n   throw an FP exception on a \"signaling NaN\".\n   (ISO/IEC TS 18661-1 and the C23 standard stated that isnan() throws no\n   exceptions even with a \"signaling NaN\") */\nstatic inline bool isNaN(double x) {\n   return !isgreaterequal(x, x);\n}\n\n/* Checks if x >= 0.0 but returns false if x is NaN. Because IEEE 754 considers\n   -0.0 == 0.0, this function treats both zeros as nonnegative. */\nstatic inline bool isNonnegative(double x) {\n   return isgreaterequal(x, 0.0);\n}\n\n/* Checks if x > 0.0 but returns false if x is NaN. */\nstatic inline bool isPositive(double x) {\n   return isgreater(x, 0.0);\n}\n\n/* This subtraction is used by Linux / NetBSD / OpenBSD for calculation of CPU usage items. */\nstatic inline unsigned long long saturatingSub(unsigned long long a, unsigned long long b) {\n   return a > b ? a - b : 0;\n}\n\n#endif\n"
  },
  {
    "path": "MainPanel.c",
    "content": "/*\nhtop - ColumnsPanel.c\n(C) 2004-2015 Hisham H. Muhammad\n(C) 2020 Red Hat, Inc.  All Rights Reserved.\nReleased under the GNU GPLv2+, see the COPYING file\nin the source distribution for its full text.\n*/\n\n#include \"config.h\" // IWYU pragma: keep\n\n#include \"MainPanel.h\"\n\n#include <ctype.h>\n#include <stdlib.h>\n#include <sys/types.h>\n\n#include \"CRT.h\"\n#include \"FunctionBar.h\"\n#include \"Machine.h\"\n#include \"Platform.h\"\n#include \"ProvideCurses.h\"\n#include \"Row.h\"\n#include \"RowField.h\"\n#include \"Settings.h\"\n#include \"Table.h\"\n#include \"XUtils.h\"\n\n\nstatic const char* const MainFunctions[]     = {\"Help  \", \"Setup \", \"Search\", \"Filter\", \"Tree  \", \"SortBy\", \"Nice -\", \"Nice +\", \"Kill  \", \"Quit  \", NULL};\nstatic const char* const MainFunctions_ro[]  = {\"Help  \", \"Setup \", \"Search\", \"Filter\", \"Tree  \", \"SortBy\", \"      \", \"      \", \"      \", \"Quit  \", NULL};\n\nvoid MainPanel_updateLabels(MainPanel* this, bool list, bool filter) {\n   FunctionBar* bar = MainPanel_getFunctionBar(this);\n   FunctionBar_setLabel(bar, KEY_F(5), list   ? \"List  \" : \"Tree  \");\n   FunctionBar_setLabel(bar, KEY_F(4), filter ? \"FILTER\" : \"Filter\");\n}\n\nstatic void MainPanel_idSearch(MainPanel* this, int ch) {\n   Panel* super = &this->super;\n   pid_t id = ch - 48 + this->idSearch;\n   for (int i = 0; i < Panel_size(super); i++) {\n      const Row* row = (const Row*) Panel_get(super, i);\n      if (row && row->id == id) {\n         Panel_setSelected(super, i);\n         break;\n      }\n   }\n   this->idSearch = id * 10;\n   if (this->idSearch > 10000000) {\n      this->idSearch = 0;\n   }\n}\n\nstatic const char* MainPanel_getValue(Panel* this, int i) {\n   Row* row = (Row*) Panel_get(this, i);\n   return Row_sortKeyString(row);\n}\n\nstatic HandlerResult MainPanel_eventHandler(Panel* super, int ch) {\n   MainPanel* this = (MainPanel*) super;\n   Machine* host = this->state->host;\n   Htop_Reaction reaction = HTOP_OK;\n   HandlerResult result = IGNORED;\n\n   /* Let supervising ScreenManager handle resize */\n   if (ch == KEY_RESIZE)\n      return IGNORED;\n\n   /* reset on every normal key */\n   bool needReset = ch != ERR;\n   #ifdef HAVE_GETMOUSE\n   /* except mouse events while mouse support is disabled */\n   if (!(ch != KEY_MOUSE || host->settings->enableMouse))\n      needReset = false;\n   #endif\n   if (needReset)\n      this->state->hideSelection = false;\n\n   Settings* settings = host->settings;\n   ScreenSettings* ss = settings->ss;\n\n   if (EVENT_IS_HEADER_CLICK(ch)) {\n      int x = EVENT_HEADER_CLICK_GET_X(ch);\n      int hx = super->scrollH + x + 1;\n      RowField field = RowField_keyAt(settings, hx);\n      if (ss->treeView && ss->treeViewAlwaysByPID) {\n         ss->treeView = false;\n         ss->direction = 1;\n         reaction |= Action_setSortKey(settings, field);\n      } else if (field == ScreenSettings_getActiveSortKey(ss)) {\n         ScreenSettings_invertSortOrder(ss);\n      } else {\n         reaction |= Action_setSortKey(settings, field);\n      }\n      reaction |= HTOP_RECALCULATE | HTOP_REDRAW_BAR | HTOP_UPDATE_PANELHDR | HTOP_SAVE_SETTINGS;\n      result = HANDLED;\n   } else if (EVENT_IS_SCREEN_TAB_CLICK(ch)) {\n      int x = EVENT_SCREEN_TAB_GET_X(ch);\n      reaction |= Action_setScreenTab(this->state, x);\n      result = HANDLED;\n   } else if (ch != ERR && this->inc->active) {\n      bool filterChanged = IncSet_handleKey(this->inc, ch, super, MainPanel_getValue, NULL);\n      if (filterChanged) {\n         host->activeTable->incFilter = IncSet_filter(this->inc);\n         reaction = HTOP_REFRESH | HTOP_REDRAW_BAR;\n      }\n      if (this->inc->found) {\n         reaction |= Action_follow(this->state);\n         reaction |= HTOP_KEEP_FOLLOWING;\n      }\n      result = HANDLED;\n   } else if (ch == 27) {\n      this->state->hideSelection = true;\n      return HANDLED;\n   } else if (ch != ERR && ch > 0 && ch < KEY_MAX && this->keys[ch]) {\n      reaction |= (this->keys[ch])(this->state);\n      result = HANDLED;\n   } else if (0 < ch && ch < 255 && isdigit((unsigned char)ch)) {\n      MainPanel_idSearch(this, ch);\n   } else if (ch == KEY_LEFT || ch == KEY_RIGHT) {\n      reaction |= HTOP_KEEP_FOLLOWING;\n   } else {\n      if (ch != ERR) {\n         this->idSearch = 0;\n      } else {\n         reaction |= HTOP_KEEP_FOLLOWING;\n      }\n   }\n\n   if ((reaction & HTOP_REDRAW_BAR) == HTOP_REDRAW_BAR) {\n      MainPanel_updateLabels(this, settings->ss->treeView, host->activeTable->incFilter);\n   }\n   if ((reaction & HTOP_RESIZE) == HTOP_RESIZE) {\n      result |= RESIZE;\n   }\n   if ((reaction & HTOP_UPDATE_PANELHDR) == HTOP_UPDATE_PANELHDR) {\n      result |= REDRAW;\n   }\n   if ((reaction & HTOP_REFRESH) == HTOP_REFRESH) {\n      result |= REFRESH;\n   }\n   if ((reaction & HTOP_RECALCULATE) == HTOP_RECALCULATE) {\n      result |= RESCAN;\n   }\n   if ((reaction & HTOP_SAVE_SETTINGS) == HTOP_SAVE_SETTINGS) {\n      host->settings->changed = true;\n   }\n   if ((reaction & HTOP_QUIT) == HTOP_QUIT) {\n      return BREAK_LOOP;\n   }\n   if ((reaction & HTOP_KEEP_FOLLOWING) != HTOP_KEEP_FOLLOWING) {\n      host->activeTable->following = -1;\n      Panel_setSelectionColor(super, PANEL_SELECTION_FOCUS);\n   }\n   return result;\n}\n\nint MainPanel_selectedRow(MainPanel* this) {\n   const Row* row = (const Row*) Panel_getSelected((Panel*)this);\n   return row ? row->id : -1;\n}\n\nbool MainPanel_foreachRow(MainPanel* this, MainPanel_foreachRowFn fn, Arg arg, bool* wasAnyTagged) {\n   Panel* super = &this->super;\n   bool ok = true;\n   bool anyTagged = false;\n   for (int i = 0; i < Panel_size(super); i++) {\n      Row* row = (Row*) Panel_get(super, i);\n      if (row->tag) {\n         ok &= fn(row, arg);\n         anyTagged = true;\n      }\n   }\n   if (!anyTagged) {\n      Row* row = (Row*) Panel_getSelected(super);\n      if (row) {\n         ok &= fn(row, arg);\n      }\n   }\n\n   if (wasAnyTagged)\n      *wasAnyTagged = anyTagged;\n\n   return ok;\n}\n\nstatic void MainPanel_drawFunctionBar(Panel* super, bool hideFunctionBar) {\n   MainPanel* this = (MainPanel*) super;\n\n   // Do not hide active search and filter bar.\n   if (hideFunctionBar && !this->inc->active)\n      return;\n\n   IncSet_drawBar(this->inc, CRT_colors[FUNCTION_BAR]);\n   if (this->state->pauseUpdate) {\n      FunctionBar_append(\"PAUSED\", CRT_colors[PAUSED]);\n   } else if (this->state->failedUpdate) {\n      FunctionBar_append(this->state->failedUpdate, CRT_colors[FAILED_READ]);\n   }\n}\n\nstatic void MainPanel_printHeader(Panel* super) {\n   MainPanel* this = (MainPanel*) super;\n   Machine* host = this->state->host;\n   Table_printHeader(host->settings, &super->header);\n}\n\nconst PanelClass MainPanel_class = {\n   .super = {\n      .extends = Class(Panel),\n      .delete = MainPanel_delete\n   },\n   .eventHandler = MainPanel_eventHandler,\n   .drawFunctionBar = MainPanel_drawFunctionBar,\n   .printHeader = MainPanel_printHeader\n};\n\nMainPanel* MainPanel_new(void) {\n   MainPanel* this = AllocThis(MainPanel);\n   this->processBar = FunctionBar_new(MainFunctions, NULL, NULL);\n   this->readonlyBar = FunctionBar_new(MainFunctions_ro, NULL, NULL);\n   FunctionBar* activeBar = Settings_isReadonly() ? this->readonlyBar : this->processBar;\n   Panel_init((Panel*) this, 1, 1, 1, 1, Class(Row), false, activeBar);\n   this->keys = xCalloc(KEY_MAX, sizeof(Htop_Action));\n   this->inc = IncSet_new(activeBar);\n\n   Action_setBindings(this->keys);\n   Platform_setBindings(this->keys);\n\n   return this;\n}\n\nvoid MainPanel_setState(MainPanel* this, State* state) {\n   this->state = state;\n}\n\nvoid MainPanel_setFunctionBar(MainPanel* this, bool readonly) {\n   this->super.defaultBar = readonly ? this->readonlyBar : this->processBar;\n   this->inc->defaultBar = this->super.defaultBar;\n}\n\nvoid MainPanel_delete(Object* object) {\n   MainPanel* this = (MainPanel*) object;\n   MainPanel_setFunctionBar(this, false);\n   FunctionBar_delete(this->readonlyBar);\n   IncSet_delete(this->inc);\n   free(this->keys);\n   Panel_done(&this->super);\n   free(this);\n}\n"
  },
  {
    "path": "MainPanel.h",
    "content": "#ifndef HEADER_MainPanel\n#define HEADER_MainPanel\n/*\nhtop - ColumnsPanel.h\n(C) 2004-2015 Hisham H. Muhammad\n(C) 2020 Red Hat, Inc.  All Rights Reserved.\nReleased under the GNU GPLv2+, see the COPYING file\nin the source distribution for its full text.\n*/\n\n#include <stdbool.h>\n\n#include \"Action.h\"\n#include \"FunctionBar.h\"\n#include \"IncSet.h\"\n#include \"Object.h\"\n#include \"Panel.h\"\n#include \"Row.h\"\n\n\ntypedef struct MainPanel_ {\n   Panel super;\n   State* state;\n   IncSet* inc;\n   Htop_Action* keys;\n   FunctionBar* processBar;  /* function bar with process-specific actions */\n   FunctionBar* readonlyBar;  /* function bar without process actions (ro) */\n   unsigned int idSearch;\n} MainPanel;\n\ntypedef bool(*MainPanel_foreachRowFn)(Row*, Arg);\n\n#define MainPanel_getFunctionBar(this_) (((Panel*)(this_))->defaultBar)\n\n// update the Label Keys in the MainPanel bar, list: list / tree mode, filter: filter (inc) active / inactive\nvoid MainPanel_updateLabels(MainPanel* this, bool list, bool filter);\n\nint MainPanel_selectedRow(MainPanel* this);\n\nbool MainPanel_foreachRow(MainPanel* this, MainPanel_foreachRowFn fn, Arg arg, bool* wasAnyTagged);\n\nextern const PanelClass MainPanel_class;\n\nMainPanel* MainPanel_new(void);\n\nvoid MainPanel_setState(MainPanel* this, State* state);\n\nvoid MainPanel_setFunctionBar(MainPanel* this, bool readonly);\n\nvoid MainPanel_delete(Object* object);\n\n#endif\n"
  },
  {
    "path": "Makefile.am",
    "content": "if !HTOP_PCP\nbin_PROGRAMS = htop\nmyhtopplatprogram = htop.c\nelse\nbin_PROGRAMS = pcp-htop\nmyhtopplatprogram = pcp-htop.c\nendif\n\ndist_man_MANS = htop.1\nEXTRA_DIST = \\\n\t$(dist_man_MANS) \\\n\tautogen.sh \\\n\thtop.desktop \\\n\thtop.png \\\n\thtop.svg \\\n\tbuild-aux/compile \\\n\tbuild-aux/depcomp \\\n\tbuild-aux/install-sh \\\n\tbuild-aux/missing\napplicationsdir = $(datadir)/applications\napplications_DATA = htop.desktop\npixmapdir = $(datadir)/pixmaps\npixmap_DATA = htop.png\nappicondir = $(datadir)/icons/hicolor/scalable/apps\nappicon_DATA = htop.svg\n\nAM_CFLAGS += -DSYSCONFDIR=\"\\\"$(sysconfdir)\\\"\" -I\"$(top_srcdir)/$(my_htop_platform)\"\nAM_LDFLAGS =\n\nmyhtopsources = \\\n\tAction.c \\\n\tAffinity.c \\\n\tAffinityPanel.c \\\n\tAvailableColumnsPanel.c \\\n\tAvailableMetersPanel.c \\\n\tBatteryMeter.c \\\n\tCategoriesPanel.c \\\n\tClockMeter.c \\\n\tColorsPanel.c \\\n\tColumnsPanel.c \\\n\tCommandLine.c \\\n\tCommandScreen.c \\\n\tCPUMeter.c \\\n\tCRT.c \\\n\tDateTimeMeter.c \\\n\tDiskIOMeter.c \\\n\tDisplayOptionsPanel.c \\\n\tDynamicColumn.c \\\n\tDynamicMeter.c \\\n\tDynamicScreen.c \\\n\tEnvScreen.c \\\n\tFileDescriptorMeter.c \\\n\tFunctionBar.c \\\n\tHashtable.c \\\n\tHeader.c \\\n\tHeaderOptionsPanel.c \\\n\tHostnameMeter.c \\\n\tIncSet.c \\\n\tInfoScreen.c \\\n\tListItem.c \\\n\tLoadAverageMeter.c \\\n\tMachine.c \\\n\tMainPanel.c \\\n\tMemoryMeter.c \\\n\tMemorySwapMeter.c \\\n\tMeter.c \\\n\tMetersPanel.c \\\n\tNetworkIOMeter.c \\\n\tObject.c \\\n\tOpenFilesScreen.c \\\n\tOptionItem.c \\\n\tPanel.c \\\n\tProcess.c \\\n\tProcessLocksScreen.c \\\n\tProcessTable.c \\\n\tRow.c \\\n\tRichString.c \\\n\tScheduling.c \\\n\tScreenManager.c \\\n\tScreensPanel.c \\\n\tScreenTabsPanel.c \\\n\tSettings.c \\\n\tSignalsPanel.c \\\n\tSwapMeter.c \\\n\tSysArchMeter.c \\\n\tTable.c \\\n\tTasksMeter.c \\\n\tTraceScreen.c \\\n\tUptimeMeter.c \\\n\tUsersTable.c \\\n\tVector.c \\\n\tXUtils.c\n\nmyhtopheaders = \\\n\tAction.h \\\n\tAffinity.h \\\n\tAffinityPanel.h \\\n\tAvailableColumnsPanel.h \\\n\tAvailableMetersPanel.h \\\n\tBatteryMeter.h \\\n\tCPUMeter.h \\\n\tCRT.h \\\n\tCategoriesPanel.h \\\n\tClockMeter.h \\\n\tColorsPanel.h \\\n\tColumnsPanel.h \\\n\tCommandLine.h \\\n\tCommandScreen.h \\\n\tDateTimeMeter.h \\\n\tDebug.h \\\n\tDiskIOMeter.h \\\n\tDisplayOptionsPanel.h \\\n\tDynamicColumn.h \\\n\tDynamicMeter.h \\\n\tDynamicScreen.h \\\n\tEnvScreen.h \\\n\tFileDescriptorMeter.h \\\n\tFunctionBar.h \\\n\tGPUMeter.h \\\n\tHashtable.h \\\n\tHeader.h \\\n\tHeaderLayout.h \\\n\tHeaderOptionsPanel.h \\\n\tHostnameMeter.h \\\n\tIncSet.h \\\n\tInfoScreen.h \\\n\tListItem.h \\\n\tLoadAverageMeter.h \\\n\tMachine.h \\\n\tMacros.h \\\n\tMainPanel.h \\\n\tMemoryMeter.h \\\n\tMemorySwapMeter.h \\\n\tMeter.h \\\n\tMeterMode.h \\\n\tMetersPanel.h \\\n\tNetworkIOMeter.h \\\n\tObject.h \\\n\tOpenFilesScreen.h \\\n\tOptionItem.h \\\n\tPanel.h \\\n\tProcess.h \\\n\tProcessLocksScreen.h \\\n\tProcessTable.h \\\n\tProvideCurses.h \\\n\tProvideTerm.h \\\n\tRichString.h \\\n\tRow.h \\\n\tRowField.h \\\n\tScheduling.h \\\n\tScreenManager.h \\\n\tScreensPanel.h \\\n\tScreenTabsPanel.h \\\n\tSettings.h \\\n\tSignalsPanel.h \\\n\tSwapMeter.h \\\n\tSysArchMeter.h \\\n\tTable.h \\\n\tTasksMeter.h \\\n\tTraceScreen.h \\\n\tUptimeMeter.h \\\n\tUsersTable.h \\\n\tVector.h \\\n\tXUtils.h\n\n# Linux\n# -----\n\nlinux_platform_headers = \\\n\tgeneric/gettime.h \\\n\tgeneric/hostname.h \\\n\tgeneric/uname.h \\\n\tlinux/CGroupUtils.h \\\n\tlinux/Compat.h \\\n\tlinux/GPU.h \\\n\tlinux/HugePageMeter.h \\\n\tlinux/IOPriority.h \\\n\tlinux/IOPriorityPanel.h \\\n\tlinux/LibSensors.h \\\n\tlinux/LinuxMachine.h \\\n\tlinux/LinuxProcess.h \\\n\tlinux/LinuxProcessTable.h \\\n\tlinux/OpenRCMeter.h \\\n\tlinux/Platform.h \\\n\tlinux/PressureStallMeter.h \\\n\tlinux/ProcessField.h \\\n\tlinux/SELinuxMeter.h \\\n\tlinux/SystemdMeter.h \\\n\tlinux/ZramMeter.h \\\n\tlinux/ZramStats.h \\\n\tlinux/ZswapStats.h \\\n\tzfs/ZfsArcMeter.h \\\n\tzfs/ZfsArcStats.h \\\n\tzfs/ZfsCompressedArcMeter.h\n\nlinux_platform_sources = \\\n\tGPUMeter.c \\\n\tgeneric/gettime.c \\\n\tgeneric/hostname.c \\\n\tgeneric/uname.c \\\n\tlinux/CGroupUtils.c \\\n\tlinux/Compat.c \\\n\tlinux/GPU.c \\\n\tlinux/HugePageMeter.c \\\n\tlinux/IOPriorityPanel.c \\\n\tlinux/LibSensors.c \\\n\tlinux/LinuxMachine.c \\\n\tlinux/LinuxProcess.c \\\n\tlinux/LinuxProcessTable.c \\\n\tlinux/OpenRCMeter.c \\\n\tlinux/Platform.c \\\n\tlinux/PressureStallMeter.c \\\n\tlinux/SELinuxMeter.c \\\n\tlinux/SystemdMeter.c \\\n\tlinux/ZramMeter.c \\\n\tzfs/ZfsArcMeter.c \\\n\tzfs/ZfsCompressedArcMeter.c\n\nif HAVE_DELAYACCT\nlinux_platform_headers += linux/LibNl.h\nlinux_platform_sources += linux/LibNl.c\nendif\n\nif HTOP_LINUX\nAM_LDFLAGS += -rdynamic\nmyhtopplatheaders = $(linux_platform_headers)\nmyhtopplatsources = $(linux_platform_sources)\nendif\n\n# FreeBSD\n# -------\n\nfreebsd_platform_headers = \\\n\tfreebsd/FreeBSDMachine.h \\\n\tfreebsd/FreeBSDProcessTable.h \\\n\tfreebsd/FreeBSDProcess.h \\\n\tfreebsd/Platform.h \\\n\tfreebsd/ProcessField.h \\\n\tgeneric/fdstat_sysctl.h \\\n\tgeneric/gettime.h \\\n\tgeneric/hostname.h \\\n\tgeneric/openzfs_sysctl.h \\\n\tgeneric/uname.h \\\n\tzfs/ZfsArcMeter.h \\\n\tzfs/ZfsArcStats.h \\\n\tzfs/ZfsCompressedArcMeter.h\n\nfreebsd_platform_sources = \\\n\tfreebsd/Platform.c \\\n\tfreebsd/FreeBSDMachine.c \\\n\tfreebsd/FreeBSDProcessTable.c \\\n\tfreebsd/FreeBSDProcess.c \\\n\tgeneric/fdstat_sysctl.c \\\n\tgeneric/gettime.c \\\n\tgeneric/hostname.c \\\n\tgeneric/openzfs_sysctl.c \\\n\tgeneric/uname.c \\\n\tzfs/ZfsArcMeter.c \\\n\tzfs/ZfsCompressedArcMeter.c\n\nif HTOP_FREEBSD\nmyhtopplatheaders = $(freebsd_platform_headers)\nmyhtopplatsources = $(freebsd_platform_sources)\nendif\n\n# DragonFlyBSD\n# ------------\n\ndragonflybsd_platform_headers = \\\n\tdragonflybsd/DragonFlyBSDMachine.h \\\n\tdragonflybsd/DragonFlyBSDProcessTable.h \\\n\tdragonflybsd/DragonFlyBSDProcess.h \\\n\tdragonflybsd/Platform.h \\\n\tdragonflybsd/ProcessField.h \\\n\tgeneric/fdstat_sysctl.h \\\n\tgeneric/gettime.h \\\n\tgeneric/hostname.h \\\n\tgeneric/uname.h\n\ndragonflybsd_platform_sources = \\\n\tdragonflybsd/DragonFlyBSDMachine.c \\\n\tdragonflybsd/DragonFlyBSDProcessTable.c \\\n\tdragonflybsd/DragonFlyBSDProcess.c \\\n\tdragonflybsd/Platform.c \\\n\tgeneric/fdstat_sysctl.c \\\n\tgeneric/gettime.c \\\n\tgeneric/hostname.c \\\n\tgeneric/uname.c\n\nif HTOP_DRAGONFLYBSD\nmyhtopplatheaders = $(dragonflybsd_platform_headers)\nmyhtopplatsources = $(dragonflybsd_platform_sources)\nendif\n\n# NetBSD\n# -------\n\nnetbsd_platform_headers = \\\n\tgeneric/fdstat_sysctl.h \\\n\tgeneric/gettime.h \\\n\tgeneric/hostname.h \\\n\tgeneric/uname.h \\\n\tnetbsd/Platform.h \\\n\tnetbsd/ProcessField.h \\\n\tnetbsd/NetBSDMachine.h \\\n\tnetbsd/NetBSDProcess.h \\\n\tnetbsd/NetBSDProcessTable.h\n\nnetbsd_platform_sources = \\\n\tgeneric/fdstat_sysctl.c \\\n\tgeneric/gettime.c \\\n\tgeneric/hostname.c \\\n\tgeneric/uname.c \\\n\tnetbsd/Platform.c \\\n\tnetbsd/NetBSDMachine.c \\\n\tnetbsd/NetBSDProcess.c \\\n\tnetbsd/NetBSDProcessTable.c\n\nif HTOP_NETBSD\nmyhtopplatheaders = $(netbsd_platform_headers)\nmyhtopplatsources = $(netbsd_platform_sources)\nendif\n\n# OpenBSD\n# -------\n\nopenbsd_platform_headers = \\\n\tgeneric/gettime.h \\\n\tgeneric/hostname.h \\\n\tgeneric/uname.h \\\n\topenbsd/OpenBSDMachine.h \\\n\topenbsd/OpenBSDProcessTable.h \\\n\topenbsd/OpenBSDProcess.h \\\n\topenbsd/Platform.h \\\n\topenbsd/ProcessField.h\n\nopenbsd_platform_sources = \\\n\tgeneric/gettime.c \\\n\tgeneric/hostname.c \\\n\tgeneric/uname.c \\\n\topenbsd/OpenBSDMachine.c \\\n\topenbsd/OpenBSDProcessTable.c \\\n\topenbsd/OpenBSDProcess.c \\\n\topenbsd/Platform.c\n\nif HTOP_OPENBSD\nmyhtopplatheaders = $(openbsd_platform_headers)\nmyhtopplatsources = $(openbsd_platform_sources)\nendif\n\n# Darwin\n# ------\n\ndarwin_platform_headers = \\\n\tdarwin/DarwinMachine.h \\\n\tdarwin/DarwinProcess.h \\\n\tdarwin/DarwinProcessTable.h \\\n\tdarwin/Platform.h \\\n\tdarwin/PlatformHelpers.h \\\n\tdarwin/ProcessField.h \\\n\tgeneric/fdstat_sysctl.h \\\n\tgeneric/gettime.h \\\n\tgeneric/hostname.h \\\n\tgeneric/openzfs_sysctl.h \\\n\tgeneric/uname.h \\\n\tzfs/ZfsArcMeter.h \\\n\tzfs/ZfsArcStats.h \\\n\tzfs/ZfsCompressedArcMeter.h\n\ndarwin_platform_sources = \\\n\tGPUMeter.c \\\n\tdarwin/Platform.c \\\n\tdarwin/PlatformHelpers.c \\\n\tdarwin/DarwinMachine.c \\\n\tdarwin/DarwinProcess.c \\\n\tdarwin/DarwinProcessTable.c \\\n\tgeneric/fdstat_sysctl.c \\\n\tgeneric/gettime.c \\\n\tgeneric/hostname.c \\\n\tgeneric/openzfs_sysctl.c \\\n\tgeneric/uname.c \\\n\tzfs/ZfsArcMeter.c \\\n\tzfs/ZfsCompressedArcMeter.c\n\nif HTOP_DARWIN\nAM_LDFLAGS += -framework IOKit -framework CoreFoundation\nmyhtopplatheaders = $(darwin_platform_headers)\nmyhtopplatsources = $(darwin_platform_sources)\nendif\n\n# Solaris\n# -------\n\nsolaris_platform_headers = \\\n\tgeneric/gettime.h \\\n\tgeneric/hostname.h \\\n\tgeneric/uname.h \\\n\tsolaris/ProcessField.h \\\n\tsolaris/Platform.h \\\n\tsolaris/SolarisMachine.h \\\n\tsolaris/SolarisProcess.h \\\n\tsolaris/SolarisProcessTable.h \\\n\tzfs/ZfsArcMeter.h \\\n\tzfs/ZfsArcStats.h \\\n\tzfs/ZfsCompressedArcMeter.h\n\nsolaris_platform_sources = \\\n\tgeneric/gettime.c \\\n\tgeneric/hostname.c \\\n\tgeneric/uname.c \\\n\tsolaris/Platform.c \\\n\tsolaris/SolarisMachine.c \\\n\tsolaris/SolarisProcess.c \\\n\tsolaris/SolarisProcessTable.c \\\n\tzfs/ZfsArcMeter.c \\\n\tzfs/ZfsCompressedArcMeter.c\n\nif HTOP_SOLARIS\nmyhtopplatheaders = $(solaris_platform_headers)\nmyhtopplatsources = $(solaris_platform_sources)\nendif\n\n# Performance Co-Pilot (PCP)\n# --------------------------\n\npcp_platform_headers = \\\n\tlinux/CGroupUtils.h \\\n\tlinux/PressureStallMeter.h \\\n\tlinux/ZramMeter.h \\\n\tlinux/ZramStats.h \\\n\tpcp/Instance.h \\\n\tpcp/InDomTable.h \\\n\tpcp/Metric.h \\\n\tpcp/Platform.h \\\n\tpcp/ProcessField.h \\\n\tpcp/PCPDynamicColumn.h \\\n\tpcp/PCPDynamicMeter.h \\\n\tpcp/PCPDynamicScreen.h \\\n\tpcp/PCPMachine.h \\\n\tpcp/PCPProcess.h \\\n\tpcp/PCPProcessTable.h \\\n\tzfs/ZfsArcMeter.h \\\n\tzfs/ZfsArcStats.h \\\n\tzfs/ZfsCompressedArcMeter.h\n\npcp_platform_sources = \\\n\tlinux/CGroupUtils.c \\\n\tlinux/PressureStallMeter.c \\\n\tlinux/ZramMeter.c \\\n\tpcp/Instance.c \\\n\tpcp/InDomTable.c \\\n\tpcp/Metric.c \\\n\tpcp/Platform.c \\\n\tpcp/PCPDynamicColumn.c \\\n\tpcp/PCPDynamicMeter.c \\\n\tpcp/PCPDynamicScreen.c \\\n\tpcp/PCPMachine.c \\\n\tpcp/PCPProcess.c \\\n\tpcp/PCPProcessTable.c \\\n\tzfs/ZfsArcMeter.c \\\n\tzfs/ZfsCompressedArcMeter.c\n\nif HTOP_PCP\nmyhtopplatheaders = $(pcp_platform_headers)\nmyhtopplatsources = $(pcp_platform_sources)\npcp_htop_SOURCES  = $(myhtopplatprogram) $(myhtopheaders) $(myhtopplatheaders) $(myhtopsources) $(myhtopplatsources)\nendif\n\n# Unsupported\n# -----------\n\nunsupported_platform_headers = \\\n\tgeneric/gettime.h \\\n\tunsupported/Platform.h \\\n\tunsupported/ProcessField.h \\\n\tunsupported/UnsupportedMachine.h \\\n\tunsupported/UnsupportedProcess.h \\\n\tunsupported/UnsupportedProcessTable.h\n\nunsupported_platform_sources = \\\n\tgeneric/gettime.c \\\n\tunsupported/Platform.c \\\n\tunsupported/UnsupportedMachine.c \\\n\tunsupported/UnsupportedProcess.c \\\n\tunsupported/UnsupportedProcessTable.c\n\nif HTOP_UNSUPPORTED\nmyhtopplatsources = $(unsupported_platform_sources)\nmyhtopplatheaders = $(unsupported_platform_headers)\nendif\n\n# ----\n\nhtop_SOURCES = $(myhtopplatprogram) $(myhtopheaders) $(myhtopplatheaders) $(myhtopsources) $(myhtopplatsources)\nnodist_htop_SOURCES = config.h\n\ntarget:\n\techo $(htop_SOURCES)\n\nprofile:\n\t$(MAKE) all AM_CPPFLAGS=\"-pg -O2 -DNDEBUG\"\n\ndebug:\n\t$(MAKE) all AM_CPPFLAGS=\"-ggdb3 -Og\" CFLAGS=\"`printf ' %s ' \"$(CFLAGS)\"|sed -E 's#[[:space:]]-O[^[:space:]]+[[:space:]]# #g'` -ggdb3 -Og\"\n\ncoverage:\n\t$(MAKE) all AM_CPPFLAGS=\"-fprofile-arcs -ftest-coverage\" AM_LDFLAGS=\"-lgcov\"\n\ncppcheck:\n\tcppcheck -q -v . --enable=all -DHAVE_OPENVZ\n\ndist-hook: $(top_distdir)/configure\n\t@if test \"x$$FORCE_MAKE_DIST\" != x || \\\n\t   grep 'pkg_m4_included' '$(top_distdir)/configure' >/dev/null; then :; \\\n\telse \\\n\t   echo 'ERROR: The configure script has incomplete pkg-config support and regenerating it is advised. Set FORCE_MAKE_DIST=1 to ignore this warning.'>&2; \\\n\t   (exit 1); \\\n\tfi\n\t@if grep 'PACKAGE_VERSION.*-g' '$(top_distdir)/configure'; then \\\n\t  echo 'WARNING: You are building a dist from a git version. Better run make dist outside of a .git repo on a tagged release.'>&2; \\\n\tfi\n\n.PHONY: lcov\n\nlcov:\n\tmkdir -p lcov\n\tlcov --capture --directory . --output-file coverage.info\n\tgenhtml coverage.info --output-directory lcov\n"
  },
  {
    "path": "MemoryMeter.c",
    "content": "/*\nhtop - MemoryMeter.c\n(C) 2004-2011 Hisham H. Muhammad\n(C) 2025 htop dev team\nReleased under the GNU GPLv2+, see the COPYING file\nin the source distribution for its full text.\n*/\n\n#include \"config.h\" // IWYU pragma: keep\n\n#include \"MemoryMeter.h\"\n\n#include <math.h>\n#include <stddef.h>\n\n#include \"CRT.h\"\n#include \"Debug.h\"\n#include \"Macros.h\"\n#include \"Object.h\"\n#include \"Platform.h\"\n#include \"RichString.h\"\n\n\nstatic const int MemoryMeter_attributes[] = {\n   MEMORY_1,\n   MEMORY_2,\n   MEMORY_3,\n   MEMORY_4,\n   MEMORY_5,\n   MEMORY_6\n};\n\nstatic void MemoryMeter_updateValues(Meter* this) {\n   char* buffer = this->txtBuffer;\n   size_t size = sizeof(this->txtBuffer);\n   int written;\n\n   Settings *settings = this->host->settings;\n\n   /* not all memory classes are supported on all platforms */\n   for (unsigned int memoryClassIdx = 0; memoryClassIdx < Platform_numberOfMemoryClasses; memoryClassIdx++) {\n      this->values[memoryClassIdx] = NAN;\n   }\n\n   Platform_setMemoryValues(this);\n   this->curItems = (uint8_t) Platform_numberOfMemoryClasses;\n\n   /* compute the used memory */\n   double used = 0.0;\n   for (unsigned int memoryClassIdx = 0; memoryClassIdx < Platform_numberOfMemoryClasses; memoryClassIdx++) {\n      if (Platform_memoryClasses[memoryClassIdx].countsAsUsed) {\n         used += this->values[memoryClassIdx];\n      }\n   }\n\n   /* clear the values we don't want to see */\n   if (this->mode == GRAPH_METERMODE || this->mode == BAR_METERMODE) {\n      for (unsigned int memoryClassIdx = 0; memoryClassIdx < Platform_numberOfMemoryClasses; memoryClassIdx++) {\n         if ((Platform_memoryClasses[memoryClassIdx].countsAsCache && !settings->showCachedMemory) || !(Platform_memoryClasses[memoryClassIdx].countsAsCache || Platform_memoryClasses[memoryClassIdx].countsAsUsed)) {\n            this->values[memoryClassIdx] = NAN;\n         }\n      }\n   }\n\n   written = Meter_humanUnit(buffer, used, size);\n   METER_BUFFER_CHECK(buffer, size, written);\n\n   METER_BUFFER_APPEND_CHR(buffer, size, '/');\n\n   Meter_humanUnit(buffer, this->total, size);\n}\n\nstatic void MemoryMeter_display(const Object* cast, RichString* out) {\n   char buffer[50];\n   const Meter* this = (const Meter*)cast;\n   const Settings* settings = this->host->settings;\n   ColorElements labelColor, valueColor;\n\n   RichString_writeAscii(out, CRT_colors[METER_TEXT], \":\");\n   Meter_humanUnit(buffer, this->total, sizeof(buffer));\n   RichString_appendAscii(out, CRT_colors[METER_VALUE], buffer);\n\n   /* print the memory classes in the order supplied (specific to each platform) */\n   for (unsigned int memoryClassIdx = 0; memoryClassIdx < Platform_numberOfMemoryClasses; memoryClassIdx++) {\n      if (!settings->showCachedMemory && Platform_memoryClasses[memoryClassIdx].countsAsCache) {\n         labelColor = valueColor = CRT_colors[METER_SHADOW];\n      } else {\n         labelColor = CRT_colors[METER_TEXT];\n         valueColor = CRT_colors[Platform_memoryClasses[memoryClassIdx].color];\n      }\n\n      Meter_humanUnit(buffer, this->values[memoryClassIdx], sizeof(buffer));\n      RichString_appendAscii(out, labelColor, \" \");\n      RichString_appendAscii(out, labelColor, Platform_memoryClasses[memoryClassIdx].label);\n      RichString_appendAscii(out, labelColor, \":\");\n      RichString_appendAscii(out, valueColor, buffer);\n   }\n}\n\nconst MeterClass MemoryMeter_class = {\n   .super = {\n      .extends = Class(Meter),\n      .delete = Meter_delete,\n      .display = MemoryMeter_display,\n   },\n   .updateValues = MemoryMeter_updateValues,\n   .defaultMode = BAR_METERMODE,\n   .supportedModes = METERMODE_DEFAULT_SUPPORTED,\n   .maxItems = 6, // maximum of MEMORY_N settings\n   .isPercentChart = true,\n   .total = 100.0,\n   .attributes = MemoryMeter_attributes,\n   .name = \"Memory\",\n   .uiName = \"Memory\",\n   .caption = \"Mem\"\n};\n"
  },
  {
    "path": "MemoryMeter.h",
    "content": "#ifndef HEADER_MemoryMeter\n#define HEADER_MemoryMeter\n/*\nhtop - MemoryMeter.h\n(C) 2004-2011 Hisham H. Muhammad\n(C) 2025 htop dev team\nReleased under the GNU GPLv2+, see the COPYING file\nin the source distribution for its full text.\n*/\n\n#include \"Meter.h\"\n\ntypedef struct MemoryClass_s {\n   const char *label; // e.g. \"wired\", \"shared\", \"compressed\" - platform-specific memory classe names\n   bool countsAsUsed; // memory class counts as \"used\" memory\n   bool countsAsCache; // memory class reclaimed under pressure (displayed with \"show cached memory\")\n   ColorElements color; // one of the MEMORY CRT color values\n} MemoryClass;\n\nextern const MeterClass MemoryMeter_class;\n\n#endif\n"
  },
  {
    "path": "MemorySwapMeter.c",
    "content": "/*\nhtop - MemorySwapMeter.c\n(C) 2021 htop dev team\nReleased under the GNU GPLv2+, see the COPYING file\nin the source distribution for its full text.\n*/\n\n#include \"config.h\" // IWYU pragma: keep\n\n#include \"MemorySwapMeter.h\"\n\n#include <assert.h>\n#include <stdbool.h>\n#include <stddef.h>\n#include <stdlib.h>\n\n#include \"Macros.h\"\n#include \"MemoryMeter.h\"\n#include \"Object.h\"\n#include \"SwapMeter.h\"\n#include \"XUtils.h\"\n\n\ntypedef struct MemorySwapMeterData_ {\n   Meter* memoryMeter;\n   Meter* swapMeter;\n} MemorySwapMeterData;\n\nstatic void MemorySwapMeter_updateValues(Meter* this) {\n   MemorySwapMeterData* data = this->meterData;\n\n   Meter_updateValues(data->memoryMeter);\n   Meter_updateValues(data->swapMeter);\n}\n\nstatic void MemorySwapMeter_draw(Meter* this, int x, int y, int w) {\n   MemorySwapMeterData* data = this->meterData;\n\n   /* Use the same width for each sub meter to align with CPU meter */\n   const int colwidth = w / 2;\n   const int diff = w % 2;\n\n   assert(data->memoryMeter->draw);\n   data->memoryMeter->draw(data->memoryMeter, x, y, colwidth);\n   assert(data->swapMeter->draw);\n   data->swapMeter->draw(data->swapMeter, x + colwidth + diff, y, colwidth);\n}\n\nstatic void MemorySwapMeter_init(Meter* this) {\n   if (!this->meterData)\n      this->meterData = xCalloc(1, sizeof(MemorySwapMeterData));\n\n   MemorySwapMeterData* data = this->meterData;\n\n   if (!data->memoryMeter)\n      data->memoryMeter = Meter_new(this->host, 0, (const MeterClass*) Class(MemoryMeter));\n   if (!data->swapMeter)\n      data->swapMeter = Meter_new(this->host, 0, (const MeterClass*) Class(SwapMeter));\n\n   if (Meter_initFn(data->memoryMeter)) {\n      Meter_init(data->memoryMeter);\n   }\n   if (Meter_initFn(data->swapMeter)) {\n      Meter_init(data->swapMeter);\n   }\n}\n\nstatic void MemorySwapMeter_updateMode(Meter* this, MeterModeId mode) {\n   MemorySwapMeterData* data = this->meterData;\n\n   this->mode = mode;\n\n   Meter_setMode(data->memoryMeter, mode);\n   Meter_setMode(data->swapMeter, mode);\n\n   this->h = MAXIMUM(data->memoryMeter->h, data->swapMeter->h);\n}\n\nstatic void MemorySwapMeter_done(Meter* this) {\n   MemorySwapMeterData* data = this->meterData;\n\n   Meter_delete((Object*)data->swapMeter);\n   Meter_delete((Object*)data->memoryMeter);\n\n   free(data);\n}\n\nconst MeterClass MemorySwapMeter_class = {\n   .super = {\n      .extends = Class(Meter),\n      .delete = Meter_delete,\n   },\n   .updateValues = MemorySwapMeter_updateValues,\n   .defaultMode = BAR_METERMODE,\n   .supportedModes = METERMODE_DEFAULT_SUPPORTED,\n   .isMultiColumn = true,\n   .name = \"MemorySwap\",\n   .uiName = \"Memory & Swap\",\n   .description = \"Combined memory and swap usage\",\n   .caption = \"M&S\",\n   .draw = MemorySwapMeter_draw,\n   .init = MemorySwapMeter_init,\n   .updateMode = MemorySwapMeter_updateMode,\n   .done = MemorySwapMeter_done\n};\n"
  },
  {
    "path": "MemorySwapMeter.h",
    "content": "#ifndef HEADER_MemorySwapMeter\n#define HEADER_MemorySwapMeter\n/*\nhtop - MemorySwapMeter.h\n(C) 2021 htop dev team\nReleased under the GNU GPLv2+, see the COPYING file\nin the source distribution for its full text.\n*/\n\n#include \"Meter.h\"\n\n\nextern const MeterClass MemorySwapMeter_class;\n\n#endif\n"
  },
  {
    "path": "Meter.c",
    "content": "/*\nhtop - Meter.c\n(C) 2004-2011 Hisham H. Muhammad\nReleased under the GNU GPLv2+, see the COPYING file\nin the source distribution for its full text.\n*/\n\n#include \"config.h\" // IWYU pragma: keep\n\n#include \"Meter.h\"\n\n#include <assert.h>\n#include <float.h>\n#include <limits.h> // IWYU pragma: keep\n#include <math.h>\n#include <stdlib.h>\n#include <string.h>\n\n#include \"CRT.h\"\n#include \"Macros.h\"\n#include \"Object.h\"\n#include \"ProvideCurses.h\"\n#include \"RichString.h\"\n#include \"Row.h\"\n#include \"Settings.h\"\n#include \"XUtils.h\"\n\n\n#ifndef UINT32_WIDTH\n#define UINT32_WIDTH 32\n#endif\n\n#define DEFAULT_GRAPH_HEIGHT 4 /* Unit: rows (lines) */\n\ntypedef struct MeterMode_ {\n   Meter_Draw draw;\n   const char* uiName;\n   int h;\n} MeterMode;\n\n/* Meter drawing modes */\n\nstatic inline void Meter_displayBuffer(const Meter* this, RichString* out) {\n   if (Object_displayFn(this)) {\n      Object_display(this, out);\n   } else {\n      RichString_writeWide(out, CRT_colors[Meter_attributes(this)[0]], this->txtBuffer);\n   }\n}\n\nstatic double Meter_computeSum(const Meter* this) {\n   assert(this->curItems > 0);\n   assert(this->values);\n   double sum = sumPositiveValues(this->values, this->curItems);\n   // Prevent rounding to infinity in IEEE 754\n   return MINIMUM(DBL_MAX, sum);\n}\n\n/* ---------- TextMeterMode ---------- */\n\nstatic void TextMeterMode_draw(Meter* this, int x, int y, int w) {\n   assert(x >= 0);\n   assert(w <= INT_MAX - x);\n\n   const char* caption = Meter_getCaption(this);\n   if (w > 0) {\n      attrset(CRT_colors[METER_TEXT]);\n      mvaddnstr(y, x, caption, w);\n   }\n   attrset(CRT_colors[RESET_COLOR]);\n\n   int captionWidth = w > 0 ? (int)strnlen(caption, w) : 0;\n   if (w <= captionWidth) {\n      return;\n   }\n   w -= captionWidth;\n   x += captionWidth;\n\n   RichString_begin(out);\n   Meter_displayBuffer(this, &out);\n   RichString_printoffnVal(out, y, x, 0, w);\n   RichString_delete(&out);\n}\n\n/* ---------- BarMeterMode ---------- */\n\nstatic const char BarMeterMode_characters[] = \"|#*@$%&.\";\n\nstatic void BarMeterMode_draw(Meter* this, int x, int y, int w) {\n   assert(x >= 0);\n   assert(w <= INT_MAX - x);\n\n   // Draw the caption\n   int captionLen = 3;\n   const char* caption = Meter_getCaption(this);\n   if (w >= captionLen) {\n      attrset(CRT_colors[METER_TEXT]);\n      mvaddnstr(y, x, caption, captionLen);\n   }\n   w -= captionLen;\n\n   // Draw the bar borders\n   if (w >= 1) {\n      x += captionLen;\n      attrset(CRT_colors[BAR_BORDER]);\n      mvaddch(y, x, '[');\n      w--;\n      mvaddch(y, x + w, ']');\n      w--;\n   }\n\n   // Update the \"total\" if necessary\n   if (!Meter_isPercentChart(this) && this->curItems > 0) {\n      double sum = Meter_computeSum(this);\n      this->total = MAXIMUM(sum, this->total);\n   }\n\n   if (w < 1) {\n      goto end;\n   }\n   attrset(CRT_colors[RESET_COLOR]); // Clear the bold attribute\n   x++;\n\n   // The text in the bar is right aligned;\n   // Pad with maximal spaces and then calculate needed starting position offset\n   RichString_begin(bar);\n   RichString_appendChr(&bar, 0, ' ', w);\n   RichString_appendWide(&bar, 0, this->txtBuffer);\n\n   int startPos = RichString_sizeVal(bar) - w;\n   if (startPos > w) {\n      // Text is too large for bar\n      // Truncate meter text at a space character\n      for (int pos = 2 * w; pos > w; pos--) {\n         if (RichString_getCharVal(bar, pos) == ' ') {\n            while (pos > w && RichString_getCharVal(bar, pos - 1) == ' ')\n               pos--;\n            startPos = pos - w;\n            break;\n         }\n      }\n\n      // If still too large, print the start not the end\n      startPos = MINIMUM(startPos, w);\n   }\n\n   assert(startPos >= 0);\n   assert(startPos <= w);\n   assert(startPos + w <= RichString_sizeVal(bar));\n\n   int blockSizes[10];\n\n   // First draw in the bar[] buffer...\n   int offset = 0;\n   for (uint8_t i = 0; i < this->curItems; i++) {\n      double value = this->values[i];\n      if (isPositive(value) && this->total > 0.0) {\n         value = MINIMUM(value, this->total);\n         blockSizes[i] = ceil((value / this->total) * w);\n         blockSizes[i] = MINIMUM(blockSizes[i], w - offset);\n      } else {\n         blockSizes[i] = 0;\n      }\n      int nextOffset = offset + blockSizes[i];\n      for (int j = offset; j < nextOffset; j++)\n         if (RichString_getCharVal(bar, startPos + j) == ' ') {\n            if (CRT_colorScheme == COLORSCHEME_MONOCHROME) {\n               assert(i < strlen(BarMeterMode_characters));\n               RichString_setChar(&bar, startPos + j, BarMeterMode_characters[i]);\n            } else {\n               RichString_setChar(&bar, startPos + j, '|');\n            }\n         }\n      offset = nextOffset;\n   }\n\n   // ...then print the buffer.\n   offset = 0;\n   for (uint8_t i = 0; i < this->curItems; i++) {\n      int attr = this->curAttributes ? this->curAttributes[i] : Meter_attributes(this)[i];\n      RichString_setAttrn(&bar, CRT_colors[attr], startPos + offset, blockSizes[i]);\n      RichString_printoffnVal(bar, y, x + offset, startPos + offset, blockSizes[i]);\n      offset += blockSizes[i];\n   }\n   if (offset < w) {\n      RichString_setAttrn(&bar, CRT_colors[BAR_SHADOW], startPos + offset, w - offset);\n      RichString_printoffnVal(bar, y, x + offset, startPos + offset, w - offset);\n   }\n\n   RichString_delete(&bar);\n\n   move(y, x + w + 1);\n\nend:\n   attrset(CRT_colors[RESET_COLOR]);\n}\n\n/* ---------- GraphMeterMode ---------- */\n\n#ifdef HAVE_LIBNCURSESW\n\n#define PIXPERROW_UTF8 4\nstatic const char* const GraphMeterMode_dotsUtf8[] = {\n   /*00*/\" \", /*01*/\"⢀\", /*02*/\"⢠\", /*03*/\"⢰\", /*04*/ \"⢸\",\n   /*10*/\"⡀\", /*11*/\"⣀\", /*12*/\"⣠\", /*13*/\"⣰\", /*14*/ \"⣸\",\n   /*20*/\"⡄\", /*21*/\"⣄\", /*22*/\"⣤\", /*23*/\"⣴\", /*24*/ \"⣼\",\n   /*30*/\"⡆\", /*31*/\"⣆\", /*32*/\"⣦\", /*33*/\"⣶\", /*34*/ \"⣾\",\n   /*40*/\"⡇\", /*41*/\"⣇\", /*42*/\"⣧\", /*43*/\"⣷\", /*44*/ \"⣿\"\n};\n\n#endif\n\n#define PIXPERROW_ASCII 2\nstatic const char* const GraphMeterMode_dotsAscii[] = {\n   /*00*/\" \", /*01*/\".\", /*02*/\":\",\n   /*10*/\".\", /*11*/\".\", /*12*/\":\",\n   /*20*/\":\", /*21*/\":\", /*22*/\":\"\n};\n\nstatic void GraphMeterMode_draw(Meter* this, int x, int y, int w) {\n   assert(x >= 0);\n   assert(w <= INT_MAX - x);\n\n   // Draw the caption\n   const int captionLen = 3;\n   const char* caption = Meter_getCaption(this);\n   if (w >= captionLen) {\n      attrset(CRT_colors[METER_TEXT]);\n      mvaddnstr(y, x, caption, captionLen);\n   }\n   w -= captionLen;\n\n   // Prepare parameters for drawing\n   assert(this->h >= 1);\n   int h = this->h;\n\n   bool isPercentChart = Meter_isPercentChart(this);\n\n   GraphData* data = &this->drawData;\n\n   // Expand the graph data buffer if necessary\n   assert(data->nValues / 2 <= INT_MAX);\n   if (w > (int)(data->nValues / 2) && MAX_METER_GRAPHDATA_VALUES > data->nValues) {\n      size_t oldNValues = data->nValues;\n      data->nValues = MAXIMUM(oldNValues + oldNValues / 2, (size_t)w * 2);\n      data->nValues = MINIMUM(data->nValues, MAX_METER_GRAPHDATA_VALUES);\n      data->values = xReallocArray(data->values, data->nValues, sizeof(*data->values));\n      memmove(data->values + (data->nValues - oldNValues), data->values, oldNValues * sizeof(*data->values));\n      memset(data->values, 0, (data->nValues - oldNValues) * sizeof(*data->values));\n   }\n\n   const size_t nValues = data->nValues;\n   if (nValues < 1)\n      goto end;\n\n   // Record new value if necessary\n   const Machine* host = this->host;\n   if (!timercmp(&host->realtime, &(data->time), <)) {\n      int globalDelay = host->settings->delay;\n      struct timeval delay = { .tv_sec = globalDelay / 10, .tv_usec = (globalDelay % 10) * 100000L };\n      timeradd(&host->realtime, &delay, &(data->time));\n\n      memmove(&data->values[0], &data->values[1], (nValues - 1) * sizeof(*data->values));\n\n      data->values[nValues - 1] = 0.0;\n      if (this->curItems > 0) {\n         data->values[nValues - 1] = Meter_computeSum(this);\n         if (isPercentChart && this->total > 0.0) {\n            data->values[nValues - 1] /= this->total;\n         }\n      }\n   }\n\n   if (w < 1) {\n      goto end;\n   }\n   x += captionLen;\n\n   // Graph drawing style (character set, etc.)\n   const char* const* GraphMeterMode_dots;\n   int GraphMeterMode_pixPerRow;\n#ifdef HAVE_LIBNCURSESW\n   if (CRT_utf8) {\n      GraphMeterMode_dots = GraphMeterMode_dotsUtf8;\n      GraphMeterMode_pixPerRow = PIXPERROW_UTF8;\n   } else\n#endif\n   {\n      GraphMeterMode_dots = GraphMeterMode_dotsAscii;\n      GraphMeterMode_pixPerRow = PIXPERROW_ASCII;\n   }\n\n   // Starting positions of graph data and terminal column\n   if ((size_t)w > nValues / 2) {\n      x += w - nValues / 2;\n      w = (int)(nValues / 2);\n   }\n   size_t i = nValues - (size_t)w * 2;\n\n   // Determine the graph scale\n   double total = 1.0;\n   if (!isPercentChart) {\n      for (size_t j = i; j < nValues; j++) {\n         total = MAXIMUM(data->values[j], total);\n      }\n      assert(total <= DBL_MAX);\n   }\n   assert(total >= 1.0);\n\n   // Draw the actual graph\n   for (int col = 0; i < nValues - 1; i += 2, col++) {\n      int pix = GraphMeterMode_pixPerRow * h;\n      int v1 = (int) lround(CLAMP(data->values[i] / total * pix, 1.0, pix));\n      int v2 = (int) lround(CLAMP(data->values[i + 1] / total * pix, 1.0, pix));\n\n      int colorIdx = GRAPH_1;\n      for (int line = 0; line < h; line++) {\n         int line1 = CLAMP(v1 - (GraphMeterMode_pixPerRow * (h - 1 - line)), 0, GraphMeterMode_pixPerRow);\n         int line2 = CLAMP(v2 - (GraphMeterMode_pixPerRow * (h - 1 - line)), 0, GraphMeterMode_pixPerRow);\n\n         attrset(CRT_colors[colorIdx]);\n         mvaddstr(y + line, x + col, GraphMeterMode_dots[line1 * (GraphMeterMode_pixPerRow + 1) + line2]);\n         colorIdx = GRAPH_2;\n      }\n   }\n\nend:\n   attrset(CRT_colors[RESET_COLOR]);\n}\n\n/* ---------- LEDMeterMode ---------- */\n\nstatic const char* const LEDMeterMode_digitsAscii[] = {\n   \" __ \", \"    \", \" __ \", \" __ \", \"    \", \" __ \", \" __ \", \" __ \", \" __ \", \" __ \",\n   \"|  |\", \"   |\", \" __|\", \" __|\", \"|__|\", \"|__ \", \"|__ \", \"   |\", \"|__|\", \"|__|\",\n   \"|__|\", \"   |\", \"|__ \", \" __|\", \"   |\", \" __|\", \"|__|\", \"   |\", \"|__|\", \" __|\"\n};\n\n#ifdef HAVE_LIBNCURSESW\n\nstatic const char* const LEDMeterMode_digitsUtf8[] = {\n   \"┌──┐\", \"  ┐ \", \"╶──┐\", \"╶──┐\", \"╷  ╷\", \"┌──╴\", \"┌──╴\", \"╶──┐\", \"┌──┐\", \"┌──┐\",\n   \"│  │\", \"  │ \", \"┌──┘\", \" ──┤\", \"└──┤\", \"└──┐\", \"├──┐\", \"   │\", \"├──┤\", \"└──┤\",\n   \"└──┘\", \"  ╵ \", \"└──╴\", \"╶──┘\", \"   ╵\", \"╶──┘\", \"└──┘\", \"   ╵\", \"└──┘\", \"╶──┘\"\n};\n\n#endif\n\nstatic const char* const* LEDMeterMode_digits;\n\nstatic void LEDMeterMode_drawDigit(int x, int y, int n) {\n   for (int i = 0; i < 3; i++)\n      mvaddstr(y + i, x, LEDMeterMode_digits[i * 10 + n]);\n}\n\nstatic void LEDMeterMode_draw(Meter* this, int x, int y, int w) {\n   assert(x >= 0);\n   assert(w <= INT_MAX - x);\n\n   int yText =\n#ifdef HAVE_LIBNCURSESW\n      CRT_utf8 ? y + 1 :\n#endif\n      y + 2;\n   attrset(CRT_colors[LED_COLOR]);\n\n   const char* caption = Meter_getCaption(this);\n   if (w > 0) {\n      mvaddnstr(yText, x, caption, w);\n   }\n\n   int captionWidth = w > 0 ? (int)strnlen(caption, w) : 0;\n   if (w <= captionWidth) {\n      goto end;\n   }\n   int xx = x + captionWidth;\n\n#ifdef HAVE_LIBNCURSESW\n   if (CRT_utf8)\n      LEDMeterMode_digits = LEDMeterMode_digitsUtf8;\n   else\n#endif\n      LEDMeterMode_digits = LEDMeterMode_digitsAscii;\n\n   RichString_begin(out);\n   Meter_displayBuffer(this, &out);\n\n   int len = RichString_sizeVal(out);\n   for (int i = 0; i < len; i++) {\n      int c = RichString_getCharVal(out, i);\n      if (c >= '0' && c <= '9') {\n         if (xx > x + w - 4)\n            break;\n\n         LEDMeterMode_drawDigit(xx, y, c - '0');\n         xx += 4;\n      } else {\n         if (xx > x + w - 1)\n            break;\n#ifdef HAVE_LIBNCURSESW\n         const cchar_t wc = { .chars = { c, '\\0' }, .attr = 0 }; /* use LED_COLOR from attrset() */\n         mvadd_wch(yText, xx, &wc);\n#else\n         mvaddch(yText, xx, c);\n#endif\n         xx += 1;\n      }\n   }\n   RichString_delete(&out);\n\nend:\n   attrset(CRT_colors[RESET_COLOR]);\n}\n\nstatic const MeterMode Meter_modes[] = {\n   [0] = {\n      .uiName = NULL,\n      .h = 0,\n      .draw = NULL,\n   },\n   [BAR_METERMODE] = {\n      .uiName = \"Bar\",\n      .h = 1,\n      .draw = BarMeterMode_draw,\n   },\n   [TEXT_METERMODE] = {\n      .uiName = \"Text\",\n      .h = 1,\n      .draw = TextMeterMode_draw,\n   },\n   [GRAPH_METERMODE] = {\n      .uiName = \"Graph\",\n      .h = DEFAULT_GRAPH_HEIGHT,\n      .draw = GraphMeterMode_draw,\n   },\n   [LED_METERMODE] = {\n      .uiName = \"LED\",\n      .h = 3,\n      .draw = LEDMeterMode_draw,\n   },\n};\n\n/* Meter class and methods */\n\nconst MeterClass Meter_class = {\n   .super = {\n      .extends = Class(Object)\n   }\n};\n\nMeter* Meter_new(const Machine* host, unsigned int param, const MeterClass* type) {\n   Meter* this = xCalloc(1, sizeof(Meter));\n   Object_setClass(this, type);\n   this->h = 1;\n   this->param = param;\n   this->host = host;\n   this->curItems = type->maxItems;\n   this->curAttributes = NULL;\n   this->values = type->maxItems ? xCalloc(type->maxItems, sizeof(double)) : NULL;\n   this->total = type->total;\n   this->caption = xStrdup(type->caption);\n   if (Meter_initFn(this)) {\n      Meter_init(this);\n   }\n\n   Meter_setMode(this, type->defaultMode);\n   assert(this->mode > 0);\n   return this;\n}\n\n/* Converts 'value' in kibibytes into a human readable string.\n   Example output strings: \"0K\", \"1023K\", \"98.7M\" and \"1.23G\" */\nint Meter_humanUnit(char* buffer, double value, size_t size) {\n   size_t i = 0;\n\n   assert(value >= 0.0);\n   while (value >= ONE_K) {\n      if (i >= ARRAYSIZE(unitPrefixes) - 1) {\n         if (value > 9999.0) {\n            return xSnprintf(buffer, size, \"inf\");\n         }\n         break;\n      }\n\n      value /= ONE_K;\n      ++i;\n   }\n\n   int precision = 0;\n\n   if (i > 0) {\n      // Fraction digits for mebibytes and above\n      precision = value <= 99.9 ? (value <= 9.99 ? 2 : 1) : 0;\n\n      // Round up if 'value' is in range (99.9, 100) or (9.99, 10)\n      if (precision < 2) {\n         double limit = precision == 1 ? 10.0 : 100.0;\n         if (value < limit) {\n            value = limit;\n         }\n      }\n   }\n\n   return xSnprintf(buffer, size, \"%.*f%c\", precision, value, unitPrefixes[i]);\n}\n\nvoid Meter_delete(Object* cast) {\n   if (!cast)\n      return;\n\n   Meter* this = (Meter*) cast;\n   if (Meter_doneFn(this)) {\n      Meter_done(this);\n   }\n   free(this->drawData.values);\n   free(this->caption);\n   free(this->values);\n   free(this);\n}\n\nvoid Meter_setCaption(Meter* this, const char* caption) {\n   free_and_xStrdup(&this->caption, caption);\n}\n\nvoid Meter_setMode(Meter* this, MeterModeId modeIndex) {\n   if (modeIndex == this->mode) {\n      assert(this->mode > 0);\n      return;\n   }\n\n   uint32_t supportedModes = Meter_supportedModes(this);\n   assert(supportedModes);\n   assert(!(supportedModes & (1 << 0)));\n\n   assert(LAST_METERMODE <= UINT32_WIDTH);\n   if (modeIndex >= LAST_METERMODE || !(supportedModes & (1UL << modeIndex)))\n      return;\n\n   assert(modeIndex >= 1);\n   if (Meter_updateModeFn(this)) {\n      assert(Meter_drawFn(this));\n      this->draw = Meter_drawFn(this);\n      Meter_updateMode(this, modeIndex);\n   } else {\n      free(this->drawData.values);\n      this->drawData.values = NULL;\n      this->drawData.nValues = 0;\n\n      const MeterMode* mode = &Meter_modes[modeIndex];\n      this->draw = mode->draw;\n      this->h = mode->h;\n   }\n   this->mode = modeIndex;\n}\n\nMeterModeId Meter_nextSupportedMode(const Meter* this) {\n   uint32_t supportedModes = Meter_supportedModes(this);\n   assert(supportedModes);\n\n   assert(this->mode < UINT32_WIDTH);\n   uint32_t modeMask = ((uint32_t)-1 << 1) << this->mode;\n   uint32_t nextModes = supportedModes & modeMask;\n   if (!nextModes) {\n      nextModes = supportedModes;\n   }\n\n   return (MeterModeId)countTrailingZeros(nextModes);\n}\n\nListItem* Meter_toListItem(const Meter* this, bool moving) {\n   char mode[20];\n   if (this->mode > 0) {\n      xSnprintf(mode, sizeof(mode), \" [%s]\", Meter_modes[this->mode].uiName);\n   } else {\n      mode[0] = '\\0';\n   }\n   char name[32];\n   if (Meter_getUiNameFn(this))\n      Meter_getUiName(this, name, sizeof(name));\n   else\n      xSnprintf(name, sizeof(name), \"%s\", Meter_uiName(this));\n   char buffer[50];\n   xSnprintf(buffer, sizeof(buffer), \"%s%s\", name, mode);\n   ListItem* li = ListItem_new(buffer, 0);\n   li->moving = moving;\n   return li;\n}\n\n/* Blank meter */\n\nstatic void BlankMeter_updateValues(Meter* this) {\n   this->txtBuffer[0] = '\\0';\n}\n\nstatic void BlankMeter_display(ATTR_UNUSED const Object* cast, ATTR_UNUSED RichString* out) {\n}\n\nstatic const int BlankMeter_attributes[] = {\n   DEFAULT_COLOR\n};\n\nconst MeterClass BlankMeter_class = {\n   .super = {\n      .extends = Class(Meter),\n      .delete = Meter_delete,\n      .display = BlankMeter_display,\n   },\n   .updateValues = BlankMeter_updateValues,\n   .defaultMode = TEXT_METERMODE,\n   .supportedModes = (1 << TEXT_METERMODE),\n   .maxItems = 0,\n   .total = 0.0,\n   .attributes = BlankMeter_attributes,\n   .name = \"Blank\",\n   .uiName = \"Blank\",\n   .caption = \"\"\n};\n"
  },
  {
    "path": "Meter.h",
    "content": "#ifndef HEADER_Meter\n#define HEADER_Meter\n/*\nhtop - Meter.h\n(C) 2004-2011 Hisham H. Muhammad\nReleased under the GNU GPLv2+, see the COPYING file\nin the source distribution for its full text.\n*/\n\n#include <stdbool.h>\n#include <stddef.h>\n#include <stdint.h>\n#include <sys/time.h>\n\n#include \"ListItem.h\"\n#include \"Machine.h\"\n#include \"Macros.h\"\n#include \"MeterMode.h\"\n#include \"Object.h\"\n\n\n#define METER_TXTBUFFER_LEN 256\n#define MAX_METER_GRAPHDATA_VALUES 32768\n\n#define METER_BUFFER_CHECK(buffer, size, written)          \\\n   do {                                                    \\\n      if ((written) < 0 || (size_t)(written) >= (size)) {  \\\n         return;                                           \\\n      }                                                    \\\n      (buffer) += (written);                               \\\n      (size) -= (size_t)(written);                         \\\n   } while (0)\n\n#define METER_BUFFER_APPEND_CHR(buffer, size, c)           \\\n   do {                                                    \\\n      if ((size) < 2) {                                    \\\n         return;                                           \\\n      }                                                    \\\n      *(buffer)++ = c;                                     \\\n      *(buffer) = '\\0';                                    \\\n      (size)--;                                            \\\n      if ((size) == 0) {                                   \\\n         return;                                           \\\n      }                                                    \\\n   } while (0)\n\n\nstruct Meter_;\ntypedef struct Meter_ Meter;\n\ntypedef ATTR_NONNULL void (*Meter_Init)(Meter*);\ntypedef ATTR_NONNULL void (*Meter_Done)(Meter*);\ntypedef ATTR_NONNULL void (*Meter_UpdateMode)(Meter*, MeterModeId);\ntypedef ATTR_NONNULL void (*Meter_UpdateValues)(Meter*);\ntypedef ATTR_NONNULL void (*Meter_Draw)(Meter*, int, int, int);\ntypedef ATTR_NONNULL const char* (*Meter_GetCaption)(const Meter*);\ntypedef ATTR_NONNULL ATTR_ACCESS3_W(2, 3) void (*Meter_GetUiName)(const Meter*, char*, size_t);\n\ntypedef struct MeterClass_ {\n   const ObjectClass super;\n   const Meter_Init init;\n   const Meter_Done done;\n   const Meter_UpdateMode updateMode;\n   const Meter_UpdateValues updateValues;\n   const Meter_Draw draw;\n   const Meter_GetCaption getCaption;\n   const Meter_GetUiName getUiName;\n   const MeterModeId defaultMode;\n   const uint32_t supportedModes;          /* bitset of supported modes, 1<<mode_id */\n   const double total;\n   const int* const attributes;\n   const char* const name;                 /* internal name of the meter, must not contain any space */\n   const char* const uiName;               /* display name in header setup menu */\n   const char* const caption;              /* prefix in the actual header */\n   const char* const description;          /* optional meter description in header setup menu */\n   const uint8_t maxItems;\n   const bool isMultiColumn;               /* whether the meter draws multiple sub-columns (defaults to false) */\n\n   /* Specifies how the meter is rendered in bar or graph mode:\n      true: a percent bar or graph with 'total' representing 100% or maximum.\n      false: the meter has no definite maximum; 'total' represents initial\n        maximum value while actual maximum is updated automatically. */\n   const bool isPercentChart;\n} MeterClass;\n\n#define As_Meter(this_)                ((const MeterClass*)((this_)->super.klass))\n#define Meter_initFn(this_)            As_Meter(this_)->init\n#define Meter_init(this_)              As_Meter(this_)->init((Meter*)(this_))\n#define Meter_done(this_)              As_Meter(this_)->done((Meter*)(this_))\n#define Meter_updateModeFn(this_)      As_Meter(this_)->updateMode\n#define Meter_updateMode(this_, m_)    As_Meter(this_)->updateMode((Meter*)(this_), m_)\n#define Meter_drawFn(this_)            As_Meter(this_)->draw\n#define Meter_doneFn(this_)            As_Meter(this_)->done\n#define Meter_updateValues(this_)      As_Meter(this_)->updateValues((Meter*)(this_))\n#define Meter_getUiNameFn(this_)       As_Meter(this_)->getUiName\n#define Meter_getUiName(this_,n_,l_)   As_Meter(this_)->getUiName((const Meter*)(this_),n_,l_)\n#define Meter_getCaptionFn(this_)      As_Meter(this_)->getCaption\n#define Meter_getCaption(this_)        (Meter_getCaptionFn(this_) ? As_Meter(this_)->getCaption((const Meter*)(this_)) : (this_)->caption)\n#define Meter_supportedModes(this_)    As_Meter(this_)->supportedModes\n#define Meter_attributes(this_)        As_Meter(this_)->attributes\n#define Meter_name(this_)              As_Meter(this_)->name\n#define Meter_uiName(this_)            As_Meter(this_)->uiName\n#define Meter_isMultiColumn(this_)     As_Meter(this_)->isMultiColumn\n#define Meter_isPercentChart(this_)    As_Meter(this_)->isPercentChart\n\ntypedef struct GraphData_ {\n   struct timeval time;\n   size_t nValues;\n   double* values;\n} GraphData;\n\nstruct Meter_ {\n   Object super;\n   Meter_Draw draw;\n   const Machine* host;\n\n   char* caption;\n   MeterModeId mode;\n   unsigned int param;\n   GraphData drawData;\n   int h;\n   int columnWidthCount;      /**< only used internally by the Header */\n   uint8_t curItems;\n   const int* curAttributes;\n   char txtBuffer[METER_TXTBUFFER_LEN];\n   double* values;\n   double total;\n   void* meterData;\n};\n\ntypedef enum {\n   RATESTATUS_DATA,\n   RATESTATUS_INIT,\n   RATESTATUS_NODATA,\n   RATESTATUS_STALE\n} MeterRateStatus;\n\nextern const MeterClass Meter_class;\n\nMeter* Meter_new(const Machine* host, unsigned int param, const MeterClass* type);\n\n/* Converts 'value' in kibibytes into a human readable string.\n   Example output strings: \"0K\", \"1023K\", \"98.7M\" and \"1.23G\" */\nint Meter_humanUnit(char* buffer, double value, size_t size);\n\nvoid Meter_delete(Object* cast);\n\nvoid Meter_setCaption(Meter* this, const char* caption);\n\nvoid Meter_setMode(Meter* this, MeterModeId modeIndex);\n\nMeterModeId Meter_nextSupportedMode(const Meter* this);\n\nListItem* Meter_toListItem(const Meter* this, bool moving);\n\nextern const MeterClass BlankMeter_class;\n\n#endif\n"
  },
  {
    "path": "MeterMode.h",
    "content": "#ifndef HEADER_MeterMode\n#define HEADER_MeterMode\n/*\nhtop - MeterMode.h\n(C) 2024 htop dev team\nReleased under the GNU GPLv2+, see the COPYING file\nin the source distribution for its full text.\n*/\n\n\nenum MeterModeId_ {\n   /* Meter mode 0 is reserved */\n   BAR_METERMODE = 1,\n   TEXT_METERMODE,\n   GRAPH_METERMODE,\n   LED_METERMODE,\n   LAST_METERMODE\n};\n\ntypedef unsigned int MeterModeId;\n\n#define METERMODE_DEFAULT_SUPPORTED ( \\\n   (1 << BAR_METERMODE) |             \\\n   (1 << TEXT_METERMODE) |            \\\n   (1 << GRAPH_METERMODE) |           \\\n   (1 << LED_METERMODE) |             \\\n   0) // Avoids edits when updating\n\n#endif\n"
  },
  {
    "path": "MetersPanel.c",
    "content": "/*\nhtop - MetersPanel.c\n(C) 2004-2011 Hisham H. Muhammad\nReleased under the GNU GPLv2+, see the COPYING file\nin the source distribution for its full text.\n*/\n\n#include \"config.h\" // IWYU pragma: keep\n\n#include \"MetersPanel.h\"\n\n#include <stdlib.h>\n\n#include \"CRT.h\"\n#include \"FunctionBar.h\"\n#include \"Header.h\"\n#include \"ListItem.h\"\n#include \"Meter.h\"\n#include \"Object.h\"\n#include \"ProvideCurses.h\"\n\n\n// Note: In code the meters are known to have bar/text/graph \"Modes\", but in UI\n// we call them \"Styles\".\nstatic const char* const MetersFunctions[] = {\"Style \", \"Move  \", \"                                       \", \"Delete\", \"Done  \", NULL};\nstatic const char* const MetersKeys[] = {\"Space\", \"Enter\", \"  \", \"Del\", \"F10\"};\nstatic const int MetersEvents[] = {' ', 13, ERR, KEY_DC, KEY_F(10)};\n\n// We avoid UTF-8 arrows ← → here as they might display full-width on Chinese\n// terminals, breaking our aligning.\n// In <http://unicode.org/reports/tr11/>, arrows (U+2019..U+2199) are\n// considered \"Ambiguous characters\".\nstatic const char* const MetersMovingFunctions[] = {\"Style \", \"Lock  \", \"Up    \", \"Down  \", \"Left  \", \"Right \", \"       \", \"Delete\", \"Done  \", NULL};\nstatic const char* const MetersMovingKeys[] = {\"Space\", \"Enter\", \"Up\", \"Dn\", \"<-\", \"->\", \"  \", \"Del\", \"F10\"};\nstatic const int MetersMovingEvents[] = {' ', 13, KEY_UP, KEY_DOWN, KEY_LEFT, KEY_RIGHT, ERR, KEY_DC, KEY_F(10)};\nstatic FunctionBar* Meters_movingBar = NULL;\n\nvoid MetersPanel_cleanup(void) {\n   if (Meters_movingBar) {\n      FunctionBar_delete(Meters_movingBar);\n      Meters_movingBar = NULL;\n   }\n}\n\nstatic void MetersPanel_delete(Object* object) {\n   MetersPanel* this = (MetersPanel*) object;\n   Panel_done(&this->super);\n   free(this);\n}\n\nvoid MetersPanel_setMoving(MetersPanel* this, bool moving) {\n   Panel* super = &this->super;\n   this->moving = moving;\n   ListItem* selected = (ListItem*)Panel_getSelected(super);\n   if (selected) {\n      selected->moving = moving;\n   }\n   if (!moving) {\n      Panel_setSelectionColor(super, PANEL_SELECTION_FOCUS);\n      Panel_setDefaultBar(super);\n   } else {\n      Panel_setSelectionColor(super, PANEL_SELECTION_FOLLOW);\n      super->currentBar = Meters_movingBar;\n   }\n}\n\nstatic inline bool moveToNeighbor(MetersPanel* this, MetersPanel* neighbor, int selected) {\n   Panel* super = &this->super;\n   if (this->moving) {\n      if (neighbor) {\n         if (selected < Vector_size(this->meters)) {\n            MetersPanel_setMoving(this, false);\n\n            Meter* meter = (Meter*) Vector_take(this->meters, selected);\n            Panel_remove(super, selected);\n            Vector_insert(neighbor->meters, selected, meter);\n            Panel_insert(&(neighbor->super), selected, (Object*) Meter_toListItem(meter, false));\n            Panel_setSelected(&(neighbor->super), selected);\n\n            MetersPanel_setMoving(neighbor, true);\n            return true;\n         }\n      }\n   }\n   return false;\n}\n\nstatic HandlerResult MetersPanel_eventHandler(Panel* super, int ch) {\n   MetersPanel* this = (MetersPanel*) super;\n\n   int selected = Panel_getSelectedIndex(super);\n   HandlerResult result = IGNORED;\n   bool sideMove = false;\n\n   switch (ch) {\n      case 0x0a:\n      case 0x0d:\n      case KEY_ENTER:\n         if (!Vector_size(this->meters))\n            break;\n         MetersPanel_setMoving(this, !(this->moving));\n         result = HANDLED;\n         break;\n      case ' ':\n      case KEY_F(4):\n      case 't': {\n         if (!Vector_size(this->meters))\n            break;\n         Meter* meter = (Meter*) Vector_get(this->meters, selected);\n         MeterModeId mode = Meter_nextSupportedMode(meter);\n         Meter_setMode(meter, mode);\n         Panel_set(super, selected, (Object*) Meter_toListItem(meter, this->moving));\n         result = HANDLED;\n         break;\n      }\n      case KEY_UP:\n         if (!this->moving)\n            break;\n         /* else fallthrough */\n      case KEY_F(7):\n      case '[':\n      case '-':\n         Vector_moveUp(this->meters, selected);\n         Panel_moveSelectedUp(super);\n         result = HANDLED;\n         break;\n      case KEY_DOWN:\n         if (!this->moving)\n            break;\n         /* else fallthrough */\n      case KEY_F(8):\n      case ']':\n      case '+':\n         Vector_moveDown(this->meters, selected);\n         Panel_moveSelectedDown(super);\n         result = HANDLED;\n         break;\n      case KEY_RIGHT:\n         sideMove = moveToNeighbor(this, this->rightNeighbor, selected);\n         if (this->moving && !sideMove) {\n            // lock user here until it exits positioning-mode\n            result = HANDLED;\n         }\n         // if user is free, don't set HANDLED;\n         // let ScreenManager handle focus.\n         break;\n      case KEY_LEFT:\n         sideMove = moveToNeighbor(this, this->leftNeighbor, selected);\n         if (this->moving && !sideMove) {\n            result = HANDLED;\n         }\n         break;\n      case KEY_F(9):\n      case KEY_DC:\n      case KEY_DEL_MAC:\n         if (!Vector_size(this->meters))\n            break;\n         if (selected < Vector_size(this->meters)) {\n            Vector_remove(this->meters, selected);\n            Panel_remove(super, selected);\n         }\n         MetersPanel_setMoving(this, false);\n         result = HANDLED;\n         break;\n   }\n\n   if (result == HANDLED || sideMove) {\n      Header* header = this->scr->header;\n      this->settings->changed = true;\n      this->settings->lastUpdate++;\n      Header_calculateHeight(header);\n      ScreenManager_resize(this->scr);\n   }\n\n   return result;\n}\n\nconst PanelClass MetersPanel_class = {\n   .super = {\n      .extends = Class(Panel),\n      .delete = MetersPanel_delete\n   },\n   .eventHandler = MetersPanel_eventHandler\n};\n\nMetersPanel* MetersPanel_new(Settings* settings, const char* header, Vector* meters, ScreenManager* scr) {\n   MetersPanel* this = AllocThis(MetersPanel);\n   Panel* super = &this->super;\n\n   FunctionBar* fuBar = FunctionBar_new(MetersFunctions, MetersKeys, MetersEvents);\n   if (!Meters_movingBar) {\n      Meters_movingBar = FunctionBar_new(MetersMovingFunctions, MetersMovingKeys, MetersMovingEvents);\n   }\n   Panel_init(super, 1, 1, 1, 1, Class(ListItem), true, fuBar);\n\n   this->settings = settings;\n   this->meters = meters;\n   this->scr = scr;\n   this->moving = false;\n   this->rightNeighbor = NULL;\n   this->leftNeighbor = NULL;\n   Panel_setHeader(super, header);\n   for (int i = 0; i < Vector_size(meters); i++) {\n      const Meter* meter = (const Meter*) Vector_get(meters, i);\n      Panel_add(super, (Object*) Meter_toListItem(meter, false));\n   }\n   return this;\n}\n"
  },
  {
    "path": "MetersPanel.h",
    "content": "#ifndef HEADER_MetersPanel\n#define HEADER_MetersPanel\n/*\nhtop - MetersPanel.h\n(C) 2004-2011 Hisham H. Muhammad\nReleased under the GNU GPLv2+, see the COPYING file\nin the source distribution for its full text.\n*/\n\n#include <stdbool.h>\n\n#include \"Panel.h\"\n#include \"ScreenManager.h\"\n#include \"Settings.h\"\n#include \"Vector.h\"\n\n\nstruct MetersPanel_;\ntypedef struct MetersPanel_ MetersPanel;\n\nstruct MetersPanel_ {\n   Panel super;\n\n   Settings* settings;\n   Vector* meters;\n   ScreenManager* scr;\n   MetersPanel* leftNeighbor;\n   MetersPanel* rightNeighbor;\n   bool moving;\n};\n\nvoid MetersPanel_cleanup(void);\n\nvoid MetersPanel_setMoving(MetersPanel* this, bool moving);\n\nextern const PanelClass MetersPanel_class;\n\nMetersPanel* MetersPanel_new(Settings* settings, const char* header, Vector* meters, ScreenManager* scr);\n\n#endif\n"
  },
  {
    "path": "NEWS",
    "content": "\nSee the commit history for news of the past.\nSee the bug tracker for news of the future.\nRun the program for news of the present.\n"
  },
  {
    "path": "NetworkIOMeter.c",
    "content": "/*\nhtop - NetworkIOMeter.c\n(C) 2020-2023 htop dev team\nReleased under the GNU GPLv2+, see the COPYING file\nin the source distribution for its full text.\n*/\n\n#include \"config.h\" // IWYU pragma: keep\n\n#include \"NetworkIOMeter.h\"\n\n#include <stdbool.h>\n\n#include \"CRT.h\"\n#include \"Machine.h\"\n#include \"Macros.h\"\n#include \"Meter.h\"\n#include \"Object.h\"\n#include \"Platform.h\"\n#include \"RichString.h\"\n#include \"Row.h\"\n#include \"XUtils.h\"\n\n\nstatic const int NetworkIOMeter_attributes[] = {\n   METER_VALUE_IOREAD,\n   METER_VALUE_IOWRITE,\n};\n\nstatic MeterRateStatus status = RATESTATUS_INIT;\nstatic double cached_rxb_diff;\nstatic char cached_rxb_diff_str[6];\nstatic uint32_t cached_rxp_diff;\nstatic double cached_txb_diff;\nstatic char cached_txb_diff_str[6];\nstatic uint32_t cached_txp_diff;\n\nstatic void NetworkIOMeter_updateValues(Meter* this) {\n   const Machine* host = this->host;\n\n   static uint64_t cached_last_update = 0;\n   uint64_t passedTimeInMs = host->realtimeMs - cached_last_update;\n   bool hasNewData = false;\n   NetworkIOData data = {0};\n\n   /* update only every 500ms to have a sane span for rate calculation */\n   if (passedTimeInMs > 500) {\n      hasNewData = Platform_getNetworkIO(&data);\n      if (!hasNewData) {\n         status = RATESTATUS_NODATA;\n      } else if (cached_last_update == 0) {\n         status = RATESTATUS_INIT;\n      } else if (passedTimeInMs > 30000) {\n         status = RATESTATUS_STALE;\n      } else {\n         status = RATESTATUS_DATA;\n      }\n\n      cached_last_update = host->realtimeMs;\n   }\n\n   if (hasNewData) {\n      static uint64_t cached_rxb_total;\n      static uint64_t cached_rxp_total;\n      static uint64_t cached_txb_total;\n      static uint64_t cached_txp_total;\n\n      if (status != RATESTATUS_INIT) {\n         uint64_t diff;\n\n         if (data.bytesReceived > cached_rxb_total) {\n            diff = data.bytesReceived - cached_rxb_total;\n            diff = (1000 * diff) / passedTimeInMs; /* convert to B/s */\n            cached_rxb_diff = diff;\n         } else {\n            cached_rxb_diff = 0;\n         }\n         Meter_humanUnit(cached_rxb_diff_str, cached_rxb_diff / ONE_K, sizeof(cached_rxb_diff_str));\n\n         if (data.packetsReceived > cached_rxp_total) {\n            diff = data.packetsReceived - cached_rxp_total;\n            diff = (1000 * diff) / passedTimeInMs; /* convert to pkt/s */\n            cached_rxp_diff = (uint32_t)diff;\n         } else {\n            cached_rxp_diff = 0;\n         }\n\n         if (data.bytesTransmitted > cached_txb_total) {\n            diff = data.bytesTransmitted - cached_txb_total;\n            diff = (1000 * diff) / passedTimeInMs; /* convert to B/s */\n            cached_txb_diff = diff;\n         } else {\n            cached_txb_diff = 0;\n         }\n         Meter_humanUnit(cached_txb_diff_str, cached_txb_diff / ONE_K, sizeof(cached_txb_diff_str));\n\n         if (data.packetsTransmitted > cached_txp_total) {\n            diff = data.packetsTransmitted - cached_txp_total;\n            diff = (1000 * diff) / passedTimeInMs; /* convert to pkt/s */\n            cached_txp_diff = (uint32_t)diff;\n         } else {\n            cached_txp_diff = 0;\n         }\n      }\n\n      cached_rxb_total = data.bytesReceived;\n      cached_rxp_total = data.packetsReceived;\n      cached_txb_total = data.bytesTransmitted;\n      cached_txp_total = data.packetsTransmitted;\n   }\n\n   this->values[0] = cached_rxb_diff;\n   this->values[1] = cached_txb_diff;\n\n   if (status == RATESTATUS_NODATA) {\n      xSnprintf(this->txtBuffer, sizeof(this->txtBuffer), \"no data\");\n      return;\n   }\n   if (status == RATESTATUS_INIT) {\n      xSnprintf(this->txtBuffer, sizeof(this->txtBuffer), \"init\");\n      return;\n   }\n   if (status == RATESTATUS_STALE) {\n      xSnprintf(this->txtBuffer, sizeof(this->txtBuffer), \"stale\");\n      return;\n   }\n\n   xSnprintf(this->txtBuffer, sizeof(this->txtBuffer), \"rx:%siB/s tx:%siB/s (%u/%upps)\",\n      cached_rxb_diff_str, cached_txb_diff_str, cached_rxp_diff, cached_txp_diff);\n}\n\nstatic void NetworkIOMeter_display(ATTR_UNUSED const Object* cast, RichString* out) {\n   switch (status) {\n      case RATESTATUS_NODATA:\n         RichString_writeAscii(out, CRT_colors[METER_VALUE_ERROR], \"no data\");\n         return;\n      case RATESTATUS_INIT:\n         RichString_writeAscii(out, CRT_colors[METER_VALUE], \"initializing...\");\n         return;\n      case RATESTATUS_STALE:\n         RichString_writeAscii(out, CRT_colors[METER_VALUE_WARN], \"stale data\");\n         return;\n      case RATESTATUS_DATA:\n         break;\n   }\n\n   char buffer[64];\n\n   RichString_writeAscii(out, CRT_colors[METER_TEXT], \"rx: \");\n   RichString_appendAscii(out, CRT_colors[METER_VALUE_IOREAD], cached_rxb_diff_str);\n   RichString_appendAscii(out, CRT_colors[METER_VALUE_IOREAD], \"iB/s\");\n\n   RichString_appendAscii(out, CRT_colors[METER_TEXT], \" tx: \");\n   RichString_appendAscii(out, CRT_colors[METER_VALUE_IOWRITE], cached_txb_diff_str);\n   RichString_appendAscii(out, CRT_colors[METER_VALUE_IOWRITE], \"iB/s\");\n\n   RichString_appendAscii(out, CRT_colors[METER_TEXT], \" (\");\n   int len = xSnprintf(buffer, sizeof(buffer), \"%u\", (unsigned int)cached_rxp_diff);\n   RichString_appendnAscii(out, CRT_colors[METER_VALUE_IOREAD], buffer, len);\n   RichString_appendAscii(out, CRT_colors[METER_TEXT], \"/\");\n   len = xSnprintf(buffer, sizeof(buffer), \"%u\", (unsigned int)cached_txp_diff);\n   RichString_appendnAscii(out, CRT_colors[METER_VALUE_IOWRITE], buffer, len);\n   RichString_appendAscii(out, CRT_colors[METER_TEXT], \" pps)\");\n}\n\nconst MeterClass NetworkIOMeter_class = {\n   .super = {\n      .extends = Class(Meter),\n      .delete = Meter_delete,\n      .display = NetworkIOMeter_display\n   },\n   .updateValues = NetworkIOMeter_updateValues,\n   .defaultMode = TEXT_METERMODE,\n   .supportedModes = METERMODE_DEFAULT_SUPPORTED,\n   .maxItems = 2,\n   .isPercentChart = false,\n   .total = 1.0,\n   .attributes = NetworkIOMeter_attributes,\n   .name = \"NetworkIO\",\n   .uiName = \"Network IO\",\n   .description = \"Network bytes & packets received/sent per second\",\n   .caption = \"Network: \"\n};\n"
  },
  {
    "path": "NetworkIOMeter.h",
    "content": "#ifndef HEADER_NetworkIOMeter\n#define HEADER_NetworkIOMeter\n/*\nhtop - NetworkIOMeter.h\n(C) 2020-2023 htop dev team\nReleased under the GNU GPLv2+, see the COPYING file\nin the source distribution for its full text.\n*/\n\n#include <stdint.h>\n\n#include \"Meter.h\"\n\n\ntypedef struct NetworkIOData_ {\n   uint64_t bytesReceived;\n   uint64_t packetsReceived;\n   uint64_t bytesTransmitted;\n   uint64_t packetsTransmitted;\n} NetworkIOData;\n\nextern const MeterClass NetworkIOMeter_class;\n\n#endif /* HEADER_NetworkIOMeter */\n"
  },
  {
    "path": "Object.c",
    "content": "/*\nhtop - Object.c\n(C) 2004-2012 Hisham H. Muhammad\n(C) 2020 Red Hat, Inc.  All Rights Reserved.\nReleased under the GNU GPLv2+, see the COPYING file\nin the source distribution for its full text.\n*/\n\n#include \"config.h\" // IWYU pragma: keep\n\n#include \"Object.h\"\n\n#include <stddef.h>\n\n\nconst ObjectClass Object_class = {\n   .extends = NULL\n};\n\nbool Object_isA(const Object* o, const ObjectClass* klass) {\n   if (!o)\n      return false;\n\n   for (const ObjectClass* type = o->klass; type; type = type->extends) {\n      if (type == klass) {\n         return true;\n      }\n   }\n\n   return false;\n}\n"
  },
  {
    "path": "Object.h",
    "content": "#ifndef HEADER_Object\n#define HEADER_Object\n/*\nhtop - Object.h\n(C) 2004-2012 Hisham H. Muhammad\n(C) 2020 Red Hat, Inc.  All Rights Reserved.\nReleased under the GNU GPLv2+, see the COPYING file\nin the source distribution for its full text.\n*/\n\n#include <assert.h>\n#include <stdbool.h>\n\n#include \"RichString.h\"\n#include \"XUtils.h\" // IWYU pragma: keep\n\n\nstruct Object_;\ntypedef struct Object_ Object;\n\ntypedef void(*Object_Display)(const Object*, RichString*);\ntypedef int(*Object_Compare)(const void*, const void*);\ntypedef void(*Object_Delete)(Object*);\n\n#define Object_getClass(obj_)         ((const Object*)(obj_))->klass\n#define Object_setClass(obj_, class_) (((Object*)(obj_))->klass = (const ObjectClass*) (class_))\n\n#define Object_delete(obj_)           (assert(Object_getClass(obj_)->delete), Object_getClass(obj_)->delete((Object*)(obj_)))\n#define Object_displayFn(obj_)        Object_getClass(obj_)->display\n#define Object_display(obj_, str_)    (assert(Object_getClass(obj_)->display), Object_getClass(obj_)->display((const Object*)(obj_), str_))\n#define Object_compare(obj_, other_)  (assert(Object_getClass(obj_)->compare), Object_getClass(obj_)->compare((const void*)(obj_), other_))\n\n#define Class(class_)                 ((const ObjectClass*)(&(class_ ## _class)))\n\n#define AllocThis(class_) (class_*)   xMalloc(sizeof(class_)); Object_setClass(this, Class(class_))\n\ntypedef struct ObjectClass_ {\n   const void* const extends;\n   const Object_Display display;\n   const Object_Delete delete;\n   const Object_Compare compare;\n} ObjectClass;\n\nstruct Object_ {\n   const ObjectClass* klass;\n};\n\ntypedef union {\n   int i;\n   void* v;\n} Arg;\n\nextern const ObjectClass Object_class;\n\nbool Object_isA(const Object* o, const ObjectClass* klass);\n\n#endif\n"
  },
  {
    "path": "OpenFilesScreen.c",
    "content": "/*\nhtop - OpenFilesScreen.c\n(C) 2005-2006 Hisham H. Muhammad\nReleased under the GNU GPLv2+, see the COPYING file\nin the source distribution for its full text.\n*/\n\n#include \"config.h\" // IWYU pragma: keep\n\n#include \"OpenFilesScreen.h\"\n\n#include <errno.h>\n#include <fcntl.h>\n#include <inttypes.h>\n#include <stdbool.h>\n#include <stdio.h>\n#include <stdlib.h>\n#include <unistd.h>\n#include <sys/types.h>\n#include <sys/wait.h>\n#include <sys/stat.h>\n\n#include \"Macros.h\"\n#include \"Panel.h\"\n#include \"ProvideCurses.h\"\n#include \"Vector.h\"\n#include \"XUtils.h\"\n\n\n// cf. getIndexForType; must be larger than the maximum value returned.\n#define LSOF_DATACOL_COUNT 8\n\ntypedef struct OpenFiles_Data_ {\n   char* data[LSOF_DATACOL_COUNT];\n} OpenFiles_Data;\n\ntypedef struct OpenFiles_ProcessData_ {\n   OpenFiles_Data data;\n   int error;\n   int cols[LSOF_DATACOL_COUNT];\n   struct OpenFiles_FileData_* files;\n} OpenFiles_ProcessData;\n\ntypedef struct OpenFiles_FileData_ {\n   OpenFiles_Data data;\n   struct OpenFiles_FileData_* next;\n} OpenFiles_FileData;\n\nstatic size_t getIndexForType(char type) {\n   switch (type) {\n      case 'f':\n         return 0;\n      case 'a':\n         return 1;\n      case 'D':\n         return 2;\n      case 'i':\n         return 3;\n      case 'n':\n         return 4;\n      case 's':\n         return 5;\n      case 't':\n         return 6;\n      case 'o':\n         return 7;\n   }\n\n   /* should never reach here */\n   abort();\n}\n\nstatic const char* getDataForType(const OpenFiles_Data* data, char type) {\n   size_t index = getIndexForType(type);\n   return data->data[index] ? data->data[index] : \"\";\n}\n\nOpenFilesScreen* OpenFilesScreen_new(const Process* process) {\n   OpenFilesScreen* this = xCalloc(1, sizeof(OpenFilesScreen));\n   Object_setClass(this, Class(OpenFilesScreen));\n   if (Process_isThread(process)) {\n      this->pid = Process_getThreadGroup(process);\n   } else {\n      this->pid = Process_getPid(process);\n   }\n   return (OpenFilesScreen*) InfoScreen_init(&this->super, process, NULL, LINES - 2, \"   FD TYPE    MODE DEVICE           SIZE     OFFSET       NODE  NAME\");\n}\n\nvoid OpenFilesScreen_delete(Object* this) {\n   free(InfoScreen_done((InfoScreen*)this));\n}\n\nstatic void OpenFilesScreen_draw(InfoScreen* this) {\n   InfoScreen_drawTitled(this, \"Snapshot of files open in process %d - %s\", ((OpenFilesScreen*)this)->pid, Process_getCommand(this->process));\n}\n\nstatic OpenFiles_ProcessData* OpenFilesScreen_getProcessData(pid_t pid) {\n   OpenFiles_ProcessData* pdata = xCalloc(1, sizeof(OpenFiles_ProcessData));\n   pdata->cols[getIndexForType('s')] = 8;\n   pdata->cols[getIndexForType('o')] = 8;\n   pdata->cols[getIndexForType('i')] = 8;\n\n   int fdpair[2] = {-1, -1};\n   if (pipe(fdpair) < 0) {\n      pdata->error = 1;\n      return pdata;\n   }\n\n   pid_t child = fork();\n   if (child < 0) {\n      close(fdpair[1]);\n      close(fdpair[0]);\n      pdata->error = 1;\n      return pdata;\n   }\n\n   if (child == 0) {\n      close(fdpair[0]);\n      dup2(fdpair[1], STDOUT_FILENO);\n      close(fdpair[1]);\n      int fdnull = open(\"/dev/null\", O_WRONLY);\n      if (fdnull < 0) {\n         exit(1);\n      }\n\n      dup2(fdnull, STDERR_FILENO);\n      close(fdnull);\n      char buffer[32] = {0};\n      xSnprintf(buffer, sizeof(buffer), \"%d\", pid);\n      // Use of NULL in variadic functions must have a pointer cast.\n      // The NULL constant is not required by standard to have a pointer type.\n      execlp(\"lsof\", \"lsof\", \"-P\", \"-o\", \"-p\", buffer, \"-F\", (char*)NULL);\n      exit(127);\n   }\n   close(fdpair[1]);\n\n   OpenFiles_Data* item = &(pdata->data);\n   OpenFiles_FileData* fdata = NULL;\n   bool lsofIncludesFileSize = false;\n\n   FILE* fp = fdopen(fdpair[0], \"r\");\n   if (!fp) {\n      pdata->error = 1;\n      return pdata;\n   }\n   for (;;) {\n      char* line = String_readLine(fp);\n      if (!line) {\n         break;\n      }\n\n      unsigned char cmd = line[0];\n      switch (cmd) {\n         case 'f':  /* file descriptor */\n         {\n            OpenFiles_FileData* nextFile = xCalloc(1, sizeof(OpenFiles_FileData));\n            if (fdata == NULL) {\n               pdata->files = nextFile;\n            } else {\n               fdata->next = nextFile;\n            }\n            fdata = nextFile;\n            item = &(fdata->data);\n         } /* FALLTHRU */\n         case 'a':  /* file access mode */\n         case 'D':  /* file's major/minor device number */\n         case 'i':  /* file's inode number */\n         case 'n':  /* file name, comment, Internet address */\n         case 's':  /* file's size */\n         case 't':  /* file's type */\n         {\n            size_t index = getIndexForType(cmd);\n            free_and_xStrdup(&item->data[index], line + 1);\n            size_t dlen = strlen(item->data[index]);\n            if (dlen > (size_t)pdata->cols[index]) {\n               pdata->cols[index] = (int)CLAMP(dlen, 0, INT16_MAX);\n            }\n            break;\n         }\n         case 'o':  /* file's offset */\n         {\n            size_t index = getIndexForType(cmd);\n            if (String_startsWith(line + 1, \"0t\")) {\n               free_and_xStrdup(&item->data[index], line + 3);\n            } else {\n               free_and_xStrdup(&item->data[index], line + 1);\n            }\n            size_t dlen = strlen(item->data[index]);\n            if (dlen > (size_t)pdata->cols[index]) {\n               pdata->cols[index] = (int)CLAMP(dlen, 0, INT16_MAX);\n            }\n            break;\n         }\n         case 'c':  /* process command name  */\n         case 'd':  /* file's device character code */\n         case 'g':  /* process group ID */\n         case 'G':  /* file flags */\n         case 'k':  /* link count */\n         case 'l':  /* file's lock status */\n         case 'L':  /* process login name */\n         case 'p':  /* process ID */\n         case 'P':  /* protocol name */\n         case 'R':  /* parent process ID */\n         case 'T':  /* TCP/TPI information, identified by prefixes */\n         case 'u':  /* process user ID */\n            /* ignore */\n            break;\n      }\n\n      if (cmd == 's')\n         lsofIncludesFileSize = true;\n\n      free(line);\n   }\n   fclose(fp);\n\n   int wstatus;\n   while (waitpid(child, &wstatus, 0) == -1)\n      if (errno != EINTR) {\n         pdata->error = 1;\n         return pdata;\n      }\n\n   if (!WIFEXITED(wstatus)) {\n      pdata->error = 1;\n   } else {\n      pdata->error = WEXITSTATUS(wstatus);\n   }\n\n   /* We got all information we need; no post-processing needed */\n   if (lsofIncludesFileSize)\n      return pdata;\n\n   /* On linux, `lsof -o -F` omits SIZE, so add it back. */\n   /* On macOS, `lsof -o -F` includes SIZE, so this block isn't needed.  If no open files have a filesize, this will still run, unfortunately. */\n   size_t fileSizeIndex = getIndexForType('s');\n   for (fdata = pdata->files; fdata != NULL; fdata = fdata->next) {\n      item = &fdata->data;\n      const char* filename = getDataForType(item, 'n');\n\n      struct stat sb;\n      if (stat(filename, &sb) == 0) {\n         char fileSizeBuf[21]; /* 20 (long long) + 1 (NULL) */\n         xSnprintf(fileSizeBuf, sizeof(fileSizeBuf), \"%\"PRIu64, (uint64_t)sb.st_size); /* sb.st_size is long long on macOS, long on linux */\n         free_and_xStrdup(&item->data[fileSizeIndex], fileSizeBuf);\n      }\n   }\n\n   return pdata;\n}\n\nstatic void OpenFiles_Data_clear(OpenFiles_Data* data) {\n   for (size_t i = 0; i < ARRAYSIZE(data->data); i++)\n      free(data->data[i]);\n}\n\nstatic void OpenFilesScreen_scan(InfoScreen* super) {\n   Panel* panel = super->display;\n   int idx = Panel_getSelectedIndex(panel);\n   Panel_prune(panel);\n   OpenFiles_ProcessData* pdata = OpenFilesScreen_getProcessData(((OpenFilesScreen*)super)->pid);\n   if (pdata->error == 127) {\n      InfoScreen_addLine(super, \"Could not execute 'lsof'. Please make sure it is available in your $PATH.\");\n   } else if (pdata->error == 1) {\n      InfoScreen_addLine(super, \"Failed listing open files.\");\n   } else {\n      char hdrbuf[128] = {0};\n      snprintf(hdrbuf, sizeof(hdrbuf), \"%5.5s %-7.7s %-4.4s %6.6s %*s %*s %*s  %s\",\n         \"FD\", \"TYPE\", \"MODE\", \"DEVICE\",\n         pdata->cols[getIndexForType('s')], \"SIZE\",\n         pdata->cols[getIndexForType('o')], \"OFFSET\",\n         pdata->cols[getIndexForType('i')], \"NODE\",\n         \"NAME\"\n      );\n      Panel_setHeader(panel, hdrbuf);\n\n      OpenFiles_FileData* fdata = pdata->files;\n      while (fdata) {\n         OpenFiles_Data* data = &fdata->data;\n         char* entry = NULL;\n         xAsprintf(&entry, \"%5.5s %-7.7s %-4.4s %6.6s %*s %*s %*s  %s\",\n                   getDataForType(data, 'f'),\n                   getDataForType(data, 't'),\n                   getDataForType(data, 'a'),\n                   getDataForType(data, 'D'),\n                   pdata->cols[getIndexForType('s')],\n                   getDataForType(data, 's'),\n                   pdata->cols[getIndexForType('o')],\n                   getDataForType(data, 'o'),\n                   pdata->cols[getIndexForType('i')],\n                   getDataForType(data, 'i'),\n                   getDataForType(data, 'n'));\n         InfoScreen_addLine(super, entry);\n         free(entry);\n         OpenFiles_Data_clear(data);\n         OpenFiles_FileData* old = fdata;\n         fdata = fdata->next;\n         free(old);\n      }\n      OpenFiles_Data_clear(&pdata->data);\n   }\n   free(pdata);\n   Vector_insertionSort(super->lines);\n   Vector_insertionSort(panel->items);\n   Panel_setSelected(panel, idx);\n}\n\nconst InfoScreenClass OpenFilesScreen_class = {\n   .super = {\n      .extends = Class(Object),\n      .delete = OpenFilesScreen_delete\n   },\n   .scan = OpenFilesScreen_scan,\n   .draw = OpenFilesScreen_draw\n};\n"
  },
  {
    "path": "OpenFilesScreen.h",
    "content": "#ifndef HEADER_OpenFilesScreen\n#define HEADER_OpenFilesScreen\n/*\nhtop - OpenFilesScreen.h\n(C) 2005-2006 Hisham H. Muhammad\nReleased under the GNU GPLv2+, see the COPYING file\nin the source distribution for its full text.\n*/\n\n#include <sys/types.h>\n\n#include \"InfoScreen.h\"\n#include \"Object.h\"\n#include \"Process.h\"\n\n\ntypedef struct OpenFilesScreen_ {\n   InfoScreen super;\n   pid_t pid;\n} OpenFilesScreen;\n\nextern const InfoScreenClass OpenFilesScreen_class;\n\nOpenFilesScreen* OpenFilesScreen_new(const Process* process);\n\nvoid OpenFilesScreen_delete(Object* this);\n\n#endif\n"
  },
  {
    "path": "OptionItem.c",
    "content": "/*\nhtop - OptionItem.c\n(C) 2004-2011 Hisham H. Muhammad\nReleased under the GNU GPLv2+, see the COPYING file\nin the source distribution for its full text.\n*/\n\n#include \"config.h\" // IWYU pragma: keep\n\n#include \"OptionItem.h\"\n\n#include <assert.h>\n#include <math.h>\n#include <stdlib.h>\n\n#include \"CRT.h\"\n#include \"Macros.h\"\n#include \"RichString.h\"\n#include \"XUtils.h\"\n\n\nstatic void OptionItem_delete(Object* cast) {\n   OptionItem* this = (OptionItem*)cast;\n   assert (this != NULL);\n\n   free(this->text);\n   free(this);\n}\n\nstatic void TextItem_display(const Object* cast, RichString* out) {\n   const TextItem* this = (const TextItem*)cast;\n   assert (this != NULL);\n\n   RichString_appendWide(out, CRT_colors[HELP_BOLD], this->super.text);\n}\n\nstatic void CheckItem_display(const Object* cast, RichString* out) {\n   const CheckItem* this = (const CheckItem*)cast;\n   assert (this != NULL);\n\n   RichString_writeAscii(out, CRT_colors[CHECK_BOX], \"[\");\n   if (CheckItem_get(this)) {\n      RichString_appendAscii(out, CRT_colors[CHECK_MARK], \"x\");\n   } else {\n      RichString_appendAscii(out, CRT_colors[CHECK_MARK], \" \");\n   }\n   RichString_appendAscii(out, CRT_colors[CHECK_BOX], \"]    \");\n   RichString_appendWide(out, CRT_colors[CHECK_TEXT], this->super.text);\n}\n\nstatic void NumberItem_display(const Object* cast, RichString* out) {\n   const NumberItem* this = (const NumberItem*)cast;\n   assert (this != NULL);\n\n   char buffer[12];\n   RichString_writeAscii(out, CRT_colors[CHECK_BOX], \"[\");\n   int written;\n   if (this->scale < 0) {\n      written = xSnprintf(buffer, sizeof(buffer), \"%.*f\", -this->scale, pow(10, this->scale) * NumberItem_get(this));\n   } else if (this->scale > 0) {\n      written = xSnprintf(buffer, sizeof(buffer), \"%d\", (int) (pow(10, this->scale) * NumberItem_get(this)));\n   } else {\n      written = xSnprintf(buffer, sizeof(buffer), \"%d\", NumberItem_get(this));\n   }\n   RichString_appendnAscii(out, CRT_colors[CHECK_MARK], buffer, written);\n   RichString_appendAscii(out, CRT_colors[CHECK_BOX], \"]\");\n   for (int i = written; i < 5; i++) {\n      RichString_appendAscii(out, CRT_colors[CHECK_BOX], \" \");\n   }\n   RichString_appendWide(out, CRT_colors[CHECK_TEXT], this->super.text);\n}\n\nconst OptionItemClass OptionItem_class = {\n   .super = {\n      .extends = Class(Object),\n      .delete = OptionItem_delete\n   }\n};\n\nconst OptionItemClass TextItem_class = {\n   .super = {\n      .extends = Class(OptionItem),\n      .delete = OptionItem_delete,\n      .display = TextItem_display\n   },\n   .kind = OPTION_ITEM_TEXT\n};\n\n\nconst OptionItemClass CheckItem_class = {\n   .super = {\n      .extends = Class(OptionItem),\n      .delete = OptionItem_delete,\n      .display = CheckItem_display\n   },\n   .kind = OPTION_ITEM_CHECK\n};\n\n\nconst OptionItemClass NumberItem_class = {\n   .super = {\n      .extends = Class(OptionItem),\n      .delete = OptionItem_delete,\n      .display = NumberItem_display\n   },\n   .kind = OPTION_ITEM_NUMBER\n};\n\nTextItem* TextItem_new(const char* text) {\n   TextItem* this = AllocThis(TextItem);\n   this->super.text = xStrdup(text);\n   return this;\n}\n\nCheckItem* CheckItem_newByRef(const char* text, bool* ref) {\n   CheckItem* this = AllocThis(CheckItem);\n   this->super.text = xStrdup(text);\n   this->value = false;\n   this->ref = ref;\n   return this;\n}\n\nCheckItem* CheckItem_newByVal(const char* text, bool value) {\n   CheckItem* this = AllocThis(CheckItem);\n   this->super.text = xStrdup(text);\n   this->value = value;\n   this->ref = NULL;\n   return this;\n}\n\nbool CheckItem_get(const CheckItem* this) {\n   if (this->ref) {\n      return *(this->ref);\n   } else {\n      return this->value;\n   }\n}\n\nvoid CheckItem_set(CheckItem* this, bool value) {\n   if (this->ref) {\n      *(this->ref) = value;\n   } else {\n      this->value = value;\n   }\n}\n\nvoid CheckItem_toggle(CheckItem* this) {\n   if (this->ref) {\n      *(this->ref) = !*(this->ref);\n   } else {\n      this->value = !this->value;\n   }\n}\n\nNumberItem* NumberItem_newByRef(const char* text, int* ref, int scale, int min, int max) {\n   assert(min <= max);\n\n   NumberItem* this = AllocThis(NumberItem);\n   this->super.text = xStrdup(text);\n   this->value = 0;\n   this->ref = ref;\n   this->scale = scale;\n   this->min = min;\n   this->max = max;\n   return this;\n}\n\nNumberItem* NumberItem_newByVal(const char* text, int value, int scale, int min, int max) {\n   assert(min <= max);\n\n   NumberItem* this = AllocThis(NumberItem);\n   this->super.text = xStrdup(text);\n   this->value = CLAMP(value, min, max);\n   this->ref = NULL;\n   this->scale = scale;\n   this->min = min;\n   this->max = max;\n   return this;\n}\n\nint NumberItem_get(const NumberItem* this) {\n   if (this->ref) {\n      return *(this->ref);\n   } else {\n      return this->value;\n   }\n}\n\nvoid NumberItem_decrease(NumberItem* this) {\n   if (this->ref) {\n      *(this->ref) = CLAMP(*(this->ref) - 1, this->min, this->max);\n   } else {\n      this->value = CLAMP(this->value - 1, this->min, this->max);\n   }\n}\n\nvoid NumberItem_increase(NumberItem* this) {\n   if (this->ref) {\n      *(this->ref) = CLAMP(*(this->ref) + 1, this->min, this->max);\n   } else {\n      this->value = CLAMP(this->value + 1, this->min, this->max);\n   }\n}\n\nvoid NumberItem_toggle(NumberItem* this) {\n   if (this->ref) {\n      if (*(this->ref) >= this->max)\n         *(this->ref) = this->min;\n      else\n         *(this->ref) += 1;\n   } else {\n      if (this->value >= this->max)\n         this->value = this->min;\n      else\n         this->value += 1;\n   }\n}\n"
  },
  {
    "path": "OptionItem.h",
    "content": "#ifndef HEADER_OptionItem\n#define HEADER_OptionItem\n/*\nhtop - OptionItem.h\n(C) 2004-2011 Hisham H. Muhammad\nReleased under the GNU GPLv2+, see the COPYING file\nin the source distribution for its full text.\n*/\n\n#include <stdbool.h>\n\n#include \"Object.h\"\n\n\nenum OptionItemType {\n   OPTION_ITEM_TEXT,\n   OPTION_ITEM_CHECK,\n   OPTION_ITEM_NUMBER,\n};\n\ntypedef struct OptionItemClass_ {\n   const ObjectClass super;\n\n   enum OptionItemType kind;\n} OptionItemClass;\n\n#define As_OptionItem(this_)                ((const OptionItemClass*)((this_)->super.klass))\n#define OptionItem_kind(this_)              As_OptionItem(this_)->kind\n\ntypedef struct OptionItem_ {\n   Object super;\n\n   char* text;\n} OptionItem;\n\ntypedef struct TextItem_ {\n   OptionItem super;\n\n   char* text;\n} TextItem;\n\ntypedef struct CheckItem_ {\n   OptionItem super;\n\n   bool* ref;\n   bool value;\n} CheckItem;\n\ntypedef struct NumberItem_ {\n   OptionItem super;\n\n   char* text;\n   int* ref;\n   int value;\n   int scale;\n   int min;\n   int max;\n} NumberItem;\n\nextern const OptionItemClass OptionItem_class;\nextern const OptionItemClass TextItem_class;\nextern const OptionItemClass CheckItem_class;\nextern const OptionItemClass NumberItem_class;\n\nTextItem* TextItem_new(const char* text);\n\nCheckItem* CheckItem_newByRef(const char* text, bool* ref);\nCheckItem* CheckItem_newByVal(const char* text, bool value);\nbool CheckItem_get(const CheckItem* this);\nvoid CheckItem_set(CheckItem* this, bool value);\nvoid CheckItem_toggle(CheckItem* this);\n\nNumberItem* NumberItem_newByRef(const char* text, int* ref, int scale, int min, int max);\nNumberItem* NumberItem_newByVal(const char* text, int value, int scale, int min, int max);\nint NumberItem_get(const NumberItem* this);\nvoid NumberItem_decrease(NumberItem* this);\nvoid NumberItem_increase(NumberItem* this);\nvoid NumberItem_toggle(NumberItem* this);\n\n#endif\n"
  },
  {
    "path": "Panel.c",
    "content": "/*\nhtop - Panel.c\n(C) 2004-2011 Hisham H. Muhammad\nReleased under the GNU GPLv2+, see the COPYING file\nin the source distribution for its full text.\n*/\n\n#include \"config.h\" // IWYU pragma: keep\n\n#include \"Panel.h\"\n\n#include <assert.h>\n#include <ctype.h>\n#include <limits.h>\n#include <stdbool.h>\n#include <stdlib.h>\n#include <string.h>\n#include <strings.h>\n\n#include \"CRT.h\"\n#include \"ListItem.h\"\n#include \"Macros.h\"\n#include \"ProvideCurses.h\"\n#include \"RichString.h\"\n#include \"XUtils.h\"\n\n\nconst PanelClass Panel_class = {\n   .super = {\n      .extends = Class(Object),\n      .delete = Panel_delete\n   },\n   .eventHandler = Panel_selectByTyping,\n};\n\nPanel* Panel_new(int x, int y, int w, int h, const ObjectClass* type, bool owner, FunctionBar* fuBar) {\n   Panel* this;\n   this = xMalloc(sizeof(Panel));\n   Object_setClass(this, Class(Panel));\n   Panel_init(this, x, y, w, h, type, owner, fuBar);\n   return this;\n}\n\nvoid Panel_delete(Object* cast) {\n   Panel* this = (Panel*)cast;\n   Panel_done(this);\n   free(this);\n}\n\nvoid Panel_init(Panel* this, int x, int y, int w, int h, const ObjectClass* type, bool owner, FunctionBar* fuBar) {\n   this->x = x;\n   this->y = y;\n   this->w = w;\n   this->h = h;\n   this->cursorX = 0;\n   this->cursorY = 0;\n   this->eventHandlerState = NULL;\n   this->items = Vector_new(type, owner, VECTOR_DEFAULT_SIZE);\n   this->scrollV = 0;\n   this->scrollH = 0;\n   this->selected = 0;\n   this->oldSelected = 0;\n   this->selectedLen = 0;\n   this->needsRedraw = true;\n   this->cursorOn = false;\n   this->wasFocus = false;\n   RichString_beginAllocated(this->header);\n   this->defaultBar = fuBar;\n   this->currentBar = fuBar;\n   this->selectionColorId = PANEL_SELECTION_FOCUS;\n}\n\nvoid Panel_done(Panel* this) {\n   assert (this != NULL);\n   free(this->eventHandlerState);\n   Vector_delete(this->items);\n   FunctionBar_delete(this->defaultBar);\n   RichString_delete(&this->header);\n}\n\nvoid Panel_setCursorToSelection(Panel* this) {\n   this->cursorY = this->y + this->selected - this->scrollV + 1;\n   this->cursorX = this->x + (int)this->selectedLen - this->scrollH;\n}\n\nvoid Panel_setSelectionColor(Panel* this, ColorElements colorId) {\n   this->selectionColorId = colorId;\n}\n\ninline void Panel_setHeader(Panel* this, const char* header) {\n   RichString_writeWide(&(this->header), CRT_colors[PANEL_HEADER_FOCUS], header);\n   this->needsRedraw = true;\n}\n\nvoid Panel_move(Panel* this, int x, int y) {\n   assert (this != NULL);\n\n   this->x = x;\n   this->y = y;\n   this->needsRedraw = true;\n}\n\nvoid Panel_resize(Panel* this, int w, int h) {\n   assert (this != NULL);\n\n   this->w = w;\n   this->h = h;\n   this->needsRedraw = true;\n}\n\nvoid Panel_prune(Panel* this) {\n   assert (this != NULL);\n\n   Vector_prune(this->items);\n   this->scrollV = 0;\n   this->selected = 0;\n   this->oldSelected = 0;\n   this->needsRedraw = true;\n}\n\nvoid Panel_add(Panel* this, Object* o) {\n   assert (this != NULL);\n\n   Vector_add(this->items, o);\n   this->needsRedraw = true;\n}\n\nvoid Panel_insert(Panel* this, int i, Object* o) {\n   assert (this != NULL);\n\n   Vector_insert(this->items, i, o);\n   this->needsRedraw = true;\n}\n\nvoid Panel_set(Panel* this, int i, Object* o) {\n   assert (this != NULL);\n\n   Vector_set(this->items, i, o);\n}\n\nObject* Panel_get(Panel* this, int i) {\n   assert (this != NULL);\n\n   return Vector_get(this->items, i);\n}\n\nObject* Panel_remove(Panel* this, int i) {\n   assert (this != NULL);\n\n   this->needsRedraw = true;\n   Object* removed = Vector_remove(this->items, i);\n   if (this->selected > 0 && this->selected >= Vector_size(this->items)) {\n      this->selected--;\n   }\n\n   return removed;\n}\n\nObject* Panel_getSelected(Panel* this) {\n   assert (this != NULL);\n   if (Vector_size(this->items) > 0) {\n      return Vector_get(this->items, this->selected);\n   } else {\n      return NULL;\n   }\n}\n\nvoid Panel_moveSelectedUp(Panel* this) {\n   assert (this != NULL);\n\n   Vector_moveUp(this->items, this->selected);\n   if (this->selected > 0) {\n      this->selected--;\n   }\n}\n\nvoid Panel_moveSelectedDown(Panel* this) {\n   assert (this != NULL);\n\n   Vector_moveDown(this->items, this->selected);\n   if (this->selected + 1 < Vector_size(this->items)) {\n      this->selected++;\n   }\n}\n\nint Panel_getSelectedIndex(const Panel* this) {\n   assert (this != NULL);\n\n   return this->selected;\n}\n\nint Panel_size(const Panel* this) {\n   assert (this != NULL);\n\n   return Vector_size(this->items);\n}\n\nvoid Panel_setSelected(Panel* this, int selected) {\n   assert (this != NULL);\n\n   int size = Vector_size(this->items);\n   if (selected >= size) {\n      selected = size - 1;\n   }\n   if (selected < 0) {\n      selected = 0;\n   }\n   this->selected = selected;\n   if (Panel_eventHandlerFn(this)) {\n      Panel_eventHandler(this, EVENT_SET_SELECTED);\n   }\n}\n\nvoid Panel_splice(Panel* this, Vector* from) {\n   assert (this != NULL);\n   assert (from != NULL);\n\n   Vector_splice(this->items, from);\n   this->needsRedraw = true;\n}\n\nvoid Panel_draw(Panel* this, bool force_redraw, bool focus, bool highlightSelected, bool hideFunctionBar) {\n   assert (this != NULL);\n\n   int size = Vector_size(this->items);\n   int scrollH = this->scrollH;\n   int y = this->y;\n   int x = this->x;\n   int h = this->h;\n\n   if (hideFunctionBar)\n      h++;\n\n   const int header_attr = focus\n                         ? CRT_colors[PANEL_HEADER_FOCUS]\n                         : CRT_colors[PANEL_HEADER_UNFOCUS];\n   if (force_redraw) {\n      if (Panel_printHeaderFn(this))\n         Panel_printHeader(this);\n      else\n         RichString_setAttr(&this->header, header_attr);\n   }\n   int headerLen = RichString_sizeVal(this->header);\n   if (headerLen > 0) {\n      attrset(header_attr);\n      mvhline(y, x, ' ', this->w);\n      if (scrollH < headerLen) {\n         RichString_printoffnVal(this->header, y, x, scrollH,\n            MINIMUM(headerLen - scrollH, this->w));\n      }\n      attrset(CRT_colors[RESET_COLOR]);\n      y++;\n      h--;\n   }\n\n   // ensure scroll area is on screen\n   if (this->scrollV < 0) {\n      this->scrollV = 0;\n      this->needsRedraw = true;\n   } else if (this->scrollV > size - h) {\n      this->scrollV = MAXIMUM(size - h, 0);\n      this->needsRedraw = true;\n   }\n   // ensure selection is on screen\n   if (this->selected < this->scrollV) {\n      this->scrollV = this->selected;\n      this->needsRedraw = true;\n   } else if (this->selected >= this->scrollV + h) {\n      this->scrollV = this->selected - h + 1;\n      this->needsRedraw = true;\n   }\n\n   int first = this->scrollV;\n   int upTo = MINIMUM(first + h, size);\n\n   int selectionColor = focus\n      ? CRT_colors[this->selectionColorId]\n      : CRT_colors[PANEL_SELECTION_UNFOCUS];\n\n   if (this->needsRedraw || force_redraw) {\n      int line = 0;\n      for (int i = first; line < h && i < upTo; i++) {\n         const Object* itemObj = Vector_get(this->items, i);\n         RichString_begin(item);\n         Object_display(itemObj, &item);\n         int itemLen = RichString_sizeVal(item);\n         int amt = MINIMUM(itemLen - scrollH, this->w);\n         if (highlightSelected && i == this->selected) {\n            item.highlightAttr = selectionColor;\n         }\n         if (item.highlightAttr) {\n            attrset(item.highlightAttr);\n            RichString_setAttr(&item, item.highlightAttr);\n            this->selectedLen = itemLen;\n         }\n         mvhline(y + line, x, ' ', this->w);\n         if (amt > 0)\n            RichString_printoffnVal(item, y + line, x, scrollH, amt);\n         if (item.highlightAttr)\n            attrset(CRT_colors[RESET_COLOR]);\n         RichString_delete(&item);\n         line++;\n      }\n      while (line < h) {\n         mvhline(y + line, x, ' ', this->w);\n         line++;\n      }\n\n   } else {\n      const Object* oldObj = Vector_get(this->items, this->oldSelected);\n      RichString_begin(old);\n      Object_display(oldObj, &old);\n      int oldLen = RichString_sizeVal(old);\n      const Object* newObj = Vector_get(this->items, this->selected);\n      RichString_begin(new);\n      Object_display(newObj, &new);\n      int newLen = RichString_sizeVal(new);\n      this->selectedLen = newLen;\n      mvhline(y + this->oldSelected - first, x + 0, ' ', this->w);\n      if (scrollH < oldLen)\n         RichString_printoffnVal(old, y + this->oldSelected - first, x,\n            scrollH, MINIMUM(oldLen - scrollH, this->w));\n      attrset(selectionColor);\n      mvhline(y + this->selected - first, x + 0, ' ', this->w);\n      RichString_setAttr(&new, selectionColor);\n      if (scrollH < newLen)\n         RichString_printoffnVal(new, y + this->selected - first, x,\n            scrollH, MINIMUM(newLen - scrollH, this->w));\n      attrset(CRT_colors[RESET_COLOR]);\n      RichString_delete(&new);\n      RichString_delete(&old);\n   }\n\n   if (focus && (this->needsRedraw || force_redraw || !this->wasFocus)) {\n      if (Panel_drawFunctionBarFn(this))\n         Panel_drawFunctionBar(this, hideFunctionBar);\n      else if (!hideFunctionBar)\n         FunctionBar_draw(this->currentBar);\n   }\n\n   this->oldSelected = this->selected;\n   this->wasFocus = focus;\n   this->needsRedraw = false;\n}\n\nstatic int Panel_headerHeight(const Panel* this) {\n   return RichString_sizeVal(this->header) > 0 ? 1 : 0;\n}\n\nbool Panel_onKey(Panel* this, int key) {\n   assert (this != NULL);\n\n   const int size = Vector_size(this->items);\n\n   #define PANEL_SCROLL(amount)                                                                                     \\\n   do {                                                                                                             \\\n      this->selected += (amount);                                                                                   \\\n      this->scrollV = CLAMP(this->scrollV + (amount), 0, MAXIMUM(0, (size - this->h - Panel_headerHeight(this))));  \\\n      this->needsRedraw = true;                                                                                     \\\n   } while (0)\n\n   switch (key) {\n      case KEY_DOWN:\n      case KEY_CTRL('N'):\n      #ifdef KEY_C_DOWN\n      case KEY_C_DOWN:\n      #endif\n         this->selected++;\n         break;\n\n      case KEY_UP:\n      case KEY_CTRL('P'):\n      #ifdef KEY_C_UP\n      case KEY_C_UP:\n      #endif\n         this->selected--;\n         break;\n\n      case KEY_LEFT:\n      case KEY_CTRL('B'):\n         if (this->scrollH > 0) {\n            this->scrollH -= MAXIMUM(CRT_scrollHAmount, 0);\n            this->needsRedraw = true;\n         }\n         break;\n\n      case KEY_RIGHT:\n      case KEY_CTRL('F'):\n         this->scrollH += CRT_scrollHAmount;\n         this->needsRedraw = true;\n         break;\n\n      case KEY_PPAGE:\n         PANEL_SCROLL(-(this->h - Panel_headerHeight(this)));\n         break;\n\n      case KEY_NPAGE:\n         PANEL_SCROLL(+(this->h - Panel_headerHeight(this)));\n         break;\n\n      case KEY_WHEELUP:\n         PANEL_SCROLL(-CRT_scrollWheelVAmount);\n         break;\n\n      case KEY_WHEELDOWN:\n         PANEL_SCROLL(+CRT_scrollWheelVAmount);\n         break;\n\n      case KEY_HOME:\n         this->selected = 0;\n         break;\n\n      case KEY_END:\n         this->selected = size - 1;\n         break;\n\n      case KEY_CTRL('A'):\n      case '^':\n         this->scrollH = 0;\n         this->needsRedraw = true;\n         break;\n\n      case KEY_CTRL('E'):\n      case '$':\n         assert(this->w > 0);\n         if (this->selectedLen < (size_t)this->w) {\n            this->scrollH = 0;\n         } else if (this->selectedLen - (size_t)this->w > (size_t)INT_MAX) {\n            this->scrollH = INT_MAX;\n         } else {\n            this->scrollH = (int)(this->selectedLen - (size_t)this->w);\n         }\n         this->needsRedraw = true;\n         break;\n\n      default:\n         return false;\n   }\n\n   #undef PANEL_SCROLL\n\n   // ensure selection within bounds\n   if (this->selected < 0 || size == 0) {\n      this->selected = 0;\n      this->needsRedraw = true;\n   } else if (this->selected >= size) {\n      this->selected = size - 1;\n      this->needsRedraw = true;\n   }\n\n   return true;\n}\n\n\nHandlerResult Panel_selectByTyping(Panel* this, int ch) {\n   int size = Panel_size(this);\n\n   if (ch == '#')\n      return IGNORED;\n\n   if (!this->eventHandlerState)\n      this->eventHandlerState = xCalloc(100, sizeof(char));\n   char* buffer = this->eventHandlerState;\n\n   if (0 < ch && ch < 255 && isgraph((unsigned char)ch)) {\n      size_t len = strlen(buffer);\n      if (!len) {\n         if ('/' == ch) {\n            ch = '\\001';\n         } else if ('q' == ch) {\n            return BREAK_LOOP;\n         }\n      } else if (1 == len && '\\001' == buffer[0]) {\n         len--;\n      }\n\n      if (len < 99) {\n         buffer[len] = (char) ch;\n         buffer[len + 1] = '\\0';\n      }\n\n      for (int try = 0; try < 2; try++) {\n         len = strlen(buffer);\n         for (int i = 0; i < size; i++) {\n            const char* cur = ((ListItem*) Panel_get(this, i))->value;\n            while (*cur == ' ')\n               cur++;\n            if (strncasecmp(cur, buffer, len) == 0) {\n               Panel_setSelected(this, i);\n               return HANDLED;\n            }\n         }\n\n         // if current word did not match,\n         // retry considering the character the start of a new word.\n         buffer[0] = (char) ch;\n         buffer[1] = '\\0';\n      }\n\n      return HANDLED;\n   } else if (ch != ERR) {\n      buffer[0] = '\\0';\n   }\n\n   if (ch == 13) {\n      return BREAK_LOOP;\n   }\n\n   return IGNORED;\n}\n\nint Panel_getCh(Panel* this) {\n   if (this->cursorOn) {\n      move(this->cursorY, this->cursorX);\n      curs_set(1);\n   } else {\n      curs_set(0);\n   }\n#ifdef HAVE_SET_ESCDELAY\n   set_escdelay(25);\n#endif\n   return getch();\n}\n"
  },
  {
    "path": "Panel.h",
    "content": "#ifndef HEADER_Panel\n#define HEADER_Panel\n/*\nhtop - Panel.h\n(C) 2004-2011 Hisham H. Muhammad\nReleased under the GNU GPLv2+, see the COPYING file\nin the source distribution for its full text.\n*/\n\n#include <assert.h>\n#include <stdbool.h>\n\n#include \"CRT.h\"\n#include \"FunctionBar.h\"\n#include \"Object.h\"\n#include \"RichString.h\"\n#include \"Vector.h\"\n\n\nstruct Panel_;\ntypedef struct Panel_ Panel;\n\ntypedef enum HandlerResult_ {\n   HANDLED     = 0x01,\n   IGNORED     = 0x02,\n   BREAK_LOOP  = 0x04,\n   REFRESH     = 0x08,\n   REDRAW      = 0x10,\n   RESCAN      = 0x20,\n   RESIZE      = 0x40,\n   SYNTH_KEY   = 0x80,\n} HandlerResult;\n\n#define EVENT_SET_SELECTED (-1)\n\n#define EVENT_HEADER_CLICK(x_) (-10000 + (x_))\n#define EVENT_IS_HEADER_CLICK(ev_) ((ev_) >= -10000 && (ev_) <= -9000)\n#define EVENT_HEADER_CLICK_GET_X(ev_) ((ev_) + 10000)\n\n#define EVENT_SCREEN_TAB_CLICK(x_) (-20000 + (x_))\n#define EVENT_IS_SCREEN_TAB_CLICK(ev_) ((ev_) >= -20000 && (ev_) < -10000)\n#define EVENT_SCREEN_TAB_GET_X(ev_) ((ev_) + 20000)\n\ntypedef HandlerResult (*Panel_EventHandler)(Panel*, int);\ntypedef void (*Panel_DrawFunctionBar)(Panel*, bool);\ntypedef void (*Panel_PrintHeader)(Panel*);\n\ntypedef struct PanelClass_ {\n   const ObjectClass super;\n   const Panel_EventHandler eventHandler;\n   const Panel_DrawFunctionBar drawFunctionBar;\n   const Panel_PrintHeader printHeader;\n} PanelClass;\n\n#define As_Panel(this_)                        ((const PanelClass*)((this_)->super.klass))\n#define Panel_eventHandlerFn(this_)            As_Panel(this_)->eventHandler\n#define Panel_eventHandler(this_, ev_)         (assert(As_Panel(this_)->eventHandler), As_Panel(this_)->eventHandler((Panel*)(this_), ev_))\n#define Panel_drawFunctionBarFn(this_)         As_Panel(this_)->drawFunctionBar\n#define Panel_drawFunctionBar(this_, hideFB_)  (assert(As_Panel(this_)->drawFunctionBar), As_Panel(this_)->drawFunctionBar((Panel*)(this_), hideFB_))\n#define Panel_printHeaderFn(this_)             As_Panel(this_)->printHeader\n#define Panel_printHeader(this_)               (assert(As_Panel(this_)->printHeader), As_Panel(this_)->printHeader((Panel*)(this_)))\n\nstruct Panel_ {\n   Object super;\n   int x, y, w, h;\n   int cursorX, cursorY;\n   Vector* items;\n   int selected;\n   int oldSelected;\n   size_t selectedLen;\n   void* eventHandlerState;\n   int scrollV;\n   int scrollH;\n   bool needsRedraw;\n   bool cursorOn;\n   bool wasFocus;\n   FunctionBar* currentBar;\n   FunctionBar* defaultBar;\n   RichString header;\n   ColorElements selectionColorId;\n};\n\n#define Panel_setDefaultBar(this_) do { (this_)->currentBar = (this_)->defaultBar; } while (0)\n\n#define KEY_CTRL(l) ((l)-'A'+1)\n\nextern const PanelClass Panel_class;\n\nPanel* Panel_new(int x, int y, int w, int h, const ObjectClass* type, bool owner, FunctionBar* fuBar);\n\nvoid Panel_delete(Object* cast);\n\nvoid Panel_init(Panel* this, int x, int y, int w, int h, const ObjectClass* type, bool owner, FunctionBar* fuBar);\n\nvoid Panel_done(Panel* this);\n\nvoid Panel_setCursorToSelection(Panel* this);\n\nvoid Panel_setSelectionColor(Panel* this, ColorElements colorId);\n\nvoid Panel_setHeader(Panel* this, const char* header);\n\nvoid Panel_move(Panel* this, int x, int y);\n\nvoid Panel_resize(Panel* this, int w, int h);\n\nvoid Panel_prune(Panel* this);\n\nvoid Panel_add(Panel* this, Object* o);\n\nvoid Panel_insert(Panel* this, int i, Object* o);\n\nvoid Panel_set(Panel* this, int i, Object* o);\n\nObject* Panel_get(Panel* this, int i);\n\nObject* Panel_remove(Panel* this, int i);\n\nObject* Panel_getSelected(Panel* this);\n\nvoid Panel_moveSelectedUp(Panel* this);\n\nvoid Panel_moveSelectedDown(Panel* this);\n\nint Panel_getSelectedIndex(const Panel* this);\n\nint Panel_size(const Panel* this);\n\nvoid Panel_setSelected(Panel* this, int selected);\n\nvoid Panel_draw(Panel* this, bool force_redraw, bool focus, bool highlightSelected, bool hideFunctionBar);\n\nvoid Panel_splice(Panel* this, Vector* from);\n\nbool Panel_onKey(Panel* this, int key);\n\nHandlerResult Panel_selectByTyping(Panel* this, int ch);\n\nint Panel_getCh(Panel* this);\n\n#endif\n"
  },
  {
    "path": "Process.c",
    "content": "/*\nhtop - Process.c\n(C) 2004-2015 Hisham H. Muhammad\n(C) 2020 Red Hat, Inc.  All Rights Reserved.\nReleased under the GNU GPLv2+, see the COPYING file\nin the source distribution for its full text.\n*/\n\n#include \"config.h\" // IWYU pragma: keep\n\n#include \"Process.h\"\n\n#include <assert.h>\n#include <math.h>\n#include <signal.h>\n#include <stdbool.h>\n#include <stdio.h>\n#include <stdlib.h>\n#include <string.h>\n#include <time.h>\n#include <sys/resource.h>\n\n#include \"CRT.h\"\n#include \"Hashtable.h\"\n#include \"Machine.h\"\n#include \"Macros.h\"\n#include \"ProcessTable.h\"\n#include \"DynamicColumn.h\"\n#include \"RichString.h\"\n#include \"Scheduling.h\"\n#include \"Settings.h\"\n#include \"Table.h\"\n#include \"XUtils.h\"\n\n#if defined(MAJOR_IN_MKDEV)\n#include <sys/mkdev.h>\n#endif\n\n\n/* Used to identify kernel threads in Comm and Exe columns */\nstatic const char* const kthreadID = \"KTHREAD\";\n\nvoid Process_fillStarttimeBuffer(Process* this) {\n   struct tm date;\n   time_t now = this->super.host->realtime.tv_sec;\n   (void) localtime_r(&this->starttime_ctime, &date);\n\n   strftime(this->starttime_show,\n            sizeof(this->starttime_show) - 1,\n            (this->starttime_ctime > now - 86400) ? \"%R \" : (this->starttime_ctime > now - 364 * 86400) ? \"%b%d \" : \" %Y \",\n            &date);\n}\n\n/*\n * TASK_COMM_LEN is defined to be 16 for /proc/[pid]/comm in man proc(5), but it is\n * not available in an userspace header - so define it.\n *\n * Note: This is taken from LINUX headers, but implicitly taken for other platforms\n * for sake of brevity.\n *\n * Note: when colorizing a basename with the comm prefix, the entire basename\n * (not just the comm prefix) is colorized for better readability, and it is\n * implicit that only up to (TASK_COMM_LEN - 1) could be comm.\n */\n#define TASK_COMM_LEN 16\n\nstatic bool findCommInCmdline(const char* comm, const char* cmdline, size_t cmdlineBasenameStart, size_t* pCommStart, size_t* pCommLen) {\n   /* Try to find procComm in tokenized cmdline - this might in rare cases\n    * mis-identify a string or fail, if comm or cmdline had been unsuitably\n    * modified by the process */\n   const char* tokenBase;\n   size_t tokenLen;\n   const size_t commLen = strlen(comm);\n\n   for (const char* token = cmdline + cmdlineBasenameStart; *token;) {\n      for (tokenBase = token; *token && *token != '\\n'; ++token) {\n         if (*token == '/') {\n            tokenBase = token + 1;\n         }\n      }\n      tokenLen = (size_t)(token - tokenBase);\n\n      if ((tokenLen == commLen || (tokenLen > commLen && commLen == (TASK_COMM_LEN - 1))) &&\n          strncmp(tokenBase, comm, commLen) == 0) {\n         *pCommStart = (size_t)(tokenBase - cmdline);\n         *pCommLen = tokenLen;\n         return true;\n      }\n\n      if (*token) {\n         do {\n            ++token;\n         } while (*token && '\\n' == *token);\n      }\n   }\n   return false;\n}\n\nstatic size_t matchCmdlinePrefixWithExeSuffix(const char* cmdline, size_t* cmdlineBasenameStart, const char* exe, size_t exeBaseOffset, size_t exeBaseLen) {\n   /* cmdline prefix is an absolute path: it must match whole exe. */\n   if (cmdline[0] == '/') {\n      size_t matchLen = exeBaseLen + exeBaseOffset;\n      if (strncmp(cmdline, exe, matchLen) == 0) {\n         char delim = cmdline[matchLen];\n         if (delim == 0 || delim == '\\n' || delim == ' ') {\n            return matchLen;\n         }\n      }\n      return 0;\n   }\n\n   /* cmdline prefix is a relative path: We need to first match the basename at\n    * cmdlineBaseOffset and then reverse match the cmdline prefix with the exe\n    * suffix. But there is a catch: Some processes modify their cmdline in ways\n    * that make htop's identification of the basename in cmdline unreliable.\n    * For e.g. /usr/libexec/gdm-session-worker modifies its cmdline to\n    * \"gdm-session-worker [pam/gdm-autologin]\" and htop ends up with\n    * cmdlineBasenameStart at \"gdm-autologin]\". This issue could arise with\n    * chrome as well as it stores in cmdline its concatenated argument vector,\n    * without NUL delimiter between the arguments (which may contain a '/')\n    *\n    * So if needed, we adjust cmdlineBaseOffset to the previous (if any)\n    * component of the cmdline relative path, and retry the procedure. */\n   size_t cmdlineBaseOffset = *cmdlineBasenameStart;\n   bool delimFound = true; /* if valid basename delimiter found */\n   do {\n      /* match basename */\n      size_t matchLen = exeBaseLen + cmdlineBaseOffset;\n      if (cmdlineBaseOffset < exeBaseOffset &&\n          strncmp(cmdline + cmdlineBaseOffset, exe + exeBaseOffset, exeBaseLen) == 0) {\n         char delim = cmdline[matchLen];\n         if (delim == 0 || delim == '\\n' || delim == ' ') {\n            /* reverse match the cmdline prefix and exe suffix */\n            size_t i = cmdlineBaseOffset;\n            size_t j = exeBaseOffset;\n            while (i >= 1 && j >= 1 && cmdline[i - 1] == exe[j - 1]) {\n               --i, --j;\n            }\n\n            /* full match, with exe suffix being a valid relative path */\n            if (i < 1 && j >= 1 && exe[j - 1] == '/') {\n               *cmdlineBasenameStart = cmdlineBaseOffset;\n               return matchLen;\n            }\n         }\n      }\n\n      /* Try to find the previous potential cmdlineBaseOffset - it would be\n       * preceded by '/' or nothing, and delimited by ' ' or '\\n' */\n      delimFound = false;\n      if (cmdlineBaseOffset <= 2) {\n         return 0;\n      }\n      for (cmdlineBaseOffset -= 2; cmdlineBaseOffset > 0; --cmdlineBaseOffset) {\n         if (delimFound) {\n            if (cmdline[cmdlineBaseOffset - 1] == '/') {\n               break;\n            }\n         } else if (cmdline[cmdlineBaseOffset] == ' ' || cmdline[cmdlineBaseOffset] == '\\n') {\n            delimFound = true;\n         }\n      }\n   } while (delimFound);\n\n   return 0;\n}\n\n/* stpcpy, but also converts newlines to spaces */\nstatic inline char* stpcpyWithNewlineConversion(char* dstStr, const char* srcStr) {\n   for (; *srcStr; ++srcStr) {\n      *dstStr++ = (*srcStr == '\\n') ? ' ' : *srcStr;\n   }\n   *dstStr = 0;\n   return dstStr;\n}\n\n/*\n * This function makes the merged Command string. It also stores the offsets of the\n * basename, comm w.r.t the merged Command string - these offsets will be used by\n * Process_writeCommand() for coloring. The merged Command string is also\n * returned by Process_getCommand() for searching, sorting and filtering.\n */\nvoid Process_makeCommandStr(Process* this, const Settings* settings) {\n   ProcessMergedCommand* mc = &this->mergedCommand;\n\n   bool showMergedCommand = settings->showMergedCommand;\n   bool showProgramPath = settings->showProgramPath;\n   bool searchCommInCmdline = settings->findCommInCmdline;\n   bool stripExeFromCmdline = settings->stripExeFromCmdline;\n   bool showThreadNames = settings->showThreadNames;\n   bool shadowDistPathPrefix = settings->shadowDistPathPrefix;\n\n   uint64_t settingsStamp = settings->lastUpdate;\n\n   /* Nothing to do to (Re)Generate the Command string, if the process is:\n    * - a kernel thread, or\n    * - a zombie from before being under htop's watch, or\n    * - a user thread and showThreadNames is not set */\n   if (Process_isKernelThread(this))\n      return;\n   if (this->state == ZOMBIE && !this->mergedCommand.str)\n      return;\n\n   /* this->mergedCommand.str needs updating only if its state or contents changed.\n    * Its content is based on the fields cmdline, comm, and exe. */\n   if (mc->lastUpdate >= settingsStamp)\n      return;\n\n   mc->lastUpdate = settingsStamp;\n\n   /* The field separator \"│\" has been chosen such that it will not match any\n    * valid string used for searching or filtering */\n   const char* SEPARATOR = CRT_treeStr[TREE_STR_VERT];\n   const size_t SEPARATOR_LEN = strlen(SEPARATOR);\n\n   /* Accommodate the column text, two field separators and terminating NUL */\n   size_t maxLen = 2 * SEPARATOR_LEN + 1;\n   maxLen += this->cmdline ? strlen(this->cmdline) : strlen(\"(zombie)\");\n   maxLen += this->procComm ? strlen(this->procComm) : 0;\n   maxLen += this->procExe ? strlen(this->procExe) : 0;\n\n   free(mc->str);\n   mc->str = xCalloc(1, maxLen);\n\n   /* Reset all locations that need extra handling when actually displaying */\n   mc->highlightCount = 0;\n   memset(mc->highlights, 0, sizeof(mc->highlights));\n\n   size_t mbMismatch = 0;\n   #define WRITE_HIGHLIGHT(_offset, _length, _attr, _flags)                                   \\\n      do {                                                                                    \\\n         /* Check if we still have capacity */                                                \\\n         assert(mc->highlightCount < ARRAYSIZE(mc->highlights));                              \\\n         if (mc->highlightCount >= ARRAYSIZE(mc->highlights))                                 \\\n            break;                                                                            \\\n                                                                                              \\\n         mc->highlights[mc->highlightCount].offset = str - strStart + (_offset) - mbMismatch; \\\n         mc->highlights[mc->highlightCount].length = _length;                                 \\\n         mc->highlights[mc->highlightCount].attr = _attr;                                     \\\n         mc->highlights[mc->highlightCount].flags = _flags;                                   \\\n         mc->highlightCount++;                                                                \\\n      } while (0)\n\n   #define WRITE_SEPARATOR                                                                    \\\n      do {                                                                                    \\\n         WRITE_HIGHLIGHT(0, 1, CRT_colors[FAILED_READ], CMDLINE_HIGHLIGHT_FLAG_SEPARATOR);    \\\n         mbMismatch += SEPARATOR_LEN - 1;                                                     \\\n         str = stpcpy(str, SEPARATOR);                                                        \\\n      } while (0)\n\n   #define CHECK_AND_MARK(str_, prefix_)                                                      \\\n      if (String_startsWith(str_, prefix_)) {                                                 \\\n         WRITE_HIGHLIGHT(0, strlen(prefix_), CRT_colors[PROCESS_SHADOW], CMDLINE_HIGHLIGHT_FLAG_PREFIXDIR); \\\n         break;                                                                               \\\n      } else (void)0\n\n   #define CHECK_AND_MARK_DIST_PATH_PREFIXES(str_)                                            \\\n      do {                                                                                    \\\n         if ((str_)[0] != '/') {                                                              \\\n            break;                                                                            \\\n         }                                                                                    \\\n         switch ((str_)[1]) {                                                                 \\\n            case 'b':                                                                         \\\n               CHECK_AND_MARK(str_, \"/bin/\");                                                 \\\n               break;                                                                         \\\n            case 'l':                                                                         \\\n               CHECK_AND_MARK(str_, \"/lib/\");                                                 \\\n               CHECK_AND_MARK(str_, \"/lib32/\");                                               \\\n               CHECK_AND_MARK(str_, \"/lib64/\");                                               \\\n               CHECK_AND_MARK(str_, \"/libx32/\");                                              \\\n               break;                                                                         \\\n            case 's':                                                                         \\\n               CHECK_AND_MARK(str_, \"/sbin/\");                                                \\\n               break;                                                                         \\\n            case 'u':                                                                         \\\n               if (String_startsWith(str_, \"/usr/\")) {                                        \\\n                  switch ((str_)[5]) {                                                        \\\n                     case 'b':                                                                \\\n                        CHECK_AND_MARK(str_, \"/usr/bin/\");                                    \\\n                        break;                                                                \\\n                     case 'l':                                                                \\\n                        CHECK_AND_MARK(str_, \"/usr/libexec/\");                                \\\n                        CHECK_AND_MARK(str_, \"/usr/lib/\");                                    \\\n                        CHECK_AND_MARK(str_, \"/usr/lib32/\");                                  \\\n                        CHECK_AND_MARK(str_, \"/usr/lib64/\");                                  \\\n                        CHECK_AND_MARK(str_, \"/usr/libx32/\");                                 \\\n                                                                                              \\\n                        CHECK_AND_MARK(str_, \"/usr/local/bin/\");                              \\\n                        CHECK_AND_MARK(str_, \"/usr/local/lib/\");                              \\\n                        CHECK_AND_MARK(str_, \"/usr/local/sbin/\");                             \\\n                        break;                                                                \\\n                     case 's':                                                                \\\n                        CHECK_AND_MARK(str_, \"/usr/sbin/\");                                   \\\n                        break;                                                                \\\n                  }                                                                           \\\n               }                                                                              \\\n               break;                                                                         \\\n            case 'n':                                                                         \\\n               CHECK_AND_MARK(str_, \"/nix/store/\");                                           \\\n               break;                                                                         \\\n            case 'r':                                                                         \\\n               CHECK_AND_MARK(str_, \"/run/current-system/\");                                  \\\n               break;                                                                         \\\n         }                                                                                    \\\n      } while (0)\n\n   const int baseAttr = Process_isThread(this) ? CRT_colors[PROCESS_THREAD_BASENAME] : CRT_colors[PROCESS_BASENAME];\n   const int commAttr = Process_isThread(this) ? CRT_colors[PROCESS_THREAD_COMM] : CRT_colors[PROCESS_COMM];\n   const int delExeAttr = CRT_colors[FAILED_READ];\n   const int delLibAttr = CRT_colors[PROCESS_TAG];\n\n   /* Establish some shortcuts to data we need */\n   const char* cmdline = this->cmdline;\n   const char* procComm = this->procComm;\n   const char* procExe = this->procExe;\n\n   char* strStart = mc->str;\n   char* str = strStart;\n\n   size_t cmdlineBasenameStart = this->cmdlineBasenameStart;\n   size_t cmdlineBasenameLen = 0;\n   if (this->cmdlineBasenameEnd > this->cmdlineBasenameStart)\n      cmdlineBasenameLen = this->cmdlineBasenameEnd - this->cmdlineBasenameStart;\n\n   if (!cmdline) {\n      cmdlineBasenameStart = 0;\n      cmdlineBasenameLen = 0;\n      cmdline = \"(zombie)\";\n   }\n\n   assert(cmdlineBasenameStart <= strlen(cmdline));\n\n   size_t exeLen = 0;\n   size_t exeBasenameOffset = 0;\n   size_t exeBasenameLen = 0;\n   size_t matchLen = 0;\n   if (procExe) {\n      exeLen = strlen(procExe);\n      exeBasenameOffset = this->procExeBasenameOffset;\n      exeBasenameLen = exeLen - exeBasenameOffset;\n\n      assert(exeBasenameOffset <= strlen(procExe));\n\n      if (this->cmdline) {\n         matchLen = matchCmdlinePrefixWithExeSuffix(this->cmdline, &cmdlineBasenameStart, procExe, exeBasenameOffset, exeBasenameLen);\n      }\n      if (matchLen) {\n         cmdlineBasenameLen = exeBasenameLen;\n      }\n   }\n\n   if (!showMergedCommand || !procExe || !procComm) { /* fall back to cmdline */\n      if ((showMergedCommand || (Process_isUserlandThread(this) && showThreadNames)) && procComm && strlen(procComm)) { /* set column to or prefix it with comm */\n         if (strncmp(cmdline + cmdlineBasenameStart, procComm, MINIMUM(TASK_COMM_LEN - 1, strlen(procComm))) != 0) {\n            WRITE_HIGHLIGHT(0, strlen(procComm), commAttr, CMDLINE_HIGHLIGHT_FLAG_COMM);\n            str = stpcpy(str, procComm);\n\n            if (!showMergedCommand)\n               return;\n\n            WRITE_SEPARATOR;\n         }\n      }\n\n      if (shadowDistPathPrefix && showProgramPath)\n         CHECK_AND_MARK_DIST_PATH_PREFIXES(cmdline);\n\n      if (cmdlineBasenameLen > 0) {\n         WRITE_HIGHLIGHT(showProgramPath ? cmdlineBasenameStart : 0, cmdlineBasenameLen, baseAttr, CMDLINE_HIGHLIGHT_FLAG_BASENAME);\n\n         if (this->procExeDeleted)\n            WRITE_HIGHLIGHT(showProgramPath ? cmdlineBasenameStart : 0, cmdlineBasenameLen, delExeAttr, CMDLINE_HIGHLIGHT_FLAG_DELETED);\n         else if (this->usesDeletedLib)\n            WRITE_HIGHLIGHT(showProgramPath ? cmdlineBasenameStart : 0, cmdlineBasenameLen, delLibAttr, CMDLINE_HIGHLIGHT_FLAG_DELETED);\n      }\n\n      (void)stpcpyWithNewlineConversion(str, cmdline + (showProgramPath ? 0 : cmdlineBasenameStart));\n\n      return;\n   }\n\n   size_t commLen = 0;\n\n   bool haveCommInExe = false;\n   if (procExe && procComm && (!Process_isUserlandThread(this) || showThreadNames)) {\n      haveCommInExe = strncmp(procExe + exeBasenameOffset, procComm, TASK_COMM_LEN - 1) == 0;\n   }\n   if (haveCommInExe) {\n      commLen = exeBasenameLen;\n   }\n\n   bool haveCommInCmdline = false;\n   size_t commStart = 0;\n\n   if (!haveCommInExe && this->cmdline && procComm && searchCommInCmdline && (!Process_isUserlandThread(this) || showThreadNames)) {\n      haveCommInCmdline = findCommInCmdline(procComm, cmdline, cmdlineBasenameStart, &commStart, &commLen);\n   }\n\n   if (!stripExeFromCmdline) {\n      matchLen = 0;\n   }\n   if (matchLen) {\n      /* strip the matched exe prefix */\n      cmdline += matchLen;\n\n      if (haveCommInCmdline) {\n         if (commStart == cmdlineBasenameStart) {\n            haveCommInExe = true;\n            haveCommInCmdline = false;\n            commStart = 0;\n         } else {\n            assert(commStart >= matchLen);\n            commStart -= matchLen;\n         }\n      }\n   }\n\n   /* Start with copying exe */\n   if (showProgramPath) {\n      if (shadowDistPathPrefix)\n         CHECK_AND_MARK_DIST_PATH_PREFIXES(procExe);\n      if (haveCommInExe)\n         WRITE_HIGHLIGHT(exeBasenameOffset, commLen, commAttr, CMDLINE_HIGHLIGHT_FLAG_COMM);\n      WRITE_HIGHLIGHT(exeBasenameOffset, exeBasenameLen, baseAttr, CMDLINE_HIGHLIGHT_FLAG_BASENAME);\n      if (this->procExeDeleted)\n         WRITE_HIGHLIGHT(exeBasenameOffset, exeBasenameLen, delExeAttr, CMDLINE_HIGHLIGHT_FLAG_DELETED);\n      else if (this->usesDeletedLib)\n         WRITE_HIGHLIGHT(exeBasenameOffset, exeBasenameLen, delLibAttr, CMDLINE_HIGHLIGHT_FLAG_DELETED);\n      str = stpcpy(str, procExe);\n   } else {\n      if (haveCommInExe)\n         WRITE_HIGHLIGHT(0, commLen, commAttr, CMDLINE_HIGHLIGHT_FLAG_COMM);\n      WRITE_HIGHLIGHT(0, exeBasenameLen, baseAttr, CMDLINE_HIGHLIGHT_FLAG_BASENAME);\n      if (this->procExeDeleted)\n         WRITE_HIGHLIGHT(0, exeBasenameLen, delExeAttr, CMDLINE_HIGHLIGHT_FLAG_DELETED);\n      else if (this->usesDeletedLib)\n         WRITE_HIGHLIGHT(0, exeBasenameLen, delLibAttr, CMDLINE_HIGHLIGHT_FLAG_DELETED);\n      str = stpcpy(str, procExe + exeBasenameOffset);\n   }\n\n   bool haveCommField = false;\n\n   if (!haveCommInExe && !haveCommInCmdline && procComm && (!Process_isUserlandThread(this) || showThreadNames)) {\n      WRITE_SEPARATOR;\n      WRITE_HIGHLIGHT(0, strlen(procComm), commAttr, CMDLINE_HIGHLIGHT_FLAG_COMM);\n      str = stpcpy(str, procComm);\n      haveCommField = true;\n   }\n\n   if (!matchLen || (haveCommField && *cmdline)) {\n      /* cmdline will be a separate field */\n      WRITE_SEPARATOR;\n   }\n\n   if (shadowDistPathPrefix)\n      CHECK_AND_MARK_DIST_PATH_PREFIXES(cmdline);\n\n   if (!haveCommInExe && haveCommInCmdline && !haveCommField && (!Process_isUserlandThread(this) || showThreadNames))\n      WRITE_HIGHLIGHT(commStart, commLen, commAttr, CMDLINE_HIGHLIGHT_FLAG_COMM);\n\n   /* Display cmdline if it hasn't been consumed by procExe */\n   if (*cmdline)\n      (void)stpcpyWithNewlineConversion(str, cmdline);\n\n   #undef CHECK_AND_MARK_DIST_PATH_PREFIXES\n   #undef CHECK_AND_MARK\n   #undef WRITE_SEPARATOR\n   #undef WRITE_HIGHLIGHT\n}\n\nvoid Process_writeCommand(const Process* this, int attr, int baseAttr, RichString* str) {\n   (void)baseAttr;\n\n   const ProcessMergedCommand* mc = &this->mergedCommand;\n   const char* mergedCommand = mc->str;\n\n   size_t strStart = RichString_size(str);\n\n   const Settings* settings = this->super.host->settings;\n   const bool highlightBaseName = settings->highlightBaseName;\n   const bool highlightSeparator = true;\n   const bool highlightDeleted = settings->highlightDeletedExe;\n\n   if (!mergedCommand) {\n      size_t len = 0;\n      const char* cmdline = this->cmdline;\n\n      if (highlightBaseName || !settings->showProgramPath) {\n         size_t basename = 0;\n         for (size_t i = 0; i < this->cmdlineBasenameEnd; i++) {\n            if (cmdline[i] == '/') {\n               basename = i + 1;\n            } else if (cmdline[i] == ':') {\n               len = i + 1;\n               break;\n            }\n         }\n         if (len == 0) {\n            if (settings->showProgramPath) {\n               strStart += basename;\n            } else {\n               cmdline += basename;\n            }\n            len = this->cmdlineBasenameEnd - basename;\n         }\n      }\n\n      RichString_appendWide(str, attr, cmdline);\n\n      if (settings->highlightBaseName) {\n         RichString_setAttrn(str, baseAttr, strStart, len);\n      }\n\n      return;\n   }\n\n   RichString_appendWide(str, attr, mergedCommand);\n\n   for (size_t i = 0, hlCount = CLAMP(mc->highlightCount, 0, ARRAYSIZE(mc->highlights)); i < hlCount; i++) {\n      const ProcessCmdlineHighlight* hl = &mc->highlights[i];\n\n      if (!hl->length)\n         continue;\n\n      if (hl->flags & CMDLINE_HIGHLIGHT_FLAG_SEPARATOR)\n         if (!highlightSeparator)\n            continue;\n\n      if (hl->flags & CMDLINE_HIGHLIGHT_FLAG_BASENAME)\n         if (!highlightBaseName)\n            continue;\n\n      if (hl->flags & CMDLINE_HIGHLIGHT_FLAG_DELETED)\n         if (!highlightDeleted)\n            continue;\n\n      if (hl->flags & CMDLINE_HIGHLIGHT_FLAG_PREFIXDIR)\n         if (!highlightDeleted)\n            continue;\n\n      RichString_setAttrn(str, hl->attr, strStart + hl->offset, hl->length);\n   }\n}\n\nstatic inline char processStateChar(ProcessState state) {\n   switch (state) {\n      case UNKNOWN: return '?';\n      case RUNNABLE: return 'U';\n      case RUNNING: return 'R';\n      case QUEUED: return 'Q';\n      case WAITING: return 'W';\n      case UNINTERRUPTIBLE_WAIT: return 'D';\n      case BLOCKED: return 'B';\n      case PAGING: return 'P';\n      case STOPPED: return 'T';\n      case TRACED: return 't';\n      case ZOMBIE: return 'Z';\n      case DEFUNCT: return 'X';\n      case IDLE: return 'I';\n      case SLEEPING: return 'S';\n      default:\n         assert(0);\n         return '!';\n   }\n}\n\nstatic void Process_rowWriteField(const Row* super, RichString* str, RowField field) {\n   const Process* this = (const Process*) super;\n   assert(Object_isA((const Object*) this, (const ObjectClass*) &Process_class));\n   Process_writeField(this, str, field);\n}\n\nvoid Process_writeField(const Process* this, RichString* str, RowField field) {\n   const Row* super = (const Row*) &this->super;\n   const Machine* host = super->host;\n   const Settings* settings = host->settings;\n\n   bool coloring = settings->highlightMegabytes;\n   char buffer[256]; buffer[255] = '\\0';\n   int attr = CRT_colors[DEFAULT_COLOR];\n   size_t n = sizeof(buffer) - 1;\n\n   switch (field) {\n   case COMM: {\n      int baseattr = CRT_colors[PROCESS_BASENAME];\n      if (settings->highlightThreads && Process_isThread(this)) {\n         attr = CRT_colors[PROCESS_THREAD];\n         baseattr = CRT_colors[PROCESS_THREAD_BASENAME];\n      }\n      const ScreenSettings* ss = settings->ss;\n      if (!ss->treeView || super->indent == 0) {\n         Process_writeCommand(this, attr, baseattr, str);\n         return;\n      }\n\n      char* buf = buffer;\n      const bool lastItem = (super->indent < 0);\n\n      for (uint32_t indent = (super->indent < 0 ? -super->indent : super->indent); indent > 1; indent >>= 1) {\n         if (!n)\n            break;\n\n         int ret;\n         if (indent & 1U) {\n            ret = xSnprintf(buf, n, \"%s  \", CRT_treeStr[TREE_STR_VERT]);\n         } else {\n            ret = xSnprintf(buf, n, \"   \");\n         }\n         assert(ret > 0 && (size_t)ret < n);\n         buf += ret;\n         n -= ret;\n      }\n\n      const char* draw = CRT_treeStr[lastItem ? TREE_STR_BEND : TREE_STR_RTEE];\n      xSnprintf(buf, n, \"%s%s \", draw, super->showChildren ? CRT_treeStr[TREE_STR_SHUT] : CRT_treeStr[TREE_STR_OPEN] );\n      RichString_appendWide(str, CRT_colors[PROCESS_TREE], buffer);\n      Process_writeCommand(this, attr, baseattr, str);\n      return;\n   }\n   case PROC_COMM: {\n      const char* procComm;\n      if (this->procComm) {\n         attr = CRT_colors[Process_isUserlandThread(this) ? PROCESS_THREAD_COMM : PROCESS_COMM];\n         procComm = this->procComm;\n      } else {\n         attr = CRT_colors[PROCESS_SHADOW];\n         procComm = Process_isKernelThread(this) ? kthreadID : \"N/A\";\n      }\n\n      Row_printLeftAlignedField(str, attr, procComm, TASK_COMM_LEN - 1);\n      return;\n   }\n   case PROC_EXE: {\n      const char* procExe;\n      if (this->procExe) {\n         attr = CRT_colors[Process_isUserlandThread(this) ? PROCESS_THREAD_BASENAME : PROCESS_BASENAME];\n         if (settings->highlightDeletedExe) {\n            if (this->procExeDeleted)\n               attr = CRT_colors[FAILED_READ];\n            else if (this->usesDeletedLib)\n               attr = CRT_colors[PROCESS_TAG];\n         }\n         procExe = this->procExe + this->procExeBasenameOffset;\n      } else {\n         attr = CRT_colors[PROCESS_SHADOW];\n         procExe = Process_isKernelThread(this) ? kthreadID : \"N/A\";\n      }\n\n      Row_printLeftAlignedField(str, attr, procExe, TASK_COMM_LEN - 1);\n      return;\n   }\n   case CWD: {\n      const char* cwd;\n      if (!this->procCwd) {\n         attr = CRT_colors[PROCESS_SHADOW];\n         cwd = \"N/A\";\n      } else if (String_startsWith(this->procCwd, \"/proc/\") && strstr(this->procCwd, \" (deleted)\") != NULL) {\n         attr = CRT_colors[PROCESS_SHADOW];\n         cwd = \"main thread terminated\";\n      } else {\n         cwd = this->procCwd;\n      }\n      Row_printLeftAlignedField(str, attr, cwd, 25);\n      return;\n   }\n   case ELAPSED: {\n      const uint64_t rt = host->realtimeMs;\n      const uint64_t st = this->starttime_ctime * 1000;\n      const uint64_t dt =\n         rt < st ? 0 :\n         rt - st;\n      Row_printTime(str, /* convert to hundreds of a second */ dt / 10, coloring);\n      return;\n   }\n   case MAJFLT: Row_printCount(str, this->majflt, coloring); return;\n   case MINFLT: Row_printCount(str, this->minflt, coloring); return;\n   case M_RESIDENT: Row_printKBytes(str, this->m_resident, coloring); return;\n   case M_VIRT: Row_printKBytes(str, this->m_virt, coloring); return;\n   case NICE:\n      if (this->nice == PROCESS_NICE_UNKNOWN) {\n         xSnprintf(buffer, n, \"N/A \");\n         attr = CRT_colors[PROCESS_SHADOW];\n      } else {\n         xSnprintf(buffer, n, \"%3d \", this->nice);\n         attr = this->nice < 0 ? CRT_colors[PROCESS_HIGH_PRIORITY]\n            : this->nice > 0 ? CRT_colors[PROCESS_LOW_PRIORITY]\n            : CRT_colors[PROCESS_SHADOW];\n      }\n      break;\n   case NLWP:\n      if (this->nlwp == 1)\n         attr = CRT_colors[PROCESS_SHADOW];\n\n      xSnprintf(buffer, n, \"%4ld \", this->nlwp);\n      break;\n   case PERCENT_CPU: Row_printPercentage(this->percent_cpu, buffer, n, Row_fieldWidths[PERCENT_CPU], &attr); break;\n   case PERCENT_NORM_CPU: {\n      float cpuPercentage = this->percent_cpu / host->activeCPUs;\n      Row_printPercentage(cpuPercentage, buffer, n, Row_fieldWidths[PERCENT_CPU], &attr);\n      break;\n   }\n   case PERCENT_MEM: Row_printPercentage(this->percent_mem, buffer, n, 4, &attr); break;\n   case PGRP: xSnprintf(buffer, n, \"%*d \", Process_pidDigits, this->pgrp); break;\n   case PID: xSnprintf(buffer, n, \"%*d \", Process_pidDigits, Process_getPid(this)); break;\n   case PPID: xSnprintf(buffer, n, \"%*d \", Process_pidDigits, Process_getParent(this)); break;\n   case PRIORITY:\n      if (this->priority <= -100)\n         xSnprintf(buffer, n, \" RT \");\n      else\n         xSnprintf(buffer, n, \"%3ld \", this->priority);\n      break;\n   case PROCESSOR: xSnprintf(buffer, n, \"%3d \", Settings_cpuId(settings, this->processor)); break;\n   case SCHEDULERPOLICY: {\n      const char* schedPolStr = \"N/A\";\n#ifdef SCHEDULER_SUPPORT\n      if (this->scheduling_policy >= 0)\n         schedPolStr = Scheduling_formatPolicy(this->scheduling_policy);\n#endif\n      xSnprintf(buffer, n, \"%-5s \", schedPolStr);\n      break;\n   }\n   case SESSION: xSnprintf(buffer, n, \"%*d \", Process_pidDigits, this->session); break;\n   case STARTTIME: xSnprintf(buffer, n, \"%s\", this->starttime_show); break;\n   case STATE:\n      xSnprintf(buffer, n, \"%c \", processStateChar(this->state));\n      switch (this->state) {\n      case RUNNABLE:\n      case RUNNING:\n      case TRACED:\n         attr = CRT_colors[PROCESS_RUN_STATE];\n         break;\n\n      case BLOCKED:\n      case DEFUNCT:\n      case STOPPED:\n      case UNINTERRUPTIBLE_WAIT:\n      case ZOMBIE:\n         attr = CRT_colors[PROCESS_D_STATE];\n         break;\n\n      case QUEUED:\n      case WAITING:\n      case IDLE:\n      case SLEEPING:\n         attr = CRT_colors[PROCESS_SHADOW];\n         break;\n\n      case UNKNOWN:\n      case PAGING:\n         break;\n      }\n      break;\n   case ST_UID: xSnprintf(buffer, n, \"%*d \", Process_uidDigits, this->st_uid); break;\n   case TIME: Row_printTime(str, this->time, coloring); return;\n   case TGID:\n      if (Process_getThreadGroup(this) == Process_getPid(this))\n         attr = CRT_colors[PROCESS_SHADOW];\n\n      xSnprintf(buffer, n, \"%*d \", Process_pidDigits, Process_getThreadGroup(this));\n      break;\n   case TPGID: xSnprintf(buffer, n, \"%*d \", Process_pidDigits, this->tpgid); break;\n   case TTY:\n      if (!this->tty_name) {\n         attr = CRT_colors[PROCESS_SHADOW];\n         xSnprintf(buffer, n, \"(no tty) \");\n      } else {\n         const char* name = String_startsWith(this->tty_name, \"/dev/\") ? (this->tty_name + strlen(\"/dev/\")) : this->tty_name;\n         xSnprintf(buffer, n, \"%-8s \", name);\n      }\n      break;\n   case USER:\n      if (this->elevated_priv == TRI_ON)\n         attr = CRT_colors[PROCESS_PRIV];\n      else if (host->htopUserId != this->st_uid)\n         attr = CRT_colors[PROCESS_SHADOW];\n\n      if (this->user) {\n         Row_printLeftAlignedField(str, attr, this->user, 10);\n         return;\n      }\n\n      xSnprintf(buffer, n, \"%-10d \", this->st_uid);\n      break;\n   default:\n      if (DynamicColumn_writeField(this, str, field))\n         return;\n      assert(0 && \"Process_writeField: default key reached\"); /* should never be reached */\n      xSnprintf(buffer, n, \"- \");\n      break;\n   }\n\n   RichString_appendAscii(str, attr, buffer);\n}\n\nvoid Process_done(Process* this) {\n   assert(this != NULL);\n   free(this->cmdline);\n   free(this->procComm);\n   free(this->procExe);\n   free(this->procCwd);\n   free(this->mergedCommand.str);\n   free(this->tty_name);\n}\n\n/* This function returns the string displayed in Command column, so that sorting\n * happens on what is displayed - whether comm, full path, basename, etc.. So\n * this follows Process_writeField(COMM) and Process_writeCommand */\nconst char* Process_getCommand(const Process* this) {\n   const Settings* settings = this->super.host->settings;\n\n   if ((Process_isUserlandThread(this) && settings->showThreadNames) || !this->mergedCommand.str) {\n      return this->cmdline;\n   }\n\n   return this->mergedCommand.str;\n}\n\nstatic const char* Process_getSortKey(const Process* this) {\n   return Process_getCommand(this);\n}\n\nconst char* Process_rowGetSortKey(Row* super) {\n   const Process* this = (const Process*) super;\n   assert(Object_isA((const Object*) this, (const ObjectClass*) &Process_class));\n   return Process_getSortKey(this);\n}\n\n/* Test whether display must highlight this row (if the htop UID matches) */\nstatic bool Process_isHighlighted(const Process* this) {\n   const Machine* host = this->super.host;\n   const Settings* settings = host->settings;\n   return settings->shadowOtherUsers && this->st_uid != host->htopUserId;\n}\n\nbool Process_rowIsHighlighted(const Row* super) {\n   const Process* this = (const Process*) super;\n   assert(Object_isA((const Object*) this, (const ObjectClass*) &Process_class));\n   return Process_isHighlighted(this);\n}\n\n/* Test whether display must follow parent process (if this thread is hidden) */\nstatic bool Process_isVisible(const Process* p, const Settings* settings) {\n   if (settings->hideUserlandThreads)\n      return !Process_isThread(p);\n   return true;\n}\n\nbool Process_rowIsVisible(const Row* super, const Table* table) {\n   const Process* this = (const Process*) super;\n   assert(Object_isA((const Object*) this, (const ObjectClass*) &Process_class));\n   return Process_isVisible(this, table->host->settings);\n}\n\n/* Test whether display must filter out this process (various mechanisms) */\nstatic bool Process_matchesFilter(const Process* this, const Table* table) {\n   const Machine* host = table->host;\n   if (host->userId != (uid_t) -1 && this->st_uid != host->userId)\n      return true;\n\n   const char* incFilter = table->incFilter;\n   if (incFilter && !String_contains_i(Process_getCommand(this), incFilter, true))\n      return true;\n\n   const ProcessTable* pt = (const ProcessTable*) host->activeTable;\n   assert(Object_isA((const Object*) pt, (const ObjectClass*) &ProcessTable_class));\n   if (pt->pidMatchList && !Hashtable_get(pt->pidMatchList, Process_getThreadGroup(this)))\n      return true;\n\n   return false;\n}\n\nbool Process_rowMatchesFilter(const Row* super, const Table* table) {\n   const Process* this = (const Process*) super;\n   assert(Object_isA((const Object*) this, (const ObjectClass*) &Process_class));\n   return Process_matchesFilter(this, table);\n}\n\nvoid Process_init(Process* this, const Machine* host) {\n   Row_init(&this->super, host);\n\n   this->cmdlineBasenameEnd = 0;\n   this->st_uid = (uid_t)-1;\n}\n\nstatic bool Process_setPriority(Process* this, int priority) {\n   if (Settings_isReadonly())\n      return false;\n\n   int old_prio = getpriority(PRIO_PROCESS, Process_getPid(this));\n   int err = setpriority(PRIO_PROCESS, Process_getPid(this), priority);\n\n   if (err == 0 && old_prio != getpriority(PRIO_PROCESS, Process_getPid(this))) {\n      this->nice = priority;\n   }\n   return (err == 0);\n}\n\nbool Process_rowChangePriorityBy(Row* super, Arg delta) {\n   Process* this = (Process*) super;\n   assert(Object_isA((const Object*) this, (const ObjectClass*) &Process_class));\n   return Process_setPriority(this, (int)this->nice + delta.i);\n}\n\nstatic bool Process_sendSignal(Process* this, Arg sgn) {\n   return kill(Process_getPid(this), sgn.i) == 0;\n}\n\nbool Process_rowSendSignal(Row* super, Arg sgn) {\n   Process* this = (Process*) super;\n   assert(Object_isA((const Object*) this, (const ObjectClass*) &Process_class));\n   return Process_sendSignal(this, sgn);\n}\n\nint Process_compare(const void* v1, const void* v2) {\n   const Process* p1 = (const Process*)v1;\n   const Process* p2 = (const Process*)v2;\n\n   const ScreenSettings* ss = p1->super.host->settings->ss;\n\n   ProcessField key = ScreenSettings_getActiveSortKey(ss);\n\n   int result = Process_compareByKey(p1, p2, key);\n\n   // Implement tie-breaker (needed to make tree mode more stable)\n   if (!result)\n      return SPACESHIP_NUMBER(Process_getPid(p1), Process_getPid(p2));\n\n   return (ScreenSettings_getActiveDirection(ss) == 1) ? result : -result;\n}\n\nint Process_compareByParent(const Row* r1, const Row* r2) {\n   int result = SPACESHIP_NUMBER(\n      r1->isRoot ? 0 : Row_getGroupOrParent(r1),\n      r2->isRoot ? 0 : Row_getGroupOrParent(r2)\n   );\n\n   if (result != 0)\n      return result;\n\n   return Process_compare(r1, r2);\n}\n\nint Process_compareByKey_Base(const Process* p1, const Process* p2, ProcessField key) {\n   int r;\n\n   switch (key) {\n   case PERCENT_CPU:\n   case PERCENT_NORM_CPU:\n      return compareRealNumbers(p1->percent_cpu, p2->percent_cpu);\n   case PERCENT_MEM:\n      return SPACESHIP_NUMBER(p1->m_resident, p2->m_resident);\n   case COMM:\n      return SPACESHIP_NULLSTR(Process_getCommand(p1), Process_getCommand(p2));\n   case PROC_COMM: {\n      const char* comm1 = p1->procComm ? p1->procComm : (Process_isKernelThread(p1) ? kthreadID : \"\");\n      const char* comm2 = p2->procComm ? p2->procComm : (Process_isKernelThread(p2) ? kthreadID : \"\");\n      return SPACESHIP_NULLSTR(comm1, comm2);\n   }\n   case PROC_EXE: {\n      const char* exe1 = p1->procExe ? (p1->procExe + p1->procExeBasenameOffset) : (Process_isKernelThread(p1) ? kthreadID : \"\");\n      const char* exe2 = p2->procExe ? (p2->procExe + p2->procExeBasenameOffset) : (Process_isKernelThread(p2) ? kthreadID : \"\");\n      return SPACESHIP_NULLSTR(exe1, exe2);\n   }\n   case CWD:\n      return SPACESHIP_NULLSTR(p1->procCwd, p2->procCwd);\n   case ELAPSED:\n      r = -SPACESHIP_NUMBER(p1->starttime_ctime, p2->starttime_ctime);\n      return r != 0 ? r : SPACESHIP_NUMBER(Process_getPid(p1), Process_getPid(p2));\n   case MAJFLT:\n      return SPACESHIP_NUMBER(p1->majflt, p2->majflt);\n   case MINFLT:\n      return SPACESHIP_NUMBER(p1->minflt, p2->minflt);\n   case M_RESIDENT:\n      return SPACESHIP_NUMBER(p1->m_resident, p2->m_resident);\n   case M_VIRT:\n      return SPACESHIP_NUMBER(p1->m_virt, p2->m_virt);\n   case NICE:\n      return SPACESHIP_NUMBER(p1->nice, p2->nice);\n   case NLWP:\n      return SPACESHIP_NUMBER(p1->nlwp, p2->nlwp);\n   case PGRP:\n      return SPACESHIP_NUMBER(p1->pgrp, p2->pgrp);\n   case PID:\n      return SPACESHIP_NUMBER(Process_getPid(p1), Process_getPid(p2));\n   case PPID:\n      return SPACESHIP_NUMBER(Process_getParent(p1), Process_getParent(p2));\n   case PRIORITY:\n      return SPACESHIP_NUMBER(p1->priority, p2->priority);\n   case PROCESSOR:\n      return SPACESHIP_NUMBER(p1->processor, p2->processor);\n   case SCHEDULERPOLICY:\n      return SPACESHIP_NUMBER(p1->scheduling_policy, p2->scheduling_policy);\n   case SESSION:\n      return SPACESHIP_NUMBER(p1->session, p2->session);\n   case STARTTIME:\n      r = SPACESHIP_NUMBER(p1->starttime_ctime, p2->starttime_ctime);\n      return r != 0 ? r : SPACESHIP_NUMBER(Process_getPid(p1), Process_getPid(p2));\n   case STATE:\n      return SPACESHIP_NUMBER(p1->state, p2->state);\n   case ST_UID:\n      return SPACESHIP_NUMBER(p1->st_uid, p2->st_uid);\n   case TIME:\n      return SPACESHIP_NUMBER(p1->time, p2->time);\n   case TGID:\n      return SPACESHIP_NUMBER(Process_getThreadGroup(p1), Process_getThreadGroup(p2));\n   case TPGID:\n      return SPACESHIP_NUMBER(p1->tpgid, p2->tpgid);\n   case TTY:\n      /* Order no tty last */\n      return SPACESHIP_DEFAULTSTR(p1->tty_name, p2->tty_name, \"\\x7F\");\n   case USER:\n      return SPACESHIP_NULLSTR(p1->user, p2->user);\n   default:\n      CRT_debug(\"Process_compareByKey_Base() called with key %d\", key);\n      assert(0 && \"Process_compareByKey_Base: default key reached\"); /* should never be reached */\n      return SPACESHIP_NUMBER(Process_getPid(p1), Process_getPid(p2));\n   }\n}\n\nvoid Process_updateComm(Process* this, const char* comm) {\n   if (!this->procComm && !comm)\n      return;\n\n   if (this->procComm && comm && String_eq(this->procComm, comm))\n      return;\n\n   free(this->procComm);\n   this->procComm = comm ? xStrdup(comm) : NULL;\n\n   this->mergedCommand.lastUpdate = 0;\n}\n\nstatic size_t skipPotentialPath(const char* cmdline, size_t end) {\n   if (cmdline[0] != '/')\n      return 0;\n\n   size_t slash = 0;\n   for (size_t i = 1; i < end; i++) {\n      if (cmdline[i] == '/' && cmdline[i + 1] != '\\0') {\n         slash = i + 1;\n         continue;\n      }\n\n      if (cmdline[i] == ' ' && cmdline[i - 1] != '\\\\')\n         return slash;\n\n      if (cmdline[i] == ':' && cmdline[i + 1] == ' ')\n         return slash;\n   }\n\n   return slash;\n}\n\nvoid Process_updateCmdline(Process* this, const char* cmdline, size_t basenameStart, size_t basenameEnd) {\n   assert((cmdline && basenameStart < strlen(cmdline)) || (!cmdline && basenameStart == 0));\n   assert((basenameEnd > basenameStart) || (basenameEnd == 0 && basenameStart == 0));\n   assert((cmdline && basenameEnd <= strlen(cmdline)) || (!cmdline && basenameEnd == 0));\n\n   if (!this->cmdline && !cmdline)\n      return;\n\n   if (this->cmdline && cmdline && String_eq(this->cmdline, cmdline))\n      return;\n\n   free(this->cmdline);\n   this->cmdline = cmdline ? xStrdup(cmdline) : NULL;\n   if (Process_isKernelThread(this)) {\n      /* kernel threads have no basename */\n      this->cmdlineBasenameStart = 0;\n      this->cmdlineBasenameEnd = 0;\n   } else {\n      this->cmdlineBasenameStart = (basenameStart || !cmdline) ? basenameStart : skipPotentialPath(cmdline, basenameEnd);\n      this->cmdlineBasenameEnd = basenameEnd;\n   }\n\n   this->mergedCommand.lastUpdate = 0;\n}\n\nvoid Process_updateExe(Process* this, const char* exe) {\n   if (!this->procExe && !exe)\n      return;\n\n   if (this->procExe && exe && String_eq(this->procExe, exe))\n      return;\n\n   free(this->procExe);\n   if (exe) {\n      this->procExe = xStrdup(exe);\n      const char* lastSlash = strrchr(exe, '/');\n      this->procExeBasenameOffset = (lastSlash && *(lastSlash + 1) != '\\0' && lastSlash != exe) ? (size_t)(lastSlash - exe + 1) : 0;\n   } else {\n      this->procExe = NULL;\n      this->procExeBasenameOffset = 0;\n   }\n\n   this->mergedCommand.lastUpdate = 0;\n}\n\nvoid Process_updateCPUFieldWidths(float percentage) {\n   if (!isgreaterequal(percentage, 99.9F)) {\n      Row_updateFieldWidth(PERCENT_CPU, 4);\n      Row_updateFieldWidth(PERCENT_NORM_CPU, 4);\n      return;\n   }\n\n   // Add additional two characters, one for \".\" and another for precision.\n   uint8_t width = ceil(log10(percentage + 0.1)) + 2;\n\n   Row_updateFieldWidth(PERCENT_CPU, width);\n   Row_updateFieldWidth(PERCENT_NORM_CPU, width);\n}\n\nconst ProcessClass Process_class = {\n   .super = {\n      .super = {\n         .extends = Class(Row),\n         .display = Row_display,\n         .delete = Process_delete,\n         .compare = Process_compare\n      },\n      .isHighlighted = Process_rowIsHighlighted,\n      .isVisible = Process_rowIsVisible,\n      .matchesFilter = Process_rowMatchesFilter,\n      .sortKeyString = Process_rowGetSortKey,\n      .compareByParent = Process_compareByParent,\n      .writeField = Process_rowWriteField\n   },\n};\n"
  },
  {
    "path": "Process.h",
    "content": "#ifndef HEADER_Process\n#define HEADER_Process\n/*\nhtop - Process.h\n(C) 2004-2015 Hisham H. Muhammad\n(C) 2020 Red Hat, Inc.  All Rights Reserved.\nReleased under the GNU GPLv2+, see the COPYING file\nin the source distribution for its full text.\n*/\n\n#include <limits.h>\n#include <stdbool.h>\n#include <stdint.h>\n#include <sys/types.h>\n\n#include \"Object.h\"\n#include \"RichString.h\"\n#include \"Row.h\"\n#include \"RowField.h\"\n\n\n#define PROCESS_FLAG_IO              0x00000001\n#define PROCESS_FLAG_CWD             0x00000002\n#define PROCESS_FLAG_SCHEDPOL        0x00000004\n\n#define DEFAULT_HIGHLIGHT_SECS 5\n\n/* Sentinel value for an unknown niceness in Process.nice */\n#define PROCESS_NICE_UNKNOWN (-INT_MAX)\n\ntypedef enum Tristate_ {\n   TRI_INITIAL = 0,\n   TRI_OFF = -1,\n   TRI_ON = 1,\n} Tristate;\n\n\n/* Core process states (shared by platforms)\n * NOTE: The enum has an ordering that is important!\n * See processStateChar in process.c for ProcessSate -> letter mapping */\ntypedef enum ProcessState_ {\n   UNKNOWN = 1,\n   RUNNABLE,\n   RUNNING,\n   QUEUED,\n   WAITING,\n   UNINTERRUPTIBLE_WAIT,\n   BLOCKED,\n   PAGING,\n   STOPPED,\n   TRACED,\n   ZOMBIE,\n   DEFUNCT,\n   IDLE,\n   SLEEPING\n} ProcessState;\n\nstruct Machine_;  // IWYU pragma: keep\nstruct Settings_; // IWYU pragma: keep\n\n/* Holds information about regions of the cmdline that should be\n * highlighted (e.g. program basename, delimiter, comm). */\ntypedef struct ProcessCmdlineHighlight_ {\n   size_t offset; /* first character to highlight */\n   size_t length; /* How many characters to highlight, zero if unused */\n   int attr;      /* The attributes used to highlight */\n   int flags;     /* Special flags used for selective highlighting, zero for always */\n} ProcessCmdlineHighlight;\n\n/* ProcessMergedCommand is populated by Process_makeCommandStr: It\n * contains the merged Command string, and the information needed by\n * Process_writeCommand to color the string. str will be NULL for kernel\n * threads and zombies */\ntypedef struct ProcessMergedCommand_ {\n   uint64_t lastUpdate;                        /* Marker based on settings->lastUpdate to track when the rendering needs refreshing */\n   char* str;                                  /* merged Command string */\n   size_t highlightCount;                      /* how many portions of cmdline to highlight */\n   ProcessCmdlineHighlight highlights[8];      /* which portions of cmdline to highlight */\n} ProcessMergedCommand;\n\ntypedef struct Process_ {\n   /* Super object for emulated OOP */\n   Row super;\n\n   /* Process group identifier */\n   int pgrp;\n\n   /* Session identifier */\n   int session;\n\n   /* Foreground group identifier of the controlling terminal */\n   int tpgid;\n\n   /* This is a kernel (helper) task */\n   bool isKernelThread;\n\n   /* This is a userland thread / LWP */\n   bool isUserlandThread;\n\n   /* This process is running inside a container */\n   Tristate isRunningInContainer;\n\n   /* Controlling terminal identifier of the process */\n   unsigned long int tty_nr;\n\n   /* Controlling terminal name of the process */\n   char* tty_name;\n\n   /* User identifier */\n   uid_t st_uid;\n\n   /* User name */\n   const char* user;\n\n   /* Non root owned process with elevated privileges\n    * Linux:\n    *   - from file capabilities\n    *   - inherited from the ambient set\n    */\n   Tristate elevated_priv;\n\n   /* Process runtime (in hundredth of a second) */\n   unsigned long long int time;\n\n   /*\n    * Process name including arguments.\n    * Use Process_getCommand() for Command actually displayed.\n    */\n   char* cmdline;\n\n   /* End Offset in cmdline of the process basename */\n   size_t cmdlineBasenameEnd;\n\n   /* Start Offset in cmdline of the process basename */\n   size_t cmdlineBasenameStart;\n\n   /* The process' \"command\" name */\n   char* procComm;\n\n   /* The main process executable */\n   char* procExe;\n\n   /* The process/thread working directory */\n   char* procCwd;\n\n   /* Offset in procExe of the process basename */\n   size_t procExeBasenameOffset;\n\n   /* Tells if the executable has been replaced in the filesystem since start */\n   bool procExeDeleted;\n\n   /* Tells if the process uses replaced shared libraries since start */\n   bool usesDeletedLib;\n\n   /* CPU number last executed on */\n   int processor;\n\n   /* CPU usage during last cycle (in percent) */\n   float percent_cpu;\n\n   /* Memory usage during last cycle (in percent) */\n   float percent_mem;\n\n   /* Scheduling priority */\n   long int priority;\n\n   /* Nice value */\n   int nice;\n\n   /* Number of threads in this process */\n   long int nlwp;\n\n   /* Process start time (in seconds elapsed since the Epoch) */\n   time_t starttime_ctime;\n\n   /* Process start time (cached formatted string) */\n   char starttime_show[8];\n\n   /* Total program size (in kilobytes) */\n   long m_virt;\n\n   /* Resident set size (in kilobytes) */\n   long m_resident;\n\n   /* Number of minor faults the process has made which have not required loading a memory page from disk */\n   unsigned long int minflt;\n\n   /* Number of major faults the process has made which have required loading a memory page from disk */\n   unsigned long int majflt;\n\n   /* Process state enum field (platform dependent) */\n   ProcessState state;\n\n   /* Current scheduling policy */\n   int scheduling_policy;\n\n   /*\n    * Internal state for merged Command display\n    */\n   ProcessMergedCommand mergedCommand;\n} Process;\n\ntypedef struct ProcessFieldData_ {\n   /* Name (displayed in setup menu) */\n   const char* name;\n\n   /* Title (display in main screen); must have same width as the printed values */\n   const char* title;\n\n   /* Description (displayed in setup menu) */\n   const char* description;\n\n   /* Scan flag to enable scan-method otherwise not run */\n   uint32_t flags;\n\n   /* Whether the values are process identifiers; adjusts the width of title and values if true */\n   bool pidColumn;\n\n   /* Whether the column should be sorted in descending order by default */\n   bool defaultSortDesc;\n\n   /* Whether the column width is dynamically adjusted (the minimum width is determined by the title length) */\n   bool autoWidth;\n\n   /* Whether the title of a column with dynamically adjusted width is right aligned (default is left aligned) */\n   bool autoTitleRightAlign;\n} ProcessFieldData;\n\n#define LAST_PROCESSFIELD LAST_RESERVED_FIELD\ntypedef int32_t ProcessField;  /* see ReservedField list in RowField.h */\n\n// Implemented in platform-specific code:\nvoid Process_writeField(const Process* this, RichString* str, ProcessField field);\nint Process_compare(const void* v1, const void* v2);\nint Process_compareByParent(const Row* r1, const Row* r2);\nvoid Process_delete(Object* cast);\nextern const ProcessFieldData Process_fields[LAST_PROCESSFIELD];\n#define Process_pidDigits Row_pidDigits\n#define Process_uidDigits Row_uidDigits\n\ntypedef Process* (*Process_New)(const struct Machine_*);\ntypedef int (*Process_CompareByKey)(const Process*, const Process*, ProcessField);\n\ntypedef struct ProcessClass_ {\n   const RowClass super;\n   const Process_CompareByKey compareByKey;\n} ProcessClass;\n\n#define As_Process(this_)   ((const ProcessClass*)((this_)->super.super.klass))\n\n#define Process_compareByKey(p1_, p2_, key_)   (As_Process(p1_)->compareByKey ? (As_Process(p1_)->compareByKey(p1_, p2_, key_)) : Process_compareByKey_Base(p1_, p2_, key_))\n\n\nstatic inline void Process_setPid(Process* this, pid_t pid) {\n   this->super.id = pid;\n}\n\nstatic inline pid_t Process_getPid(const Process* this) {\n   return (pid_t)this->super.id;\n}\n\nstatic inline void Process_setThreadGroup(Process* this, pid_t pid) {\n   this->super.group = pid;\n}\n\nstatic inline pid_t Process_getThreadGroup(const Process* this) {\n   return (pid_t)this->super.group;\n}\n\nstatic inline void Process_setParent(Process* this, pid_t pid) {\n   this->super.parent = pid;\n}\n\nstatic inline pid_t Process_getParent(const Process* this) {\n   return (pid_t)this->super.parent;\n}\n\nstatic inline pid_t Process_getGroupOrParent(const Process* this) {\n   return Row_getGroupOrParent(&this->super);\n}\n\nstatic inline bool Process_isKernelThread(const Process* this) {\n   return this->isKernelThread;\n}\n\nstatic inline bool Process_isUserlandThread(const Process* this) {\n   return this->isUserlandThread;\n}\n\nstatic inline bool Process_isThread(const Process* this) {\n   return Process_isUserlandThread(this) || Process_isKernelThread(this);\n}\n\n#define CMDLINE_HIGHLIGHT_FLAG_SEPARATOR  0x00000001\n#define CMDLINE_HIGHLIGHT_FLAG_BASENAME   0x00000002\n#define CMDLINE_HIGHLIGHT_FLAG_COMM       0x00000004\n#define CMDLINE_HIGHLIGHT_FLAG_DELETED    0x00000008\n#define CMDLINE_HIGHLIGHT_FLAG_PREFIXDIR  0x00000010\n\nvoid Process_fillStarttimeBuffer(Process* this);\n\nvoid Process_done(Process* this);\n\nextern const ProcessClass Process_class;\n\nvoid Process_init(Process* this, const struct Machine_* host);\n\nconst char* Process_rowGetSortKey(Row* super);\n\nbool Process_rowChangePriorityBy(Row* super, Arg delta);\n\nbool Process_rowSendSignal(Row* super, Arg sgn);\n\nbool Process_rowIsHighlighted(const Row* super);\n\nbool Process_rowIsVisible(const Row* super, const struct Table_* table);\n\nbool Process_rowMatchesFilter(const Row* super, const struct Table_* table);\n\nstatic inline int Process_pidEqualCompare(const void* v1, const void* v2) {\n   return Row_idEqualCompare(v1, v2);\n}\n\nint Process_compareByKey_Base(const Process* p1, const Process* p2, ProcessField key);\n\nconst char* Process_getCommand(const Process* this);\n\nvoid Process_updateComm(Process* this, const char* comm);\nvoid Process_updateCmdline(Process* this, const char* cmdline, size_t basenameStart, size_t basenameEnd);\nvoid Process_updateExe(Process* this, const char* exe);\n\n/* This function constructs the string that is displayed by\n * Process_writeCommand and also returned by Process_getCommand */\nvoid Process_makeCommandStr(Process* this, const struct Settings_ *settings);\n\nvoid Process_writeCommand(const Process* this, int attr, int baseAttr, RichString* str);\n\nvoid Process_updateCPUFieldWidths(float percentage);\n\n#endif\n"
  },
  {
    "path": "ProcessLocksScreen.c",
    "content": "/*\nhtop - ProcessLocksScreen.c\n(C) 2020 htop dev team\nReleased under the GNU GPLv2+, see the COPYING file\nin the source distribution for its full text.\n*/\n\n#include \"config.h\" // IWYU pragma: keep\n\n#include \"ProcessLocksScreen.h\"\n\n#include <inttypes.h>\n#include <limits.h>\n#include <stdlib.h>\n\n#include \"Panel.h\"\n#include \"Platform.h\"\n#include \"ProvideCurses.h\"\n#include \"Vector.h\"\n#include \"XUtils.h\"\n\n\nProcessLocksScreen* ProcessLocksScreen_new(const Process* process) {\n   ProcessLocksScreen* this = xMalloc(sizeof(ProcessLocksScreen));\n   Object_setClass(this, Class(ProcessLocksScreen));\n   if (Process_isThread(process))\n      this->pid = Process_getThreadGroup(process);\n   else\n      this->pid = Process_getPid(process);\n\n   return (ProcessLocksScreen*) InfoScreen_init(&this->super, process, NULL, LINES - 2, \"   FD TYPE       EXCLUSION  READ/WRITE DEVICE       NODE               START                 END  FILENAME\");\n}\n\nvoid ProcessLocksScreen_delete(Object* this) {\n   free(InfoScreen_done((InfoScreen*)this));\n}\n\nstatic void ProcessLocksScreen_draw(InfoScreen* this) {\n   InfoScreen_drawTitled(this, \"Snapshot of file locks of process %d - %s\", ((ProcessLocksScreen*)this)->pid, Process_getCommand(this->process));\n}\n\nstatic inline void FileLocks_Data_clear(FileLocks_Data* data) {\n   free(data->locktype);\n   free(data->exclusive);\n   free(data->readwrite);\n   free(data->filename);\n}\n\nstatic void ProcessLocksScreen_scan(InfoScreen* this) {\n   Panel* panel = this->display;\n   int idx = Panel_getSelectedIndex(panel);\n   Panel_prune(panel);\n   FileLocks_ProcessData* pdata = Platform_getProcessLocks(((ProcessLocksScreen*)this)->pid);\n   if (!pdata) {\n      InfoScreen_addLine(this, \"This feature is not supported on your platform.\");\n   } else if (pdata->error) {\n      InfoScreen_addLine(this, \"Could not determine file locks.\");\n   } else {\n      FileLocks_LockData* ldata = pdata->locks;\n      if (!ldata) {\n         InfoScreen_addLine(this, \"No locks have been found for the selected process.\");\n      }\n      while (ldata) {\n         FileLocks_Data* data = &ldata->data;\n\n         char entry[512];\n         if (ULLONG_MAX == data->end) {\n            xSnprintf(entry, sizeof(entry), \"%5d %-10s %-10s %-10s %#6\"PRIx64\" %10\"PRIu64\" %19\"PRIu64\" %19s  %s\",\n               data->fd,\n               data->locktype, data->exclusive, data->readwrite,\n               (uint64_t) data->dev, data->inode,\n               data->start, \"<END OF FILE>\",\n               data->filename ? data->filename : \"<N/A>\"\n            );\n         } else {\n            xSnprintf(entry, sizeof(entry), \"%5d %-10s %-10s %-10s %#6\"PRIx64\" %10\"PRIu64\" %19\"PRIu64\" %19\"PRIu64\"  %s\",\n               data->fd,\n               data->locktype, data->exclusive, data->readwrite,\n               (uint64_t) data->dev, data->inode,\n               data->start, data->end,\n               data->filename ? data->filename : \"<N/A>\"\n            );\n         }\n\n         InfoScreen_addLine(this, entry);\n         FileLocks_Data_clear(&ldata->data);\n\n         FileLocks_LockData* old = ldata;\n         ldata = ldata->next;\n         free(old);\n      }\n   }\n   free(pdata);\n   Vector_insertionSort(this->lines);\n   Vector_insertionSort(panel->items);\n   Panel_setSelected(panel, idx);\n}\n\nconst InfoScreenClass ProcessLocksScreen_class = {\n   .super = {\n      .extends = Class(Object),\n      .delete = ProcessLocksScreen_delete\n   },\n   .scan = ProcessLocksScreen_scan,\n   .draw = ProcessLocksScreen_draw\n};\n"
  },
  {
    "path": "ProcessLocksScreen.h",
    "content": "#ifndef HEADER_ProcessLocksScreen\n#define HEADER_ProcessLocksScreen\n/*\nhtop - ProcessLocksScreen.h\n(C) 2020 htop dev team\nReleased under the GNU GPLv2+, see the COPYING file\nin the source distribution for its full text.\n*/\n\n#include <stdbool.h>\n#include <stdint.h>\n#include <sys/types.h>\n\n#include \"InfoScreen.h\"\n#include \"Object.h\"\n#include \"Process.h\"\n\n\ntypedef struct ProcessLocksScreen_ {\n   InfoScreen super;\n   pid_t pid;\n} ProcessLocksScreen;\n\ntypedef struct FileLocks_Data_ {\n   char* locktype;\n   char* exclusive;\n   char* readwrite;\n   char* filename;\n   int fd;\n   dev_t dev;\n   uint64_t inode;\n   uint64_t start;\n   uint64_t end;\n} FileLocks_Data;\n\ntypedef struct FileLocks_LockData_ {\n   FileLocks_Data data;\n   struct FileLocks_LockData_* next;\n} FileLocks_LockData;\n\ntypedef struct FileLocks_ProcessData_ {\n   bool error;\n   struct FileLocks_LockData_* locks;\n} FileLocks_ProcessData;\n\nextern const InfoScreenClass ProcessLocksScreen_class;\n\nProcessLocksScreen* ProcessLocksScreen_new(const Process* process);\n\nvoid ProcessLocksScreen_delete(Object* this);\n\n#endif\n"
  },
  {
    "path": "ProcessTable.c",
    "content": "/*\nhtop - ProcessTable.c\n(C) 2004,2005 Hisham H. Muhammad\nReleased under the GNU GPLv2+, see the COPYING file\nin the source distribution for its full text.\n*/\n\n#include \"config.h\" // IWYU pragma: keep\n\n#include \"ProcessTable.h\"\n\n#include <assert.h>\n#include <stdlib.h>\n\n#include \"Hashtable.h\"\n#include \"Row.h\"\n#include \"Settings.h\"\n#include \"Vector.h\"\n\n\nvoid ProcessTable_init(ProcessTable* this, const ObjectClass* klass, Machine* host, Hashtable* pidMatchList) {\n   Table_init(&this->super, klass, host);\n\n   this->pidMatchList = pidMatchList;\n}\n\nvoid ProcessTable_done(ProcessTable* this) {\n   Table_done(&this->super);\n}\n\nProcess* ProcessTable_getProcess(ProcessTable* this, pid_t pid, bool* preExisting, Process_New constructor) {\n   const Table* table = &this->super;\n   Process* proc = (Process*) Hashtable_get(table->table, pid);\n   *preExisting = proc != NULL;\n   if (proc) {\n      assert(Vector_indexOf(table->rows, proc, Row_idEqualCompare) != -1);\n      assert(Process_getPid(proc) == pid);\n   } else {\n      proc = constructor(table->host);\n      assert(proc->cmdline == NULL);\n      Process_setPid(proc, pid);\n   }\n   return proc;\n}\n\nstatic void ProcessTable_prepareEntries(Table* super) {\n   ProcessTable* this = (ProcessTable*) super;\n   this->totalTasks = 0;\n   this->userlandThreads = 0;\n   this->kernelThreads = 0;\n   this->runningTasks = 0;\n\n   Table_prepareEntries(super);\n}\n\nstatic void ProcessTable_iterateEntries(Table* super) {\n   ProcessTable* this = (ProcessTable*) super;\n   // calling into platform-specific code\n   ProcessTable_goThroughEntries(this);\n}\n\nstatic void ProcessTable_cleanupEntries(Table* super) {\n   Machine* host = super->host;\n   const Settings* settings = host->settings;\n\n   // Lowest index of the row that is soft-removed. Used to speed up\n   // compaction.\n   int dirtyIndex = Vector_size(super->rows);\n\n   // Finish process table update, culling any exit'd processes\n   for (int i = Vector_size(super->rows) - 1; i >= 0; i--) {\n      Process* p = (Process*) Vector_get(super->rows, i);\n\n      // tidy up Process state after refreshing the ProcessTable table\n      Process_makeCommandStr(p, settings);\n\n      // keep track of the highest UID and PID for column scaling\n      if (p->st_uid > host->maxUserId)\n         host->maxUserId = p->st_uid;\n\n      pid_t pid = Process_getPid(p);\n      if (pid > host->maxProcessId)\n         host->maxProcessId = pid;\n\n      if (!Table_cleanupRow(super, &p->super, i)) {\n         dirtyIndex = i;\n      }\n   }\n\n   // compact the table in case of deletions\n   Table_compact(super, dirtyIndex);\n}\n\nconst TableClass ProcessTable_class = {\n   .super = {\n      .extends = Class(Table),\n      .delete = ProcessTable_delete,\n   },\n   .prepare = ProcessTable_prepareEntries,\n   .iterate = ProcessTable_iterateEntries,\n   .cleanup = ProcessTable_cleanupEntries,\n};\n"
  },
  {
    "path": "ProcessTable.h",
    "content": "#ifndef HEADER_ProcessTable\n#define HEADER_ProcessTable\n/*\nhtop - ProcessTable.h\n(C) 2004,2005 Hisham H. Muhammad\nReleased under the GNU GPLv2+, see the COPYING file\nin the source distribution for its full text.\n*/\n\n#include <stdbool.h>\n#include <sys/types.h>\n\n#include \"Hashtable.h\"\n#include \"Machine.h\"\n#include \"Object.h\"\n#include \"Process.h\"\n#include \"Table.h\"\n\n\ntypedef struct ProcessTable_ {\n   Table super;\n\n   Hashtable* pidMatchList;\n\n   unsigned int totalTasks;\n   unsigned int runningTasks;\n   unsigned int userlandThreads;\n   unsigned int kernelThreads;\n} ProcessTable;\n\n/* Implemented by platforms */\nProcessTable* ProcessTable_new(Machine* host, Hashtable* pidMatchList);\nvoid ProcessTable_delete(Object* cast);\nvoid ProcessTable_goThroughEntries(ProcessTable* this);\n\nvoid ProcessTable_init(ProcessTable* this, const ObjectClass* klass, Machine* host, Hashtable* pidMatchList);\n\nvoid ProcessTable_done(ProcessTable* this);\n\nextern const TableClass ProcessTable_class;\n\nstatic inline void ProcessTable_add(ProcessTable* this, Process* process) {\n   Table_add(&this->super, &process->super);\n}\n\nProcess* ProcessTable_getProcess(ProcessTable* this, pid_t pid, bool* preExisting, Process_New constructor);\n\nstatic inline Process* ProcessTable_findProcess(ProcessTable* this, pid_t pid) {\n   return (Process*) Table_findRow(&this->super, pid);\n}\n\n#endif\n"
  },
  {
    "path": "ProvideCurses.h",
    "content": "#ifndef HEADER_ProvideCurses\n#define HEADER_ProvideCurses\n/*\nhtop - ProvideCurses.h\n(C) 2004,2011 Hisham H. Muhammad\nReleased under the GNU GPLv2+, see the COPYING file\nin the source distribution for its full text.\n*/\n\n\n// This header is also used in tests by configure, thus conditionally\n// including \"config.h\".\n#if defined(HAVE_CONFIG_H)\n#include \"config.h\" // IWYU pragma: keep\n#endif\n\n// IWYU pragma: begin_exports\n\n#if defined(HAVE_NCURSESW_CURSES_H)\n#include <ncursesw/curses.h>\n#elif defined(HAVE_NCURSES_NCURSES_H)\n#include <ncurses/ncurses.h>\n#elif defined(HAVE_NCURSES_CURSES_H)\n#include <ncurses/curses.h>\n#elif defined(HAVE_NCURSES_H)\n#include <ncurses.h>\n#elif defined(HAVE_CURSES_H)\n#include <curses.h>\n#endif\n\n#ifdef HAVE_LIBNCURSESW\n#include <wchar.h>\n#include <wctype.h>\n#endif\n\n// IWYU pragma: end_exports\n\n#endif // HEADER_ProvideCurses\n"
  },
  {
    "path": "ProvideTerm.h",
    "content": "#ifndef HEADER_ProvideTerm\n#define HEADER_ProvideTerm\n/*\nhtop - ProvideTerm.h\n(C) 2023 htop dev team\nReleased under the GNU GPLv2+, see the COPYING file\nin the source distribution for its full text.\n*/\n\n#include \"config.h\" // IWYU pragma: keep\n\n// IWYU pragma: begin_exports\n\n#if defined(HAVE_NCURSESW_TERM_H)\n#include <ncursesw/term.h>\n#elif defined(HAVE_NCURSES_TERM_H)\n#include <ncurses/term.h>\n#elif defined(HAVE_TERM_H)\n#include <term.h>\n#endif\n\n// IWYU pragma: end_exports\n\n#endif // HEADER_ProvideTerm\n"
  },
  {
    "path": "README.md",
    "content": "# [![htop logo](htop.png)](https://htop.dev)\n\n[![CI](https://github.com/htop-dev/htop/workflows/CI/badge.svg)](https://github.com/htop-dev/htop/actions)\n[![Coverity Scan Build Status](https://scan.coverity.com/projects/21665/badge.svg)](https://scan.coverity.com/projects/21665)\n[![Mailing List](https://img.shields.io/badge/Mailing%20List-htop-blue.svg)](https://groups.io/g/htop)\n[![IRC #htop](https://img.shields.io/badge/IRC-htop-blue.svg)](https://web.libera.chat/#htop)\n[![GitHub Release](https://img.shields.io/github/release/htop-dev/htop.svg)](https://github.com/htop-dev/htop/releases/latest)\n[![Packaging status](https://repology.org/badge/tiny-repos/htop.svg)](https://repology.org/project/htop/versions)\n[![License: GPL v2+](https://img.shields.io/badge/License-GPL%20v2+-blue.svg)](COPYING?raw=true)\n\n![Screenshot of htop](docs/images/screenshot.png?raw=true)\n\n## Introduction\n\n`htop` is a cross-platform interactive process viewer.\n\n`htop` allows scrolling the list of processes vertically and horizontally to see their full command lines and related information like memory and CPU consumption.\nAlso system wide information, like load average or swap usage, is shown.\n\nThe information displayed is configurable through a graphical setup and can be sorted and filtered interactively.\n\nTasks related to processes (e.g. killing and renicing) can be done without entering their PIDs.\n\nRunning `htop` requires `ncurses` libraries, typically named libncurses(w).\n\n`htop` is written in C.\n\nFor more information and details visit [htop.dev](https://htop.dev).\n\n## Usage\nSee the manual page (`man htop`) or the help menu (`h` or `F1` inside `htop`) for a list of supported key commands.\n\n### Quick Start\n\nSome common actions to get you started with `htop`\n\n- Search processes: press `/`\n- Filter processes: press `\\`\n- Toggle tree view: press `t`\n- Change process sort column: press `.`\n- Kill a process: select the process and press `k`\n\n## Build instructions\n\n### Prerequisite\nList of build-time dependencies:\n  * standard GNU autotools-based C toolchain\n    - C99 compliant compiler\n    - `autoconf`\n    - `automake`\n    - `autotools`\n  * `ncurses`\n\n**Note about `ncurses`:**\n> `htop` requires `ncurses` 6.0. Be aware the appropriate package is sometimes still called libncurses5 (on Debian/Ubuntu). Also `ncurses` usually comes in two flavours:\n>* With Unicode support.\n>* Without Unicode support.\n>\n> This is also something that is reflected in the package name on Debian/Ubuntu (via the additional 'w' - 'w'ide character support).\n\nList of additional build-time dependencies (based on feature flags):\n*  `pkg-config`\n*  `sensors`\n*  `hwloc`\n*  `libcap` (v2.21 or later)\n*  `libnl-3` and `libnl-genl-3`\n\n`pkg-config` is optional but recommended. The configure script of `htop` might utilize `pkg-config` to obtain the compiler and linker flags required for a library. Some OS distributions provide `pkg-config` functionalities through an alternative implementation such as `pkgconf`. Look for both names in your package manager.\n\nInstall these and other required packages for C development from your package manager.\n\n**Debian/Ubuntu**\n~~~ shell\nsudo apt install libncursesw5-dev autotools-dev autoconf automake build-essential\n~~~\n\n**Fedora/RHEL**\n~~~ shell\nsudo dnf install ncurses-devel automake autoconf gcc\n~~~\n\n**Archlinux/Manjaro**\n~~~ shell\nsudo pacman -S ncurses automake autoconf gcc\n~~~\n\n**macOS**\n~~~ shell\nbrew install ncurses automake autoconf gcc\n~~~\n\n### Compile from source:\nTo compile from source, download from the Git repository (`git clone` or downloads from [GitHub releases](https://github.com/htop-dev/htop/releases/)), then run:\n~~~ shell\n./autogen.sh && ./configure && make\n~~~\n\n### Install\nTo install on the local system run `make install`. By default `make install` installs into `/usr/local`. To change this path use `./configure --prefix=/some/path`.\n\n### Build Options\n\n`htop` has several build-time options to enable/disable additional features.\n\n#### Generic\n\n  * `--enable-unicode`:\n    enable Unicode support\n    - dependency: *libncursesw*\n    - default: *yes*\n  * `--enable-affinity`:\n    enable `sched_setaffinity(2)` and `sched_getaffinity(2)` for affinity support; conflicts with hwloc\n    - default: *check*\n  * `--enable-hwloc`:\n    enable hwloc support for CPU affinity; disables affinity support\n    - dependency: *libhwloc*\n    - default: *no*\n  * `--enable-static`:\n    build a static htop binary; hwloc and delay accounting are not supported\n    - default: *no*\n  * `--enable-debug`:\n    Enable asserts and internal sanity checks; implies a performance penalty\n    - default: *no*\n\n#### Performance Co-Pilot\n\n  * `--enable-pcp`:\n    enable Performance Co-Pilot support via a new pcp-htop utility\n    - dependency: *libpcp*\n    - default: *no*\n\n#### Linux\n\n  * `--enable-sensors`:\n    enable libsensors(3) support for reading temperature data\n    - dependencies: *libsensors-dev*(build-time), at runtime *libsensors* is loaded via `dlopen(3)` if available\n    - default: *check*\n  * `--enable-capabilities`:\n    enable Linux capabilities support\n    - dependency: *libcap*\n    - default: *check*\n  * `--with-proc`:\n    location of a Linux-compatible proc filesystem\n    - default: */proc*\n  * `--enable-openvz`:\n    enable OpenVZ support\n    - default: *no*\n  * `--enable-vserver`:\n    enable VServer support\n    - default: *no*\n  * `--enable-ancient-vserver`:\n    enable ancient VServer support (implies `--enable-vserver`)\n    - default: *no*\n  * `--enable-delayacct`:\n    enable Linux delay accounting support\n    - dependencies: *libnl-3-dev*(build-time) and *libnl-genl-3-dev*(build-time), at runtime *libnl-3* and *libnl-genl-3* are loaded via `dlopen(3)` if available and requested\n    - default: *check*\n\n\n## Runtime dependencies:\n`htop` has a set of fixed minimum runtime dependencies, which is kept as minimal as possible:\n* `ncurses` libraries for terminal handling (wide character support).\n\n### Runtime optional dependencies:\n`htop` has a set of fixed optional dependencies, depending on build/configure option used:\n\n#### Linux\n* `libdl`, if not building a static binary, is always required when support for optional dependencies (i.e. `libsensors`, `libsystemd`) is present.\n* `libcap`, user-space interfaces to POSIX 1003.1e capabilities, is always required when `--enable-capabilities` was used to configure `htop`.\n* `libsensors`, readout of temperatures and CPU speeds, is optional even when `--enable-sensors` was used to configure `htop`.\n* `libsystemd` is optional when `--enable-static` was not used to configure `htop`. If building statically and `libsystemd` is not found by `configure`, support for the systemd meter is disabled entirely.\n* `libnl-3` and `libnl-genl-3`, if `htop` was configured with `--enable-delayacct` and delay accounting process fields are active.\n* I/O counters are available when the kernel is compiled with `CONFIG_TASK_IO_ACCOUNTING=Y`.\n\n`htop` checks for the availability of the actual runtime libraries as `htop` runs.\n\n#### BSD\nOn most BSD systems `kvm` is a requirement to read kernel information.\n\nMore information on required and optional dependencies can be found in [configure.ac](configure.ac).\n\n## Support\n\nIf you have trouble running `htop` please consult your operating system / Linux distribution documentation for getting support and filing bugs.\n\n## Bugs, development feedback\n\nWe have a [development mailing list](https://htop.dev/mailinglist.html). Feel free to subscribe for release announcements or asking questions on the development of `htop`.\n\nYou can also join our IRC channel [#htop on Libera.Chat](https://web.libera.chat/#htop) and talk to the developers there.\n\nIf you have found an issue within the source of `htop`, please check whether this has already been reported in our [GitHub issue tracker](https://github.com/htop-dev/htop/issues).\nIf not, please file a new issue describing the problem you have found, the potential location in the source code you are referring to and a possible fix if available.\n\n## History\n\n`htop` was invented, developed and maintained by [Hisham Muhammad](https://hisham.hm/) from 2004 to 2019. His [legacy repository](https://github.com/hishamhm/htop/) has been archived to preserve the history.\n\nIn 2020 a [team](https://github.com/orgs/htop-dev/people) took over the development amicably and continues to maintain `htop` collaboratively.\n\n## License\n\nGNU General Public License, version 2 (GPL-2.0) or, at your option, any later version.\n"
  },
  {
    "path": "RichString.c",
    "content": "/*\nhtop - RichString.c\n(C) 2004,2011 Hisham H. Muhammad\nReleased under the GNU GPLv2+, see the COPYING file\nin the source distribution for its full text.\n*/\n\n#include \"config.h\" // IWYU pragma: keep\n\n#include \"RichString.h\"\n\n#include <assert.h>\n#include <ctype.h>\n#include <limits.h> // IWYU pragma: keep\n#include <stdlib.h>\n#include <string.h>\n\n#include \"Macros.h\"\n#include \"XUtils.h\"\n\n\n#define charBytes(n) (sizeof(CharType) * (n))\n\nstatic void RichString_extendLen(RichString* this, size_t len) {\n   if (this->chptr == this->chstr) {\n      // String is in internal buffer\n      if (len > RICHSTRING_MAXLEN) {\n         // Copy from internal buffer to allocated string\n         this->chptr = xMalloc(charBytes(len + 1));\n         memcpy(this->chptr, this->chstr, charBytes(this->chlen));\n      } else {\n         // Still fits in internal buffer, do nothing\n         assert(this->chlen <= RICHSTRING_MAXLEN);\n      }\n   } else {\n      // String is managed externally\n      if (len > RICHSTRING_MAXLEN) {\n         // Just reallocate the buffer accordingly\n         this->chptr = xRealloc(this->chptr, charBytes(len + 1));\n      } else {\n         // Move string into internal buffer and free resources\n         memcpy(this->chstr, this->chptr, charBytes(len));\n         free(this->chptr);\n         this->chptr = this->chstr;\n      }\n   }\n\n   RichString_setChar(this, len, 0);\n   this->chlen = (int)len;\n}\n\nstatic void RichString_setLen(RichString* this, size_t len) {\n   if (len < RICHSTRING_MAXLEN && this->chlen < RICHSTRING_MAXLEN) {\n      RichString_setChar(this, len, 0);\n      this->chlen = (int)len;\n   } else {\n      RichString_extendLen(this, len);\n   }\n}\n\nvoid RichString_rewind(RichString* this, int count) {\n   RichString_setLen(this, this->chlen - count);\n}\n\n#ifdef HAVE_LIBNCURSESW\n\nstatic size_t mbstowcs_nonfatal(wchar_t* restrict dest, const char* restrict src, size_t n) {\n   size_t written = 0;\n   mbstate_t ps = { 0 };\n   bool broken = false;\n\n   while (n > 0) {\n      size_t ret = mbrtowc(dest, src, n, &ps);\n      if (ret == (size_t)-1 || ret == (size_t)-2) {\n         if (!broken) {\n            broken = true;\n            *dest++ = L'\\xFFFD';\n            written++;\n         }\n         src++;\n         n--;\n         continue;\n      }\n\n      broken = false;\n\n      if (ret == 0) {\n         break;\n      }\n\n      dest++;\n      written++;\n      src += ret;\n      n -= ret;\n   }\n\n   return written;\n}\n\nstatic inline int RichString_writeFromWide(RichString* this, int attrs, const char* data_c, int from, size_t len) {\n   if (len < 1)\n      return 0;\n\n   wchar_t data[len];\n   len = mbstowcs_nonfatal(data, data_c, len);\n   if (len <= 0)\n      return 0;\n\n   size_t newLen = from + len;\n   RichString_setLen(this, newLen);\n   for (size_t i = from, j = 0; i < newLen; i++, j++) {\n      this->chptr[i] = (CharType) { .attr = attrs & 0xffffff, .chars = { (iswprint(data[j]) ? data[j] : L'\\xFFFD') } };\n   }\n\n   return (int)len;\n}\n\nint RichString_appendnWideColumns(RichString* this, int attrs, const char* data_c, size_t len, int* columns) {\n   wchar_t data[len];\n   len = mbstowcs_nonfatal(data, data_c, len);\n   if (len <= 0)\n      return 0;\n\n   int from = this->chlen;\n   size_t newLen = from + len;\n   RichString_setLen(this, newLen);\n   int columnsWritten = 0;\n   int pos = from;\n   for (size_t j = 0; j < len; j++) {\n      wchar_t c = iswprint(data[j]) ? data[j] : L'\\xFFFD';\n      int cwidth = wcwidth(c);\n      if (cwidth > *columns)\n         break;\n\n      *columns -= cwidth;\n      columnsWritten += cwidth;\n\n      this->chptr[pos] = (CharType) { .attr = attrs & 0xffffff, .chars = { c, '\\0' } };\n      pos++;\n   }\n\n   RichString_setLen(this, pos);\n   *columns = columnsWritten;\n\n   return pos - from;\n}\n\nstatic inline int RichString_writeFromAscii(RichString* this, int attrs, const char* data, int from, size_t len) {\n   size_t newLen = from + len;\n   RichString_setLen(this, newLen);\n   for (size_t i = from, j = 0; i < newLen; i++, j++) {\n      assert((unsigned char)data[j] <= SCHAR_MAX);\n      this->chptr[i] = (CharType) { .attr = attrs & 0xffffff, .chars = { (isprint((unsigned char)data[j]) ? data[j] : L'\\xFFFD') } };\n   }\n\n   return (int)len;\n}\n\ninline void RichString_setAttrn(RichString* this, int attrs, size_t start, size_t charcount) {\n   size_t end = CLAMP(start + charcount, 0, (size_t)this->chlen);\n   for (size_t i = start; i < end; i++) {\n      this->chptr[i].attr = attrs;\n   }\n}\n\nvoid RichString_appendChr(RichString* this, int attrs, char c, int count) {\n   int from = this->chlen;\n   int newLen = from + count;\n   RichString_setLen(this, newLen);\n   for (int i = from; i < newLen; i++) {\n      this->chptr[i] = (CharType) { .attr = attrs, .chars = { c, 0 } };\n   }\n}\n\nint RichString_findChar(const RichString* this, char c, int start) {\n   const wchar_t wc = btowc(c);\n   const cchar_t* ch = this->chptr + start;\n   for (int i = start; i < this->chlen; i++) {\n      if (ch->chars[0] == wc)\n         return i;\n      ch++;\n   }\n   return -1;\n}\n\n#else /* HAVE_LIBNCURSESW */\n\nstatic inline int RichString_writeFromWide(RichString* this, int attrs, const char* data_c, int from, size_t len) {\n   size_t newLen = from + len;\n   RichString_setLen(this, newLen);\n   for (size_t i = from, j = 0; i < newLen; i++, j++) {\n      this->chptr[i] = (((unsigned char)data_c[j]) >= 32 ? ((unsigned char)data_c[j]) : '?') | attrs;\n   }\n   this->chptr[newLen] = 0;\n\n   return (int)len;\n}\n\nint RichString_appendnWideColumns(RichString* this, int attrs, const char* data_c, size_t len, int* columns) {\n   size_t minlen = MINIMUM(len, (size_t) *columns);\n   int written = RichString_writeFromWide(this, attrs, data_c, this->chlen, minlen);\n   *columns = written;\n   return written;\n}\n\nstatic inline int RichString_writeFromAscii(RichString* this, int attrs, const char* data_c, int from, size_t len) {\n   return RichString_writeFromWide(this, attrs, data_c, from, len);\n}\n\nvoid RichString_setAttrn(RichString* this, int attrs, size_t start, size_t charcount) {\n   size_t end = CLAMP(start + charcount, 0, (size_t)this->chlen);\n   for (size_t i = start; i < end; i++) {\n      this->chptr[i] = (this->chptr[i] & 0xff) | attrs;\n   }\n}\n\nvoid RichString_appendChr(RichString* this, int attrs, char c, int count) {\n   int from = this->chlen;\n   int newLen = from + count;\n   RichString_setLen(this, newLen);\n   for (int i = from; i < newLen; i++) {\n      this->chptr[i] = c | attrs;\n   }\n}\n\nint RichString_findChar(const RichString* this, char c, int start) {\n   const chtype* ch = this->chptr + start;\n   for (int i = start; i < this->chlen; i++) {\n      if ((*ch & 0xff) == (chtype) c)\n         return i;\n      ch++;\n   }\n   return -1;\n}\n\n#endif /* HAVE_LIBNCURSESW */\n\nvoid RichString_delete(RichString* this) {\n   if (this->chlen > RICHSTRING_MAXLEN) {\n      free(this->chptr);\n      this->chptr = this->chstr;\n   }\n}\n\nvoid RichString_setAttr(RichString* this, int attrs) {\n   RichString_setAttrn(this, attrs, 0, this->chlen);\n}\n\nint RichString_appendWide(RichString* this, int attrs, const char* data) {\n   return RichString_writeFromWide(this, attrs, data, this->chlen, strlen(data));\n}\n\nint RichString_appendnWide(RichString* this, int attrs, const char* data, size_t len) {\n   return RichString_writeFromWide(this, attrs, data, this->chlen, len);\n}\n\nint RichString_writeWide(RichString* this, int attrs, const char* data) {\n   return RichString_writeFromWide(this, attrs, data, 0, strlen(data));\n}\n\nint RichString_appendAscii(RichString* this, int attrs, const char* data) {\n   return RichString_writeFromAscii(this, attrs, data, this->chlen, strlen(data));\n}\n\nint RichString_appendnAscii(RichString* this, int attrs, const char* data, size_t len) {\n   return RichString_writeFromAscii(this, attrs, data, this->chlen, len);\n}\n\nint RichString_writeAscii(RichString* this, int attrs, const char* data) {\n   return RichString_writeFromAscii(this, attrs, data, 0, strlen(data));\n}\n"
  },
  {
    "path": "RichString.h",
    "content": "#ifndef HEADER_RichString\n#define HEADER_RichString\n/*\nhtop - RichString.h\n(C) 2004,2011 Hisham H. Muhammad\nReleased under the GNU GPLv2+, see the COPYING file\nin the source distribution for its full text.\n*/\n\n#include \"Macros.h\"\n#include \"ProvideCurses.h\"\n\n\n#define RichString_size(this) ((this)->chlen)\n#define RichString_sizeVal(this) ((this).chlen)\n\n#define RichString_begin(this) RichString this; RichString_beginAllocated(this)\n#define RichString_beginAllocated(this)   \\\n   do {                                   \\\n      (this).chlen = 0;                   \\\n      (this).chptr = (this).chstr;        \\\n      RichString_setChar(&(this), 0, 0);  \\\n      (this).highlightAttr = 0;           \\\n   } while(0)\n\n#ifdef HAVE_LIBNCURSESW\n#define RichString_printVal(this, y, x) mvadd_wchstr(y, x, (this).chptr)\n#define RichString_printoffnVal(this, y, x, off, n) mvadd_wchnstr(y, x, (this).chptr + (off), n)\n#define RichString_getCharVal(this, i) ((this).chptr[i].chars[0])\n#define RichString_setChar(this, at, ch) do { (this)->chptr[(at)] = (CharType) { .chars = { ch, 0 } }; } while (0)\n#define CharType cchar_t\n#else\n#define RichString_printVal(this, y, x) mvaddchstr(y, x, (this).chptr)\n#define RichString_printoffnVal(this, y, x, off, n) mvaddchnstr(y, x, (this).chptr + (off), n)\n#define RichString_getCharVal(this, i) ((this).chptr[i] & 0xff)\n#define RichString_setChar(this, at, ch) do { (this)->chptr[(at)] = ch; } while (0)\n#define CharType chtype\n#endif\n\n#define RICHSTRING_MAXLEN 350\n\ntypedef struct RichString_ {\n   int chlen;\n   CharType* chptr;\n   CharType chstr[RICHSTRING_MAXLEN + 1];\n   int highlightAttr;\n} RichString;\n\nvoid RichString_delete(RichString* this);\n\nvoid RichString_rewind(RichString* this, int count);\n\nvoid RichString_setAttrn(RichString* this, int attrs, size_t start, size_t charcount);\n\nint RichString_findChar(const RichString* this, char c, int start);\n\nvoid RichString_setAttr(RichString* this, int attrs);\n\nvoid RichString_appendChr(RichString* this, int attrs, char c, int count);\n\n/* All appending and writing functions return the number of written characters (not columns). */\n\nint RichString_appendWide(RichString* this, int attrs, const char* data);\n\nATTR_ACCESS3_R(3, 4)\nint RichString_appendnWide(RichString* this, int attrs, const char* data, size_t len);\n\n/* columns takes the maximum number of columns to write and contains on return the number of columns written. */\nint RichString_appendnWideColumns(RichString* this, int attrs, const char* data, size_t len, int* columns);\n\nint RichString_writeWide(RichString* this, int attrs, const char* data);\n\nint RichString_appendAscii(RichString* this, int attrs, const char* data);\n\nATTR_ACCESS3_R(3, 4)\nint RichString_appendnAscii(RichString* this, int attrs, const char* data, size_t len);\n\nint RichString_writeAscii(RichString* this, int attrs, const char* data);\n\n#endif\n"
  },
  {
    "path": "Row.c",
    "content": "/*\nhtop - Row.c\n(C) 2004-2015 Hisham H. Muhammad\n(C) 2020-2023 Red Hat, Inc.  All Rights Reserved.\nReleased under the GNU GPLv2+, see the COPYING file\nin the source distribution for its full text.\n*/\n\n#include \"config.h\" // IWYU pragma: keep\n\n#include \"Row.h\"\n\n#include <assert.h>\n#include <limits.h>\n#include <math.h>\n#include <stdbool.h>\n#include <stdio.h>\n#include <stdlib.h>\n#include <string.h>\n\n#include \"CRT.h\"\n#include \"DynamicColumn.h\"\n#include \"Hashtable.h\"\n#include \"Machine.h\"\n#include \"Macros.h\"\n#include \"Process.h\"\n#include \"RichString.h\"\n#include \"Settings.h\"\n#include \"XUtils.h\"\n\n\nint Row_pidDigits = ROW_MIN_PID_DIGITS;\nint Row_uidDigits = ROW_MIN_UID_DIGITS;\n\nvoid Row_init(Row* this, const Machine* host) {\n   this->host = host;\n   this->tag = false;\n   this->showChildren = true;\n   this->show = true;\n   this->wasShown = false;\n   this->updated = false;\n}\n\nvoid Row_done(Row* this) {\n   assert(this != NULL);\n   (void) this;\n}\n\nstatic inline bool Row_isNew(const Row* this) {\n   const Machine* host = this->host;\n   if (host->monotonicMs < this->seenStampMs)\n      return false;\n\n   const Settings* settings = host->settings;\n   return host->monotonicMs - this->seenStampMs <= 1000 * (uint64_t)settings->highlightDelaySecs;\n}\n\nstatic inline bool Row_isTomb(const Row* this) {\n   return this->tombStampMs > 0;\n}\n\nvoid Row_display(const Object* cast, RichString* out) {\n   const Row* this = (const Row*) cast;\n   const Settings* settings = this->host->settings;\n   const RowField* fields = settings->ss->fields;\n\n   for (int i = 0; fields[i]; i++)\n      As_Row(this)->writeField(this, out, fields[i]);\n\n   if (Row_isHighlighted(this))\n      RichString_setAttr(out, CRT_colors[PROCESS_SHADOW]);\n\n   if (this->tag == true)\n      RichString_setAttr(out, CRT_colors[PROCESS_TAG]);\n\n   if (settings->highlightChanges) {\n      if (Row_isTomb(this))\n         out->highlightAttr = CRT_colors[PROCESS_TOMB];\n      else if (Row_isNew(this))\n         out->highlightAttr = CRT_colors[PROCESS_NEW];\n   }\n\n   assert(RichString_size(out) > 0);\n}\n\nvoid Row_setPidColumnWidth(pid_t maxPid) {\n   if (maxPid < (int)pow(10, ROW_MIN_PID_DIGITS)) {\n      Row_pidDigits = ROW_MIN_PID_DIGITS;\n      return;\n   }\n\n   Row_pidDigits = (int)countDigits((size_t)maxPid, 10);\n   assert(Row_pidDigits <= ROW_MAX_PID_DIGITS);\n}\n\nvoid Row_setUidColumnWidth(uid_t maxUid) {\n   if (maxUid < (uid_t)pow(10, ROW_MIN_UID_DIGITS)) {\n      Row_uidDigits = ROW_MIN_UID_DIGITS;\n      return;\n   }\n\n   Row_uidDigits = (int)countDigits((size_t)maxUid, 10);\n   assert(Row_uidDigits <= ROW_MAX_UID_DIGITS);\n}\n\nuint8_t Row_fieldWidths[LAST_PROCESSFIELD] = { 0 };\n\nvoid Row_resetFieldWidths(void) {\n   for (size_t i = 0; i < LAST_PROCESSFIELD; i++) {\n      if (!Process_fields[i].autoWidth)\n         continue;\n\n      size_t len = strlen(Process_fields[i].title);\n      assert(len <= UINT8_MAX);\n      Row_fieldWidths[i] = (uint8_t)len;\n   }\n}\n\nvoid Row_updateFieldWidth(RowField key, size_t width) {\n   if (width > UINT8_MAX)\n      Row_fieldWidths[key] = UINT8_MAX;\n   else if (width > Row_fieldWidths[key])\n      Row_fieldWidths[key] = (uint8_t)width;\n}\n\n// helper function to fill an aligned title string for a dynamic column\nstatic const char* alignedTitleDynamicColumn(const Settings* settings, int key, char* titleBuffer, size_t titleBufferSize) {\n   const DynamicColumn* column = Hashtable_get(settings->dynamicColumns, key);\n   if (column == NULL)\n      return \"- \";\n\n   int width = column->width;\n   if (!width || abs(width) > DYNAMIC_MAX_COLUMN_WIDTH)\n      width = DYNAMIC_DEFAULT_COLUMN_WIDTH;\n\n   xSnprintf(titleBuffer, titleBufferSize, \"%*s \", width, column->heading);\n   return titleBuffer;\n}\n\n// helper function to fill an aligned title string for a process field\nstatic const char* alignedTitleProcessField(ProcessField field, char* titleBuffer, size_t titleBufferSize) {\n   const char* title = Process_fields[field].title;\n   if (!title)\n      return \"- \";\n\n   if (Process_fields[field].pidColumn) {\n      xSnprintf(titleBuffer, titleBufferSize, \"%*s \", Row_pidDigits, title);\n      return titleBuffer;\n   }\n\n   if (field == ST_UID) {\n      xSnprintf(titleBuffer, titleBufferSize, \"%*s \", Row_uidDigits, title);\n      return titleBuffer;\n   }\n\n   if (Process_fields[field].autoWidth) {\n      if (Process_fields[field].autoTitleRightAlign)\n         xSnprintf(titleBuffer, titleBufferSize, \"%*s \", Row_fieldWidths[field], title);\n      else\n         xSnprintf(titleBuffer, titleBufferSize, \"%-*.*s \", Row_fieldWidths[field], Row_fieldWidths[field], title);\n      return titleBuffer;\n   }\n\n   return title;\n}\n\n// helper function to create an aligned title string for a given field\nconst char* RowField_alignedTitle(const Settings* settings, RowField field) {\n   static char titleBuffer[UINT8_MAX + sizeof(\" \")];\n   assert(sizeof(titleBuffer) >= DYNAMIC_MAX_COLUMN_WIDTH + sizeof(\" \"));\n   assert(sizeof(titleBuffer) >= ROW_MAX_PID_DIGITS + sizeof(\" \"));\n   assert(sizeof(titleBuffer) >= ROW_MAX_UID_DIGITS + sizeof(\" \"));\n\n   if (field < LAST_PROCESSFIELD)\n      return alignedTitleProcessField((ProcessField)field, titleBuffer, sizeof(titleBuffer));\n   return alignedTitleDynamicColumn(settings, field, titleBuffer, sizeof(titleBuffer));\n}\n\nRowField RowField_keyAt(const Settings* settings, int at) {\n   const RowField* fields = settings->ss->fields;\n   RowField field;\n   int rem = at;\n   for (int i = 0; (field = fields[i]); i++) {\n      int len = rem > 0 ? (int)strnlen(RowField_alignedTitle(settings, field), rem) : 0;\n      if (rem <= len) {\n         return field;\n      }\n      rem -= len;\n   }\n   return COMM;\n}\n\nvoid Row_printKBytes(RichString* str, unsigned long long number, bool coloring) {\n   char buffer[16];\n   int len;\n\n   int color = CRT_colors[PROCESS];\n   int nextUnitColor = CRT_colors[PROCESS];\n\n   const int colors[4] = {\n      [0] = CRT_colors[PROCESS],\n      [1] = CRT_colors[PROCESS_MEGABYTES],\n      [2] = CRT_colors[PROCESS_GIGABYTES],\n      [3] = CRT_colors[LARGE_NUMBER]\n   };\n\n   if (number == ULLONG_MAX)\n      goto invalidNumber;\n\n   if (coloring) {\n      color = colors[0];\n      nextUnitColor = colors[1];\n   }\n\n   if (number < 1000) {\n      // Plain number, no markings\n      len = xSnprintf(buffer, sizeof(buffer), \"%5u \", (unsigned int)number);\n      RichString_appendnAscii(str, color, buffer, len);\n      return;\n   }\n\n   if (number < 100000) {\n      // 2 digits for M, 3 digits for K\n      len = xSnprintf(buffer, sizeof(buffer), \"%2u\", (unsigned int)(number / 1000));\n      RichString_appendnAscii(str, nextUnitColor, buffer, len);\n      len = xSnprintf(buffer, sizeof(buffer), \"%03u \", (unsigned int)(number % 1000));\n      RichString_appendnAscii(str, color, buffer, len);\n      return;\n   }\n\n   // 100000 KiB (97.6 MiB) or greater. A unit prefix would be added.\n   const size_t maxUnitIndex = (sizeof(number) * CHAR_BIT - 1) / 10 + 1;\n   const bool canOverflow = maxUnitIndex >= ARRAYSIZE(unitPrefixes);\n\n   size_t i = 1;\n   int prevUnitColor;\n   // Convert KiB to (1/100) of MiB\n   unsigned long long hundredths = (number / 256) * 25 + (number % 256) * 25 / 256;\n   while (true) {\n      if (canOverflow && i >= ARRAYSIZE(unitPrefixes))\n         goto invalidNumber;\n\n      prevUnitColor = color;\n      color = nextUnitColor;\n\n      if (coloring && i + 1 < ARRAYSIZE(colors))\n         nextUnitColor = colors[i + 1];\n\n      if (hundredths < 1000000)\n         break;\n\n      hundredths /= ONE_K;\n      i++;\n   }\n\n   number = hundredths / 100;\n   hundredths %= 100;\n   if (number < 100) {\n      if (number < 10) {\n         // 1 digit + decimal point + 2 digits\n         // \"9.76G\", \"9.99G\", \"9.76T\", \"9.99T\", etc.\n         len = xSnprintf(buffer, sizeof(buffer), \"%1u\", (unsigned int)number);\n         RichString_appendnAscii(str, color, buffer, len);\n         len = xSnprintf(buffer, sizeof(buffer), \".%02u\", (unsigned int)hundredths);\n      } else {\n         // 2 digits + decimal point + 1 digit\n         // \"97.6M\", \"99.9M\", \"10.0G\", \"99.9G\", etc.\n         len = xSnprintf(buffer, sizeof(buffer), \"%2u\", (unsigned int)number);\n         RichString_appendnAscii(str, color, buffer, len);\n         len = xSnprintf(buffer, sizeof(buffer), \".%1u\", (unsigned int)hundredths / 10);\n      }\n      RichString_appendnAscii(str, prevUnitColor, buffer, len);\n      len = xSnprintf(buffer, sizeof(buffer), \"%c \", unitPrefixes[i]);\n   } else if (number < 1000) {\n      // 3 digits\n      // \"100M\", \"999M\", \"100G\", \"999G\", etc.\n      len = xSnprintf(buffer, sizeof(buffer), \"%4u%c \", (unsigned int)number, unitPrefixes[i]);\n   } else {\n      // 1 digit + 3 digits\n      // \"1000M\", \"9999M\", \"1000G\", \"9999G\", etc.\n      assert(number < 10000);\n\n      len = xSnprintf(buffer, sizeof(buffer), \"%1u\", (unsigned int)number / 1000);\n      RichString_appendnAscii(str, nextUnitColor, buffer, len);\n      len = xSnprintf(buffer, sizeof(buffer), \"%03u%c \", (unsigned int)number % 1000, unitPrefixes[i]);\n   }\n   RichString_appendnAscii(str, color, buffer, len);\n   return;\n\ninvalidNumber:\n   if (coloring)\n      color = CRT_colors[PROCESS_SHADOW];\n\n   RichString_appendAscii(str, color, \"  N/A \");\n}\n\nvoid Row_printBytes(RichString* str, unsigned long long number, bool coloring) {\n   if (number == ULLONG_MAX)\n      Row_printKBytes(str, ULLONG_MAX, coloring);\n   else\n      Row_printKBytes(str, number / ONE_K, coloring);\n}\n\nvoid Row_printCount(RichString* str, unsigned long long number, bool coloring) {\n   char buffer[13];\n\n   int largeNumberColor = coloring ? CRT_colors[LARGE_NUMBER] : CRT_colors[PROCESS];\n   int megabytesColor = coloring ? CRT_colors[PROCESS_MEGABYTES] : CRT_colors[PROCESS];\n   int shadowColor = coloring ? CRT_colors[PROCESS_SHADOW] : CRT_colors[PROCESS];\n   int baseColor = CRT_colors[PROCESS];\n\n   if (number == ULLONG_MAX) {\n      RichString_appendAscii(str, CRT_colors[PROCESS_SHADOW], \"        N/A \");\n   } else if (number >= 100000LL * ONE_DECIMAL_T) {\n      xSnprintf(buffer, sizeof(buffer), \"%11llu \", number / ONE_DECIMAL_G);\n      RichString_appendnAscii(str, largeNumberColor, buffer, 12);\n   } else if (number >= 100LL * ONE_DECIMAL_T) {\n      xSnprintf(buffer, sizeof(buffer), \"%11llu \", number / ONE_DECIMAL_M);\n      RichString_appendnAscii(str, largeNumberColor, buffer, 8);\n      RichString_appendnAscii(str, megabytesColor, buffer + 8, 4);\n   } else if (number >= 10LL * ONE_DECIMAL_G) {\n      xSnprintf(buffer, sizeof(buffer), \"%11llu \", number / ONE_DECIMAL_K);\n      RichString_appendnAscii(str, largeNumberColor, buffer, 5);\n      RichString_appendnAscii(str, megabytesColor, buffer + 5, 3);\n      RichString_appendnAscii(str, baseColor, buffer + 8, 4);\n   } else {\n      xSnprintf(buffer, sizeof(buffer), \"%11llu \", number);\n      RichString_appendnAscii(str, largeNumberColor, buffer, 2);\n      RichString_appendnAscii(str, megabytesColor, buffer + 2, 3);\n      RichString_appendnAscii(str, baseColor, buffer + 5, 3);\n      RichString_appendnAscii(str, shadowColor, buffer + 8, 4);\n   }\n}\n\nvoid Row_printTime(RichString* str, unsigned long long totalHundredths, bool coloring) {\n   char buffer[10];\n   int len;\n\n   if (totalHundredths == 0) {\n      int shadowColor = coloring ? CRT_colors[PROCESS_SHADOW] : CRT_colors[PROCESS];\n\n      RichString_appendAscii(str, shadowColor, \" 0:00.00 \");\n      return;\n   }\n\n   int yearColor = coloring ? CRT_colors[LARGE_NUMBER]      : CRT_colors[PROCESS];\n   int dayColor  = coloring ? CRT_colors[PROCESS_GIGABYTES] : CRT_colors[PROCESS];\n   int hourColor = coloring ? CRT_colors[PROCESS_MEGABYTES] : CRT_colors[PROCESS];\n   int baseColor = CRT_colors[PROCESS];\n\n   unsigned long long totalSeconds = totalHundredths / 100;\n   unsigned long long totalMinutes = totalSeconds / 60;\n   unsigned long long totalHours = totalMinutes / 60;\n   unsigned int seconds = totalSeconds % 60;\n   unsigned int minutes = totalMinutes % 60;\n\n   if (totalMinutes < 60) {\n      unsigned int hundredths = totalHundredths % 100;\n      len = xSnprintf(buffer, sizeof(buffer), \"%2u:%02u.%02u \", (unsigned int)totalMinutes, seconds, hundredths);\n      RichString_appendnAscii(str, baseColor, buffer, len);\n      return;\n   }\n   if (totalHours < 24) {\n      len = xSnprintf(buffer, sizeof(buffer), \"%2uh\", (unsigned int)totalHours);\n      RichString_appendnAscii(str, hourColor, buffer, len);\n      len = xSnprintf(buffer, sizeof(buffer), \"%02u:%02u \", minutes, seconds);\n      RichString_appendnAscii(str, baseColor, buffer, len);\n      return;\n   }\n\n   unsigned long long totalDays = totalHours / 24;\n   unsigned int hours = totalHours % 24;\n   if (totalDays < 10) {\n      len = xSnprintf(buffer, sizeof(buffer), \"%1ud\", (unsigned int)totalDays);\n      RichString_appendnAscii(str, dayColor, buffer, len);\n      len = xSnprintf(buffer, sizeof(buffer), \"%02uh\", hours);\n      RichString_appendnAscii(str, hourColor, buffer, len);\n      len = xSnprintf(buffer, sizeof(buffer), \"%02um \", minutes);\n      RichString_appendnAscii(str, baseColor, buffer, len);\n      return;\n   }\n   if (totalDays < /* Ignore leap years */365) {\n      len = xSnprintf(buffer, sizeof(buffer), \"%4ud\", (unsigned int)totalDays);\n      RichString_appendnAscii(str, dayColor, buffer, len);\n      len = xSnprintf(buffer, sizeof(buffer), \"%02uh \", hours);\n      RichString_appendnAscii(str, hourColor, buffer, len);\n      return;\n   }\n\n   unsigned long long years = totalDays / 365;\n   unsigned int days = totalDays % 365;\n   if (years < 1000) {\n      len = xSnprintf(buffer, sizeof(buffer), \"%3uy\", (unsigned int)years);\n      RichString_appendnAscii(str, yearColor, buffer, len);\n      len = xSnprintf(buffer, sizeof(buffer), \"%03ud \", days);\n      RichString_appendnAscii(str, dayColor, buffer, len);\n   } else if (years < 10000000) {\n      len = xSnprintf(buffer, sizeof(buffer), \"%7luy \", (unsigned long)years);\n      RichString_appendnAscii(str, yearColor, buffer, len);\n   } else {\n      RichString_appendAscii(str, yearColor, \"eternity \");\n   }\n}\n\nvoid Row_printNanoseconds(RichString* str, unsigned long long totalNanoseconds, bool coloring) {\n   if (totalNanoseconds == 0) {\n      int shadowColor = coloring ? CRT_colors[PROCESS_SHADOW] : CRT_colors[PROCESS];\n\n      RichString_appendAscii(str, shadowColor, \"     0ns \");\n      return;\n   }\n\n   char buffer[10];\n   int len;\n   int baseColor = CRT_colors[PROCESS];\n\n   if (totalNanoseconds < 1000000) {\n      len = xSnprintf(buffer, sizeof(buffer), \"%6luns \", (unsigned long)totalNanoseconds);\n      RichString_appendnAscii(str, baseColor, buffer, len);\n      return;\n   }\n\n   unsigned long long totalMicroseconds = totalNanoseconds / 1000;\n   if (totalMicroseconds < 1000000) {\n      len = xSnprintf(buffer, sizeof(buffer), \".%06lus \", (unsigned long)totalMicroseconds);\n      RichString_appendnAscii(str, baseColor, buffer, len);\n      return;\n   }\n\n   unsigned long long totalSeconds = totalMicroseconds / 1000000;\n   uint32_t microseconds = totalMicroseconds % 1000000;\n   if (totalSeconds < 60) {\n      int width = 5;\n      uint32_t fraction = microseconds / 10;\n      if (totalSeconds >= 10) {\n         width--;\n         fraction /= 10;\n      }\n      len = xSnprintf(buffer, sizeof(buffer), \"%u.%0*lus \", (unsigned int)totalSeconds, width, (unsigned long)fraction);\n      RichString_appendnAscii(str, baseColor, buffer, len);\n      return;\n   }\n\n   if (totalSeconds < 600) {\n      unsigned int minutes = (unsigned int)totalSeconds / 60;\n      unsigned int seconds = (unsigned int)totalSeconds % 60;\n      unsigned int milliseconds = (unsigned int)(microseconds / 1000);\n      len = xSnprintf(buffer, sizeof(buffer), \"%u:%02u.%03u \", minutes, seconds, milliseconds);\n      RichString_appendnAscii(str, baseColor, buffer, len);\n      return;\n   }\n\n   unsigned long long totalHundredths = totalMicroseconds / 1000 / 10;\n   Row_printTime(str, totalHundredths, coloring);\n}\n\nvoid Row_printRate(RichString* str, double rate, bool coloring) {\n   char buffer[16];\n\n   int largeNumberColor = CRT_colors[LARGE_NUMBER];\n   int megabytesColor = CRT_colors[PROCESS_MEGABYTES];\n   int shadowColor = CRT_colors[PROCESS_SHADOW];\n   int baseColor = CRT_colors[PROCESS];\n\n   if (!coloring) {\n      largeNumberColor = CRT_colors[PROCESS];\n      megabytesColor = CRT_colors[PROCESS];\n   }\n\n   if (!isNonnegative(rate)) {\n      RichString_appendAscii(str, shadowColor, \"        N/A \");\n   } else if (rate < 0.005) {\n      int len = snprintf(buffer, sizeof(buffer), \"%7.2f B/s \", rate);\n      RichString_appendnAscii(str, shadowColor, buffer, len);\n   } else if (rate < ONE_K) {\n      int len = snprintf(buffer, sizeof(buffer), \"%7.2f B/s \", rate);\n      RichString_appendnAscii(str, baseColor, buffer, len);\n   } else if (rate < ONE_M) {\n      int len = snprintf(buffer, sizeof(buffer), \"%7.2f K/s \", rate / ONE_K);\n      RichString_appendnAscii(str, baseColor, buffer, len);\n   } else if (rate < ONE_G) {\n      int len = snprintf(buffer, sizeof(buffer), \"%7.2f M/s \", rate / ONE_M);\n      RichString_appendnAscii(str, megabytesColor, buffer, len);\n   } else if (rate < ONE_T) {\n      int len = snprintf(buffer, sizeof(buffer), \"%7.2f G/s \", rate / ONE_G);\n      RichString_appendnAscii(str, largeNumberColor, buffer, len);\n   } else if (rate < ONE_P) {\n      int len = snprintf(buffer, sizeof(buffer), \"%7.2f T/s \", rate / ONE_T);\n      RichString_appendnAscii(str, largeNumberColor, buffer, len);\n   } else {\n      int len = snprintf(buffer, sizeof(buffer), \"%7.2f P/s \", rate / ONE_P);\n      RichString_appendnAscii(str, largeNumberColor, buffer, len);\n   }\n}\n\nvoid Row_printLeftAlignedField(RichString* str, int attr, const char* content, unsigned int width) {\n   int columns = width;\n   RichString_appendnWideColumns(str, attr, content, strlen(content), &columns);\n   RichString_appendChr(str, attr, ' ', width + 1 - columns);\n}\n\nint Row_printPercentage(float val, char* buffer, size_t n, uint8_t width, int* attr) {\n   assert(n >= 6 && width >= 4 && \"Invalid width in Row_printPercentage()\");\n   // truncate in favour of abort in xSnprintf()\n   width = (uint8_t)CLAMP(width, 4, n - 2);\n   assert(width < n - 1 && \"Insufficient space to print column\");\n\n   if (isNonnegative(val)) {\n      if (val < 0.05F)\n         *attr = CRT_colors[PROCESS_SHADOW];\n      else if (val >= 99.9F)\n         *attr = CRT_colors[PROCESS_MEGABYTES];\n\n      int precision = 1;\n\n      // Display \"val\" as \"100\" for columns like \"MEM%\".\n      if (width == 4 && val > 99.9F) {\n         precision = 0;\n         val = 100.0F;\n      }\n\n      return xSnprintf(buffer, n, \"%*.*f \", width, precision, val);\n   }\n\n   *attr = CRT_colors[PROCESS_SHADOW];\n   return xSnprintf(buffer, n, \"%*.*s \", width, width, \"N/A\");\n}\n\nvoid Row_toggleTag(Row* this) {\n   this->tag = !this->tag;\n}\n\nint Row_compare(const void* v1, const void* v2) {\n   const Row* r1 = (const Row*)v1;\n   const Row* r2 = (const Row*)v2;\n\n   return SPACESHIP_NUMBER(r1->id, r2->id);\n}\n\nint Row_compareByParent_Base(const void* v1, const void* v2) {\n   const Row* r1 = (const Row*)v1;\n   const Row* r2 = (const Row*)v2;\n\n   int result = SPACESHIP_NUMBER(\n      r1->isRoot ? 0 : Row_getGroupOrParent(r1),\n      r2->isRoot ? 0 : Row_getGroupOrParent(r2)\n   );\n\n   if (result != 0)\n      return result;\n\n   return Row_compare(v1, v2);\n}\n\nconst RowClass Row_class = {\n   .super = {\n      .extends = Class(Object),\n      .compare = Row_compare\n   },\n};\n"
  },
  {
    "path": "Row.h",
    "content": "#ifndef HEADER_Row\n#define HEADER_Row\n/*\nhtop - Row.h\n(C) 2004-2015 Hisham H. Muhammad\n(C) 2023 htop dev team\nReleased under the GNU GPLv2+, see the COPYING file\nin the source distribution for its full text.\n*/\n\n#include <stdbool.h>\n#include <stddef.h>\n#include <stdint.h>\n#include <sys/types.h>\n\n#include \"Object.h\"\n#include \"RichString.h\"\n#include \"RowField.h\"\n\n\nextern uint8_t Row_fieldWidths[LAST_RESERVED_FIELD];\n#define ROW_MIN_PID_DIGITS 5\n#define ROW_MAX_PID_DIGITS 19\n#define ROW_MIN_UID_DIGITS 5\n#define ROW_MAX_UID_DIGITS 20\nextern int Row_pidDigits;\nextern int Row_uidDigits;\n\nstruct Machine_;     // IWYU pragma: keep\nstruct Settings_;    // IWYU pragma: keep\nstruct Table_;       // IWYU pragma: keep\n\n/* Class representing entities (such as processes) that can be\n * represented in a tabular form in the lower half of the htop\n * display. */\n\ntypedef struct Row_ {\n   /* Super object for emulated OOP */\n   Object super;\n\n   /* Pointer to quasi-global data */\n   const struct Machine_* host;\n\n   int id;\n   int group;\n   int parent;\n\n   /* Has no known parent */\n   bool isRoot;\n\n   /* Whether the row was tagged by the user */\n   bool tag;\n\n   /* Whether to display this row */\n   bool show;\n\n   /* Whether this row was shown last cycle */\n   bool wasShown;\n\n   /* Whether to show children of this row in tree-mode */\n   bool showChildren;\n\n   /* Whether the row was updated during the last scan */\n   bool updated;\n\n   /*\n    * Internal state for tree-mode.\n    */\n   int32_t indent;\n   unsigned int tree_depth;\n\n   /*\n    * Internal time counts for showing new and exited processes.\n    */\n   uint64_t seenStampMs;\n   uint64_t tombStampMs;\n} Row;\n\ntypedef Row* (*Row_New)(const struct Machine_*);\ntypedef void (*Row_WriteField)(const Row*, RichString*, RowField);\ntypedef bool (*Row_IsHighlighted)(const Row*);\ntypedef bool (*Row_IsVisible)(const Row*, const struct Table_*);\ntypedef bool (*Row_MatchesFilter)(const Row*, const struct Table_*);\ntypedef const char* (*Row_SortKeyString)(Row*);\ntypedef int (*Row_CompareByParent)(const Row*, const Row*);\n\nint Row_compare(const void* v1, const void* v2);\n\ntypedef struct RowClass_ {\n   const ObjectClass super;\n   const Row_IsHighlighted isHighlighted;\n   const Row_IsVisible isVisible;\n   const Row_WriteField writeField;\n   const Row_MatchesFilter matchesFilter;\n   const Row_SortKeyString sortKeyString;\n   const Row_CompareByParent compareByParent;\n} RowClass;\n\n#define As_Row(this_)  ((const RowClass*)((this_)->super.klass))\n\n#define Row_isHighlighted(r_)  (As_Row(r_)->isHighlighted ? (As_Row(r_)->isHighlighted(r_)) : false)\n#define Row_isVisible(r_, t_)  (As_Row(r_)->isVisible ? (As_Row(r_)->isVisible(r_, t_)) : true)\n#define Row_matchesFilter(r_, t_)  (As_Row(r_)->matchesFilter ? (As_Row(r_)->matchesFilter(r_, t_)) : false)\n#define Row_sortKeyString(r_)  (As_Row(r_)->sortKeyString ? (As_Row(r_)->sortKeyString(r_)) : \"\")\n#define Row_compareByParent(r1_, r2_)  (As_Row(r1_)->compareByParent ? (As_Row(r1_)->compareByParent(r1_, r2_)) : Row_compareByParent_Base(r1_, r2_))\n\n#define ONE_K 1024UL\n#define ONE_M (ONE_K * ONE_K)\n#define ONE_G (ONE_M * ONE_K)\n#define ONE_T (1ULL * ONE_G * ONE_K)\n#define ONE_P (1ULL * ONE_T * ONE_K)\n\n#define ONE_DECIMAL_K 1000UL\n#define ONE_DECIMAL_M (ONE_DECIMAL_K * ONE_DECIMAL_K)\n#define ONE_DECIMAL_G (ONE_DECIMAL_M * ONE_DECIMAL_K)\n#define ONE_DECIMAL_T (1ULL * ONE_DECIMAL_G * ONE_DECIMAL_K)\n#define ONE_DECIMAL_P (1ULL * ONE_DECIMAL_T * ONE_DECIMAL_K)\n\nextern const RowClass Row_class;\n\nvoid Row_init(Row* this, const struct Machine_* host);\n\nvoid Row_done(Row* this);\n\nvoid Row_display(const Object* cast, RichString* out);\n\nvoid Row_toggleTag(Row* this);\n\nvoid Row_resetFieldWidths(void);\n\nvoid Row_updateFieldWidth(RowField key, size_t width);\n\nvoid Row_printLeftAlignedField(RichString* str, int attr, const char* content, unsigned int width);\n\nconst char* RowField_alignedTitle(const struct Settings_* settings, RowField field);\n\nRowField RowField_keyAt(const struct Settings_* settings, int at);\n\n/* Sets the size of the PID column based on the passed PID */\nvoid Row_setPidColumnWidth(pid_t maxPid);\n\n/* Sets the size of the UID column based on the passed UID */\nvoid Row_setUidColumnWidth(uid_t maxUid);\n\n/* Takes number in kibibytes (base 1024). Prints 6 columns. */\nvoid Row_printKBytes(RichString* str, unsigned long long number, bool coloring);\n\n/* Takes number in bytes (base 1024). Prints 6 columns. */\nvoid Row_printBytes(RichString* str, unsigned long long number, bool coloring);\n\n/* Takes number as count (base 1000). Prints 12 columns. */\nvoid Row_printCount(RichString* str, unsigned long long number, bool coloring);\n\n/* Takes time in hundredths of a seconds. Prints 9 columns. */\nvoid Row_printTime(RichString* str, unsigned long long totalHundredths, bool coloring);\n\n/* Takes time in nanoseconds. Prints 9 columns. */\nvoid Row_printNanoseconds(RichString* str, unsigned long long totalNanoseconds, bool coloring);\n\n/* Takes rate in bare unit (base 1024) per second. Prints 12 columns. */\nvoid Row_printRate(RichString* str, double rate, bool coloring);\n\nint Row_printPercentage(float val, char* buffer, size_t n, uint8_t width, int* attr);\n\nstatic inline int Row_idEqualCompare(const void* v1, const void* v2) {\n   const int p1 = ((const Row*)v1)->id;\n   const int p2 = ((const Row*)v2)->id;\n   return p1 != p2; /* return zero when equal */\n}\n\n/* Routines used primarily with the tree view */\nstatic inline int Row_getGroupOrParent(const Row* this) {\n   return this->group == this->id ? this->parent : this->group;\n}\n\nstatic inline bool Row_isChildOf(const Row* this, int id) {\n   return id == Row_getGroupOrParent(this);\n}\n\nint Row_compareByParent_Base(const void* v1, const void* v2);\n\n#endif\n"
  },
  {
    "path": "RowField.h",
    "content": "#ifndef HEADER_RowField\n#define HEADER_RowField\n/*\nhtop - RowField.h\n(C) 2023 htop dev team\nReleased under the GNU GPLv2+, see the COPYING file\nin the source distribution for its full text.\n*/\n\n#include \"ProcessField.h\" // platform-specific fields reserved for processes\n\n\ntypedef enum ReservedFields_ {\n   NULL_FIELD = 0,\n   PID = 1,\n   COMM = 2,\n   STATE = 3,\n   PPID = 4,\n   PGRP = 5,\n   SESSION = 6,\n   TTY = 7,\n   TPGID = 8,\n   MINFLT = 10,\n   MAJFLT = 12,\n   PRIORITY = 18,\n   NICE = 19,\n   STARTTIME = 21,\n   PROCESSOR = 38,\n   M_VIRT = 39,\n   M_RESIDENT = 40,\n   ST_UID = 46,\n   PERCENT_CPU = 47,\n   PERCENT_MEM = 48,\n   USER = 49,\n   TIME = 50,\n   NLWP = 51,\n   TGID = 52,\n   PERCENT_NORM_CPU = 53,\n   ELAPSED = 54,\n   SCHEDULERPOLICY = 55,\n   PROC_COMM = 124,\n   PROC_EXE = 125,\n   CWD = 126,\n\n   /* Platform specific fields, defined in ${platform}/ProcessField.h */\n   PLATFORM_PROCESS_FIELDS\n\n   /* Do not add new fields after this entry (dynamic entries follow) */\n   LAST_RESERVED_FIELD\n} ReservedFields;\n\n/* Follow ReservedField entries with dynamic fields defined at runtime */\n#define ROW_DYNAMIC_FIELDS LAST_RESERVED_FIELD\ntypedef int32_t RowField;\n\n#endif\n"
  },
  {
    "path": "Scheduling.c",
    "content": "/*\nhtop - Scheduling.c\n(C) 2023 htop dev team\nReleased under the GNU GPLv2+, see the COPYING file\nin the source distribution for its full text.\n*/\n\n#include \"config.h\" // IWYU pragma: keep\n\n#include \"Scheduling.h\"\n\n#ifdef SCHEDULER_SUPPORT\n\n#include <assert.h>\n#include <stddef.h>\n\n#include \"FunctionBar.h\"\n#include \"ListItem.h\"\n#include \"Macros.h\"\n#include \"Object.h\"\n#include \"Panel.h\"\n#include \"XUtils.h\"\n\n\nstatic const SchedulingPolicy policies[] = {\n   [SCHED_OTHER] = { \"Other\",      SCHED_OTHER, false },\n#ifdef SCHED_BATCH\n   [SCHED_BATCH] = { \"Batch\",      SCHED_BATCH, false },\n#endif\n#ifdef SCHED_IDLE\n   [SCHED_IDLE]  = { \"Idle\",       SCHED_IDLE,  false },\n#endif\n   [SCHED_FIFO]  = { \"FiFo\",       SCHED_FIFO,  true },\n   [SCHED_RR]    = { \"RoundRobin\", SCHED_RR,    true },\n};\n\n#ifdef SCHED_RESET_ON_FORK\nstatic bool reset_on_fork = false;\n#endif\n\n\nPanel* Scheduling_newPolicyPanel(int preSelectedPolicy) {\n   Panel* this = Panel_new(0, 0, 0, 0, Class(ListItem), true, FunctionBar_newEnterEsc(\"Select \", \"Cancel \"));\n   Panel_setHeader(this, \"New policy:\");\n\n#ifdef SCHED_RESET_ON_FORK\n   Panel_add(this, (Object*) ListItem_new(reset_on_fork ? \"Reset on fork: on\" : \"Reset on fork: off\", -1));\n#endif\n\n   for (size_t i = 0; i < ARRAYSIZE(policies); i++) {\n      if (!policies[i].name)\n         continue;\n\n      Panel_add(this, (Object*) ListItem_new(policies[i].name, policies[i].id));\n      if (policies[i].id == preSelectedPolicy)\n         Panel_setSelected(this, (int) i);\n   }\n\n   return this;\n}\n\nvoid Scheduling_togglePolicyPanelResetOnFork(Panel* schedPanel) {\n#ifdef SCHED_RESET_ON_FORK\n   reset_on_fork = !reset_on_fork;\n\n   ListItem* item = (ListItem*) Panel_get(schedPanel, 0);\n\n   free_and_xStrdup(&item->value, reset_on_fork ? \"Reset on fork: on\" : \"Reset on fork: off\");\n#else\n   (void)schedPanel;\n#endif\n}\n\nPanel* Scheduling_newPriorityPanel(int policy, int preSelectedPriority) {\n   if (policy < 0 || (size_t)policy >= ARRAYSIZE(policies) || policies[policy].name == NULL)\n      return NULL;\n\n   if (!policies[policy].prioritySupport)\n      return NULL;\n\n   int min = sched_get_priority_min(policy);\n   if (min < 0)\n      return NULL;\n   int max = sched_get_priority_max(policy);\n   if (max < 0)\n      return NULL;\n\n   Panel* this = Panel_new(0, 0, 0, 0, Class(ListItem), true, FunctionBar_newEnterEsc(\"Select \", \"Cancel \"));\n   Panel_setHeader(this, \"Priority:\");\n\n   for (int i = min; i <= max; i++) {\n      char buf[16];\n      xSnprintf(buf, sizeof(buf), \"%d\", i);\n      Panel_add(this, (Object*) ListItem_new(buf, i));\n      if (i == preSelectedPriority)\n         Panel_setSelected(this, i);\n   }\n\n   return this;\n}\n\nstatic bool Scheduling_setPolicy(Process* p, Arg arg) {\n   const SchedulingArg* sarg = arg.v;\n   int policy = sarg->policy;\n\n   assert(policy >= 0);\n   assert((size_t)policy < ARRAYSIZE(policies));\n   assert(policies[policy].name);\n\n   const struct sched_param param = { .sched_priority = policies[policy].prioritySupport ? sarg->priority : 0 };\n\n   #ifdef SCHED_RESET_ON_FORK\n   if (reset_on_fork)\n      policy &= SCHED_RESET_ON_FORK;\n   #endif\n\n   int r = sched_setscheduler(Process_getPid(p), policy, &param);\n\n   /* POSIX says on success the previous scheduling policy should be returned,\n    * but Linux always returns 0. */\n   return r != -1;\n}\n\nbool Scheduling_rowSetPolicy(Row* row, Arg arg) {\n   Process* p = (Process*) row;\n   assert(Object_isA((const Object*) p, (const ObjectClass*) &Process_class));\n   return Scheduling_setPolicy(p, arg);\n}\n\nconst char* Scheduling_formatPolicy(int policy) {\n#ifdef SCHED_RESET_ON_FORK\n   policy = policy & ~SCHED_RESET_ON_FORK;\n#endif\n\n   switch (policy) {\n      case SCHED_OTHER:\n         return \"OTHER\";\n      case SCHED_FIFO:\n         return \"FIFO\";\n      case SCHED_RR:\n         return \"RR\";\n#ifdef SCHED_BATCH\n      case SCHED_BATCH:\n         return \"BATCH\";\n#endif\n#ifdef SCHED_IDLE\n      case SCHED_IDLE:\n         return \"IDLE\";\n#endif\n#ifdef SCHED_DEADLINE\n      case SCHED_DEADLINE:\n         return \"EDF\";\n#endif\n      default:\n         return \"???\";\n   }\n}\n\n/*\n * Gather scheduling policy (thread-specific data)\n */\nvoid Scheduling_readProcessPolicy(Process* proc) {\n   proc->scheduling_policy = sched_getscheduler(Process_getPid(proc));\n}\n#endif  /* SCHEDULER_SUPPORT */\n"
  },
  {
    "path": "Scheduling.h",
    "content": "#ifndef HEADER_Scheduling\n#define HEADER_Scheduling\n/*\nhtop - Scheduling.h\n(C) 2023 htop dev team\nReleased under the GNU GPLv2+, see the COPYING file\nin the source distribution for its full text.\n*/\n\n#include <sched.h>\n#include <stdbool.h>\n\n#include \"Object.h\"\n#include \"Panel.h\"\n#include \"Process.h\"\n#include \"Row.h\"\n\n\n#if defined(HAVE_SCHED_SETSCHEDULER) && defined(HAVE_SCHED_GETSCHEDULER)\n#define SCHEDULER_SUPPORT\n\ntypedef struct {\n   const char* name;\n   int id;\n   bool prioritySupport;\n} SchedulingPolicy;\n\n#define SCHEDULINGPANEL_INITSELECTEDPOLICY SCHED_OTHER\n#define SCHEDULINGPANEL_INITSELECTEDPRIORITY 50\n\nPanel* Scheduling_newPolicyPanel(int preSelectedPolicy);\nvoid Scheduling_togglePolicyPanelResetOnFork(Panel* schedPanel);\n\nPanel* Scheduling_newPriorityPanel(int policy, int preSelectedPriority);\n\n\ntypedef struct {\n   int policy;\n   int priority;\n} SchedulingArg;\n\nbool Scheduling_rowSetPolicy(Row* proc, Arg arg);\n\nconst char* Scheduling_formatPolicy(int policy);\n\nvoid Scheduling_readProcessPolicy(Process* proc);\n\n#endif\n\n#endif  /* HEADER_Scheduling */\n"
  },
  {
    "path": "ScreenManager.c",
    "content": "/*\nhtop - ScreenManager.c\n(C) 2004-2011 Hisham H. Muhammad\nReleased under the GNU GPLv2+, see the COPYING file\nin the source distribution for its full text.\n*/\n\n#include \"config.h\" // IWYU pragma: keep\n\n#include \"ScreenManager.h\"\n\n#include <assert.h>\n#include <stdbool.h>\n#include <stdlib.h>\n#include <string.h>\n#include <sys/time.h>\n\n#include \"CRT.h\"\n#include \"FunctionBar.h\"\n#include \"Machine.h\"\n#include \"Macros.h\"\n#include \"Object.h\"\n#include \"Platform.h\"\n#include \"Process.h\"\n#include \"ProvideCurses.h\"\n#include \"Settings.h\"\n#include \"Table.h\"\n#include \"XUtils.h\"\n\n\nScreenManager* ScreenManager_new(Header* header, Machine* host, State* state, bool owner) {\n   ScreenManager* this;\n   this = xMalloc(sizeof(ScreenManager));\n   this->x1 = 0;\n   this->y1 = 0;\n   this->x2 = 0;\n   this->y2 = -1;\n   this->panels = Vector_new(Class(Panel), owner, VECTOR_DEFAULT_SIZE);\n   this->panelCount = 0;\n   this->header = header;\n   this->host = host;\n   this->state = state;\n   this->allowFocusChange = true;\n   return this;\n}\n\nvoid ScreenManager_delete(ScreenManager* this) {\n   Vector_delete(this->panels);\n   free(this);\n}\n\ninline int ScreenManager_size(const ScreenManager* this) {\n   return this->panelCount;\n}\n\nvoid ScreenManager_add(ScreenManager* this, Panel* item, int size) {\n   ScreenManager_insert(this, item, size, Vector_size(this->panels));\n}\n\nstatic int header_height(const ScreenManager* this) {\n   if (this->state->hideMeters)\n      return 0;\n\n   if (this->header)\n      return this->header->height;\n\n   return 0;\n}\n\nvoid ScreenManager_insert(ScreenManager* this, Panel* item, int size, int idx) {\n   int lastX = 0;\n   if (idx > 0) {\n      const Panel* last = (const Panel*) Vector_get(this->panels, idx - 1);\n      lastX = last->x + last->w + 1;\n   }\n   int height = LINES - this->y1 - header_height(this) + this->y2;\n   if (size <= 0) {\n      size = COLS - this->x1 + this->x2 - lastX;\n   }\n   Panel_resize(item, size, height);\n   Panel_move(item, lastX, this->y1 + header_height(this));\n   if ((size_t)idx < this->panelCount) {\n      for (int i =  idx + 1; (size_t)i <= this->panelCount; i++) {\n         Panel* p = (Panel*) Vector_get(this->panels, i);\n         Panel_move(p, p->x + size, p->y);\n      }\n   }\n   Vector_insert(this->panels, idx, item);\n   item->needsRedraw = true;\n   this->panelCount++;\n}\n\nPanel* ScreenManager_remove(ScreenManager* this, int idx) {\n   assert((size_t)idx < this->panelCount);\n   int w = ((Panel*) Vector_get(this->panels, idx))->w;\n   Panel* panel = (Panel*) Vector_remove(this->panels, idx);\n   this->panelCount--;\n   if ((size_t)idx < this->panelCount) {\n      for (size_t i = idx; i < this->panelCount; i++) {\n         Panel* p = (Panel*) Vector_get(this->panels, i);\n         Panel_move(p, p->x - w, p->y);\n      }\n   }\n   return panel;\n}\n\nvoid ScreenManager_resize(ScreenManager* this) {\n   int y1_header = this->y1 + header_height(this);\n   int panels = this->panelCount;\n   int lastX = 0;\n   for (int i = 0; i < panels - 1; i++) {\n      Panel* panel = (Panel*) Vector_get(this->panels, i);\n      Panel_resize(panel, panel->w, LINES - y1_header + this->y2);\n      Panel_move(panel, lastX, y1_header);\n      lastX = panel->x + panel->w + 1;\n   }\n   Panel* panel = (Panel*) Vector_get(this->panels, panels - 1);\n   Panel_resize(panel, COLS - this->x1 + this->x2 - lastX, LINES - y1_header + this->y2);\n   Panel_move(panel, lastX, y1_header);\n}\n\nstatic void checkRecalculation(ScreenManager* this, double* oldTime, int* sortTimeout, bool* redraw, bool* rescan, bool* timedOut, bool* force_redraw) {\n   Machine* host = this->host;\n\n   Platform_gettime_realtime(&host->realtime, &host->realtimeMs);\n   double newTime = ((double)host->realtime.tv_sec * 10) + ((double)host->realtime.tv_usec / 100000);\n\n   *timedOut = (newTime - *oldTime > host->settings->delay);\n   *rescan |= *timedOut;\n\n   if (newTime < *oldTime) {\n      *rescan = true; // clock was adjusted?\n   }\n\n   if (*rescan) {\n      *oldTime = newTime;\n\n      if (!this->state->pauseUpdate && (*sortTimeout == 0 || host->settings->ss->treeView)) {\n         host->activeTable->needsSort = true;\n         *sortTimeout = 1;\n      }\n\n      int oldUidDigits = Process_uidDigits;\n      int oldPidDigits = Process_pidDigits;\n\n      // sample current values for system metrics and processes if not paused\n      Machine_scan(host);\n      if (!this->state->pauseUpdate)\n         Machine_scanTables(host);\n      this->state->failedUpdate = Platform_getFailedState();\n\n      // always update header, especially to avoid gaps in graph meters\n      Header_updateData(this->header);\n\n      // force redraw if the number of UID/PID digits changed\n      if (Process_uidDigits != oldUidDigits || Process_pidDigits != oldPidDigits)\n         *force_redraw = true;\n\n      *redraw = true;\n   }\n\n   if (*redraw) {\n      Table_rebuildPanel(host->activeTable);\n      if (!this->state->hideMeters)\n         Header_draw(this->header);\n   }\n\n   *rescan = false;\n}\n\nstatic inline bool drawTab(const int* y, int* x, int l, const char* name, bool cur) {\n   assert(*x >= 0);\n   assert(*x < l);\n\n   attrset(CRT_colors[cur ? SCREENS_CUR_BORDER : SCREENS_OTH_BORDER]);\n   mvaddch(*y, *x, '[');\n   (*x)++;\n   if (*x >= l)\n      return false;\n   int nameWidth = (int)strnlen(name, l - *x);\n   attrset(CRT_colors[cur ? SCREENS_CUR_TEXT : SCREENS_OTH_TEXT]);\n   mvaddnstr(*y, *x, name, nameWidth);\n   *x += nameWidth;\n   if (*x >= l)\n      return false;\n   attrset(CRT_colors[cur ? SCREENS_CUR_BORDER : SCREENS_OTH_BORDER]);\n   mvaddch(*y, *x, ']');\n   *x += 1 + SCREEN_TAB_COLUMN_GAP;\n   if (*x >= l)\n      return false;\n   return true;\n}\n\nstatic void ScreenManager_drawScreenTabs(ScreenManager* this) {\n   Settings* settings = this->host->settings;\n   ScreenSettings** screens = settings->screens;\n   int cur = settings->ssIndex;\n   int l = COLS;\n   Panel* panel = (Panel*) Vector_get(this->panels, 0);\n   int y = panel->y - 1;\n   int x = SCREEN_TAB_MARGIN_LEFT;\n\n   if (x >= l)\n      goto end;\n\n   if (this->name) {\n      drawTab(&y, &x, l, this->name, true);\n      goto end;\n   }\n\n   for (int s = 0; screens[s]; s++) {\n      bool ok = drawTab(&y, &x, l, screens[s]->heading, s == cur);\n      if (!ok) {\n         break;\n      }\n   }\n\nend:\n   attrset(CRT_colors[RESET_COLOR]);\n}\n\nstatic void ScreenManager_drawPanels(ScreenManager* this, size_t focus, bool force_redraw) {\n   Settings* settings = this->host->settings;\n   if (settings->screenTabs) {\n      ScreenManager_drawScreenTabs(this);\n   }\n   const size_t nPanels = this->panelCount;\n   for (size_t i = 0; i < nPanels; i++) {\n      Panel* panel = (Panel*) Vector_get(this->panels, i);\n      Panel_draw(panel,\n                 force_redraw,\n                 i == focus,\n                 panel != (Panel*)this->state->mainPanel || !this->state->hideSelection,\n                 State_hideFunctionBar(this->state));\n      mvvline(panel->y, panel->x + panel->w, ' ', panel->h + (State_hideFunctionBar(this->state) ? 1 : 0));\n   }\n}\n\nvoid ScreenManager_run(ScreenManager* this, Panel** lastFocus, int* lastKey, const char* name) {\n   bool quit = false;\n   size_t focus = 0;\n\n   Panel* panelFocus = (Panel*) Vector_get(this->panels, focus);\n   Settings* settings = this->host->settings;\n\n   double oldTime = 0.0;\n\n   int ch = ERR;\n   int closeTimeout = 0;\n\n   bool timedOut = true;\n   bool redraw = true;\n   bool force_redraw = true;\n   bool rescan = false;\n   int sortTimeout = 0;\n   int resetSortTimeout = 5;\n\n   this->name = name;\n\n   while (!quit) {\n      if (this->header) {\n         checkRecalculation(this, &oldTime, &sortTimeout, &redraw, &rescan, &timedOut, &force_redraw);\n      }\n\n      if (redraw || force_redraw) {\n         ScreenManager_drawPanels(this, focus, force_redraw);\n         force_redraw = false;\n         if (this->host->iterationsRemaining != -1) {\n            if (!--this->host->iterationsRemaining) {\n               quit = true;\n               continue;\n            }\n         }\n      }\n\n      int prevCh = ch;\n      ch = Panel_getCh(panelFocus);\n\n      HandlerResult result = IGNORED;\n#ifdef HAVE_GETMOUSE\n      if (ch == KEY_MOUSE && settings->enableMouse) {\n         ch = ERR;\n         MEVENT mevent;\n         int ok = getmouse(&mevent);\n         if (ok == OK) {\n            if (mevent.bstate & BUTTON1_RELEASED) {\n               if (mevent.y == LINES - 1) {\n                  ch = FunctionBar_synthesizeEvent(panelFocus->currentBar, mevent.x);\n               } else {\n                  for (size_t i = 0; i < this->panelCount; i++) {\n                     Panel* panel = (Panel*) Vector_get(this->panels, i);\n                     if (mevent.x >= panel->x && mevent.x <= panel->x + panel->w) {\n                        if (mevent.y == panel->y) {\n                           ch = EVENT_HEADER_CLICK(mevent.x - panel->x);\n                           break;\n                        } else if (settings->screenTabs && mevent.y == panel->y - 1) {\n                           ch = EVENT_SCREEN_TAB_CLICK(mevent.x);\n                           break;\n                        } else if (mevent.y > panel->y && mevent.y <= panel->y + panel->h) {\n                           ch = KEY_MOUSE;\n                           if (panel == panelFocus || this->allowFocusChange) {\n                              focus = i;\n                              panelFocus = panel;\n                              const Object* oldSelection = Panel_getSelected(panel);\n                              Panel_setSelected(panel, mevent.y - panel->y + panel->scrollV - 1);\n                              if (Panel_getSelected(panel) == oldSelection) {\n                                 ch = KEY_RECLICK;\n                              }\n                           }\n                           break;\n                        }\n                     }\n                  }\n               }\n            } else if (mevent.bstate & BUTTON3_RELEASED) {\n               ch = KEY_RIGHTCLICK;\n            #if NCURSES_MOUSE_VERSION > 1\n            } else if (mevent.bstate & BUTTON4_PRESSED) {\n               ch = KEY_WHEELUP;\n            } else if (mevent.bstate & BUTTON5_PRESSED) {\n               ch = KEY_WHEELDOWN;\n            #endif\n            }\n         }\n      }\n#endif\n      if (ch == ERR) {\n         if (sortTimeout > 0)\n            sortTimeout--;\n         if (prevCh == ch && !timedOut) {\n            closeTimeout++;\n            if (closeTimeout == 100) {\n               break;\n            }\n         } else {\n            closeTimeout = 0;\n         }\n         redraw = false;\n         continue;\n      }\n\n      switch (ch) {\n         case KEY_ALT('H'): ch = KEY_LEFT; break;\n         case KEY_ALT('J'): ch = KEY_DOWN; break;\n         case KEY_ALT('K'): ch = KEY_UP; break;\n         case KEY_ALT('L'): ch = KEY_RIGHT; break;\n      }\n\n      redraw = true;\n      if (Panel_eventHandlerFn(panelFocus)) {\n         result = Panel_eventHandler(panelFocus, ch);\n      }\n      if (result & SYNTH_KEY) {\n         ch = result >> 16;\n      }\n      if (result & REFRESH) {\n         sortTimeout = 0;\n      }\n      if (result & REDRAW) {\n         force_redraw = true;\n      }\n      if (result & RESIZE) {\n         ScreenManager_resize(this);\n         force_redraw = true;\n      }\n      if (result & RESCAN) {\n         rescan = true;\n         sortTimeout = 0;\n      }\n      if (result & HANDLED) {\n         continue;\n      } else if (result & BREAK_LOOP) {\n         quit = true;\n         continue;\n      }\n\n      switch (ch) {\n      case KEY_RESIZE:\n      {\n         ScreenManager_resize(this);\n         continue;\n      }\n      case KEY_FOCUS_IN:\n      case KEY_FOCUS_OUT:\n         break;\n      case KEY_LEFT:\n      case KEY_CTRL('B'):\n         if (this->panelCount < 2) {\n            goto defaultHandler;\n         }\n\n         if (!this->allowFocusChange) {\n            break;\n         }\n\ntryLeft:\n         if (focus > 0) {\n            focus--;\n         }\n\n         panelFocus = (Panel*) Vector_get(this->panels, focus);\n         if (Panel_size(panelFocus) == 0 && focus > 0) {\n            goto tryLeft;\n         }\n\n         break;\n      case KEY_RIGHT:\n      case KEY_CTRL('F'):\n      case 9:\n         if (this->panelCount < 2) {\n            goto defaultHandler;\n         }\n         if (!this->allowFocusChange) {\n            break;\n         }\n\ntryRight:\n         if ((size_t)focus < this->panelCount - 1) {\n            focus++;\n         }\n\n         panelFocus = (Panel*) Vector_get(this->panels, focus);\n         if (Panel_size(panelFocus) == 0 && (size_t)focus < this->panelCount - 1) {\n            goto tryRight;\n         }\n\n         break;\n      case '#':\n         this->state->hideMeters = !this->state->hideMeters;\n         ScreenManager_resize(this);\n         force_redraw = true;\n         break;\n      case 27:\n      case 'q':\n      case KEY_F(10):\n         quit = true;\n         continue;\n      default:\ndefaultHandler:\n         sortTimeout = resetSortTimeout;\n         Panel_onKey(panelFocus, ch);\n         break;\n      }\n   }\n\n   if (lastFocus) {\n      *lastFocus = panelFocus;\n   }\n\n   if (lastKey) {\n      *lastKey = ch;\n   }\n}\n"
  },
  {
    "path": "ScreenManager.h",
    "content": "#ifndef HEADER_ScreenManager\n#define HEADER_ScreenManager\n/*\nhtop - ScreenManager.h\n(C) 2004-2011 Hisham H. Muhammad\nReleased under the GNU GPLv2+, see the COPYING file\nin the source distribution for its full text.\n*/\n\n#include <stdbool.h>\n\n#include \"Action.h\"\n#include \"Header.h\"\n#include \"Machine.h\"\n#include \"Panel.h\"\n#include \"Vector.h\"\n\n\ntypedef struct ScreenManager_ {\n   int x1;\n   int y1;\n   int x2;\n   int y2;\n   bool allowFocusChange;\n   uint32_t panelCount;\n   Vector* panels;\n   const char* name;\n   Header* header;\n   Machine* host;\n   State* state;\n} ScreenManager;\n\nScreenManager* ScreenManager_new(Header* header, Machine* host, State* state, bool owner);\n\nvoid ScreenManager_delete(ScreenManager* this);\n\nint ScreenManager_size(const ScreenManager* this);\n\nvoid ScreenManager_add(ScreenManager* this, Panel* item, int size);\n\nvoid ScreenManager_insert(ScreenManager* this, Panel* item, int size, int idx);\n\nPanel* ScreenManager_remove(ScreenManager* this, int idx);\n\nvoid ScreenManager_resize(ScreenManager* this);\n\nvoid ScreenManager_run(ScreenManager* this, Panel** lastFocus, int* lastKey, const char* name);\n\n#endif\n"
  },
  {
    "path": "ScreenTabsPanel.c",
    "content": "/*\nhtop - ScreenTabsPanel.c\n(C) 2023 htop dev team\nReleased under the GNU GPLv2+, see the COPYING file\nin the source distribution for its full text.\n*/\n\n#include \"config.h\" // IWYU pragma: keep\n\n#include \"ScreenTabsPanel.h\"\n\n#include <assert.h>\n#include <ctype.h>\n#include <stdbool.h>\n#include <stdlib.h>\n#include <string.h>\n\n#include \"CRT.h\"\n#include \"FunctionBar.h\"\n#include \"Hashtable.h\"\n#include \"Macros.h\"\n#include \"ProvideCurses.h\"\n#include \"Settings.h\"\n#include \"XUtils.h\"\n\n\nstatic HandlerResult ScreenNamesPanel_eventHandlerNormal(Panel* super, int ch);\n\nObjectClass ScreenTabListItem_class = {\n   .extends = Class(ListItem),\n   .display = ListItem_display,\n   .delete = ListItem_delete,\n   .compare = ListItem_compare\n};\n\nstatic void ScreenNamesPanel_fill(ScreenNamesPanel* this, DynamicScreen* ds) {\n   const Settings* settings = this->settings;\n   Panel* super = &this->super;\n   Panel_prune(super);\n\n   for (unsigned int i = 0; i < settings->nScreens; i++) {\n      const ScreenSettings* ss = settings->screens[i];\n\n      if (ds == NULL) {\n         if (ss->dynamic != NULL)\n            continue;\n         /* built-in (processes, not dynamic) - e.g. Main or I/O */\n      } else {\n         if (ss->dynamic == NULL)\n            continue;\n         if (!String_eq(ds->name, ss->dynamic))\n            continue;\n         /* matching dynamic screen found, add it into the Panel */\n      }\n      Panel_add(super, (Object*) ListItem_new(ss->heading, i));\n   }\n\n   this->ds = ds;\n}\n\nstatic void ScreenTabsPanel_delete(Object* object) {\n   ScreenTabsPanel* this = (ScreenTabsPanel*) object;\n   Panel_done(&this->super);\n   free(this);\n}\n\nstatic HandlerResult ScreenTabsPanel_eventHandler(Panel* super, int ch) {\n   ScreenTabsPanel* const this = (ScreenTabsPanel* const) super;\n\n   HandlerResult result = IGNORED;\n\n   int selected = Panel_getSelectedIndex(super);\n   switch (ch) {\n      case EVENT_SET_SELECTED:\n         result = HANDLED;\n         break;\n      case KEY_F(5):\n      case KEY_CTRL('N'):\n         /* pass onto the Names panel for creating new screen */\n         return ScreenNamesPanel_eventHandlerNormal(&this->names->super, ch);\n      case KEY_UP:\n      case KEY_DOWN:\n      case KEY_NPAGE:\n      case KEY_PPAGE:\n      case KEY_HOME:\n      case KEY_END: {\n         int previous = selected;\n         Panel_onKey(super, ch);\n         selected = Panel_getSelectedIndex(super);\n         if (previous != selected)\n            result = HANDLED;\n         break;\n      }\n      default:\n         if (ch < 255 && isalpha(ch))\n            result = Panel_selectByTyping(super, ch);\n         if (result == BREAK_LOOP)\n            result = IGNORED;\n         break;\n   }\n\n   if (result == HANDLED) {\n      ScreenTabListItem* focus = (ScreenTabListItem*) Panel_getSelected(super);\n      if (focus) {\n         ScreenNamesPanel_fill(this->names, focus->ds);\n      }\n   }\n\n   return result;\n}\n\nPanelClass ScreenTabsPanel_class = {\n   .super = {\n      .extends = Class(Panel),\n      .delete = ScreenTabsPanel_delete,\n   },\n   .eventHandler = ScreenTabsPanel_eventHandler\n};\n\nstatic ScreenTabListItem* ScreenTabListItem_new(const char* value, DynamicScreen* ds) {\n   ScreenTabListItem* this = AllocThis(ScreenTabListItem);\n   ListItem_init((ListItem*)this, value, 0);\n   this->ds = ds;\n   return this;\n}\n\nstatic void addDynamicScreen(ATTR_UNUSED ht_key_t key, void* value, void* userdata) {\n   DynamicScreen* screen = (DynamicScreen*) value;\n   Panel* super = (Panel*) userdata;\n   const char* name = screen->heading ? screen->heading : screen->name;\n\n   Panel_add(super, (Object*) ScreenTabListItem_new(name, screen));\n}\n\nstatic const char* const ScreenTabsFunctions[] = {\"      \", \"      \", \"      \", \"      \", \"New   \", \"      \", \"      \", \"      \", \"      \", \"Done  \", NULL};\n\nScreenTabsPanel* ScreenTabsPanel_new(Settings* settings) {\n   ScreenTabsPanel* this = AllocThis(ScreenTabsPanel);\n   Panel* super = &this->super;\n\n   FunctionBar* fuBar = FunctionBar_new(ScreenTabsFunctions, NULL, NULL);\n   Panel_init(super, 1, 1, 1, 1, Class(ListItem), true, fuBar);\n\n   this->settings = settings;\n   this->names = ScreenNamesPanel_new(settings);\n   super->cursorOn = false;\n   this->cursor = 0;\n   Panel_setHeader(super, \"Screen tabs\");\n\n   assert(settings->dynamicScreens != NULL);\n   Panel_add(super, (Object*) ScreenTabListItem_new(\"Processes\", NULL));\n   Hashtable_foreach(settings->dynamicScreens, addDynamicScreen, super);\n\n   return this;\n}\n\n// -------------\n\nObjectClass ScreenNameListItem_class = {\n   .extends = Class(ListItem),\n   .display = ListItem_display,\n   .delete = ListItem_delete,\n   .compare = ListItem_compare\n};\n\nScreenNameListItem* ScreenNameListItem_new(const char* value, ScreenSettings* ss) {\n   ScreenNameListItem* this = AllocThis(ScreenNameListItem);\n   ListItem_init((ListItem*)this, value, 0);\n   this->ss = ss;\n   return this;\n}\n\nstatic const char* const ScreenNamesFunctions[] = {\"      \", \"      \", \"      \", \"      \", \"New   \", \"      \", \"      \", \"      \", \"      \", \"Done  \", NULL};\nstatic const char* const ScreenNamesRenamingFunctions[] = {\"      \", \"Cancel\", \"      \", \"      \", \"      \", \"      \", \"      \", \"      \", \"      \", \"Done  \", NULL};\nstatic FunctionBar* ScreenNames_renamingBar = NULL;\n\nvoid ScreenTabsPanel_cleanup(void) {\n   if (ScreenNames_renamingBar) {\n      FunctionBar_delete(ScreenNames_renamingBar);\n      ScreenNames_renamingBar = NULL;\n   }\n}\n\nstatic void ScreenNamesPanel_delete(Object* object) {\n   ScreenNamesPanel* this = (ScreenNamesPanel*) object;\n   Panel* super = &this->super;\n\n   /* do not delete screen settings still in use */\n   int n = Panel_size(super);\n   for (int i = 0; i < n; i++) {\n      ScreenNameListItem* item = (ScreenNameListItem*) Panel_get(super, i);\n      item->ss = NULL;\n   }\n\n   /* during renaming the ListItem's value points to our static buffer */\n   if (this->renamingItem)\n      this->renamingItem->value = this->saved;\n\n   Panel_done(super);\n   free(this);\n}\n\nstatic void renameScreenSettings(ScreenNamesPanel* this, const ListItem* item) {\n   const ScreenNameListItem* nameItem = (const ScreenNameListItem*) item;\n\n   ScreenSettings* ss = nameItem->ss;\n   free_and_xStrdup(&ss->heading, item->value);\n\n   Settings* settings = this->settings;\n   settings->changed = true;\n   settings->lastUpdate++;\n}\n\nstatic HandlerResult ScreenNamesPanel_eventHandlerRenaming(Panel* super, int ch) {\n   ScreenNamesPanel* const this = (ScreenNamesPanel*) super;\n\n   if (ch >= 32 && ch < 127 && ch != '=') {\n      if (this->cursor < SCREEN_NAME_LEN - 1) {\n         this->buffer[this->cursor] = (char)ch;\n         this->cursor++;\n         super->selectedLen = strlen(this->buffer);\n         Panel_setCursorToSelection(super);\n      }\n\n      return HANDLED;\n   }\n\n   switch (ch) {\n      case 127:\n      case KEY_BACKSPACE:\n         if (this->cursor > 0) {\n            this->cursor--;\n            this->buffer[this->cursor] = '\\0';\n            super->selectedLen = strlen(this->buffer);\n            Panel_setCursorToSelection(super);\n         }\n         break;\n      case '\\n':\n      case '\\r':\n      case KEY_ENTER:\n      case KEY_F(10): {\n         ListItem* item = (ListItem*) Panel_getSelected(super);\n         if (!item)\n            break;\n         assert(item == this->renamingItem);\n         free(this->saved);\n         item->value = xStrdup(this->buffer);\n         this->renamingItem = NULL;\n         super->cursorOn = false;\n         Panel_setSelectionColor(super, PANEL_SELECTION_FOCUS);\n         Panel_setDefaultBar(super);\n         renameScreenSettings(this, item);\n         break;\n      }\n      case 27: // Esc\n      case KEY_F(2): {\n         ListItem* item = (ListItem*) Panel_getSelected(super);\n         if (!item)\n            break;\n         assert(item == this->renamingItem);\n         item->value = this->saved;\n         this->renamingItem = NULL;\n         super->cursorOn = false;\n         Panel_setSelectionColor(super, PANEL_SELECTION_FOCUS);\n         Panel_setDefaultBar(super);\n         break;\n      }\n   }\n\n   return HANDLED;\n}\n\nstatic void startRenaming(Panel* super) {\n   ScreenNamesPanel* const this = (ScreenNamesPanel*) super;\n\n   ListItem* item = (ListItem*) Panel_getSelected(super);\n   if (item == NULL)\n      return;\n\n   this->renamingItem = item;\n   super->cursorOn = true;\n   char* name = item->value;\n   this->saved = name;\n   strncpy(this->buffer, name, SCREEN_NAME_LEN);\n   this->buffer[SCREEN_NAME_LEN] = '\\0';\n   this->cursor = strlen(this->buffer);\n   item->value = this->buffer;\n   Panel_setSelectionColor(super, PANEL_EDIT);\n   super->selectedLen = strlen(this->buffer);\n   Panel_setCursorToSelection(super);\n   super->currentBar = ScreenNames_renamingBar;\n}\n\nstatic void addNewScreen(Panel* super, DynamicScreen* ds) {\n   ScreenNamesPanel* const this = (ScreenNamesPanel*) super;\n   const char* name = \"New\";\n   ScreenSettings* ss = (ds != NULL) ? Settings_newDynamicScreen(this->settings, name, ds, NULL) : Settings_newScreen(this->settings, &(const ScreenDefaults) { .name = name, .columns = \"PID Command\", .sortKey = \"PID\" });\n   ScreenNameListItem* item = ScreenNameListItem_new(name, ss);\n   int idx = Panel_getSelectedIndex(super);\n   Panel_insert(super, idx + 1, (Object*) item);\n   Panel_setSelected(super, idx + 1);\n}\n\nstatic HandlerResult ScreenNamesPanel_eventHandlerNormal(Panel* super, int ch) {\n   ScreenNamesPanel* const this = (ScreenNamesPanel*) super;\n   ScreenNameListItem* oldFocus = (ScreenNameListItem*) Panel_getSelected(super);\n   HandlerResult result = IGNORED;\n\n   switch (ch) {\n      case '\\n':\n      case '\\r':\n      case KEY_ENTER:\n      case KEY_MOUSE:\n      case KEY_RECLICK:\n         Panel_setSelectionColor(super, PANEL_SELECTION_FOCUS);\n         result = HANDLED;\n         break;\n      case EVENT_SET_SELECTED:\n         result = HANDLED;\n         break;\n      case KEY_NPAGE:\n      case KEY_PPAGE:\n      case KEY_HOME:\n      case KEY_END:\n         Panel_onKey(super, ch);\n         break;\n      case KEY_F(5):\n      case KEY_CTRL('N'):\n         addNewScreen(super, this->ds);\n         startRenaming(super);\n         result = HANDLED;\n         break;\n      default:\n         if (ch < 255 && isalpha(ch))\n            result = Panel_selectByTyping(super, ch);\n         if (result == BREAK_LOOP)\n            result = IGNORED;\n         break;\n   }\n\n   ScreenNameListItem* newFocus = (ScreenNameListItem*) Panel_getSelected(super);\n   if (newFocus && oldFocus != newFocus)\n      result = HANDLED;\n\n   return result;\n}\n\nstatic HandlerResult ScreenNamesPanel_eventHandler(Panel* super, int ch) {\n   ScreenNamesPanel* const this = (ScreenNamesPanel*) super;\n\n   if (!this->renamingItem)\n      return ScreenNamesPanel_eventHandlerNormal(super, ch);\n   return ScreenNamesPanel_eventHandlerRenaming(super, ch);\n}\n\nPanelClass ScreenNamesPanel_class = {\n   .super = {\n      .extends = Class(Panel),\n      .delete = ScreenNamesPanel_delete\n   },\n   .eventHandler = ScreenNamesPanel_eventHandler\n};\n\nScreenNamesPanel* ScreenNamesPanel_new(Settings* settings) {\n   ScreenNamesPanel* this = AllocThis(ScreenNamesPanel);\n   Panel* super = &this->super;\n\n   FunctionBar* fuBar = FunctionBar_new(ScreenNamesFunctions, NULL, NULL);\n   if (!ScreenNames_renamingBar) {\n      ScreenNames_renamingBar = FunctionBar_new(ScreenNamesRenamingFunctions, NULL, NULL);\n   }\n   Panel_init(super, 1, 1, 1, 1, Class(ListItem), true, fuBar);\n\n   this->settings = settings;\n   this->renamingItem = NULL;\n   memset(this->buffer, 0, sizeof(this->buffer));\n   this->ds = NULL;\n   this->saved = NULL;\n   this->cursor = 0;\n   super->cursorOn = false;\n   Panel_setHeader(super, \"Screens\");\n\n   for (unsigned int i = 0; i < settings->nScreens; i++) {\n      ScreenSettings* ss = settings->screens[i];\n      /* initially show only for Processes tabs (selected) */\n      if (ss->dynamic)\n         continue;\n      Panel_add(super, (Object*) ScreenNameListItem_new(ss->heading, ss));\n   }\n   return this;\n}\n"
  },
  {
    "path": "ScreenTabsPanel.h",
    "content": "#ifndef HEADER_ScreenTabsPanel\n#define HEADER_ScreenTabsPanel\n/*\nhtop - ScreenTabsPanel.h\n(C) 2023 htop dev team\nReleased under the GNU GPLv2+, see the COPYING file\nin the source distribution for its full text.\n*/\n\n#include \"DynamicScreen.h\"\n#include \"ListItem.h\"\n#include \"Object.h\"\n#include \"Panel.h\"\n#include \"ScreensPanel.h\"\n#include \"ScreenManager.h\"\n#include \"Settings.h\"\n\n\ntypedef struct ScreenNamesPanel_ {\n   Panel super;\n\n   ScreenManager* scr;\n   Settings* settings;\n   char buffer[SCREEN_NAME_LEN + 1];\n   DynamicScreen* ds;\n   char* saved;\n   size_t cursor;\n   ListItem* renamingItem;\n} ScreenNamesPanel;\n\ntypedef struct ScreenNameListItem_ {\n   ListItem super;\n   ScreenSettings* ss;\n} ScreenNameListItem;\n\ntypedef struct ScreenTabsPanel_ {\n   Panel super;\n\n   ScreenManager* scr;\n   Settings* settings;\n   ScreenNamesPanel* names;\n   int cursor;\n} ScreenTabsPanel;\n\ntypedef struct ScreenTabListItem_ {\n   ListItem super;\n   DynamicScreen* ds;\n} ScreenTabListItem;\n\n\nScreenTabsPanel* ScreenTabsPanel_new(Settings* settings);\n\nextern ObjectClass ScreenNameListItem_class;\n\nScreenNameListItem* ScreenNameListItem_new(const char* value, ScreenSettings* ss);\n\nextern PanelClass ScreenNamesPanel_class;\n\nScreenNamesPanel* ScreenNamesPanel_new(Settings* settings);\n\nvoid ScreenTabsPanel_cleanup(void);\n\n#endif\n"
  },
  {
    "path": "ScreensPanel.c",
    "content": "/*\nhtop - ScreensPanel.c\n(C) 2004-2011 Hisham H. Muhammad\n(C) 2020-2026 htop dev team\nReleased under the GNU GPLv2+, see the COPYING file\nin the source distribution for its full text.\n*/\n\n#include \"config.h\" // IWYU pragma: keep\n\n#include \"ScreensPanel.h\"\n\n#include <assert.h>\n#include <ctype.h>\n#include <stdlib.h>\n#include <string.h>\n\n#include \"AvailableColumnsPanel.h\"\n#include \"CRT.h\"\n#include \"FunctionBar.h\"\n#include \"Hashtable.h\"\n#include \"ProvideCurses.h\"\n#include \"Settings.h\"\n#include \"XUtils.h\"\n\n\nstatic void ScreenListItem_delete(Object* cast) {\n   ScreenListItem* this = (ScreenListItem*)cast;\n   if (this->ss) {\n      ScreenSettings_delete(this->ss);\n   }\n   ListItem_delete(cast);\n}\n\nObjectClass ScreenListItem_class = {\n   .extends = Class(ListItem),\n   .display = ListItem_display,\n   .delete = ScreenListItem_delete,\n   .compare = ListItem_compare\n};\n\nScreenListItem* ScreenListItem_new(const char* value, ScreenSettings* ss) {\n   ScreenListItem* this = AllocThis(ScreenListItem);\n   ListItem_init((ListItem*)this, value, 0);\n   this->ss = ss;\n   return this;\n}\n\nstatic const char* const ScreensFunctions[] = {\"      \", \"Rename\", \"      \", \"      \", \"New   \", \"      \", \"MoveUp\", \"MoveDn\", \"Remove\", \"Done  \", NULL};\nstatic const char* const DynamicFunctions[] = {\"      \", \"Rename\", \"      \", \"      \", \"      \", \"      \", \"MoveUp\", \"MoveDn\", \"Remove\", \"Done  \", NULL};\nstatic const char* const ScreensRenamingFunctions[] = {\"      \", \"Cancel\", \"      \", \"      \", \"      \", \"      \", \"      \", \"      \", \"      \", \"Done  \", NULL};\nstatic FunctionBar* Screens_renamingBar = NULL;\n\nstatic void rebuildSettingsArray(Panel* super, int selected);\n\nvoid ScreensPanel_cleanup(void) {\n   if (Screens_renamingBar) {\n      FunctionBar_delete(Screens_renamingBar);\n      Screens_renamingBar = NULL;\n   }\n}\n\nstatic void ScreensPanel_delete(Object* object) {\n   Panel* super = (Panel*) object;\n\n   /* do not delete screen settings still in use */\n   int n = Panel_size(super);\n   for (int i = 0; i < n; i++) {\n      ScreenListItem* item = (ScreenListItem*) Panel_get(super, i);\n      item->ss = NULL;\n   }\n\n   Panel_delete(object);\n}\n\nstatic HandlerResult ScreensPanel_eventHandlerRenaming(Panel* super, int ch) {\n   ScreensPanel* const this = (ScreensPanel*) super;\n\n   if (ch >= 32 && ch < 127 && ch != '=') {\n      if (this->cursor < SCREEN_NAME_LEN - 1) {\n         this->buffer[this->cursor] = (char)ch;\n         this->cursor++;\n         super->selectedLen = strlen(this->buffer);\n         Panel_setCursorToSelection(super);\n      }\n\n      return HANDLED;\n   }\n\n   switch (ch) {\n      case 127:\n      case KEY_BACKSPACE:\n         if (this->cursor > 0) {\n            this->cursor--;\n            this->buffer[this->cursor] = '\\0';\n            super->selectedLen = strlen(this->buffer);\n            Panel_setCursorToSelection(super);\n         }\n         break;\n      case '\\n':\n      case '\\r':\n      case KEY_ENTER:\n      case KEY_F(10): {\n         ListItem* item = (ListItem*) Panel_getSelected(super);\n         if (!item)\n            break;\n         assert(item == this->renamingItem);\n         free(this->saved);\n         item->value = xStrdup(this->buffer);\n         this->renamingItem = NULL;\n         this->renamingNewItem = false;\n         super->cursorOn = false;\n         Panel_setSelectionColor(super, PANEL_SELECTION_FOCUS);\n         Panel_setDefaultBar(super);\n         ScreensPanel_update(super);\n         break;\n      }\n      case 27: // Esc\n      case KEY_F(2): {\n         ListItem* item = (ListItem*) Panel_getSelected(super);\n         if (!item)\n            break;\n         assert(item == this->renamingItem);\n\n         // Restore item->value to the heap-allocated saved string.\n         // This is safe for both cases: existing items keep their name,\n         // and new items need this before deletion to avoid freeing the stack buffer.\n         item->value = this->saved;\n\n         if (this->renamingNewItem) {\n            // If canceling a newly created item, delete it\n            Panel_remove(super, Panel_getSelectedIndex(super));\n            // Rebuild with the updated selection after removal\n            rebuildSettingsArray(super, Panel_getSelectedIndex(super));\n         }\n\n         this->renamingNewItem = false;\n         this->renamingItem = NULL;\n         super->cursorOn = false;\n         Panel_setSelectionColor(super, PANEL_SELECTION_FOCUS);\n         Panel_setDefaultBar(super);\n         break;\n      }\n   }\n\n   return HANDLED;\n}\n\nstatic void startRenaming(Panel* super) {\n   ScreensPanel* const this = (ScreensPanel*) super;\n\n   ListItem* item = (ListItem*) Panel_getSelected(super);\n   if (item == NULL)\n      return;\n   this->renamingItem = item;\n   super->cursorOn = true;\n   char* name = item->value;\n   this->saved = name;\n   strncpy(this->buffer, name, SCREEN_NAME_LEN);\n   this->buffer[SCREEN_NAME_LEN] = '\\0';\n   this->cursor = strlen(this->buffer);\n   item->value = this->buffer;\n   Panel_setSelectionColor(super, PANEL_EDIT);\n   super->selectedLen = strlen(this->buffer);\n   Panel_setCursorToSelection(super);\n   super->currentBar = Screens_renamingBar;\n}\n\nstatic void rebuildSettingsArray(Panel* super, int selected) {\n   ScreensPanel* const this = (ScreensPanel*) super;\n\n   int n = Panel_size(super);\n   free(this->settings->screens);\n   this->settings->screens = xMallocArray(n + 1, sizeof(ScreenSettings*));\n   this->settings->screens[n] = NULL;\n   for (int i = 0; i < n; i++) {\n      ScreenListItem* item = (ScreenListItem*) Panel_get(super, i);\n      this->settings->screens[i] = item->ss;\n   }\n   this->settings->nScreens = n;\n   /* ensure selection is in valid range */\n   if (selected > n - 1)\n      selected = n - 1;\n   else if (selected < 0)\n      selected = 0;\n   this->settings->ssIndex = selected;\n   this->settings->ss = this->settings->screens[selected];\n}\n\nstatic void addNewScreen(Panel* super) {\n   ScreensPanel* const this = (ScreensPanel*) super;\n\n   const char* name = \"New\";\n   ScreenSettings* ss = Settings_newScreen(this->settings, &(const ScreenDefaults) { .name = name, .columns = \"PID Command\", .sortKey = \"PID\" });\n   ScreenListItem* item = ScreenListItem_new(name, ss);\n   int idx = Panel_getSelectedIndex(super);\n   Panel_insert(super, idx + 1, (Object*) item);\n   Panel_setSelected(super, idx + 1);\n}\n\nstatic HandlerResult ScreensPanel_eventHandlerNormal(Panel* super, int ch) {\n   ScreensPanel* const this = (ScreensPanel*) super;\n\n   int selected = Panel_getSelectedIndex(super);\n   ScreenListItem* oldFocus = (ScreenListItem*) Panel_getSelected(super);\n   bool shouldRebuildArray = false;\n   HandlerResult result = IGNORED;\n\n   switch (ch) {\n      case '\\n':\n      case '\\r':\n      case KEY_ENTER:\n      case KEY_MOUSE:\n      case KEY_RECLICK: {\n         this->moving = !this->moving;\n         Panel_setSelectionColor(super, this->moving ? PANEL_SELECTION_FOLLOW : PANEL_SELECTION_FOCUS);\n         ListItem* item = (ListItem*) Panel_getSelected(super);\n         if (item)\n            item->moving = this->moving;\n         result = HANDLED;\n         break;\n      }\n      case EVENT_SET_SELECTED:\n         result = HANDLED;\n         break;\n      case KEY_NPAGE:\n      case KEY_PPAGE:\n      case KEY_HOME:\n      case KEY_END:\n         Panel_onKey(super, ch);\n         break;\n      case KEY_F(2):\n      case KEY_CTRL('R'):\n         this->renamingNewItem = false;\n         startRenaming(super);\n         result = HANDLED;\n         break;\n      case KEY_F(5):\n      case KEY_CTRL('N'):\n         if (this->settings->dynamicScreens)\n            break;\n         addNewScreen(super);\n         this->renamingNewItem = true;\n         startRenaming(super);\n         shouldRebuildArray = true;\n         result = HANDLED;\n         break;\n      case KEY_UP:\n         if (!this->moving) {\n            Panel_onKey(super, ch);\n            break;\n         }\n         /* FALLTHRU */\n      case KEY_F(7):\n      case '[':\n      case '-':\n         Panel_moveSelectedUp(super);\n         shouldRebuildArray = true;\n         result = HANDLED;\n         break;\n      case KEY_DOWN:\n         if (!this->moving) {\n            Panel_onKey(super, ch);\n            break;\n         }\n         /* FALLTHRU */\n      case KEY_F(8):\n      case ']':\n      case '+':\n         Panel_moveSelectedDown(super);\n         shouldRebuildArray = true;\n         result = HANDLED;\n         break;\n      case KEY_F(9):\n      //case KEY_DC:\n         if (Panel_size(super) > 1)\n            Panel_remove(super, selected);\n         shouldRebuildArray = true;\n         result = HANDLED;\n         break;\n      default:\n         if (ch < 255 && isalpha(ch))\n            result = Panel_selectByTyping(super, ch);\n         if (result == BREAK_LOOP)\n            result = IGNORED;\n         break;\n   }\n\n   ScreenListItem* newFocus = (ScreenListItem*) Panel_getSelected(super);\n   if (newFocus && oldFocus != newFocus) {\n      Hashtable* dynamicColumns = this->settings->dynamicColumns;\n      ColumnsPanel_fill(this->columns, newFocus->ss, dynamicColumns);\n      AvailableColumnsPanel_fill(this->availableColumns, newFocus->ss->dynamic, dynamicColumns);\n      result = HANDLED;\n   }\n\n   if (shouldRebuildArray)\n      rebuildSettingsArray(super, selected);\n   if (result == HANDLED)\n      ScreensPanel_update(super);\n\n   return result;\n}\n\nstatic HandlerResult ScreensPanel_eventHandler(Panel* super, int ch) {\n   ScreensPanel* const this = (ScreensPanel*) super;\n\n   if (this->renamingItem) {\n      return ScreensPanel_eventHandlerRenaming(super, ch);\n   } else {\n      return ScreensPanel_eventHandlerNormal(super, ch);\n   }\n}\n\nPanelClass ScreensPanel_class = {\n   .super = {\n      .extends = Class(Panel),\n      .delete = ScreensPanel_delete\n   },\n   .eventHandler = ScreensPanel_eventHandler\n};\n\nScreensPanel* ScreensPanel_new(Settings* settings) {\n   ScreensPanel* this = AllocThis(ScreensPanel);\n   Panel* super = &this->super;\n\n   FunctionBar* fuBar = FunctionBar_new(settings->dynamicScreens ? DynamicFunctions : ScreensFunctions, NULL, NULL);\n   if (!Screens_renamingBar) {\n      Screens_renamingBar = FunctionBar_new(ScreensRenamingFunctions, NULL, NULL);\n   }\n   Panel_init(super, 1, 1, 1, 1, Class(ListItem), true, fuBar);\n\n   Hashtable* columns = settings->dynamicColumns;\n\n   this->settings = settings;\n   this->columns = ColumnsPanel_new(settings->screens[0], columns, &(settings->changed));\n   this->availableColumns = AvailableColumnsPanel_new((Panel*) this->columns, columns);\n   this->moving = false;\n   this->renamingItem = NULL;\n   this->renamingNewItem = false;\n   super->cursorOn = false;\n   this->cursor = 0;\n   Panel_setHeader(super, \"Screens\");\n\n   for (unsigned int i = 0; i < settings->nScreens; i++) {\n      ScreenSettings* ss = settings->screens[i];\n      char* name = ss->heading;\n      Panel_add(super, (Object*) ScreenListItem_new(name, ss));\n   }\n   return this;\n}\n\nvoid ScreensPanel_update(Panel* super) {\n   ScreensPanel* this = (ScreensPanel*) super;\n   int size = Panel_size(super);\n   this->settings->changed = true;\n   this->settings->lastUpdate++;\n   this->settings->screens = xReallocArray(this->settings->screens, size + 1, sizeof(ScreenSettings*));\n   for (int i = 0; i < size; i++) {\n      ScreenListItem* item = (ScreenListItem*) Panel_get(super, i);\n      ScreenSettings* ss = item->ss;\n      free_and_xStrdup(&ss->heading, ((ListItem*) item)->value);\n      this->settings->screens[i] = ss;\n   }\n   this->settings->screens[size] = NULL;\n}\n"
  },
  {
    "path": "ScreensPanel.h",
    "content": "#ifndef HEADER_ScreensPanel\n#define HEADER_ScreensPanel\n/*\nhtop - ScreensPanel.h\n(C) 2004-2011 Hisham H. Muhammad\n(C) 2020-2026 htop dev team\nReleased under the GNU GPLv2+, see the COPYING file\nin the source distribution for its full text.\n*/\n\n#include <stdbool.h>\n\n#include \"AvailableColumnsPanel.h\"\n#include \"ColumnsPanel.h\"\n#include \"DynamicScreen.h\"\n#include \"ListItem.h\"\n#include \"Object.h\"\n#include \"Panel.h\"\n#include \"ScreenManager.h\"\n#include \"Settings.h\"\n\n#ifndef SCREEN_NAME_LEN\n#define SCREEN_NAME_LEN 20\n#endif\n\ntypedef struct ScreensPanel_ {\n   Panel super;\n\n   ScreenManager* scr;\n   Settings* settings;\n   ColumnsPanel* columns;\n   AvailableColumnsPanel* availableColumns;\n   char buffer[SCREEN_NAME_LEN + 1];\n   bool moving;\n   char* saved;\n   size_t cursor;\n   ListItem* renamingItem;\n   bool renamingNewItem;\n} ScreensPanel;\n\ntypedef struct ScreenListItem_ {\n   ListItem super;\n   DynamicScreen* ds;\n   ScreenSettings* ss;\n} ScreenListItem;\n\n\nextern ObjectClass ScreenListItem_class;\n\nScreenListItem* ScreenListItem_new(const char* value, ScreenSettings* ss);\n\nScreensPanel* ScreensPanel_new(Settings* settings);\n\nvoid ScreensPanel_update(Panel* super);\n\nvoid ScreensPanel_cleanup(void);\n\n#endif\n"
  },
  {
    "path": "Settings.c",
    "content": "/*\nhtop - Settings.c\n(C) 2004-2011 Hisham H. Muhammad\nReleased under the GNU GPLv2+, see the COPYING file\nin the source distribution for its full text.\n*/\n\n#include \"config.h\" // IWYU pragma: keep\n\n#include \"Settings.h\"\n\n#include <ctype.h>\n#include <errno.h>\n#include <fcntl.h>\n#include <limits.h>\n#include <pwd.h>\n#include <stdarg.h>\n#include <stdio.h>\n#include <stdlib.h>\n#include <string.h>\n#include <unistd.h>\n#include <sys/stat.h>\n\n#include \"CRT.h\"\n#include \"DynamicColumn.h\"\n#include \"DynamicScreen.h\"\n#include \"Macros.h\"\n#include \"Meter.h\"\n#include \"Platform.h\"\n#include \"Process.h\"\n#include \"Table.h\"\n#include \"XUtils.h\"\n\n\nstatic void Settings_deleteColumns(Settings* this) {\n   for (size_t i = 0; i < HeaderLayout_getColumns(this->hLayout); i++) {\n      String_freeArray(this->hColumns[i].names);\n      free(this->hColumns[i].modes);\n   }\n   free(this->hColumns);\n}\n\nstatic void Settings_deleteScreens(Settings* this) {\n   if (this->screens) {\n      for (size_t i = 0; this->screens[i]; i++)\n         ScreenSettings_delete(this->screens[i]);\n      free(this->screens);\n   }\n}\n\nvoid Settings_delete(Settings* this) {\n   free(this->filename);\n   free(this->initialFilename);\n   Settings_deleteColumns(this);\n   Settings_deleteScreens(this);\n   free(this);\n}\n\nstatic char** Settings_splitLineToIDs(const char* line) {\n   char* trim = String_trim(line);\n   char** ids = String_split(trim, ' ', NULL);\n   free(trim);\n   return ids;\n}\n\nstatic void Settings_readMeters(Settings* this, const char* line, size_t column) {\n   column = MINIMUM(column, HeaderLayout_getColumns(this->hLayout) - 1);\n   this->hColumns[column].names = Settings_splitLineToIDs(line);\n}\n\nstatic void Settings_readMeterModes(Settings* this, const char* line, size_t column) {\n   char** ids = Settings_splitLineToIDs(line);\n\n   size_t len = 0;\n   for (size_t i = 0; ids[i]; i++) {\n      len++;\n   }\n\n   column = MINIMUM(column, HeaderLayout_getColumns(this->hLayout) - 1);\n   this->hColumns[column].len = len;\n   MeterModeId* modes = len ? xCalloc(len, sizeof(MeterModeId)) : NULL;\n   for (size_t i = 0; i < len; i++) {\n      modes[i] = (MeterModeId) atoi(ids[i]);\n   }\n   this->hColumns[column].modes = modes;\n\n   String_freeArray(ids);\n}\n\nstatic bool Settings_validateMeters(Settings* this) {\n   const size_t colCount = HeaderLayout_getColumns(this->hLayout);\n\n   bool anyMeter = false;\n\n   for (size_t column = 0; column < colCount; column++) {\n      char** names = this->hColumns[column].names;\n      const MeterModeId* modes = this->hColumns[column].modes;\n      const size_t len = this->hColumns[column].len;\n\n      if (!len)\n         continue;\n\n      if (!names || !modes)\n         return false;\n\n      anyMeter |= !!len;\n\n      // Check for each mode there is an entry with a non-NULL name\n      for (size_t meterIdx = 0; meterIdx < len; meterIdx++)\n         if (!names[meterIdx])\n            return false;\n\n      if (names[len])\n         return false;\n   }\n\n   return anyMeter;\n}\n\nstatic void Settings_defaultMeters(Settings* this, const Machine* host) {\n   unsigned int initialCpuCount = host->activeCPUs;\n   size_t sizes[] = { 3, 3 };\n\n   if (initialCpuCount > 4 && initialCpuCount <= 128) {\n      sizes[1]++;\n   }\n\n   // Release any previously allocated memory\n   Settings_deleteColumns(this);\n\n   this->hLayout = HF_TWO_50_50;\n   this->hColumns = xCalloc(HeaderLayout_getColumns(this->hLayout), sizeof(MeterColumnSetting));\n   for (size_t i = 0; i < 2; i++) {\n      this->hColumns[i].names = xCalloc(sizes[i] + 1, sizeof(*this->hColumns[0].names));\n      this->hColumns[i].modes = xCalloc(sizes[i], sizeof(*this->hColumns[0].modes));\n      this->hColumns[i].len = sizes[i];\n   }\n\n   int r = 0;\n\n   if (initialCpuCount > 128) {\n      // Just show the average, ricers need to config for impressive screenshots\n      this->hColumns[0].names[0] = xStrdup(\"CPU\");\n      this->hColumns[0].modes[0] = BAR_METERMODE;\n   } else if (initialCpuCount > 32) {\n      this->hColumns[0].names[0] = xStrdup(\"LeftCPUs8\");\n      this->hColumns[0].modes[0] = BAR_METERMODE;\n      this->hColumns[1].names[r] = xStrdup(\"RightCPUs8\");\n      this->hColumns[1].modes[r++] = BAR_METERMODE;\n   } else if (initialCpuCount > 16) {\n      this->hColumns[0].names[0] = xStrdup(\"LeftCPUs4\");\n      this->hColumns[0].modes[0] = BAR_METERMODE;\n      this->hColumns[1].names[r] = xStrdup(\"RightCPUs4\");\n      this->hColumns[1].modes[r++] = BAR_METERMODE;\n   } else if (initialCpuCount > 8) {\n      this->hColumns[0].names[0] = xStrdup(\"LeftCPUs2\");\n      this->hColumns[0].modes[0] = BAR_METERMODE;\n      this->hColumns[1].names[r] = xStrdup(\"RightCPUs2\");\n      this->hColumns[1].modes[r++] = BAR_METERMODE;\n   } else if (initialCpuCount > 4) {\n      this->hColumns[0].names[0] = xStrdup(\"LeftCPUs\");\n      this->hColumns[0].modes[0] = BAR_METERMODE;\n      this->hColumns[1].names[r] = xStrdup(\"RightCPUs\");\n      this->hColumns[1].modes[r++] = BAR_METERMODE;\n   } else {\n      this->hColumns[0].names[0] = xStrdup(\"AllCPUs\");\n      this->hColumns[0].modes[0] = BAR_METERMODE;\n   }\n   this->hColumns[0].names[1] = xStrdup(\"Memory\");\n   this->hColumns[0].modes[1] = BAR_METERMODE;\n   this->hColumns[0].names[2] = xStrdup(\"Swap\");\n   this->hColumns[0].modes[2] = BAR_METERMODE;\n   this->hColumns[1].names[r] = xStrdup(\"Tasks\");\n   this->hColumns[1].modes[r++] = TEXT_METERMODE;\n   this->hColumns[1].names[r] = xStrdup(\"LoadAverage\");\n   this->hColumns[1].modes[r++] = TEXT_METERMODE;\n   this->hColumns[1].names[r] = xStrdup(\"Uptime\");\n   this->hColumns[1].modes[r++] = TEXT_METERMODE;\n}\n\nstatic const char* toFieldName(Hashtable* columns, int id, bool* enabled) {\n   if (id < 0) {\n      if (enabled)\n         *enabled = false;\n      return NULL;\n   }\n   if (id >= ROW_DYNAMIC_FIELDS) {\n      const DynamicColumn* column = DynamicColumn_lookup(columns, id);\n      if (enabled)\n         *enabled = column ? column->enabled : false;\n      return column ? column->name : NULL;\n   }\n   if (enabled)\n      *enabled = true;\n   return Process_fields[id].name;\n}\n\nstatic int toFieldIndex(Hashtable* columns, const char* str) {\n   if (isdigit((unsigned char)str[0])) {\n      // This \"+1\" is for compatibility with the older enum format.\n      int id = atoi(str) + 1;\n      if (toFieldName(columns, id, NULL)) {\n         return id;\n      }\n   } else {\n      // Dynamically-defined columns are always stored by-name.\n      char dynamic[32] = {0};\n      if (sscanf(str, \"Dynamic(%30s)\", dynamic) == 1) {\n         char* end;\n         if ((end = strrchr(dynamic, ')')) != NULL) {\n            bool success;\n            unsigned int key;\n            *end = '\\0';\n            success = DynamicColumn_search(columns, dynamic, &key) != NULL;\n            *end = ')';\n            if (success)\n               return key;\n         }\n      }\n      // Fallback to iterative scan of table of fields by-name.\n      for (int p = 1; p < LAST_PROCESSFIELD; p++) {\n         const char* pName = toFieldName(columns, p, NULL);\n         if (pName && strcmp(pName, str) == 0)\n            return p;\n      }\n   }\n   return -1;\n}\n\nstatic void ScreenSettings_readFields(ScreenSettings* ss, Hashtable* columns, const char* line) {\n   char* trim = String_trim(line);\n   char** ids = String_split(trim, ' ', NULL);\n   free(trim);\n\n   /* reset default fields */\n   memset(ss->fields, '\\0', LAST_PROCESSFIELD * sizeof(ProcessField));\n\n   for (size_t j = 0, i = 0; ids[i]; i++) {\n      if (j >= UINT_MAX / sizeof(ProcessField))\n         continue;\n      if (j >= LAST_PROCESSFIELD) {\n         ss->fields = xRealloc(ss->fields, (j + 1) * sizeof(ProcessField));\n         memset(&ss->fields[j], 0, sizeof(ProcessField));\n      }\n      int id = toFieldIndex(columns, ids[i]);\n      if (id >= 0)\n         ss->fields[j++] = id;\n      if (id > 0 && id < LAST_PROCESSFIELD)\n         ss->flags |= Process_fields[id].flags;\n   }\n   String_freeArray(ids);\n}\n\nstatic ScreenSettings* Settings_initScreenSettings(ScreenSettings* ss, Settings* this, const char* columns) {\n   ScreenSettings_readFields(ss, this->dynamicColumns, columns);\n   this->screens[this->nScreens] = ss;\n   this->nScreens++;\n   this->screens = xRealloc(this->screens, sizeof(ScreenSettings*) * (this->nScreens + 1));\n   this->screens[this->nScreens] = NULL;\n   return ss;\n}\n\nScreenSettings* Settings_newScreen(Settings* this, const ScreenDefaults* defaults) {\n   int sortKey = defaults->sortKey ? toFieldIndex(this->dynamicColumns, defaults->sortKey) : PID;\n   int treeSortKey = defaults->treeSortKey ? toFieldIndex(this->dynamicColumns, defaults->treeSortKey) : PID;\n   int sortDesc = (sortKey >= 0 && sortKey < LAST_PROCESSFIELD) ? Process_fields[sortKey].defaultSortDesc : 1;\n\n   ScreenSettings* ss = xMalloc(sizeof(ScreenSettings));\n   *ss = (ScreenSettings) {\n      .heading = xStrdup(defaults->name),\n      .dynamic = NULL,\n      .table = NULL,\n      .fields = xCalloc(LAST_PROCESSFIELD, sizeof(ProcessField)),\n      .flags = 0,\n      .direction = sortDesc ? -1 : 1,\n      .treeDirection = 1,\n      .sortKey = sortKey,\n      .treeSortKey = treeSortKey,\n      .treeView = false,\n      .treeViewAlwaysByPID = false,\n      .allBranchesCollapsed = false,\n   };\n   return Settings_initScreenSettings(ss, this, defaults->columns);\n}\n\nScreenSettings* Settings_newDynamicScreen(Settings* this, const char* tab, const DynamicScreen* screen, Table* table) {\n   int sortKey = toFieldIndex(this->dynamicColumns, screen->columnKeys);\n\n   ScreenSettings* ss = xMalloc(sizeof(ScreenSettings));\n   *ss = (ScreenSettings) {\n      .heading = xStrdup(tab),\n      .dynamic = xStrdup(screen->name),\n      .table = table,\n      .fields = xCalloc(LAST_PROCESSFIELD, sizeof(ProcessField)),\n      .direction = screen->direction,\n      .treeDirection = 1,\n      .sortKey = sortKey,\n   };\n   return Settings_initScreenSettings(ss, this, screen->columnKeys);\n}\n\nvoid ScreenSettings_delete(ScreenSettings* this) {\n   free(this->heading);\n   free(this->dynamic);\n   free(this->fields);\n   free(this);\n}\n\nstatic ScreenSettings* Settings_defaultScreens(Settings* this) {\n   if (this->nScreens)\n      return this->screens[0];\n   for (unsigned int i = 0; i < Platform_numberOfDefaultScreens; i++) {\n      const ScreenDefaults* defaults = &Platform_defaultScreens[i];\n      Settings_newScreen(this, defaults);\n   }\n   Platform_defaultDynamicScreens(this);\n   return this->screens[0];\n}\n\nstatic bool Settings_read(Settings* this, const char* fileName, const Machine* host, bool checkWritability) {\n   int fd = -1;\n   const char* fopen_mode = \"r+\";\n   if (checkWritability) {\n      do {\n         fd = open(fileName, O_RDWR | O_NOCTTY | O_NOFOLLOW);\n      } while (fd < 0 && errno == EINTR);\n\n      if (fd < 0) {\n         this->writeConfig = (errno == ENOENT);\n         if (errno != EACCES && errno != EPERM && errno != EROFS) {\n            return false;\n         }\n      } else {\n         // Write the config only if the file is:\n         // (1) a regular file (not a device file like /dev/null)\n         // (2) owned by the effective user ID\n         // (3) has write permission for owner\n         //     (the root user usually bypasses access control\n         //     by default; see CAP_DAC_OVERRIDE on Linux)\n         struct stat sb;\n         int err = fstat(fd, &sb);\n         this->writeConfig = !err && S_ISREG(sb.st_mode) && (sb.st_mode & S_IWUSR) && sb.st_uid == geteuid();\n      }\n   }\n\n   // If opening for read & write is not needed or fails, open for read only.\n   // There is no risk of following symlink in this case.\n   if (fd < 0) {\n      fopen_mode = \"r\";\n      do {\n         fd = open(fileName, O_RDONLY | O_NOCTTY);\n      } while (fd < 0 && errno == EINTR);\n   }\n\n   if (fd < 0)\n      return false;\n\n   FILE* fp = fdopen(fd, fopen_mode);\n   if (!fp) {\n      close(fd);\n      return false;\n   }\n\n   ScreenSettings* screen = NULL;\n   bool didReadMeters = false;\n   bool didReadAny = false;\n   for (;;) {\n      char* line = String_readLine(fp);\n      if (!line) {\n         break;\n      }\n      didReadAny = true;\n      size_t nOptions;\n      char** option = String_split(line, '=', &nOptions);\n      free (line);\n      if (nOptions < 2) {\n         String_freeArray(option);\n         continue;\n      }\n      if (String_eq(option[0], \"config_reader_min_version\")) {\n         this->config_version = atoi(option[1]);\n         if (this->config_version > CONFIG_READER_MIN_VERSION) {\n            // the version of the config file on disk is newer than what we can read\n            fprintf(stderr, \"WARNING: %s specifies configuration format\\n\", fileName);\n            fprintf(stderr, \"         version v%d, but this %s binary only supports up to version v%d.\\n\", this->config_version, PACKAGE, CONFIG_READER_MIN_VERSION);\n            fprintf(stderr, \"         The configuration file will be downgraded to v%d when %s exits.\\n\", CONFIG_READER_MIN_VERSION, PACKAGE);\n            String_freeArray(option);\n            fclose(fp);\n            return false;\n         }\n      } else if (String_eq(option[0], \"fields\") && this->config_version <= 2) {\n         // old (no screen) naming also supported for backwards compatibility\n         screen = Settings_defaultScreens(this);\n         ScreenSettings_readFields(screen, this->dynamicColumns, option[1]);\n      } else if (String_eq(option[0], \"sort_key\") && this->config_version <= 2) {\n         // old (no screen) naming also supported for backwards compatibility\n         // This \"+1\" is for compatibility with the older enum format.\n         screen = Settings_defaultScreens(this);\n         screen->sortKey = atoi(option[1]) + 1;\n      } else if (String_eq(option[0], \"tree_sort_key\") && this->config_version <= 2) {\n         // old (no screen) naming also supported for backwards compatibility\n         // This \"+1\" is for compatibility with the older enum format.\n         screen = Settings_defaultScreens(this);\n         screen->treeSortKey = atoi(option[1]) + 1;\n      } else if (String_eq(option[0], \"sort_direction\") && this->config_version <= 2) {\n         // old (no screen) naming also supported for backwards compatibility\n         screen = Settings_defaultScreens(this);\n         screen->direction = atoi(option[1]);\n      } else if (String_eq(option[0], \"tree_sort_direction\") && this->config_version <= 2) {\n         // old (no screen) naming also supported for backwards compatibility\n         screen = Settings_defaultScreens(this);\n         screen->treeDirection = atoi(option[1]);\n      } else if (String_eq(option[0], \"tree_view\") && this->config_version <= 2) {\n         // old (no screen) naming also supported for backwards compatibility\n         screen = Settings_defaultScreens(this);\n         screen->treeView = atoi(option[1]);\n      } else if (String_eq(option[0], \"tree_view_always_by_pid\") && this->config_version <= 2) {\n         // old (no screen) naming also supported for backwards compatibility\n         screen = Settings_defaultScreens(this);\n         screen->treeViewAlwaysByPID = atoi(option[1]);\n      } else if (String_eq(option[0], \"all_branches_collapsed\") && this->config_version <= 2) {\n         // old (no screen) naming also supported for backwards compatibility\n         screen = Settings_defaultScreens(this);\n         screen->allBranchesCollapsed = atoi(option[1]);\n      } else if (String_eq(option[0], \"hide_kernel_threads\")) {\n         this->hideKernelThreads = atoi(option[1]);\n      } else if (String_eq(option[0], \"hide_userland_threads\")) {\n         this->hideUserlandThreads = atoi(option[1]);\n      } else if (String_eq(option[0], \"hide_running_in_container\")) {\n         this->hideRunningInContainer = atoi(option[1]);\n      } else if (String_eq(option[0], \"shadow_other_users\")) {\n         this->shadowOtherUsers = atoi(option[1]);\n      } else if (String_eq(option[0], \"show_thread_names\")) {\n         this->showThreadNames = atoi(option[1]);\n      } else if (String_eq(option[0], \"show_program_path\")) {\n         this->showProgramPath = atoi(option[1]);\n      } else if (String_eq(option[0], \"highlight_base_name\")) {\n         this->highlightBaseName = atoi(option[1]);\n      } else if (String_eq(option[0], \"highlight_deleted_exe\")) {\n         this->highlightDeletedExe = atoi(option[1]);\n      } else if (String_eq(option[0], \"shadow_distribution_path_prefix\")) {\n         this->shadowDistPathPrefix = atoi(option[1]);\n      } else if (String_eq(option[0], \"highlight_megabytes\")) {\n         this->highlightMegabytes = atoi(option[1]);\n      } else if (String_eq(option[0], \"highlight_threads\")) {\n         this->highlightThreads = atoi(option[1]);\n      } else if (String_eq(option[0], \"highlight_changes\")) {\n         this->highlightChanges = atoi(option[1]);\n      } else if (String_eq(option[0], \"highlight_changes_delay_secs\")) {\n         this->highlightDelaySecs = CLAMP(atoi(option[1]), 1, 24 * 60 * 60);\n      } else if (String_eq(option[0], \"find_comm_in_cmdline\")) {\n         this->findCommInCmdline = atoi(option[1]);\n      } else if (String_eq(option[0], \"strip_exe_from_cmdline\")) {\n         this->stripExeFromCmdline = atoi(option[1]);\n      } else if (String_eq(option[0], \"show_merged_command\")) {\n         this->showMergedCommand = atoi(option[1]);\n      } else if (String_eq(option[0], \"header_margin\")) {\n         this->headerMargin = atoi(option[1]);\n      } else if (String_eq(option[0], \"screen_tabs\")) {\n         this->screenTabs = atoi(option[1]);\n      } else if (String_eq(option[0], \"expand_system_time\")) {\n         // Compatibility option.\n         this->detailedCPUTime = atoi(option[1]);\n      } else if (String_eq(option[0], \"detailed_cpu_time\")) {\n         this->detailedCPUTime = atoi(option[1]);\n      } else if (String_eq(option[0], \"cpu_count_from_one\")) {\n         this->countCPUsFromOne = atoi(option[1]);\n      } else if (String_eq(option[0], \"cpu_count_from_zero\")) {\n         // old (inverted) naming also supported for backwards compatibility\n         this->countCPUsFromOne = !atoi(option[1]);\n      } else if (String_eq(option[0], \"show_cpu_usage\")) {\n         this->showCPUUsage = atoi(option[1]);\n      } else if (String_eq(option[0], \"show_cpu_frequency\")) {\n         this->showCPUFrequency = atoi(option[1]);\n      } else if (String_eq(option[0], \"show_cached_memory\")) {\n         this->showCachedMemory = atoi(option[1]);\n      #ifdef BUILD_WITH_CPU_TEMP\n      } else if (String_eq(option[0], \"show_cpu_temperature\")) {\n         this->showCPUTemperature = atoi(option[1]);\n      } else if (String_eq(option[0], \"degree_fahrenheit\")) {\n         this->degreeFahrenheit = atoi(option[1]);\n      #endif\n      } else if (String_eq(option[0], \"update_process_names\")) {\n         this->updateProcessNames = atoi(option[1]);\n      } else if (String_eq(option[0], \"account_guest_in_cpu_meter\")) {\n         this->accountGuestInCPUMeter = atoi(option[1]);\n      } else if (String_eq(option[0], \"delay\")) {\n         this->delay = CLAMP(atoi(option[1]), 1, 255);\n      } else if (String_eq(option[0], \"color_scheme\")) {\n         this->colorScheme = atoi(option[1]);\n         if (this->colorScheme < 0 || this->colorScheme >= LAST_COLORSCHEME) {\n            this->colorScheme = 0;\n         }\n      #ifdef HAVE_GETMOUSE\n      } else if (String_eq(option[0], \"enable_mouse\")) {\n         this->enableMouse = atoi(option[1]);\n      #endif\n      } else if (String_eq(option[0], \"header_layout\")) {\n         this->hLayout = isdigit((unsigned char)option[1][0]) ? ((HeaderLayout) atoi(option[1])) : HeaderLayout_fromName(option[1]);\n         if (this->hLayout < 0 || this->hLayout >= LAST_HEADER_LAYOUT)\n            this->hLayout = HF_TWO_50_50;\n         free(this->hColumns);\n         this->hColumns = xCalloc(HeaderLayout_getColumns(this->hLayout), sizeof(MeterColumnSetting));\n      } else if (String_eq(option[0], \"left_meters\")) {\n         Settings_readMeters(this, option[1], 0);\n         didReadMeters = true;\n      } else if (String_eq(option[0], \"right_meters\")) {\n         Settings_readMeters(this, option[1], 1);\n         didReadMeters = true;\n      } else if (String_eq(option[0], \"left_meter_modes\")) {\n         Settings_readMeterModes(this, option[1], 0);\n         didReadMeters = true;\n      } else if (String_eq(option[0], \"right_meter_modes\")) {\n         Settings_readMeterModes(this, option[1], 1);\n         didReadMeters = true;\n      } else if (String_startsWith(option[0], \"column_meters_\")) {\n         Settings_readMeters(this, option[1], atoi(option[0] + strlen(\"column_meters_\")));\n         didReadMeters = true;\n      } else if (String_startsWith(option[0], \"column_meter_modes_\")) {\n         Settings_readMeterModes(this, option[1], atoi(option[0] + strlen(\"column_meter_modes_\")));\n         didReadMeters = true;\n      } else if (String_eq(option[0], \"hide_function_bar\")) {\n         this->hideFunctionBar = atoi(option[1]);\n      #ifdef HAVE_LIBHWLOC\n      } else if (String_eq(option[0], \"topology_affinity\")) {\n         this->topologyAffinity = !!atoi(option[1]);\n      #endif\n      } else if (strncmp(option[0], \"screen:\", 7) == 0) {\n         screen = Settings_newScreen(this, &(const ScreenDefaults) { .name = option[0] + 7, .columns = option[1] });\n      } else if (String_eq(option[0], \".sort_key\")) {\n         if (screen) {\n            int key = toFieldIndex(this->dynamicColumns, option[1]);\n            screen->sortKey = key > 0 ? key : PID;\n         }\n      } else if (String_eq(option[0], \".tree_sort_key\")) {\n         if (screen) {\n            int key = toFieldIndex(this->dynamicColumns, option[1]);\n            screen->treeSortKey = key > 0 ? key : PID;\n         }\n      } else if (String_eq(option[0], \".sort_direction\")) {\n         if (screen)\n            screen->direction = atoi(option[1]);\n      } else if (String_eq(option[0], \".tree_sort_direction\")) {\n         if (screen)\n            screen->treeDirection = atoi(option[1]);\n      } else if (String_eq(option[0], \".tree_view\")) {\n         if (screen)\n            screen->treeView = atoi(option[1]);\n      } else if (String_eq(option[0], \".tree_view_always_by_pid\")) {\n         if (screen)\n            screen->treeViewAlwaysByPID = atoi(option[1]);\n      } else if (String_eq(option[0], \".all_branches_collapsed\")) {\n         if (screen)\n            screen->allBranchesCollapsed = atoi(option[1]);\n      } else if (String_eq(option[0], \".dynamic\")) {\n         if (screen) {\n            free_and_xStrdup(&screen->dynamic, option[1]);\n            Platform_addDynamicScreen(screen);\n         }\n      }\n      String_freeArray(option);\n   }\n   fclose(fp);\n   if (!didReadMeters || !Settings_validateMeters(this))\n      Settings_defaultMeters(this, host);\n   if (!this->nScreens)\n      Settings_defaultScreens(this);\n   return didReadAny;\n}\n\ntypedef ATTR_FORMAT(printf, 2, 3) int (*OutputFunc)(FILE*, const char*,...);\n\nstatic void writeFields(OutputFunc of, FILE* fp,\n                        const ProcessField* fields, Hashtable* columns,\n                        bool byName, char separator) {\n   const char* sep = \"\";\n   for (unsigned int i = 0; fields[i]; i++) {\n      if (fields[i] < LAST_PROCESSFIELD && byName) {\n         const char* pName = toFieldName(columns, fields[i], NULL);\n         of(fp, \"%s%s\", sep, pName);\n      } else if (fields[i] >= LAST_PROCESSFIELD && byName) {\n         bool enabled;\n         const char* pName = toFieldName(columns, fields[i], &enabled);\n         if (enabled)\n            of(fp, \"%sDynamic(%s)\", sep, pName);\n      } else {\n         // This \"-1\" is for compatibility with the older enum format.\n         of(fp, \"%s%d\", sep, (int) fields[i] - 1);\n      }\n      sep = \" \";\n   }\n   of(fp, \"%c\", separator);\n}\n\nstatic void writeList(OutputFunc of, FILE* fp,\n                      char** list, size_t len, char separator) {\n   const char* sep = \"\";\n   for (size_t i = 0; i < len; i++) {\n      of(fp, \"%s%s\", sep, list[i]);\n      sep = \" \";\n   }\n   of(fp, \"%c\", separator);\n}\n\nstatic void writeMeters(const Settings* this, OutputFunc of,\n                        FILE* fp, char separator, unsigned int column) {\n   if (this->hColumns[column].len) {\n      writeList(of, fp, this->hColumns[column].names, this->hColumns[column].len, separator);\n   } else {\n      of(fp, \"!%c\", separator);\n   }\n}\n\nstatic void writeMeterModes(const Settings* this, OutputFunc of,\n                            FILE* fp, char separator, unsigned int column) {\n   if (this->hColumns[column].len) {\n      const char* sep = \"\";\n      for (size_t i = 0; i < this->hColumns[column].len; i++) {\n         of(fp, \"%s%u\", sep, this->hColumns[column].modes[i]);\n         sep = \" \";\n      }\n   } else {\n      of(fp, \"!\");\n   }\n\n   of(fp, \"%c\", separator);\n}\n\nATTR_FORMAT(printf, 2, 3)\nstatic int signal_safe_fprintf(FILE* stream, const char* fmt, ...) {\n   char buf[2048];\n\n   va_list vl;\n   va_start(vl, fmt);\n   int n = vsnprintf(buf, sizeof(buf), fmt, vl);\n   va_end(vl);\n\n   if (n <= 0)\n      return n;\n\n   ssize_t ret = full_write_str(fileno(stream), buf);\n   return (int)MINIMUM(INT_MAX, ret);\n}\n\nint Settings_write(const Settings* this, bool onCrash) {\n   FILE* fp;\n   char separator;\n   char* tmpFilename = NULL;\n   OutputFunc of;\n   if (onCrash) {\n      fp = stderr;\n      separator = ';';\n      of = signal_safe_fprintf;\n   } else if (!this->writeConfig) {\n      return 0;\n   } else {\n      /* create tempfile with mode 0600 */\n      mode_t cur_umask = umask(S_IXUSR | S_IRWXG | S_IRWXO);\n      xAsprintf(&tmpFilename, \"%s.tmp.XXXXXX\", this->filename);\n      int fdtmp = mkstemp(tmpFilename);\n      umask(cur_umask);\n      if (fdtmp == -1) {\n         free(tmpFilename);\n         return -errno;\n      }\n      fp = fdopen(fdtmp, \"w\");\n      if (!fp) {\n         close(fdtmp);\n         free(tmpFilename);\n         return -errno;\n      }\n      separator = '\\n';\n      of = fprintf;\n   }\n\n   #define printSettingInteger(setting_, value_) \\\n      of(fp, setting_ \"=%d%c\", (int) (value_), separator)\n   #define printSettingString(setting_, value_) \\\n      of(fp, setting_ \"=%s%c\", value_, separator)\n\n   if (!onCrash) {\n      of(fp, \"# Beware! This file is rewritten by htop when settings are changed in the interface.\\n\");\n      of(fp, \"# The parser is also very primitive, and not human-friendly.\\n\");\n   }\n   printSettingString(\"htop_version\", VERSION);\n   printSettingInteger(\"config_reader_min_version\", CONFIG_READER_MIN_VERSION);\n   of(fp, \"fields=\"); writeFields(of, fp, this->screens[0]->fields, this->dynamicColumns, false, separator);\n   printSettingInteger(\"hide_kernel_threads\", this->hideKernelThreads);\n   printSettingInteger(\"hide_userland_threads\", this->hideUserlandThreads);\n   printSettingInteger(\"hide_running_in_container\", this->hideRunningInContainer);\n   printSettingInteger(\"shadow_other_users\", this->shadowOtherUsers);\n   printSettingInteger(\"show_thread_names\", this->showThreadNames);\n   printSettingInteger(\"show_program_path\", this->showProgramPath);\n   printSettingInteger(\"highlight_base_name\", this->highlightBaseName);\n   printSettingInteger(\"highlight_deleted_exe\", this->highlightDeletedExe);\n   printSettingInteger(\"shadow_distribution_path_prefix\", this->shadowDistPathPrefix);\n   printSettingInteger(\"highlight_megabytes\", this->highlightMegabytes);\n   printSettingInteger(\"highlight_threads\", this->highlightThreads);\n   printSettingInteger(\"highlight_changes\", this->highlightChanges);\n   printSettingInteger(\"highlight_changes_delay_secs\", this->highlightDelaySecs);\n   printSettingInteger(\"find_comm_in_cmdline\", this->findCommInCmdline);\n   printSettingInteger(\"strip_exe_from_cmdline\", this->stripExeFromCmdline);\n   printSettingInteger(\"show_merged_command\", this->showMergedCommand);\n   printSettingInteger(\"header_margin\", this->headerMargin);\n   printSettingInteger(\"screen_tabs\", this->screenTabs);\n   printSettingInteger(\"detailed_cpu_time\", this->detailedCPUTime);\n   printSettingInteger(\"cpu_count_from_one\", this->countCPUsFromOne);\n   printSettingInteger(\"show_cpu_usage\", this->showCPUUsage);\n   printSettingInteger(\"show_cpu_frequency\", this->showCPUFrequency);\n   #ifdef BUILD_WITH_CPU_TEMP\n   printSettingInteger(\"show_cpu_temperature\", this->showCPUTemperature);\n   printSettingInteger(\"degree_fahrenheit\", this->degreeFahrenheit);\n   #endif\n   printSettingInteger(\"show_cached_memory\", this->showCachedMemory);\n   printSettingInteger(\"update_process_names\", this->updateProcessNames);\n   printSettingInteger(\"account_guest_in_cpu_meter\", this->accountGuestInCPUMeter);\n   printSettingInteger(\"color_scheme\", this->colorScheme);\n   #ifdef HAVE_GETMOUSE\n   printSettingInteger(\"enable_mouse\", this->enableMouse);\n   #endif\n   printSettingInteger(\"delay\", (int) this->delay);\n   printSettingInteger(\"hide_function_bar\", (int) this->hideFunctionBar);\n   #ifdef HAVE_LIBHWLOC\n   printSettingInteger(\"topology_affinity\", this->topologyAffinity);\n   #endif\n\n   printSettingString(\"header_layout\", HeaderLayout_getName(this->hLayout));\n   for (unsigned int i = 0; i < HeaderLayout_getColumns(this->hLayout); i++) {\n      of(fp, \"column_meters_%u=\", i);\n      writeMeters(this, of, fp, separator, i);\n      of(fp, \"column_meter_modes_%u=\", i);\n      writeMeterModes(this, of, fp, separator, i);\n   }\n\n   // Legacy compatibility with older versions of htop\n   printSettingInteger(\"tree_view\", this->screens[0]->treeView);\n   // This \"-1\" is for compatibility with the older enum format.\n   printSettingInteger(\"sort_key\", this->screens[0]->sortKey - 1);\n   printSettingInteger(\"tree_sort_key\", this->screens[0]->treeSortKey - 1);\n   printSettingInteger(\"sort_direction\", this->screens[0]->direction);\n   printSettingInteger(\"tree_sort_direction\", this->screens[0]->treeDirection);\n   printSettingInteger(\"tree_view_always_by_pid\", this->screens[0]->treeViewAlwaysByPID);\n   printSettingInteger(\"all_branches_collapsed\", this->screens[0]->allBranchesCollapsed);\n\n   for (unsigned int i = 0; i < this->nScreens; i++) {\n      ScreenSettings* ss = this->screens[i];\n      const char* sortKey = toFieldName(this->dynamicColumns, ss->sortKey, NULL);\n      const char* treeSortKey = toFieldName(this->dynamicColumns, ss->treeSortKey, NULL);\n\n      of(fp, \"screen:%s=\", ss->heading);\n      writeFields(of, fp, ss->fields, this->dynamicColumns, true, separator);\n      if (ss->dynamic) {\n         printSettingString(\".dynamic\", ss->dynamic);\n         if (ss->sortKey && ss->sortKey != PID)\n            of(fp, \"%s=Dynamic(%s)%c\", \".sort_key\", sortKey, separator);\n         if (ss->treeSortKey && ss->treeSortKey != PID)\n            of(fp, \"%s=Dynamic(%s)%c\", \".tree_sort_key\", treeSortKey, separator);\n      } else {\n         printSettingString(\".sort_key\", sortKey);\n         printSettingString(\".tree_sort_key\", treeSortKey);\n         printSettingInteger(\".tree_view_always_by_pid\", ss->treeViewAlwaysByPID);\n      }\n      printSettingInteger(\".tree_view\", ss->treeView);\n      printSettingInteger(\".sort_direction\", ss->direction);\n      printSettingInteger(\".tree_sort_direction\", ss->treeDirection);\n      printSettingInteger(\".all_branches_collapsed\", ss->allBranchesCollapsed);\n   }\n\n   #undef printSettingString\n   #undef printSettingInteger\n\n   if (onCrash)\n      return 0;\n\n   int r = 0;\n\n   if (ferror(fp) != 0)\n      r = (errno != 0) ? -errno : -EBADF;\n\n   if (fclose(fp) != 0)\n      r = r ? r : -errno;\n\n   if (r == 0)\n      r = (rename(tmpFilename, this->filename) == -1) ? -errno : 0;\n\n   free(tmpFilename);\n\n   return r;\n}\n\nSettings* Settings_new(const Machine* host, Hashtable* dynamicMeters, Hashtable* dynamicColumns, Hashtable* dynamicScreens) {\n   Settings* this = xCalloc(1, sizeof(Settings));\n\n   this->writeConfig = true;\n\n   this->dynamicScreens = dynamicScreens;\n   this->dynamicColumns = dynamicColumns;\n   this->dynamicMeters = dynamicMeters;\n   this->hLayout = HF_TWO_50_50;\n   this->hColumns = xCalloc(HeaderLayout_getColumns(this->hLayout), sizeof(MeterColumnSetting));\n\n   this->shadowOtherUsers = false;\n   this->showThreadNames = false;\n   this->hideKernelThreads = true;\n   this->hideUserlandThreads = false;\n   this->hideRunningInContainer = false;\n   this->highlightBaseName = false;\n   this->highlightDeletedExe = true;\n   this->shadowDistPathPrefix = false;\n   this->highlightMegabytes = true;\n   this->detailedCPUTime = false;\n   this->countCPUsFromOne = false;\n   this->showCPUUsage = true;\n   this->showCPUFrequency = false;\n   #ifdef BUILD_WITH_CPU_TEMP\n   this->showCPUTemperature = false;\n   this->degreeFahrenheit = false;\n   #endif\n   this->showCachedMemory = true;\n   this->updateProcessNames = false;\n   this->showProgramPath = true;\n   this->highlightThreads = true;\n   this->highlightChanges = false;\n   this->highlightDelaySecs = DEFAULT_HIGHLIGHT_SECS;\n   this->findCommInCmdline = true;\n   this->stripExeFromCmdline = true;\n   this->showMergedCommand = false;\n   this->hideFunctionBar = 0;\n   this->headerMargin = true;\n   #ifdef HAVE_LIBHWLOC\n   this->topologyAffinity = false;\n   #endif\n\n   this->screens = xCalloc(Platform_numberOfDefaultScreens, sizeof(ScreenSettings*));\n   this->nScreens = 0;\n\n   char* legacyDotfile = NULL;\n   const char* rcfile = getenv(\"HTOPRC\");\n   if (rcfile) {\n      this->initialFilename = xStrdup(rcfile);\n   } else {\n      const char* home = getenv(\"HOME\");\n      if (!home || home[0] != '/') {\n         const struct passwd* pw = getpwuid(getuid());\n         home = (pw && pw->pw_dir && pw->pw_dir[0] == '/') ? pw->pw_dir : \"\";\n      }\n      const char* xdgConfigHome = getenv(\"XDG_CONFIG_HOME\");\n      char* configDir = NULL;\n      char* htopDir = NULL;\n      if (xdgConfigHome && xdgConfigHome[0] == '/') {\n         this->initialFilename = String_cat(xdgConfigHome, \"/htop/htoprc\");\n         configDir = xStrdup(xdgConfigHome);\n         htopDir = String_cat(xdgConfigHome, \"/htop\");\n      } else {\n         this->initialFilename = String_cat(home, CONFIGDIR \"/htop/htoprc\");\n         configDir = String_cat(home, CONFIGDIR);\n         htopDir = String_cat(home, CONFIGDIR \"/htop\");\n      }\n      (void) mkdir(configDir, 0700);\n      (void) mkdir(htopDir, 0700);\n      free(htopDir);\n      free(configDir);\n\n      legacyDotfile = String_cat(home, \"/.htoprc\");\n   }\n\n   this->filename = xMalloc(PATH_MAX);\n   if (!realpath(this->initialFilename, this->filename))\n      free_and_xStrdup(&this->filename, this->initialFilename);\n\n   this->colorScheme = 0;\n#ifdef HAVE_GETMOUSE\n   this->enableMouse = true;\n#endif\n   this->changed = false;\n   this->delay = DEFAULT_DELAY;\n\n   bool ok = Settings_read(this, this->filename, host, /*checkWritability*/true);\n   if (!ok && legacyDotfile) {\n      ok = Settings_read(this, legacyDotfile, host, this->writeConfig);\n      if (ok && this->writeConfig) {\n         // Transition to new location and delete old configuration file\n         if (Settings_write(this, false) == 0) {\n            unlink(legacyDotfile);\n         }\n      }\n   }\n   if (!ok) {\n      this->screenTabs = true;\n      this->changed = true;\n\n      ok = Settings_read(this, SYSCONFDIR \"/htoprc\", host, /*checkWritability*/false);\n   }\n   if (!ok) {\n      Settings_defaultMeters(this, host);\n      Settings_defaultScreens(this);\n   }\n\n   this->ssIndex = 0;\n   this->ss = this->screens[this->ssIndex];\n\n   this->lastUpdate = 1;\n\n   free(legacyDotfile);\n\n   return this;\n}\n\nvoid ScreenSettings_invertSortOrder(ScreenSettings* this) {\n   int* attr = (this->treeView) ? &(this->treeDirection) : &(this->direction);\n   *attr = (*attr == 1) ? -1 : 1;\n}\n\nvoid ScreenSettings_setSortKey(ScreenSettings* this, ProcessField sortKey) {\n   if (this->treeViewAlwaysByPID || !this->treeView) {\n      this->sortKey = sortKey;\n      this->direction = (Process_fields[sortKey].defaultSortDesc) ? -1 : 1;\n      this->treeView = false;\n   } else {\n      this->treeSortKey = sortKey;\n      this->treeDirection = (Process_fields[sortKey].defaultSortDesc) ? -1 : 1;\n   }\n}\n\nstatic bool readonly = false;\n\nvoid Settings_enableReadonly(void) {\n   readonly = true;\n}\n\nbool Settings_isReadonly(void) {\n   return readonly;\n}\n\nvoid Settings_setHeaderLayout(Settings* this, HeaderLayout hLayout) {\n   size_t oldColumns = HeaderLayout_getColumns(this->hLayout);\n   size_t newColumns = HeaderLayout_getColumns(hLayout);\n\n   if (newColumns > oldColumns) {\n      this->hColumns = xReallocArray(this->hColumns, newColumns, sizeof(MeterColumnSetting));\n      memset(this->hColumns + oldColumns, 0, (newColumns - oldColumns) * sizeof(MeterColumnSetting));\n   } else if (newColumns < oldColumns) {\n      for (size_t i = newColumns; i < oldColumns; i++) {\n         if (this->hColumns[i].names) {\n            for (size_t j = 0; j < this->hColumns[i].len; j++)\n               free(this->hColumns[i].names[j]);\n            free(this->hColumns[i].names);\n         }\n         free(this->hColumns[i].modes);\n      }\n      this->hColumns = xReallocArray(this->hColumns, newColumns, sizeof(MeterColumnSetting));\n   }\n\n   this->hLayout = hLayout;\n   this->changed = true;\n}\n"
  },
  {
    "path": "Settings.h",
    "content": "#ifndef HEADER_Settings\n#define HEADER_Settings\n/*\nhtop - Settings.h\n(C) 2004-2011 Hisham H. Muhammad\nReleased under the GNU GPLv2+, see the COPYING file\nin the source distribution for its full text.\n*/\n\n#include <stdbool.h>\n#include <stddef.h>\n#include <stdint.h>\n\n#include \"Hashtable.h\"\n#include \"HeaderLayout.h\"\n#include \"MeterMode.h\"\n#include \"Row.h\"\n#include \"RowField.h\"\n\n\n#define DEFAULT_DELAY 15\n\n#define CONFIG_READER_MIN_VERSION 3\n\nstruct DynamicScreen_;  // IWYU pragma: keep\nstruct Machine_;        // IWYU pragma: keep\nstruct Table_;          // IWYU pragma: keep\n\ntypedef struct {\n   const char* name;\n   const char* columns;\n   const char* sortKey;\n   const char* treeSortKey;\n} ScreenDefaults;\n\ntypedef struct {\n   size_t len;\n   char** names;\n   MeterModeId* modes;\n} MeterColumnSetting;\n\ntypedef struct ScreenSettings_ {\n   char* heading;  /* user-editable screen name (pretty) */\n   char* dynamic;  /* from DynamicScreen config (fixed) */\n   struct Table_* table;\n   RowField* fields;\n   uint32_t flags;\n   int direction;\n   int treeDirection;\n   RowField sortKey;\n   RowField treeSortKey;\n   bool treeView;\n   bool treeViewAlwaysByPID;\n   bool allBranchesCollapsed;\n} ScreenSettings;\n\ntypedef struct Settings_ {\n   char* filename;\n   char* initialFilename;\n   bool writeConfig; /* whether to write the current settings on exit */\n   int config_version;\n   HeaderLayout hLayout;\n   MeterColumnSetting* hColumns;\n   Hashtable* dynamicColumns; /* runtime-discovered columns */\n   Hashtable* dynamicMeters;  /* runtime-discovered meters */\n   Hashtable* dynamicScreens; /* runtime-discovered screens */\n\n   ScreenSettings** screens;\n   unsigned int nScreens;\n   unsigned int ssIndex;\n   ScreenSettings* ss;\n\n   int colorScheme;\n   int delay;\n\n   bool countCPUsFromOne;\n   bool detailedCPUTime;\n   bool showCPUUsage;\n   bool showCPUFrequency;\n   #ifdef BUILD_WITH_CPU_TEMP\n   bool showCPUTemperature;\n   bool degreeFahrenheit;\n   #endif\n   bool showProgramPath;\n   bool shadowOtherUsers;\n   bool showThreadNames;\n   bool hideKernelThreads;\n   bool hideRunningInContainer;\n   bool hideUserlandThreads;\n   bool highlightBaseName;\n   bool highlightDeletedExe;\n   bool shadowDistPathPrefix;\n   bool highlightMegabytes;\n   bool highlightThreads;\n   bool highlightChanges;\n   int highlightDelaySecs;\n   bool findCommInCmdline;\n   bool stripExeFromCmdline;\n   bool showMergedCommand;\n   bool updateProcessNames;\n   bool accountGuestInCPUMeter;\n   bool headerMargin;\n   bool screenTabs;\n   bool showCachedMemory;\n   #ifdef HAVE_GETMOUSE\n   bool enableMouse;\n   #endif\n   int hideFunctionBar;  // 0 - off, 1 - on ESC until next input, 2 - permanently\n   #ifdef HAVE_LIBHWLOC\n   bool topologyAffinity;\n   #endif\n\n   bool changed;\n   uint64_t lastUpdate;\n} Settings;\n\n#define Settings_cpuId(settings, cpu) ((settings)->countCPUsFromOne ? (cpu)+1 : (cpu))\n\nstatic inline RowField ScreenSettings_getActiveSortKey(const ScreenSettings* this) {\n   return (this->treeView)\n          ? (this->treeViewAlwaysByPID ? 1 : this->treeSortKey)\n          : this->sortKey;\n}\n\nstatic inline int ScreenSettings_getActiveDirection(const ScreenSettings* this) {\n   return this->treeView ? this->treeDirection : this->direction;\n}\n\nvoid Settings_delete(Settings* this);\n\nint Settings_write(const Settings* this, bool onCrash);\n\nSettings* Settings_new(const struct Machine_* host, Hashtable* dynamicMeters, Hashtable* dynamicColumns, Hashtable* dynamicScreens);\n\nScreenSettings* Settings_newScreen(Settings* this, const ScreenDefaults* defaults);\n\nScreenSettings* Settings_newDynamicScreen(Settings* this, const char* tab, const struct DynamicScreen_* screen, struct Table_* table);\n\nvoid ScreenSettings_delete(ScreenSettings* this);\n\nvoid ScreenSettings_invertSortOrder(ScreenSettings* this);\n\nvoid ScreenSettings_setSortKey(ScreenSettings* this, RowField sortKey);\n\nvoid Settings_enableReadonly(void);\n\nbool Settings_isReadonly(void);\n\nvoid Settings_setHeaderLayout(Settings* this, HeaderLayout hLayout);\n\n#endif\n"
  },
  {
    "path": "SignalsPanel.c",
    "content": "/*\nhtop - SignalsPanel.c\n(C) 2004-2011 Hisham H. Muhammad\nReleased under the GNU GPLv2+, see the COPYING file\nin the source distribution for its full text.\n*/\n\n#include \"config.h\" // IWYU pragma: keep\n\n#include \"SignalsPanel.h\"\n// the above contains #include <signal.h> so do not add that here again (breaks Solaris build)\n\n#include <stdbool.h>\n\n#include \"FunctionBar.h\"\n#include \"ListItem.h\"\n#include \"Object.h\"\n#include \"Panel.h\"\n#include \"Platform.h\"\n#include \"XUtils.h\"\n\n\nPanel* SignalsPanel_new(int preSelectedSignal) {\n   Panel* this = Panel_new(1, 1, 1, 1, Class(ListItem), true, FunctionBar_newEnterEsc(\"Send   \", \"Cancel \"));\n   int defaultPosition = 15;\n   unsigned int i;\n   for (i = 0; i < Platform_numberOfSignals; i++) {\n      Panel_set(this, i, (Object*) ListItem_new(Platform_signals[i].name, Platform_signals[i].number));\n      // signal 15 is not always the 15th signal in the table\n      if (Platform_signals[i].number == preSelectedSignal) {\n         defaultPosition = i;\n      }\n   }\n   #if (defined(SIGRTMIN) && defined(SIGRTMAX))\n   if (SIGRTMAX - SIGRTMIN <= 100) {\n      static char buf[16];\n      for (int sig = SIGRTMIN; sig <= SIGRTMAX; i++, sig++) {\n         int n = sig - SIGRTMIN;\n         xSnprintf(buf, sizeof(buf), \"%2d SIGRTMIN%-+3d\", sig, n);\n         if (n == 0) {\n            buf[11] = '\\0';\n         }\n         Panel_set(this, i, (Object*) ListItem_new(buf, sig));\n      }\n   }\n   #endif\n   Panel_setHeader(this, \"Send signal:\");\n   Panel_setSelected(this, defaultPosition);\n   return this;\n}\n"
  },
  {
    "path": "SignalsPanel.h",
    "content": "#ifndef HEADER_SignalsPanel\n#define HEADER_SignalsPanel\n/*\nhtop - SignalsPanel.h\n(C) 2004-2011 Hisham H. Muhammad\nReleased under the GNU GPLv2+, see the COPYING file\nin the source distribution for its full text.\n*/\n\n#include \"Panel.h\"\n\n#ifndef HTOP_SOLARIS\n#include <signal.h>\n#endif\n\n\ntypedef struct SignalItem_ {\n   const char* name;\n   int number;\n} SignalItem;\n\n#define SIGNALSPANEL_INITSELECTEDSIGNAL SIGTERM\n\nPanel* SignalsPanel_new(int preSelectedSignal);\n\n#endif\n"
  },
  {
    "path": "SwapMeter.c",
    "content": "/*\nhtop - SwapMeter.c\n(C) 2004-2011 Hisham H. Muhammad\nReleased under the GNU GPLv2+, see the COPYING file\nin the source distribution for its full text.\n*/\n\n#include \"config.h\" // IWYU pragma: keep\n\n#include \"SwapMeter.h\"\n\n#include <math.h>\n#include <stddef.h>\n\n#include \"CRT.h\"\n#include \"Macros.h\"\n#include \"Object.h\"\n#include \"Platform.h\"\n#include \"RichString.h\"\n\n\nstatic const int SwapMeter_attributes[] = {\n   SWAP,\n   SWAP_CACHE,\n   SWAP_FRONTSWAP,\n};\n\nstatic void SwapMeter_updateValues(Meter* this) {\n   char* buffer = this->txtBuffer;\n   size_t size = sizeof(this->txtBuffer);\n   int written;\n\n   this->values[SWAP_METER_CACHE] = NAN;   /* 'cached' not present on all platforms */\n   this->values[SWAP_METER_FRONTSWAP] = NAN;   /* 'frontswap' not present on all platforms */\n   Platform_setSwapValues(this);\n\n   written = Meter_humanUnit(buffer, this->values[SWAP_METER_USED], size);\n   METER_BUFFER_CHECK(buffer, size, written);\n\n   METER_BUFFER_APPEND_CHR(buffer, size, '/');\n\n   Meter_humanUnit(buffer, this->total, size);\n}\n\nstatic void SwapMeter_display(const Object* cast, RichString* out) {\n   char buffer[50];\n   const Meter* this = (const Meter*)cast;\n   RichString_writeAscii(out, CRT_colors[METER_TEXT], \":\");\n   Meter_humanUnit(buffer, this->total, sizeof(buffer));\n   RichString_appendAscii(out, CRT_colors[METER_VALUE], buffer);\n   Meter_humanUnit(buffer, this->values[SWAP_METER_USED], sizeof(buffer));\n   RichString_appendAscii(out, CRT_colors[METER_TEXT], \" used:\");\n   RichString_appendAscii(out, CRT_colors[METER_VALUE], buffer);\n\n   if (isNonnegative(this->values[SWAP_METER_CACHE])) {\n      Meter_humanUnit(buffer, this->values[SWAP_METER_CACHE], sizeof(buffer));\n      RichString_appendAscii(out, CRT_colors[METER_TEXT], \" cache:\");\n      RichString_appendAscii(out, CRT_colors[SWAP_CACHE], buffer);\n   }\n\n   if (isNonnegative(this->values[SWAP_METER_FRONTSWAP])) {\n      Meter_humanUnit(buffer, this->values[SWAP_METER_FRONTSWAP], sizeof(buffer));\n      RichString_appendAscii(out, CRT_colors[METER_TEXT], \" frontswap:\");\n      RichString_appendAscii(out, CRT_colors[SWAP_FRONTSWAP], buffer);\n   }\n}\n\nconst MeterClass SwapMeter_class = {\n   .super = {\n      .extends = Class(Meter),\n      .delete = Meter_delete,\n      .display = SwapMeter_display,\n   },\n   .updateValues = SwapMeter_updateValues,\n   .defaultMode = BAR_METERMODE,\n   .supportedModes = METERMODE_DEFAULT_SUPPORTED,\n   .maxItems = SWAP_METER_ITEMCOUNT,\n   .isPercentChart = true,\n   .total = 100.0,\n   .attributes = SwapMeter_attributes,\n   .name = \"Swap\",\n   .uiName = \"Swap\",\n   .caption = \"Swp\"\n};\n"
  },
  {
    "path": "SwapMeter.h",
    "content": "#ifndef HEADER_SwapMeter\n#define HEADER_SwapMeter\n/*\nhtop - SwapMeter.h\n(C) 2004-2011 Hisham H. Muhammad\nReleased under the GNU GPLv2+, see the COPYING file\nin the source distribution for its full text.\n*/\n\n#include \"Meter.h\"\n\ntypedef enum {\n   SWAP_METER_USED = 0,\n   SWAP_METER_CACHE = 1,\n   SWAP_METER_FRONTSWAP = 2,\n   SWAP_METER_ITEMCOUNT = 3, // number of entries in this enum\n} SwapMeterValues;\n\nextern const MeterClass SwapMeter_class;\n\n#endif\n"
  },
  {
    "path": "SysArchMeter.c",
    "content": "/*\nhtop - SysArchMeter.c\n(C) 2021 htop dev team\nReleased under the GNU GPLv2+, see the COPYING file\nin the source distribution for its full text.\n*/\n\n#include \"config.h\"  // IWYU pragma: keep\n\n#include \"SysArchMeter.h\"\n\n#include <stddef.h>\n\n#include \"CRT.h\"\n#include \"Object.h\"\n#include \"Platform.h\"\n#include \"XUtils.h\"\n\n\nstatic const int SysArchMeter_attributes[] = {HOSTNAME};\n\nstatic void SysArchMeter_updateValues(Meter* this) {\n   const char* string = Platform_getRelease();\n\n   String_safeStrncpy(this->txtBuffer, string, sizeof(this->txtBuffer));\n}\n\nconst MeterClass SysArchMeter_class = {\n   .super = {\n      .extends = Class(Meter),\n      .delete = Meter_delete\n   },\n   .updateValues = SysArchMeter_updateValues,\n   .defaultMode = TEXT_METERMODE,\n   .supportedModes = (1 << TEXT_METERMODE),\n   .maxItems = 0,\n   .total = 0.0,\n   .attributes = SysArchMeter_attributes,\n   .name = \"System\",\n   .uiName = \"System\",\n   .caption = \"System: \",\n};\n"
  },
  {
    "path": "SysArchMeter.h",
    "content": "#ifndef HEADER_SysArchMeter\n#define HEADER_SysArchMeter\n/*\nhtop - SysArchMeter.h\n(C) 2021 htop dev team\nReleased under the GNU GPLv2+, see the COPYING file\nin the source distribution for its full text.\n*/\n#include \"Meter.h\"\n\n\nextern const MeterClass SysArchMeter_class;\n\n#endif\n"
  },
  {
    "path": "TESTPLAN",
    "content": "\nMain screen:\n\n   For all views, all modes:\n\n      Mouse click header - nothing happens.\n\n      Mouse click on ProcessTable title bar - exit Tree view, update FunctionBar, title bar updates, sort by clicked field.\n*** FAILING: wrong FB update depending on mode; does not change sort in wip branch\n         click on same entry - invert sort.\n            click on another entry - sort another field.\n\n      Mouse click on a process - select that process.\n\n      for each entry in FunctionBar:\n         Mouse click entry - perform action of associated key.\n\n   In Normal mode, Sorted view:\n\n      <+> or <-> - do nothing.\n\n      <F6> - enter SortBy screen.\n\n   In Normal mode, Tree view:\n\n      select process - update F6 in FunctionBar if subtree is collapsed or expanded.\n\n      <F6>, <+> or <-> - expand/collapse subtree.\n\n   In Normal mode, either Sorted or Tree view:\n\n      <F3>, </> - activate Search mode.\n\n      <F4>, <\\> - activate Filter mode.\n\n      <F7>, <]> - as root only, decrease process NICE value.\n\n      <F8>, <[> - increase process NICE value.\n\n      <a> - enter Affinity screen.\n\n      <b> - do nothing.\n\n      <c> - select process and all its children.\n\n      <d>, <e>, <f>, <g> - do nothing.\n\n      <F1>, <h>, <?> - enter Help screen.\n\n      <i> - on Linux, enter IOPriority screen.\n\n      <j> - do nothing.\n\n      <F9>, <k> - enter Kill screen.\n\n      <l> - enter LSOF screen.\n\n      <m>, <n>, <o> - do nothing.\n\n      <p> - hide/show program path.\n\n      <F10>, <q> - quit program.\n\n      <r> - do nothing.\n\n      <s> - enter STrace screen.\n\n      <F5>, <t> - toggle between Tree and Sorted view, update F5 in FunctionBar, follow process\n\n      <u> - enter User screen.\n\n      <v>, <w>, <x>, <y>, <z> - do nothing.\n\n      <A>, <B> - do nothing.\n\n      <F2>, <C>, <S> - enter Setup screen.\n\n      <D>, <E> - do nothing.\n\n      <F> - follow process.\n\n      <G> - do nothing.\n\n      <H> - toggle show/hide userland threads.\n\n      <I> - invert sort order.\n\n      <J> - do nothing.\n\n      <K> - toggle show/hide kernel threads.\n\n      <L> - do nothing.\n\n      <M> - enter Sorted view, update function bar, sort by MEM%.\n\n      <N>, <O> - do nothing.\n\n      <P> - enter Sorted view, update function bar, sort by CPU%.\n\n      <Q>, <R> - do nothing.\n\n      <T> - enter Sorted view, update function bar, sort by TIME.\n\n      <U> - untag all processes.\n\n      <V>, <W>, <X>, <Y>, <Z> - do nothing.\n\n      <<>, <>>, <,>, <.> - enter SortBy screen.\n\n      space - tag current process, move down cursor.\n\n      numbers - incremental PID search.\n\n   In Search mode:\n\n      TODO\n\n   In Filter mode:\n\n      TODO\n\nSetup screen:\n\n   TODO\n\nSortBy screen:\n\n   TODO\n\nUser screen:\n\n   TODO\n\nKill screen:\n\n   TODO\n\nAffinity screen:\n\n   TODO\n\nHelp screen:\n\n   any key - back to Main screen.\n\nIOPriority screen:\n\n   TODO\n\nSTrace screen:\n\n   TODO\n\nLSOF screen:\n\n   TODO\n"
  },
  {
    "path": "Table.c",
    "content": "/*\nhtop - Table.c\n(C) 2004,2005 Hisham H. Muhammad\n(C) 2023 htop dev team\nReleased under the GNU GPLv2+, see the COPYING file\nin the source distribution for its full text.\n*/\n\n#include \"config.h\" // IWYU pragma: keep\n\n#include \"Table.h\"\n\n#include <assert.h>\n#include <stdint.h>\n#include <stdlib.h>\n\n#include \"CRT.h\"\n#include \"Hashtable.h\"\n#include \"Machine.h\"\n#include \"Macros.h\"\n#include \"Panel.h\"\n#include \"RowField.h\"\n#include \"Vector.h\"\n\n\nTable* Table_init(Table* this, const ObjectClass* klass, Machine* host) {\n   this->rows = Vector_new(klass, true, VECTOR_DEFAULT_SIZE);\n   this->displayList = Vector_new(klass, false, VECTOR_DEFAULT_SIZE);\n   this->table = Hashtable_new(200, false);\n   this->needsSort = true;\n   this->following = -1;\n   this->host = host;\n   return this;\n}\n\nvoid Table_done(Table* this) {\n   Hashtable_delete(this->table);\n   Vector_delete(this->displayList);\n   Vector_delete(this->rows);\n}\n\nstatic void Table_delete(Object* cast) {\n   Table* this = (Table*) cast;\n   Table_done(this);\n   free(this);\n}\n\nvoid Table_setPanel(Table* this, Panel* panel) {\n   this->panel = panel;\n}\n\nvoid Table_add(Table* this, Row* row) {\n   assert(Vector_indexOf(this->rows, row, Row_idEqualCompare) == -1);\n   assert(Hashtable_get(this->table, row->id) == NULL);\n\n   // highlighting row found in first scan by first scan marked \"far in the past\"\n   row->seenStampMs = this->host->monotonicMs;\n\n   Vector_add(this->rows, row);\n   Hashtable_put(this->table, row->id, row);\n\n   assert(Vector_indexOf(this->rows, row, Row_idEqualCompare) != -1);\n   assert(Hashtable_get(this->table, row->id) != NULL);\n   assert(Vector_countEquals(this->rows, Hashtable_count(this->table)));\n}\n\n// Table_removeIndex removes a given row from the lists map and soft deletes\n// it from its vector. Vector_compact *must* be called once the caller is done\n// removing items.\n// Note: for processes should only be called from ProcessTable_iterate to avoid\n// breaking dying process highlighting.\nstatic void Table_removeIndex(Table* this, const Row* row, int idx) {\n   int rowid = row->id;\n\n   assert(row == (Row*)Vector_get(this->rows, idx));\n   assert(Hashtable_get(this->table, rowid) != NULL);\n\n   Hashtable_remove(this->table, rowid);\n   Vector_softRemove(this->rows, idx);\n\n   if (this->following != -1 && this->following == rowid) {\n      this->following = -1;\n      Panel_setSelectionColor(this->panel, PANEL_SELECTION_FOCUS);\n   }\n\n   assert(Hashtable_get(this->table, rowid) == NULL);\n   assert(Vector_countEquals(this->rows, Hashtable_count(this->table)));\n}\n\nstatic void Table_buildTreeBranch(Table* this, int rowid, unsigned int level, int32_t indent, bool show) {\n   // Do not treat zero as root of any tree.\n   // (e.g. on OpenBSD the kernel thread 'swapper' has pid 0.)\n   if (rowid == 0)\n      return;\n\n   // The vector is sorted by parent, find the start of the range by bisection\n   int vsize = Vector_size(this->rows);\n   int l = 0;\n   int r = vsize;\n   while (l < r) {\n      int c = l + (r - l) / 2;\n      Row* row = (Row*)Vector_get(this->rows, c);\n      int parent = row->isRoot ? 0 : Row_getGroupOrParent(row);\n      if (parent < rowid) {\n         l = c + 1;\n      } else {\n         r = c;\n      }\n   }\n   // Find the end to know the last line for indent handling purposes\n   int lastShown = r;\n   while (r < vsize) {\n      Row* row = (Row*)Vector_get(this->rows, r);\n      if (!Row_isChildOf(row, rowid))\n         break;\n      if (row->show)\n         lastShown = r;\n      r++;\n   }\n\n   for (int i = l; i < r; i++) {\n      Row* row = (Row*)Vector_get(this->rows, i);\n\n      if (!show)\n         row->show = false;\n\n      Vector_add(this->displayList, row);\n\n      int32_t nextIndent = indent | ((int32_t)1 << MINIMUM(level, sizeof(row->indent) * 8 - 2));\n      Table_buildTreeBranch(this, row->id, level + 1, (i < lastShown) ? nextIndent : indent, row->show && row->showChildren);\n      if (i == lastShown)\n         row->indent = -nextIndent;\n      else\n         row->indent = nextIndent;\n\n      row->tree_depth = level + 1;\n   }\n}\n\nstatic int compareRowByKnownParentThenNatural(const void* v1, const void* v2) {\n   return Row_compareByParent((const Row*) v1, (const Row*) v2);\n}\n\n// Builds a sorted tree from scratch, without relying on previously gathered information\nstatic void Table_buildTree(Table* this) {\n   Vector_prune(this->displayList);\n\n   // Mark root processes\n   int vsize = Vector_size(this->rows);\n   for (int i = 0; i < vsize; i++) {\n      Row* row = (Row*) Vector_get(this->rows, i);\n      int parent = Row_getGroupOrParent(row);\n      row->isRoot = false;\n\n      if (row->id == parent) {\n         row->isRoot = true;\n         continue;\n      }\n\n      if (!parent) {\n         row->isRoot = true;\n         continue;\n      }\n\n      // We don't know about its parent for whatever reason\n      if (Table_findRow(this, parent) == NULL)\n         row->isRoot = true;\n   }\n\n   // Sort by known parent (roots first), then row ID\n   Vector_quickSortCustomCompare(this->rows, compareRowByKnownParentThenNatural);\n\n   // Find all processes whose parent is not visible\n   for (int i = 0; i < vsize; i++) {\n      Row* row = (Row*)Vector_get(this->rows, i);\n\n      // If parent not found, then construct the tree with this node as root\n      if (row->isRoot) {\n         row = (Row*)Vector_get(this->rows, i);\n         row->indent = 0;\n         row->tree_depth = 0;\n         Vector_add(this->displayList, row);\n         Table_buildTreeBranch(this, row->id, 0, 0, row->showChildren);\n         continue;\n      }\n   }\n\n   this->needsSort = false;\n\n   // Check consistency of the built structures\n   assert(Vector_size(this->displayList) == vsize); (void)vsize;\n}\n\nvoid Table_updateDisplayList(Table* this) {\n   const Settings* settings = this->host->settings;\n\n   if (settings->ss->treeView) {\n      if (this->needsSort)\n         Table_buildTree(this);\n   } else {\n      if (this->needsSort)\n         Vector_insertionSort(this->rows);\n      Vector_prune(this->displayList);\n      int size = Vector_size(this->rows);\n      for (int i = 0; i < size; i++)\n         Vector_add(this->displayList, Vector_get(this->rows, i));\n   }\n   this->needsSort = false;\n}\n\nvoid Table_expandTree(Table* this) {\n   int size = Vector_size(this->rows);\n   for (int i = 0; i < size; i++) {\n      Row* row = (Row*) Vector_get(this->rows, i);\n      row->showChildren = true;\n   }\n}\n\n// Called on collapse-all toggle and on startup, possibly in non-tree mode\nvoid Table_collapseAllBranches(Table* this) {\n   Table_buildTree(this); // Update `tree_depth` fields of the rows\n   this->needsSort = true; // Table is sorted by parent now, force new sort\n   int size = Vector_size(this->rows);\n   for (int i = 0; i < size; i++) {\n      Row* row = (Row*) Vector_get(this->rows, i);\n      // FreeBSD has pid 0 = kernel and pid 1 = init, so init has tree_depth = 1\n      if (row->tree_depth > 0 && row->id > 1)\n         row->showChildren = false;\n   }\n}\n\nvoid Table_rebuildPanel(Table* this) {\n   Table_updateDisplayList(this);\n\n   const int currPos = Panel_getSelectedIndex(this->panel);\n   const int currScrollV = this->panel->scrollV;\n   const int currSize = Panel_size(this->panel);\n\n   Panel_prune(this->panel);\n\n   /* Follow main group row instead if following a row that is occluded (hidden) */\n   if (this->following != -1) {\n      const Row* followed = (const Row*) Hashtable_get(this->table, this->following);\n      if (followed != NULL\n         && Hashtable_get(this->table, followed->group)\n         && Row_isVisible(followed, this) == false ) {\n         this->following = followed->group;\n      }\n   }\n\n   const int rowCount = Vector_size(this->displayList);\n   bool foundFollowed = false;\n   int idx = 0;\n\n   for (int i = 0; i < rowCount; i++) {\n      Row* row = (Row*) Vector_get(this->displayList, i);\n\n      if ( !row->show || (Row_matchesFilter(row, this) == true) )\n         continue;\n\n      Panel_set(this->panel, idx, (Object*)row);\n\n      if (this->following != -1 && row->id == this->following) {\n         foundFollowed = true;\n         Panel_setSelected(this->panel, idx);\n         /* Keep scroll position relative to followed row */\n         this->panel->scrollV = idx - (currPos - currScrollV);\n      }\n      idx++;\n   }\n\n   if (this->following != -1 && !foundFollowed) {\n      /* Reset if current followed row not found */\n      this->following = -1;\n      Panel_setSelectionColor(this->panel, PANEL_SELECTION_FOCUS);\n   }\n\n   if (this->following == -1) {\n      /* If the last item was selected, keep the new last item selected */\n      if (currPos > 0 && currPos == currSize - 1)\n         Panel_setSelected(this->panel, Panel_size(this->panel) - 1);\n      else\n         Panel_setSelected(this->panel, currPos);\n\n      this->panel->scrollV = currScrollV;\n   }\n}\n\nvoid Table_printHeader(const Settings* settings, RichString* header) {\n   RichString_rewind(header, RichString_size(header));\n\n   const ScreenSettings* ss = settings->ss;\n   const RowField* fields = ss->fields;\n\n   RowField key = ScreenSettings_getActiveSortKey(ss);\n\n   for (int i = 0; fields[i]; i++) {\n      int color;\n      if (ss->treeView && ss->treeViewAlwaysByPID) {\n         color = CRT_colors[PANEL_HEADER_FOCUS];\n      } else if (key == fields[i]) {\n         color = CRT_colors[PANEL_SELECTION_FOCUS];\n      } else {\n         color = CRT_colors[PANEL_HEADER_FOCUS];\n      }\n\n      RichString_appendWide(header, color, RowField_alignedTitle(settings, fields[i]));\n      if (key == fields[i] && RichString_getCharVal(*header, RichString_size(header) - 1) == ' ') {\n         bool ascending = ScreenSettings_getActiveDirection(ss) == 1;\n         RichString_rewind(header, 1);  // rewind to override space\n         RichString_appendWide(header,\n                                CRT_colors[PANEL_SELECTION_FOCUS],\n                                CRT_treeStr[ascending ? TREE_STR_ASC : TREE_STR_DESC]);\n      }\n      if (COMM == fields[i] && settings->showMergedCommand) {\n         RichString_appendAscii(header, color, \"(merged)\");\n      }\n   }\n}\n\n// set flags on an existing rows before refreshing table\nvoid Table_prepareEntries(Table* this) {\n   for (int i = 0; i < Vector_size(this->rows); i++) {\n      Row* row = (struct Row_*) Vector_get(this->rows, i);\n      row->updated = false;\n      row->wasShown = row->show;\n      row->show = true;\n   }\n}\n\n// tidy up Row state after refreshing the table\nRow* Table_cleanupRow(Table* table, Row* row, int idx) {\n   Machine* host = table->host;\n   const Settings* settings = host->settings;\n\n   if (row->tombStampMs > 0) {\n      // remove tombed process\n      if (host->monotonicMs >= row->tombStampMs) {\n         goto remove;\n      }\n   } else if (!row->updated) {\n      // process no longer exists\n      if (settings->highlightChanges && row->wasShown) {\n         // mark tombed\n         row->tombStampMs = host->monotonicMs + 1000 * settings->highlightDelaySecs;\n      } else {\n         // immediately remove\n         goto remove;\n      }\n   }\n   return row;\n\nremove:\n   Table_removeIndex(table, row, idx);\n   return NULL;\n}\n\nvoid Table_cleanupEntries(Table* this) {\n   // Lowest index of the row that is soft-removed. Used to speed up\n   // compaction.\n   int dirtyIndex = Vector_size(this->rows);\n\n   // Finish process table update, culling any removed rows\n   for (int i = Vector_size(this->rows) - 1; i >= 0; i--) {\n      Row* row = (Row*) Vector_get(this->rows, i);\n      if (!Table_cleanupRow(this, row, i)) {\n         dirtyIndex = i;\n      }\n   }\n\n   // compact the table in case of any earlier row removals\n   Table_compact(this, dirtyIndex);\n}\n\nconst TableClass Table_class = {\n   .super = {\n      .extends = Class(Object),\n      .delete = Table_delete,\n   },\n   .prepare = Table_prepareEntries,\n   .cleanup = Table_cleanupEntries,\n};\n"
  },
  {
    "path": "Table.h",
    "content": "#ifndef HEADER_Table\n#define HEADER_Table\n/*\nhtop - Table.h\n(C) 2004,2005 Hisham H. Muhammad\n(C) 2023 htop dev team\nReleased under the GNU GPLv2+, see the COPYING file\nin the source distribution for its full text.\n*/\n\n#include <stdbool.h>\n\n#include \"Hashtable.h\"\n#include \"Object.h\"\n#include \"RichString.h\"\n#include \"Settings.h\"\n#include \"Vector.h\"\n\n\nstruct Machine_;  // IWYU pragma: keep\nstruct Panel_;    // IWYU pragma: keep\nstruct Row_;      // IWYU pragma: keep\n\ntypedef struct Table_ {\n   /* Super object for emulated OOP */\n   Object super;\n\n   Vector* rows;          /* all known; sort order can vary and differ from display order */\n   Vector* displayList;   /* row tree flattened in display order (borrowed);\n                             updated in Table_updateDisplayList when rebuilding panel */\n   Hashtable* table;      /* fast known row lookup by identifier */\n\n   struct Machine_* host;\n   const char* incFilter;\n   bool needsSort;\n   int following;         /* -1 or row being visually tracked in the user interface */\n\n   struct Panel_* panel;\n} Table;\n\ntypedef Table* (*Table_New)(const struct Machine_*);\ntypedef void (*Table_ScanPrepare)(Table* this);\ntypedef void (*Table_ScanIterate)(Table* this);\ntypedef void (*Table_ScanCleanup)(Table* this);\n\ntypedef struct TableClass_ {\n   const ObjectClass super;\n   const Table_ScanPrepare prepare;\n   const Table_ScanIterate iterate;\n   const Table_ScanCleanup cleanup;\n} TableClass;\n\n#define As_Table(this_)  ((const TableClass*)((this_)->super.klass))\n\n#define Table_scanPrepare(t_)  (As_Table(t_)->prepare ? (As_Table(t_)->prepare(t_)) : Table_prepareEntries(t_))\n#define Table_scanIterate(t_)  (As_Table(t_)->iterate(t_))  /* mandatory; must have a custom iterate method */\n#define Table_scanCleanup(t_)  (As_Table(t_)->cleanup ? (As_Table(t_)->cleanup(t_)) : Table_cleanupEntries(t_))\n\nTable* Table_init(Table* this, const ObjectClass* klass, struct Machine_* host);\n\nvoid Table_done(Table* this);\n\nextern const TableClass Table_class;\n\nvoid Table_setPanel(Table* this, struct Panel_* panel);\n\nvoid Table_printHeader(const Settings* settings, RichString* header);\n\nvoid Table_add(Table* this, struct Row_* row);\n\nvoid Table_updateDisplayList(Table* this);\n\nvoid Table_expandTree(Table* this);\n\nvoid Table_collapseAllBranches(Table* this);\n\nvoid Table_rebuildPanel(Table* this);\n\nstatic inline struct Row_* Table_findRow(Table* this, int id) {\n   return (struct Row_*) Hashtable_get(this->table, id);\n}\n\nvoid Table_prepareEntries(Table* this);\n\nvoid Table_cleanupEntries(Table* this);\n\nRow* Table_cleanupRow(Table* this, Row* row, int idx);\n\nstatic inline void Table_compact(Table* this, int dirtyIndex) {\n   Vector_compact(this->rows, dirtyIndex);\n   this->needsSort = true;\n}\n\n#endif\n"
  },
  {
    "path": "TasksMeter.c",
    "content": "/*\nhtop - TasksMeter.c\n(C) 2004-2011 Hisham H. Muhammad\nReleased under the GNU GPLv2+, see the COPYING file\nin the source distribution for its full text.\n*/\n\n#include \"config.h\" // IWYU pragma: keep\n\n#include \"TasksMeter.h\"\n\n#include \"CRT.h\"\n#include \"Machine.h\"\n#include \"Macros.h\"\n#include \"Object.h\"\n#include \"ProcessTable.h\"\n#include \"RichString.h\"\n#include \"Settings.h\"\n#include \"XUtils.h\"\n\n\nstatic const int TasksMeter_attributes[] = {\n   CPU_SYSTEM,\n   PROCESS_THREAD,\n   PROCESS,\n   TASKS_RUNNING\n};\n\nstatic void TasksMeter_updateValues(Meter* this) {\n   const Machine* host = this->host;\n   const ProcessTable* pt = (const ProcessTable*) host->processTable;\n\n   this->values[0] = pt->kernelThreads;\n   this->values[1] = pt->userlandThreads;\n   this->values[2] = pt->totalTasks - pt->kernelThreads - pt->userlandThreads;\n   this->values[3] = MINIMUM(pt->runningTasks, host->activeCPUs);\n\n   xSnprintf(this->txtBuffer, sizeof(this->txtBuffer), \"%u/%u\", MINIMUM(pt->runningTasks, host->activeCPUs), pt->totalTasks);\n}\n\nstatic void TasksMeter_display(const Object* cast, RichString* out) {\n   const Meter* this = (const Meter*)cast;\n   const Settings* settings = this->host->settings;\n   char buffer[20];\n   int len;\n\n   len = xSnprintf(buffer, sizeof(buffer), \"%d\", (int)this->values[2]);\n   RichString_appendnAscii(out, CRT_colors[METER_VALUE], buffer, len);\n\n   RichString_appendAscii(out, settings->hideUserlandThreads ? CRT_colors[METER_SHADOW] : CRT_colors[METER_TEXT], \", \");\n   len = xSnprintf(buffer, sizeof(buffer), \"%d\", (int)this->values[1]);\n   RichString_appendnAscii(out, settings->hideUserlandThreads ? CRT_colors[METER_SHADOW] : CRT_colors[TASKS_RUNNING], buffer, len);\n   RichString_appendAscii(out, settings->hideUserlandThreads ? CRT_colors[METER_SHADOW] : CRT_colors[METER_TEXT], \" thr\");\n\n   RichString_appendAscii(out, settings->hideKernelThreads ? CRT_colors[METER_SHADOW] : CRT_colors[METER_TEXT], \", \");\n   len = xSnprintf(buffer, sizeof(buffer), \"%d\", (int)this->values[0]);\n   RichString_appendnAscii(out, settings->hideKernelThreads ? CRT_colors[METER_SHADOW] : CRT_colors[TASKS_RUNNING], buffer, len);\n   RichString_appendAscii(out, settings->hideKernelThreads ? CRT_colors[METER_SHADOW] : CRT_colors[METER_TEXT], \" kthr\");\n\n   RichString_appendAscii(out, CRT_colors[METER_TEXT], \"; \");\n   len = xSnprintf(buffer, sizeof(buffer), \"%d\", (int)this->values[3]);\n   RichString_appendnAscii(out, CRT_colors[TASKS_RUNNING], buffer, len);\n   RichString_appendAscii(out, CRT_colors[METER_TEXT], \" running\");\n}\n\nconst MeterClass TasksMeter_class = {\n   .super = {\n      .extends = Class(Meter),\n      .delete = Meter_delete,\n      .display = TasksMeter_display,\n   },\n   .updateValues = TasksMeter_updateValues,\n   .defaultMode = TEXT_METERMODE,\n   .supportedModes = METERMODE_DEFAULT_SUPPORTED,\n   .maxItems = 4,\n   .isPercentChart = false,\n   .total = 1.0,\n   .attributes = TasksMeter_attributes,\n   .name = \"Tasks\",\n   .uiName = \"Task counter\",\n   .caption = \"Tasks: \"\n};\n"
  },
  {
    "path": "TasksMeter.h",
    "content": "#ifndef HEADER_TasksMeter\n#define HEADER_TasksMeter\n/*\nhtop - TasksMeter.h\n(C) 2004-2011 Hisham H. Muhammad\nReleased under the GNU GPLv2+, see the COPYING file\nin the source distribution for its full text.\n*/\n\n#include \"Meter.h\"\n\n\nextern const MeterClass TasksMeter_class;\n\n#endif\n"
  },
  {
    "path": "TraceScreen.c",
    "content": "/*\nhtop - TraceScreen.c\n(C) 2005-2006 Hisham H. Muhammad\nReleased under the GNU GPLv2+, see the COPYING file\nin the source distribution for its full text.\n*/\n\n#include \"config.h\" // IWYU pragma: keep\n\n#include \"TraceScreen.h\"\n\n#include <assert.h>\n#include <errno.h>\n#include <fcntl.h>\n#include <signal.h>\n#include <stdbool.h>\n#include <stdio.h>\n#include <stdlib.h>\n#include <string.h>\n#include <unistd.h>\n#include <sys/select.h>\n#include <sys/wait.h>\n\n#include \"CRT.h\"\n#include \"FunctionBar.h\"\n#include \"Panel.h\"\n#include \"ProvideCurses.h\"\n#include \"XUtils.h\"\n\n\nstatic const char* const TraceScreenFunctions[] = {\"Search \", \"Filter \", \"AutoScroll \", \"Stop Tracing   \", \"Done   \", NULL};\n\nstatic const char* const TraceScreenKeys[] = {\"F3\", \"F4\", \"F8\", \"F9\", \"Esc\"};\n\nstatic const int TraceScreenEvents[] = {KEY_F(3), KEY_F(4), KEY_F(8), KEY_F(9), 27};\n\nTraceScreen* TraceScreen_new(const Process* process) {\n   // This initializes all TraceScreen variables to \"false\" so only default = true ones need to be set below\n   TraceScreen* this = xCalloc(1, sizeof(TraceScreen));\n   Object_setClass(this, Class(TraceScreen));\n   this->tracing = true;\n   this->strace_alive = false;\n   FunctionBar* fuBar = FunctionBar_new(TraceScreenFunctions, TraceScreenKeys, TraceScreenEvents);\n   CRT_disableDelay();\n   return (TraceScreen*) InfoScreen_init(&this->super, process, fuBar, LINES - 2, \" \");\n}\n\nvoid TraceScreen_delete(Object* cast) {\n   TraceScreen* this = (TraceScreen*) cast;\n   if (this->child > 0) {\n      kill(this->child, SIGTERM);\n      while (waitpid(this->child, NULL, 0) == -1)\n         if (errno != EINTR)\n            break;\n   }\n\n   if (this->strace) {\n      fclose(this->strace);\n   }\n\n   CRT_enableDelay();\n   free(InfoScreen_done((InfoScreen*)this));\n}\n\nstatic void TraceScreen_draw(InfoScreen* this) {\n   InfoScreen_drawTitled(this, \"Trace of process %d - %s\", Process_getPid(this->process), Process_getCommand(this->process));\n}\n\nbool TraceScreen_forkTracer(TraceScreen* this) {\n   int fdpair[2] = {-1, -1};\n\n   if (pipe(fdpair) < 0)\n      return false;\n\n   if (fcntl(fdpair[0], F_SETFL, O_NONBLOCK) < 0)\n      goto err;\n\n   if (fcntl(fdpair[1], F_SETFL, O_NONBLOCK) < 0)\n      goto err;\n\n   pid_t child = fork();\n   if (child < 0)\n      goto err;\n\n   if (child == 0) {\n      close(fdpair[0]);\n\n      dup2(fdpair[1], STDOUT_FILENO);\n      dup2(fdpair[1], STDERR_FILENO);\n      close(fdpair[1]);\n\n      char buffer[32] = {0};\n      xSnprintf(buffer, sizeof(buffer), \"%d\", Process_getPid(this->super.process));\n\n      #if defined(HTOP_FREEBSD) || defined(HTOP_OPENBSD) || defined(HTOP_NETBSD) || defined(HTOP_DRAGONFLYBSD) || defined(HTOP_SOLARIS)\n         // Use of NULL in variadic functions must have a pointer cast.\n         // The NULL constant is not required by standard to have a pointer type.\n         execlp(\"truss\", \"truss\", \"-s\", \"512\", \"-p\", buffer, (void*)NULL);\n\n         // Should never reach here, unless execlp fails ...\n         const char* message = \"Could not execute 'truss'. Please make sure it is available in your $PATH.\";\n         (void)! write(STDERR_FILENO, message, strlen(message));\n      #elif defined(HTOP_LINUX)\n         execlp(\"strace\", \"strace\", \"-T\", \"-tt\", \"-s\", \"512\", \"-p\", buffer, (void*)NULL);\n\n         // Should never reach here, unless execlp fails ...\n         const char* message = \"Could not execute 'strace'. Please make sure it is available in your $PATH.\";\n         (void)! write(STDERR_FILENO, message, strlen(message));\n      #else // HTOP_DARWIN, HTOP_PCP == HTOP_UNSUPPORTED\n         const char* message = \"Tracing unavailable on not supported system.\";\n         (void)! write(STDERR_FILENO, message, strlen(message));\n      #endif\n\n      exit(127);\n   }\n\n   FILE* fp = fdopen(fdpair[0], \"r\");\n   if (!fp)\n      goto err;\n\n   close(fdpair[1]);\n\n   this->child = child;\n   this->strace = fp;\n   this->strace_alive = true;\n\n   return true;\n\nerr:\n   close(fdpair[1]);\n   close(fdpair[0]);\n   return false;\n}\n\nstatic void TraceScreen_updateTrace(InfoScreen* super) {\n   TraceScreen* this = (TraceScreen*) super;\n\n   int fd_strace = fileno(this->strace);\n\n   fd_set fds;\n   FD_ZERO(&fds);\n   FD_SET(STDIN_FILENO, &fds);\n   if (this->strace_alive) {\n      assert(fd_strace != -1);\n      FD_SET(fd_strace, &fds);\n   }\n\n   struct timeval tv = { .tv_sec = 0, .tv_usec = 500 };\n   int ready = select(MAXIMUM(STDIN_FILENO, fd_strace) + 1, &fds, NULL, NULL, &tv);\n\n   char buffer[1025];\n   size_t nread = 0;\n   if (ready > 0 && FD_ISSET(fd_strace, &fds))\n      nread = fread(buffer, 1, sizeof(buffer) - 1, this->strace);\n\n   if (nread && this->tracing) {\n      const char* line = buffer;\n      buffer[nread] = '\\0';\n      for (size_t i = 0; i < nread; i++) {\n         if (buffer[i] == '\\n') {\n            buffer[i] = '\\0';\n            if (this->contLine) {\n               InfoScreen_appendLine(&this->super, line);\n               this->contLine = false;\n            } else {\n               InfoScreen_addLine(&this->super, line);\n            }\n            line = buffer + i + 1;\n         }\n      }\n      if (line < buffer + nread) {\n         InfoScreen_addLine(&this->super, line);\n         buffer[nread] = '\\0';\n         this->contLine = true;\n      }\n      if (this->follow) {\n         Panel_setSelected(this->super.display, Panel_size(this->super.display) - 1);\n      }\n   } else {\n      if (this->strace_alive && waitpid(this->child, NULL, WNOHANG) != 0)\n         this->strace_alive = false;\n   }\n}\n\nstatic bool TraceScreen_onKey(InfoScreen* super, int ch) {\n   TraceScreen* this = (TraceScreen*) super;\n\n   switch (ch) {\n      case 'f':\n      case KEY_F(8):\n         this->follow = !(this->follow);\n         if (this->follow)\n            Panel_setSelected(super->display, Panel_size(super->display) - 1);\n         return true;\n      case 't':\n      case KEY_F(9):\n         this->tracing = !this->tracing;\n         FunctionBar_setLabel(super->display->defaultBar, KEY_F(9), this->tracing ? \"Stop Tracing   \" : \"Resume Tracing \");\n         InfoScreen_draw(this);\n         return true;\n   }\n\n   this->follow = false;\n   return false;\n}\n\nconst InfoScreenClass TraceScreen_class = {\n   .super = {\n      .extends = Class(Object),\n      .delete = TraceScreen_delete\n   },\n   .draw = TraceScreen_draw,\n   .onErr = TraceScreen_updateTrace,\n   .onKey = TraceScreen_onKey,\n};\n"
  },
  {
    "path": "TraceScreen.h",
    "content": "#ifndef HEADER_TraceScreen\n#define HEADER_TraceScreen\n/*\nhtop - TraceScreen.h\n(C) 2005-2006 Hisham H. Muhammad\nReleased under the GNU GPLv2+, see the COPYING file\nin the source distribution for its full text.\n*/\n\n#include <stdbool.h>\n#include <stdio.h>\n#include <sys/types.h>\n\n#include \"InfoScreen.h\"\n#include \"Object.h\"\n#include \"Process.h\"\n\n\ntypedef struct TraceScreen_ {\n   InfoScreen super;\n   FILE* strace;\n   pid_t child;\n   bool tracing;\n   bool contLine;\n   bool follow;\n   bool strace_alive;\n} TraceScreen;\n\n\nextern const InfoScreenClass TraceScreen_class;\n\nTraceScreen* TraceScreen_new(const Process* process);\n\nvoid TraceScreen_delete(Object* cast);\n\nbool TraceScreen_forkTracer(TraceScreen* this);\n\n#endif\n"
  },
  {
    "path": "UptimeMeter.c",
    "content": "/*\nhtop - UptimeMeter.c\n(C) 2004-2011 Hisham H. Muhammad\nReleased under the GNU GPLv2+, see the COPYING file\nin the source distribution for its full text.\n*/\n\n#include \"config.h\" // IWYU pragma: keep\n\n#include \"UptimeMeter.h\"\n\n#include \"CRT.h\"\n#include \"Object.h\"\n#include \"Platform.h\"\n#include \"XUtils.h\"\n\n\nstatic const int UptimeMeter_attributes[] = {\n   UPTIME\n};\n\nstatic void UptimeMeter_updateValues(Meter* this) {\n   int totalseconds = Platform_getUptime();\n   if (totalseconds <= 0) {\n      xSnprintf(this->txtBuffer, sizeof(this->txtBuffer), \"(unknown)\");\n      return;\n   }\n\n   int seconds = totalseconds % 60;\n   int minutes = (totalseconds / 60) % 60;\n   int hours = (totalseconds / 3600) % 24;\n   int days = (totalseconds / 86400);\n\n   char daysbuf[32];\n   if (days > 100) {\n      xSnprintf(daysbuf, sizeof(daysbuf), \"%d days(!), \", days);\n   } else if (days > 1) {\n      xSnprintf(daysbuf, sizeof(daysbuf), \"%d days, \", days);\n   } else if (days == 1) {\n      xSnprintf(daysbuf, sizeof(daysbuf), \"1 day, \");\n   } else {\n      daysbuf[0] = '\\0';\n   }\n   xSnprintf(this->txtBuffer, sizeof(this->txtBuffer), \"%s%02d:%02d:%02d\", daysbuf, hours, minutes, seconds);\n}\n\nconst MeterClass UptimeMeter_class = {\n   .super = {\n      .extends = Class(Meter),\n      .delete = Meter_delete\n   },\n   .updateValues = UptimeMeter_updateValues,\n   .defaultMode = TEXT_METERMODE,\n   .supportedModes = (1 << TEXT_METERMODE) | (1 << LED_METERMODE),\n   .maxItems = 0,\n   .total = 0.0,\n   .attributes = UptimeMeter_attributes,\n   .name = \"Uptime\",\n   .uiName = \"Uptime\",\n   .caption = \"Uptime: \"\n};\n\n\nstatic void SecondsUptimeMeter_updateValues(Meter* this) {\n   int totalseconds = Platform_getUptime();\n   if (totalseconds <= 0) {\n      xSnprintf(this->txtBuffer, sizeof(this->txtBuffer), \"(unknown)\");\n      return;\n   }\n   xSnprintf(this->txtBuffer, sizeof(this->txtBuffer), \"%d s\", totalseconds);\n}\n\nconst MeterClass SecondsUptimeMeter_class = {\n   .super = {\n      .extends = Class(Meter),\n      .delete = Meter_delete\n   },\n   .updateValues = SecondsUptimeMeter_updateValues,\n   .defaultMode = TEXT_METERMODE,\n   .supportedModes = (1 << TEXT_METERMODE) | (1 << LED_METERMODE),\n   .maxItems = 0,\n   .total = 0.0,\n   .attributes = UptimeMeter_attributes,\n   .name = \"SecondsUptime\",\n   .uiName = \"Uptime (seconds)\",\n   .caption = \"Uptime: \"\n};\n"
  },
  {
    "path": "UptimeMeter.h",
    "content": "#ifndef HEADER_UptimeMeter\n#define HEADER_UptimeMeter\n/*\nhtop - UptimeMeter.h\n(C) 2004-2011 Hisham H. Muhammad\nReleased under the GNU GPLv2+, see the COPYING file\nin the source distribution for its full text.\n*/\n\n#include \"Meter.h\"\n\n\nextern const MeterClass UptimeMeter_class;\n\nextern const MeterClass SecondsUptimeMeter_class;\n\n#endif\n"
  },
  {
    "path": "UsersTable.c",
    "content": "/*\nhtop - UsersTable.c\n(C) 2004-2011 Hisham H. Muhammad\nReleased under the GNU GPLv2+, see the COPYING file\nin the source distribution for its full text.\n*/\n\n#include \"config.h\" // IWYU pragma: keep\n\n#include \"UsersTable.h\"\n\n#include <pwd.h>\n#include <stdbool.h>\n#include <stdio.h>\n#include <stdlib.h>\n\n#include \"XUtils.h\"\n\n\nUsersTable* UsersTable_new(void) {\n   UsersTable* this;\n   this = xMalloc(sizeof(UsersTable));\n   this->users = Hashtable_new(10, true);\n   return this;\n}\n\nvoid UsersTable_delete(UsersTable* this) {\n   Hashtable_delete(this->users);\n   free(this);\n}\n\nchar* UsersTable_getRef(UsersTable* this, unsigned int uid) {\n   char* name = Hashtable_get(this->users, uid);\n   if (name == NULL) {\n      const struct passwd* userData = getpwuid(uid);\n      if (userData != NULL) {\n         name = xStrdup(userData->pw_name);\n      } else {\n         name = xStrdup(\"\");\n      }\n      Hashtable_put(this->users, uid, name);\n   }\n   if (!name || !*name) {\n      return NULL;\n   }\n   return name;\n}\n\ninline void UsersTable_foreach(UsersTable* this, Hashtable_PairFunction f, void* userData) {\n   Hashtable_foreach(this->users, f, userData);\n}\n"
  },
  {
    "path": "UsersTable.h",
    "content": "#ifndef HEADER_UsersTable\n#define HEADER_UsersTable\n/*\nhtop - UsersTable.h\n(C) 2004-2011 Hisham H. Muhammad\nReleased under the GNU GPLv2+, see the COPYING file\nin the source distribution for its full text.\n*/\n\n#include \"Hashtable.h\"\n\n\ntypedef struct UsersTable_ {\n   Hashtable* users;\n} UsersTable;\n\nUsersTable* UsersTable_new(void);\n\nvoid UsersTable_delete(UsersTable* this);\n\nchar* UsersTable_getRef(UsersTable* this, unsigned int uid);\n\nvoid UsersTable_foreach(UsersTable* this, Hashtable_PairFunction f, void* userData);\n\n#endif\n"
  },
  {
    "path": "Vector.c",
    "content": "/*\nhtop - Vector.c\n(C) 2004-2011 Hisham H. Muhammad\nReleased under the GNU GPLv2+, see the COPYING file\nin the source distribution for its full text.\n*/\n\n#include \"config.h\" // IWYU pragma: keep\n\n#include \"Vector.h\"\n\n#include <assert.h>\n#include <stdlib.h>\n#include <string.h>\n\n#include \"XUtils.h\"\n\n\nVector* Vector_new(const ObjectClass* type, bool owner, int size) {\n   Vector* this;\n\n   assert(size > 0);\n   this = xMalloc(sizeof(Vector));\n   *this = (Vector) {\n      .growthRate = size,\n      .array = xCalloc(size, sizeof(Object*)),\n      .arraySize = size,\n      .items = 0,\n      .type = type,\n      .owner = owner,\n      .isDirty = false,\n   };\n   return this;\n}\n\nvoid Vector_delete(Vector* this) {\n   if (this->owner) {\n      for (int i = 0; i < this->items; i++) {\n         if (this->array[i]) {\n            Object_delete(this->array[i]);\n         }\n      }\n   }\n   free(this->array);\n   free(this);\n}\n\n#ifndef NDEBUG\n\nstatic bool Vector_isConsistent(const Vector* this) {\n   assert(this->items <= this->arraySize);\n   assert(!this->isDirty);\n\n   return true;\n}\n\nbool Vector_countEquals(const Vector* this, unsigned int expectedCount) {\n   unsigned int n = 0;\n   for (int i = 0; i < this->items; i++) {\n      if (this->array[i]) {\n         n++;\n      }\n   }\n   return n == expectedCount;\n}\n\nObject* Vector_get(const Vector* this, size_t idx) {\n   assert(idx < (size_t)this->arraySize);\n   assert(idx < (size_t)this->items);\n   assert(this->array[idx]);\n   assert(Object_isA(this->array[idx], this->type));\n   return this->array[idx];\n}\n\nint Vector_size(const Vector* this) {\n   assert(Vector_isConsistent(this));\n   return this->items;\n}\n\n#endif /* NDEBUG */\n\nvoid Vector_prune(Vector* this) {\n   assert(Vector_isConsistent(this));\n   if (this->owner) {\n      for (int i = 0; i < this->items; i++) {\n         if (this->array[i]) {\n            Object_delete(this->array[i]);\n         }\n      }\n   }\n   this->items = 0;\n   this->isDirty = false;\n   memset(this->array, '\\0', this->arraySize * sizeof(Object*));\n}\n\n//static int comparisons = 0;\n\nstatic void swap(Object** array, int indexA, int indexB) {\n   assert(indexA >= 0);\n   assert(indexB >= 0);\n   Object* tmp = array[indexA];\n   array[indexA] = array[indexB];\n   array[indexB] = tmp;\n}\n\nstatic int partition(Object** array, int left, int right, int pivotIndex, Object_Compare compare) {\n   const Object* pivotValue = array[pivotIndex];\n   swap(array, pivotIndex, right);\n   int storeIndex = left;\n   for (int i = left; i < right; i++) {\n      //comparisons++;\n      if (compare(array[i], pivotValue) <= 0) {\n         swap(array, i, storeIndex);\n         storeIndex++;\n      }\n   }\n   swap(array, storeIndex, right);\n   return storeIndex;\n}\n\nstatic void quickSort(Object** array, int left, int right, Object_Compare compare) {\n   if (left >= right)\n      return;\n\n   int pivotIndex = left + (right - left) / 2;\n   int pivotNewIndex = partition(array, left, right, pivotIndex, compare);\n   quickSort(array, left, pivotNewIndex - 1, compare);\n   quickSort(array, pivotNewIndex + 1, right, compare);\n}\n\n// If I were to use only one sorting algorithm for both cases, it would probably be this one:\n/*\n\nstatic void combSort(Object** array, int left, int right, Object_Compare compare) {\n   int gap = right - left;\n   bool swapped = true;\n   while ((gap > 1) || swapped) {\n      if (gap > 1) {\n         gap = (int)((double)gap / 1.247330950103979);\n      }\n      swapped = false;\n      for (int i = left; gap + i <= right; i++) {\n         comparisons++;\n         if (compare(array[i], array[i+gap]) > 0) {\n            swap(array, i, i+gap);\n            swapped = true;\n         }\n      }\n   }\n}\n\n*/\n\nstatic void insertionSort(Object** array, int left, int right, Object_Compare compare) {\n   for (int i = left + 1; i <= right; i++) {\n      Object* t = array[i];\n      int j = i - 1;\n      while (j >= left) {\n         //comparisons++;\n         if (compare(array[j], t) <= 0)\n            break;\n\n         array[j + 1] = array[j];\n         j--;\n      }\n      array[j + 1] = t;\n   }\n}\n\nvoid Vector_quickSortCustomCompare(Vector* this, Object_Compare compare) {\n   assert(compare);\n   assert(Vector_isConsistent(this));\n   quickSort(this->array, 0, this->items - 1, compare);\n   assert(Vector_isConsistent(this));\n}\n\nvoid Vector_insertionSort(Vector* this) {\n   assert(this->type->compare);\n   assert(Vector_isConsistent(this));\n   insertionSort(this->array, 0, this->items - 1, this->type->compare);\n   assert(Vector_isConsistent(this));\n}\n\nstatic void Vector_resizeIfNecessary(Vector* this, int newSize) {\n   assert(newSize >= 0);\n   if (newSize > this->arraySize) {\n      assert(Vector_isConsistent(this));\n      int oldSize = this->arraySize;\n      this->arraySize = newSize + this->growthRate;\n      this->array = (Object**)xReallocArrayZero(this->array, oldSize, this->arraySize, sizeof(Object*));\n   }\n   assert(Vector_isConsistent(this));\n}\n\nvoid Vector_insert(Vector* this, int idx, void* data_) {\n   Object* data = data_;\n   assert(idx >= 0);\n   assert(Object_isA(data, this->type));\n   assert(Vector_isConsistent(this));\n\n   if (idx > this->items) {\n      idx = this->items;\n   }\n\n   Vector_resizeIfNecessary(this, this->items + 1);\n   //assert(this->array[this->items] == NULL);\n   if (idx < this->items) {\n      memmove(&this->array[idx + 1], &this->array[idx], (this->items - idx) * sizeof(this->array[0]));\n   }\n   this->array[idx] = data;\n   this->items++;\n   assert(Vector_isConsistent(this));\n}\n\nObject* Vector_take(Vector* this, int idx) {\n   assert(idx >= 0 && idx < this->items);\n   assert(Vector_isConsistent(this));\n   Object* removed = this->array[idx];\n   assert(removed);\n   this->items--;\n   if (idx < this->items) {\n      memmove(&this->array[idx], &this->array[idx + 1], (this->items - idx) * sizeof(this->array[0]));\n   }\n   this->array[this->items] = NULL;\n   assert(Vector_isConsistent(this));\n   return removed;\n}\n\nObject* Vector_remove(Vector* this, int idx) {\n   Object* removed = Vector_take(this, idx);\n   if (this->owner) {\n      Object_delete(removed);\n      return NULL;\n   } else {\n      return removed;\n   }\n}\n\nObject* Vector_softRemove(Vector* this, int idx) {\n   assert(idx >= 0 && idx < this->items);\n\n   Object* removed = this->array[idx];\n   assert(removed);\n   if (removed) {\n      this->array[idx] = NULL;\n\n      this->isDirty = true;\n\n      if (this->owner) {\n         Object_delete(removed);\n         return NULL;\n      }\n   }\n\n   return removed;\n}\n\nvoid Vector_compact(Vector* this, int dirtyIndex) {\n   if (!this->isDirty)\n      return;\n\n   assert(0 <= dirtyIndex);\n   if (dirtyIndex >= this->items)\n      return;\n\n   assert(!this->array[dirtyIndex]);\n\n   for (int i = dirtyIndex + 1; i < this->items; i++) {\n      if (this->array[i]) {\n         this->array[dirtyIndex++] = this->array[i];\n      }\n   }\n   int dirtyCount = this->items - dirtyIndex;\n   memset(&this->array[dirtyIndex], 0, dirtyCount * sizeof(this->array[0]));\n\n   this->items = dirtyIndex;\n   this->isDirty = false;\n\n   assert(Vector_isConsistent(this));\n}\n\nvoid Vector_moveUp(Vector* this, int idx) {\n   assert(idx >= 0 && idx < this->items);\n   assert(Vector_isConsistent(this));\n\n   if (idx == 0)\n      return;\n\n   Object* temp = this->array[idx];\n   this->array[idx] = this->array[idx - 1];\n   this->array[idx - 1] = temp;\n}\n\nvoid Vector_moveDown(Vector* this, int idx) {\n   assert(idx >= 0 && idx < this->items);\n   assert(Vector_isConsistent(this));\n\n   if (idx == this->items - 1)\n      return;\n\n   Object* temp = this->array[idx];\n   this->array[idx] = this->array[idx + 1];\n   this->array[idx + 1] = temp;\n}\n\nvoid Vector_set(Vector* this, int idx, void* data_) {\n   Object* data = data_;\n   assert(idx >= 0);\n   assert(Object_isA(data, this->type));\n   assert(Vector_isConsistent(this));\n\n   Vector_resizeIfNecessary(this, idx + 1);\n   if (idx >= this->items) {\n      this->items = idx + 1;\n   } else {\n      if (this->owner) {\n         Object* removed = this->array[idx];\n         if (removed != NULL) {\n            Object_delete(removed);\n         }\n      }\n   }\n   this->array[idx] = data;\n   assert(Vector_isConsistent(this));\n}\n\n/*\n\nstatic void Vector_merge(Vector* this, Vector* v2) {\n   int i;\n   assert(Vector_isConsistent(this));\n\n   for (i = 0; i < v2->items; i++)\n      Vector_add(this, v2->array[i]);\n   v2->items = 0;\n   Vector_delete(v2);\n   assert(Vector_isConsistent(this));\n}\n\n*/\n\nvoid Vector_add(Vector* this, void* data_) {\n   Object* data = data_;\n   assert(Object_isA(data, this->type));\n   assert(Vector_isConsistent(this));\n   int i = this->items;\n   Vector_set(this, this->items, data);\n   assert(this->items == i + 1); (void)(i);\n   assert(Vector_isConsistent(this));\n}\n\nint Vector_indexOf(const Vector* this, const void* search_, Object_Compare compare) {\n   const Object* search = search_;\n   assert(Object_isA(search, this->type));\n   assert(compare);\n   assert(Vector_isConsistent(this));\n   for (int i = 0; i < this->items; i++) {\n      const Object* o = this->array[i];\n      assert(o);\n      if (compare(search, o) == 0) {\n         return i;\n      }\n   }\n   return -1;\n}\n\nvoid Vector_splice(Vector* this, Vector* from) {\n   assert(Vector_isConsistent(this));\n   assert(Vector_isConsistent(from));\n   assert(!this->owner);\n\n   int olditems = this->items;\n   Vector_resizeIfNecessary(this, this->items + from->items);\n   this->items += from->items;\n   for (int j = 0; j < from->items; j++) {\n      this->array[olditems + j] = from->array[j];\n   }\n}\n"
  },
  {
    "path": "Vector.h",
    "content": "#ifndef HEADER_Vector\n#define HEADER_Vector\n/*\nhtop - Vector.h\n(C) 2004-2011 Hisham H. Muhammad\nReleased under the GNU GPLv2+, see the COPYING file\nin the source distribution for its full text.\n*/\n\n#include \"Object.h\"\n\n#include <stdbool.h>\n\n\n#define VECTOR_DEFAULT_SIZE (10)\n\ntypedef struct Vector_ {\n   Object** array;\n   const ObjectClass* type;\n   int arraySize;\n   int growthRate;\n   int items;\n\n   /* If true, the items would be freed when they are removed from the\n      vector. */\n   bool owner;\n   /* Whether the Vector is pending a \"compact\" operation. This field\n      is currently only used for debugging. */\n   bool isDirty;\n} Vector;\n\nVector* Vector_new(const ObjectClass* type, bool owner, int size);\n\nvoid Vector_delete(Vector* this);\n\nvoid Vector_prune(Vector* this);\n\nvoid Vector_quickSortCustomCompare(Vector* this, Object_Compare compare);\nstatic inline void Vector_quickSort(Vector* this) {\n   Vector_quickSortCustomCompare(this, this->type->compare);\n}\n\nvoid Vector_insertionSort(Vector* this);\n\nvoid Vector_insert(Vector* this, int idx, void* data_);\n\nObject* Vector_take(Vector* this, int idx);\n\nObject* Vector_remove(Vector* this, int idx);\n\n/* Vector_softRemove marks the item at index idx for deletion without\n   reclaiming any space. If owned, the item is immediately freed.\n\n   Vector_compact must be called to reclaim space.*/\nObject* Vector_softRemove(Vector* this, int idx);\n\n/* Vector_compact reclaims space free'd up by Vector_softRemove, if any. */\nvoid Vector_compact(Vector* this, int dirtyIndex);\n\nvoid Vector_moveUp(Vector* this, int idx);\n\nvoid Vector_moveDown(Vector* this, int idx);\n\nvoid Vector_set(Vector* this, int idx, void* data_);\n\n#ifndef NDEBUG\n\nObject* Vector_get(const Vector* this, size_t idx);\nint Vector_size(const Vector* this);\n\n/* Vector_countEquals returns true if the number of non-NULL items\n   in the Vector is equal to expectedCount. This is only for debugging\n   and consistency checks. */\nbool Vector_countEquals(const Vector* this, unsigned int expectedCount);\n\n#else /* NDEBUG */\n\nstatic inline Object* Vector_get(const Vector* this, size_t idx) {\n   return this->array[idx];\n}\n\nstatic inline int Vector_size(const Vector* this) {\n   return this->items;\n}\n\n#endif /* NDEBUG */\n\nstatic inline const ObjectClass* Vector_type(const Vector* this) {\n   return this->type;\n}\n\nvoid Vector_add(Vector* this, void* data_);\n\nint Vector_indexOf(const Vector* this, const void* search_, Object_Compare compare);\n\nvoid Vector_splice(Vector* this, Vector* from);\n\n#endif\n"
  },
  {
    "path": "XUtils.c",
    "content": "/*\nhtop - StringUtils.c\n(C) 2004-2011 Hisham H. Muhammad\nReleased under the GNU GPLv2+, see the COPYING file\nin the source distribution for its full text.\n*/\n\n#include \"config.h\" // IWYU pragma: keep\n\n#include \"XUtils.h\"\n\n#include <assert.h>\n#include <errno.h>\n#include <fcntl.h>\n#include <limits.h>\n#include <math.h>\n#include <stdarg.h>\n#include <stdint.h>\n#include <stdlib.h>\n#include <string.h>\n#include <unistd.h>\n\n#include \"CRT.h\"\n#include \"Macros.h\"\n\n\nvoid fail(void) {\n   CRT_done();\n   abort();\n\n   _exit(1); // Should never reach here\n}\n\nvoid* xMalloc(size_t size) {\n   assert(size > 0);\n   void* data = malloc(size);\n   if (!data) {\n      fail();\n   }\n   return data;\n}\n\nvoid* xMallocArray(size_t nmemb, size_t size) {\n   assert(nmemb > 0);\n   assert(size > 0);\n   if (SIZE_MAX / nmemb < size) {\n      fail();\n   }\n   return xMalloc(nmemb * size);\n}\n\nvoid* xCalloc(size_t nmemb, size_t size) {\n   assert(nmemb > 0);\n   assert(size > 0);\n   if (SIZE_MAX / nmemb < size) {\n      fail();\n   }\n   void* data = calloc(nmemb, size);\n   if (!data) {\n      fail();\n   }\n   return data;\n}\n\nvoid* xRealloc(void* ptr, size_t size) {\n   assert(size > 0);\n   void* data = realloc(ptr, size);\n   if (!data) {\n      /* free'ing ptr here causes an indirect memory leak if pointers\n       * are held as part of an potential array referenced in ptr.\n       * In GCC 14 -fanalyzer recognizes this leak, but fails to\n       * ignore it given that this path ends in a noreturn function.\n       * Thus to avoid this confusing diagnostic we opt to leave\n       * that pointer alone instead.\n       */\n      // free(ptr);\n      fail();\n   }\n   return data;\n}\n\nvoid* xReallocArray(void* ptr, size_t nmemb, size_t size) {\n   assert(nmemb > 0);\n   assert(size > 0);\n   if (SIZE_MAX / nmemb < size) {\n      fail();\n   }\n   return xRealloc(ptr, nmemb * size);\n}\n\nvoid* xReallocArrayZero(void* ptr, size_t prevmemb, size_t newmemb, size_t size) {\n   assert((ptr == NULL) == (prevmemb == 0));\n\n   if (prevmemb == newmemb) {\n      return ptr;\n   }\n\n   void* ret = xReallocArray(ptr, newmemb, size);\n\n   if (newmemb > prevmemb) {\n      memset((unsigned char*)ret + prevmemb * size, '\\0', (newmemb - prevmemb) * size);\n   }\n\n   return ret;\n}\n\ninline bool String_contains_i(const char* s1, const char* s2, bool multi) {\n   // we have a multi-string search term, handle as special case for performance reasons\n   if (multi && strstr(s2, \"|\")) {\n      size_t nNeedles;\n      char** needles = String_split(s2, '|', &nNeedles);\n      for (size_t i = 0; i < nNeedles; i++) {\n         if (strcasestr(s1, needles[i]) != NULL) {\n            String_freeArray(needles);\n            return true;\n         }\n      }\n      String_freeArray(needles);\n      return false;\n   } else {\n      return strcasestr(s1, s2) != NULL;\n   }\n}\n\nchar* String_cat(const char* s1, const char* s2) {\n   const size_t l1 = strlen(s1);\n   const size_t l2 = strlen(s2);\n   if (SIZE_MAX - l1 <= l2) {\n      fail();\n   }\n   char* out = xMalloc(l1 + l2 + 1);\n   memcpy(out, s1, l1);\n   memcpy(out + l1, s2, l2);\n   out[l1 + l2] = '\\0';\n   return out;\n}\n\nchar* String_trim(const char* in) {\n   while (in[0] == ' ' || in[0] == '\\t' || in[0] == '\\n') {\n      in++;\n   }\n\n   size_t len = strlen(in);\n   while (len > 0 && (in[len - 1] == ' ' || in[len - 1] == '\\t' || in[len - 1] == '\\n')) {\n      len--;\n   }\n\n   return xStrndup(in, len);\n}\n\nchar** String_split(const char* s, char sep, size_t* n) {\n   const size_t rate = 10;\n   char** out = xCalloc(rate, sizeof(char*));\n   size_t ctr = 0;\n   size_t blocks = rate;\n   const char* where;\n   while ((where = strchr(s, sep)) != NULL) {\n      size_t size = (size_t)(where - s);\n      out[ctr] = xStrndup(s, size);\n      ctr++;\n      if (ctr == blocks) {\n         blocks += rate;\n         out = (char**) xRealloc(out, sizeof(char*) * blocks);\n      }\n      s += size + 1;\n   }\n   if (s[0] != '\\0') {\n      out[ctr] = xStrdup(s);\n      ctr++;\n   }\n   out = xRealloc(out, sizeof(char*) * (ctr + 1));\n   out[ctr] = NULL;\n\n   if (n)\n      *n = ctr;\n\n   return out;\n}\n\nvoid String_freeArray(char** s) {\n   if (!s) {\n      return;\n   }\n   for (size_t i = 0; s[i] != NULL; i++) {\n      free(s[i]);\n   }\n   free(s);\n}\n\nchar* String_readLine(FILE* fp) {\n   const size_t step = 1024;\n   size_t bufSize = step;\n   char* buffer = xMalloc(step + 1);\n   char* at = buffer;\n   for (;;) {\n      const char* ok = fgets(at, step + 1, fp);\n      if (!ok) {\n         free(buffer);\n         return NULL;\n      }\n      char* newLine = strrchr(at, '\\n');\n      if (newLine) {\n         *newLine = '\\0';\n         return buffer;\n      } else {\n         if (feof(fp)) {\n            return buffer;\n         }\n      }\n      bufSize += step;\n      buffer = xRealloc(buffer, bufSize + 1);\n      at = buffer + bufSize - step;\n   }\n}\n\nsize_t String_safeStrncpy(char* restrict dest, const char* restrict src, size_t size) {\n   assert(size > 0);\n\n   size_t i = 0;\n   for (; i < size - 1 && src[i]; i++)\n      dest[i] = src[i];\n\n   dest[i] = '\\0';\n\n   return i;\n}\n\n#ifndef HAVE_STRNLEN\nsize_t strnlen(const char* str, size_t maxLen) {\n   for (size_t len = 0; len < maxLen; len++) {\n      if (!str[len]) {\n         return len;\n      }\n   }\n   return maxLen;\n}\n#endif\n\nint xAsprintf(char** strp, const char* fmt, ...) {\n   *strp = NULL;\n\n   va_list vl;\n   va_start(vl, fmt);\n   int r = vasprintf(strp, fmt, vl);\n   va_end(vl);\n\n   if (r < 0 || !*strp) {\n      fail();\n   }\n\n   return r;\n}\n\nint xSnprintf(char* buf, size_t len, const char* fmt, ...) {\n   assert(len > 0);\n\n   // POSIX says snprintf() can fail if (len > INT_MAX).\n   len = MINIMUM(INT_MAX, len);\n\n   va_list vl;\n   va_start(vl, fmt);\n   int n = vsnprintf(buf, len, fmt, vl);\n   va_end(vl);\n\n   if (n < 0 || (size_t)n >= len) {\n      fail();\n   }\n\n   return n;\n}\n\nchar* xStrdup(const char* str) {\n   char* data = strdup(str);\n   if (!data) {\n      fail();\n   }\n   return data;\n}\n\nvoid free_and_xStrdup(char** ptr, const char* str) {\n   if (*ptr && String_eq(*ptr, str))\n      return;\n\n   free(*ptr);\n   *ptr = xStrdup(str);\n}\n\nchar* xStrndup(const char* str, size_t len) {\n   char* data = strndup(str, len);\n   if (!data) {\n      fail();\n   }\n   return data;\n}\n\nssize_t full_write(int fd, const void* buf, size_t count) {\n   ssize_t written = 0;\n\n   while (count > 0) {\n      ssize_t r = write(fd, buf, count);\n      if (r < 0) {\n         if (errno == EINTR)\n            continue;\n\n         return r;\n      }\n\n      if (r == 0)\n         break;\n\n      written += r;\n      buf = (const unsigned char*)buf + r;\n      count -= (size_t)r;\n   }\n\n   return written;\n}\n\n/* Compares floating point values for ordering data entries. In this function,\n   NaN is considered \"less than\" any other floating point value (regardless of\n   sign), and two NaNs are considered \"equal\" regardless of payload. */\nint compareRealNumbers(double a, double b) {\n   int result = isgreater(a, b) - isgreater(b, a);\n   if (result)\n      return result;\n   return !isNaN(a) - !isNaN(b);\n}\n\n/* Computes the sum of all positive floating point values in an array.\n   NaN values in the array are skipped. The returned sum will always be\n   nonnegative. */\ndouble sumPositiveValues(const double* array, size_t count) {\n   double sum = 0.0;\n   for (size_t i = 0; i < count; i++) {\n      if (isPositive(array[i]))\n         sum += array[i];\n   }\n   return sum;\n}\n\n/* Counts the number of digits needed to print \"n\" with a given base.\n   If \"n\" is zero, returns 1. This function expects small numbers to\n   appear often, hence it uses a O(log(n)) time algorithm. */\nsize_t countDigits(size_t n, size_t base) {\n   assert(base > 1);\n   size_t res = 1;\n   for (size_t limit = base; n >= limit; limit *= base) {\n      res++;\n      if (base && limit > SIZE_MAX / base) {\n         break;\n      }\n   }\n   return res;\n}\n\n#if !defined(HAVE_BUILTIN_CTZ)\n// map a bit value mod 37 to its position\nstatic const uint8_t mod37BitPosition[] = {\n  32, 0, 1, 26, 2, 23, 27, 0, 3, 16, 24, 30, 28, 11, 0, 13, 4,\n  7, 17, 0, 25, 22, 31, 15, 29, 10, 12, 6, 0, 21, 14, 9, 5,\n  20, 8, 19, 18\n};\n\n/* Returns the number of trailing zero bits */\nunsigned int countTrailingZeros(unsigned int x) {\n   return mod37BitPosition[(-x & x) % 37];\n}\n#endif\n"
  },
  {
    "path": "XUtils.h",
    "content": "#ifndef HEADER_XUtils\n#define HEADER_XUtils\n/*\nhtop - StringUtils.h\n(C) 2004-2011 Hisham H. Muhammad\n(C) 2020-2023 htop dev team\nReleased under the GNU GPLv2+, see the COPYING file\nin the source distribution for its full text.\n*/\n\n// IWYU pragma: no_include \"config.h\"\n#ifndef PACKAGE\n// strchrnul() needs _GNU_SOURCE defined, see PR #1337 for details\n#error \"Must have #include \\\"config.h\\\" line at the top of the file that includes these XUtils helper functions\"\n#endif\n\n#include <dirent.h>\n#include <stdbool.h>\n#include <stddef.h> // IWYU pragma: keep\n#include <stdio.h>\n#include <stdlib.h> // IWYU pragma: keep\n#include <string.h> // IWYU pragma: keep\n\n#include \"Macros.h\"\n\n\nATTR_NORETURN\nvoid fail(void);\n\nATTR_RETNONNULL ATTR_MALLOC ATTR_ALLOC_SIZE1(1)\nvoid* xMalloc(size_t size);\n\nATTR_RETNONNULL ATTR_MALLOC ATTR_ALLOC_SIZE2(1, 2)\nvoid* xMallocArray(size_t nmemb, size_t size);\n\nATTR_RETNONNULL ATTR_MALLOC ATTR_ALLOC_SIZE2(1, 2)\nvoid* xCalloc(size_t nmemb, size_t size);\n\nATTR_RETNONNULL ATTR_ALLOC_SIZE1(2)\nvoid* xRealloc(void* ptr, size_t size);\n\nATTR_RETNONNULL ATTR_ALLOC_SIZE2(2, 3)\nvoid* xReallocArray(void* ptr, size_t nmemb, size_t size);\n\nATTR_RETNONNULL ATTR_ALLOC_SIZE2(3, 4)\nvoid* xReallocArrayZero(void* ptr, size_t prevmemb, size_t newmemb, size_t size);\n\n/*\n * String_startsWith gives better performance if strlen(match) can be computed\n * at compile time (e.g. when they are immutable string literals). :)\n */\nATTR_NONNULL\nstatic inline bool String_startsWith(const char* s, const char* match) {\n   return strncmp(s, match, strlen(match)) == 0;\n}\n\nbool String_contains_i(const char* s1, const char* s2, bool multi);\n\nATTR_NONNULL\nstatic inline bool String_eq(const char* s1, const char* s2) {\n   return strcmp(s1, s2) == 0;\n}\n\nstatic inline bool String_eq_nullable(const char* s1, const char* s2) {\n   if (s1 == s2)\n      return true;\n\n   if (s1 && s2)\n      return String_eq(s1, s2);\n\n   return false;\n}\n\nATTR_NONNULL ATTR_RETNONNULL ATTR_MALLOC\nchar* String_cat(const char* s1, const char* s2);\n\nATTR_NONNULL ATTR_RETNONNULL ATTR_MALLOC\nchar* String_trim(const char* in);\n\nATTR_NONNULL_N(1) ATTR_RETNONNULL\nchar** String_split(const char* s, char sep, size_t* n);\n\nvoid String_freeArray(char** s);\n\nATTR_NONNULL ATTR_MALLOC\nchar* String_readLine(FILE* fp);\n\nATTR_NONNULL ATTR_RETNONNULL\nstatic inline const char* String_strchrnul(const char* s, int c) {\n#ifdef HAVE_STRCHRNUL\n   return strchrnul(s, c);\n#else\n   const char* result = strchr(s, c);\n   if (result)\n      return result;\n   return strchr(s, '\\0');\n#endif\n}\n\n/* Always null-terminates dest. Caller must pass a strictly positive size. */\nATTR_NONNULL ATTR_ACCESS3_W(1, 3) ATTR_ACCESS3_R(2, 3)\nsize_t String_safeStrncpy(char* restrict dest, const char* restrict src, size_t size);\n\n#ifndef HAVE_STRNLEN\nsize_t strnlen(const char* str, size_t maxLen);\n#endif\n\nATTR_FORMAT(printf, 2, 3) ATTR_NONNULL_N(1, 2)\nint xAsprintf(char** strp, const char* fmt, ...);\n\nATTR_FORMAT(printf, 3, 4) ATTR_NONNULL_N(1, 3) ATTR_ACCESS3_W(1, 2)\nint xSnprintf(char* buf, size_t len, const char* fmt, ...);\n\nATTR_NONNULL ATTR_RETNONNULL ATTR_MALLOC\nchar* xStrdup(const char* str);\n\nATTR_NONNULL\nvoid free_and_xStrdup(char** ptr, const char* str);\n\nATTR_NONNULL ATTR_RETNONNULL ATTR_MALLOC ATTR_ACCESS3_R(1, 2)\nchar* xStrndup(const char* str, size_t len);\n\nATTR_NONNULL ATTR_ACCESS3_R(2, 3)\nssize_t full_write(int fd, const void* buf, size_t count);\n\nATTR_NONNULL\nstatic inline ssize_t full_write_str(int fd, const char* str) {\n   return full_write(fd, str, strlen(str));\n}\n\n/* Compares floating point values for ordering data entries. In this function,\n   NaN is considered \"less than\" any other floating point value (regardless of\n   sign), and two NaNs are considered \"equal\" regardless of payload. */\nint compareRealNumbers(double a, double b);\n\n/* Computes the sum of all positive floating point values in an array.\n   NaN values in the array are skipped. The returned sum will always be\n   nonnegative. */\nATTR_NONNULL ATTR_ACCESS3_R(1, 2)\ndouble sumPositiveValues(const double* array, size_t count);\n\n/* Counts the number of digits needed to print \"n\" with a given base.\n   If \"n\" is zero, returns 1. This function expects small numbers to\n   appear often, hence it uses a O(log(n)) time algorithm. */\nsize_t countDigits(size_t n, size_t base);\n\n/* Returns the number of trailing zero bits */\n#if defined(HAVE_BUILTIN_CTZ)\nstatic inline unsigned int countTrailingZeros(unsigned int x) {\n   return !x ? 32 : __builtin_ctz(x);\n}\n#else\nunsigned int countTrailingZeros(unsigned int x);\n#endif\n\n/* IEC unit prefixes */\nstatic const char unitPrefixes[] = { 'K', 'M', 'G', 'T', 'P', 'E', 'Z', 'Y', 'R', 'Q' };\n\nstatic inline bool skipEndOfLine(FILE* fp) {\n   char buffer[1024];\n   while (fgets(buffer, sizeof(buffer), fp)) {\n      if (strchr(buffer, '\\n')) {\n         return true;\n      }\n   }\n   return false;\n}\n\nstatic inline int xDirfd(DIR* dirp) {\n   int r = dirfd(dirp);\n   assert(r >= 0);\n   return r;\n}\n\n#endif\n"
  },
  {
    "path": "autogen.sh",
    "content": "#!/bin/sh\nautoreconf --force --install --verbose -Wall\n"
  },
  {
    "path": "check-pcp-style.sh",
    "content": "#!/bin/bash\n\nset -eu -o pipefail\n\nPCP_DIR=\"$(dirname \"$0\")/pcp\"\n\nif [ ! -d \"${PCP_DIR}\" ]; then\n   echo Could not find PCP platform sources. >&2\n   exit 1\nfi\n\necho Scanning definitions in ${PCP_DIR} ...\n\nPCP_COLUMNS=\"${PCP_DIR}/columns\"\nPCP_METERS=\"${PCP_DIR}/meters\"\nPCP_SCREENS=\"${PCP_DIR}/screens\"\n\nif [ ! -d \"${PCP_COLUMNS}\" ]; then\n   echo Could not find PCP column definitions. >&2\n   exit 1\nfi\n\nif [ ! -d \"${PCP_METERS}\" ]; then\n   echo Could not find PCP meter definitions. >&2\n   exit 1\nfi\n\nif [ ! -d \"${PCP_SCREENS}\" ]; then\n   echo Could not find PCP screen definitions. >&2\n   exit 1\nfi\n\ncheck_file() {\n   f=\"$1\"\n   r=\"$2\"\n\n   echo \"Processing $f ...\"\n\n   awk -v required_names_str=\"$2\" '\n      BEGIN {\n         # Define the required names\n         split(required_names_str, required_names)\n         section = \"\"\n      }\n\n      # Skip comment lines\n      /^#/ {\n         next\n      }\n\n      # Detect section headers\n      /^\\[.*\\]$/ {\n         if (section != \"\") {\n            check_section()\n         }\n         section = $0\n         if (section in sections_seen) {\n            print \"Error: Duplicate section \" section\n            exit 1\n         }\n         sections_seen[section] = 1\n         delete seen\n         delete groups\n         next\n      }\n\n      # Process name = value pairs with whitespace around the equals sign\n      /^[^=]+ = [^=]+$/ {\n         split($0, pair, \" = \")\n\n         name = trim(pair[1])\n         value = trim(pair[2])\n         group = \"\"\n\n         known = 0\n         for (i in required_names) {\n            rname = required_names[i]\n            if (rname ~ /\\?$/) {\n               rname = substr(rname, 1, length(rname) - 1)\n            }\n            if (rname ~ /^\\*\\./) {\n               rlname = substr(rname, 2, length(rname) - 1)\n               if (substr(name, length(name) - length(rlname) + 1) == rlname) {\n                  group = substr(name, 1, length(name) - length(rlname) + 1)\n                  known = 1\n                  break\n               }\n            }\n            if (rname == name) {\n               known = 1\n               break\n            }\n         }\n         if (!known) {\n            print \"Error: Unknown name \" name \" in section \" section\n            exit 1\n         }\n\n         if (name in seen) {\n            print \"Error: Duplicate name \" name \" in section \" section\n            exit 1\n         }\n\n         seen[name] = 1\n         groups[group] = 1\n\n         if (name ~ /(^|\\.)width$/) {\n            if (!(value ~ /^-?[0-9]{1,3}$/)) {\n               print \"Error: Specified width \" value \" for \" name \" in section \" section \" is not an integer or out of range (-999..999).\"\n               exit 1\n            }\n         }\n\n         if (name ~ /(^|\\.)type$/) {\n            if (!(value ~ /^(bar|text|graph|led)$/)) {\n               print \"Error: Specified type \" value \" for \" name \" in section \" section \" is not recognized.\"\n               exit 1\n            }\n         }\n\n         if (name ~ /(^|\\.)color$/) {\n            if (!(value ~ /^(black|blue|green|red|cyan|magenta|yellow|(dark)?gray|white)$/)) {\n               print \"Error: Specified color \" value \" for \" name \" in section \" section \" is not recognized.\"\n               exit 1\n            }\n         }\n\n         next\n      }\n\n      # Function to trim whitespace\n      function trim(s) {\n         gsub(/^[ \\t]+|[ \\t]+$/, \"\", s)\n         return s\n      }\n\n      # Function to check if all required names are present\n      function check_section() {\n         missing = \"\"\n         for (i in required_names) {\n            rname = required_names[i]\n            if (rname ~ /\\?$/) {\n               continue\n            }\n            if (rname ~ /^\\*\\./) {\n               rname = substr(rname, 3, length(rname) - 2)\n               for (g in groups) {\n                  if (g == \"\") {\n                     continue\n                  }\n                  if (!(g rname in seen)) {\n                     missing = missing g rname \" \"\n                  }\n               }\n               continue\n            }\n            if (!(rname in seen)) {\n               missing = missing rname \" \"\n            }\n         }\n         if (missing != \"\") {\n            print \"Error: Missing \" missing \"in section \" section\n            exit 1\n         }\n      }\n\n      # End of file processing\n      END {\n         if (section != \"\") {\n            check_section()\n         }\n\n         # Ensure the file ends with a single newline\n         if (NR > 0 && length($0) < 1) {\n            print \"Error: Whitespace at EOF.\"\n            exit 1\n         }\n      }\n   ' \"$f\"\n}\n\nerror_occurred=0\n\nfor pcp_file in \"${PCP_COLUMNS}\"/*; do\n   if ! check_file \"$pcp_file\" \"heading caption? width metric description\"; then\n      echo \"Error processing file: $pcp_file\" >&2\n      error_occurred=1\n   fi\ndone\n\nfor pcp_file in \"${PCP_METERS}\"/*; do\n   if ! check_file \"$pcp_file\" \"caption description? type? *.metric *.color? *.label? *.suffix?\"; then\n      echo \"Error processing file: $pcp_file\" >&2\n      error_occurred=1\n   fi\ndone\n\nfor pcp_file in \"${PCP_SCREENS}\"/*; do\n   if ! check_file \"$pcp_file\" \"heading caption default? *.heading *.metric *.default? *.caption? *.format? *.instances? *.width?\"; then\n      echo \"Error processing file: $pcp_file\" >&2\n      error_occurred=1\n   fi\ndone\n\nif [ $error_occurred -ne 0 ]; then\n   echo \"One or more files failed to process.\" >&2\n   exit 1\nelse\n   echo \"All files processed successfully.\" >&2\nfi\n"
  },
  {
    "path": "configure.ac",
    "content": "#                                               -*- Autoconf -*-\n# Process this file with autoconf to produce a configure script.\n\n# ----------------------------------------------------------------------\n# Build version string.\n# ----------------------------------------------------------------------\n\nm4_define([htop_git_version],\n   m4_normalize(m4_esyscmd([git describe --abbrev=7 --dirty --always --tags 2> /dev/null || echo ''])))\nm4_define([htop_release_version], [3.5.0-dev])\n\n# ----------------------------------------------------------------------\n# Autoconf initialization.\n# ----------------------------------------------------------------------\n\nAC_PREREQ([2.69])\nAC_INIT([htop], [m4_join([-],m4_defn([htop_release_version]),m4_defn([htop_git_version]))], [htop@groups.io], [], [https://htop.dev/])\n\nAC_CONFIG_SRCDIR([htop.c])\nAC_CONFIG_MACRO_DIRS([m4])\nAC_CONFIG_AUX_DIR([build-aux])\nAC_CONFIG_HEADERS([config.h])\n\nAC_CANONICAL_HOST\nAM_INIT_AUTOMAKE([-Wall std-options subdir-objects])\n\n# ----------------------------------------------------------------------\n\n\n# ----------------------------------------------------------------------\n# Checks for platform.\n# ----------------------------------------------------------------------\n\ncase \"$host_os\" in\nlinux*|gnu*)\n   my_htop_platform=linux\n   AC_DEFINE([HTOP_LINUX], [], [Building for Linux.])\n   ;;\nfreebsd*|kfreebsd*)\n   my_htop_platform=freebsd\n   AC_DEFINE([HTOP_FREEBSD], [], [Building for FreeBSD.])\n   ;;\nnetbsd*)\n   my_htop_platform=netbsd\n   AC_DEFINE([HTOP_NETBSD], [], [Building for NetBSD.])\n   ;;\nopenbsd*)\n   my_htop_platform=openbsd\n   AC_DEFINE([HTOP_OPENBSD], [], [Building for OpenBSD.])\n   ;;\ndragonfly*)\n   my_htop_platform=dragonflybsd\n   AC_DEFINE([HTOP_DRAGONFLYBSD], [], [Building for DragonFlyBSD.])\n   ;;\ndarwin*)\n   my_htop_platform=darwin\n   AC_DEFINE([HTOP_DARWIN], [], [Building for Darwin.])\n   ;;\nsolaris*)\n   my_htop_platform=solaris\n   AC_DEFINE([HTOP_SOLARIS], [], [Building for Solaris.])\n   ;;\n*)\n   my_htop_platform=unsupported\n   AC_DEFINE([HTOP_UNSUPPORTED], [], [Building for an unsupported platform.])\n   ;;\nesac\n\n# Enable extensions, required by hwloc scripts\nAC_USE_SYSTEM_EXTENSIONS\n\n# The header of ncurses exposes wide-character interfaces only if\n# _XOPEN_SOURCE_EXTENDED is defined or _XOPEN_SOURCE defined to be\n# >= 500. In DragonFly BSD, FreeBSD and OpenBSD, defining\n# _XOPEN_SOURCE to any value *hides* interfaces that would be useful\n# for htop.\ndnl\ndnl Note: The \"#undef\" in AH_VERBATIM will be replaced with \"#define\"\ndnl when config.h is generated.\nAH_VERBATIM([_XOPEN_SOURCE_EXTENDED], [\n/* Enables XPG4v2 (SUSv1) interfaces if they are not enabled already with _XOPEN_SOURCE */\n#ifndef _XOPEN_SOURCE_EXTENDED\n#undef _XOPEN_SOURCE_EXTENDED\n#endif\n])\nAC_DEFINE([_XOPEN_SOURCE_EXTENDED], 1)\n\n# Activate some more of the missing global defines\nAC_SYS_LARGEFILE\n\n# ----------------------------------------------------------------------\n\n\n# ----------------------------------------------------------------------\n# Checks for compiler.\n# ----------------------------------------------------------------------\n\nAC_PROG_CC\nAM_PROG_CC_C_O\nm4_version_prereq([2.70], [], [AC_PROG_CC_C99])\nAS_IF([test \"x$ac_cv_prog_cc_c99\" = xno], [AC_MSG_ERROR([htop is written in C99. A newer compiler is required.])])\nAM_CFLAGS=\"-std=c99 -pedantic\"\n\n# ----------------------------------------------------------------------\n\n\n# ----------------------------------------------------------------------\n# Checks for static build.\n# ----------------------------------------------------------------------\n\nAC_ARG_ENABLE(\n   [static],\n   [AS_HELP_STRING(\n      [--enable-static],\n      [build a static htop binary @<:@default=no@:>@]\n   )],\n   [],\n   [enable_static=no]\n)\ncase \"$enable_static\" in\n   no)\n      ;;\n   yes)\n      AC_DEFINE([BUILD_STATIC], [1], [Define if building static binary.])\n      CFLAGS=\"$CFLAGS -static\"\n      LDFLAGS=\"$LDFLAGS -static\"\n      ;;\n   *)\n      AC_MSG_ERROR([bad value '$enable_static' for --enable-static option])\n      ;;\nesac\n\n# ----------------------------------------------------------------------\n\n# ----------------------------------------------------------------------\n# Checks for a PCP-based htop build.  (https://pcp.io)\n# ----------------------------------------------------------------------\n\nAC_ARG_ENABLE(\n   [pcp],\n   [AS_HELP_STRING(\n      [--enable-pcp],\n      [build a pcp-htop binary @<:@default=no@:>@]\n   )],\n   [],\n   [enable_pcp=no]\n)\ncase \"$enable_pcp\" in\n   no)\n      ;;\n   yes)\n      AC_CHECK_HEADERS(\n         [pcp/pmapi.h],\n         [my_htop_platform=pcp],\n         [AC_MSG_ERROR([cannot find PCP header file])]\n      )\n      AC_SEARCH_LIBS(\n         [pmNewContext],\n         [pcp],\n         [],\n         [AC_MSG_ERROR([cannot find PCP library])]\n      )\n      AC_DEFINE([HTOP_PCP], [1], [Define if building pcp-htop binary.])\n      AC_CONFIG_FILES([pcp-htop.5])\n      ;;\n   *)\n      AC_MSG_ERROR([bad value '$enable_pcp' for --enable-pcp option])\n      ;;\nesac\n\n# ----------------------------------------------------------------------\n\n\n# ----------------------------------------------------------------------\n# Checks for generic header files.\n# ----------------------------------------------------------------------\n\nAC_HEADER_DIRENT\nm4_version_prereq(\n   [2.70],\n   [AC_CHECK_INCLUDES_DEFAULT],\n   [AC_HEADER_STDC]\n)\nAC_CHECK_HEADERS(\n   [ \\\n      stdlib.h \\\n      string.h \\\n      strings.h \\\n      sys/param.h \\\n      sys/time.h \\\n      sys/utsname.h \\\n      unistd.h \\\n   ],\n   [],\n   [AC_MSG_ERROR([cannot find required generic header files])]\n)\n\nAC_HEADER_MAJOR\ndnl glibc 2.25 deprecates 'major' and 'minor' in <sys/types.h> and requires to\ndnl include <sys/sysmacros.h>. However the logic in AC_HEADER_MAJOR has not yet\ndnl been updated in Autoconf 2.69, so use a workaround:\nm4_version_prereq(\n   [2.70],\n   [],\n   [\n      if test \"x$ac_cv_header_sys_mkdev_h\" != xyes; then\n         AC_CHECK_HEADER(\n            [sys/sysmacros.h],\n            [AC_DEFINE(\n               [MAJOR_IN_SYSMACROS],\n               [1],\n               [Define to 1 if 'major', 'minor', and 'makedev' are declared in <sys/sysmacros.h>.]\n            )]\n         )\n      fi\n   ]\n)\n\n# Optional Section\n\nAC_CHECK_HEADERS([execinfo.h])\n\nif test \"$my_htop_platform\" = darwin; then\n   AC_CHECK_HEADERS([mach/mach_time.h])\n   AC_CHECK_TYPES([thread_extended_info_data_t], [], [], [[#include <mach/thread_info.h>]])\n   AC_CHECK_MEMBERS([struct vm_statistics64.compressor_page_count], [], [], [[#include <mach/mach_host.h>]])\n   AC_CHECK_MEMBERS([struct vm_statistics64.external_page_count], [], [], [[#include <mach/mach_host.h>]])\n\n   AC_CHECK_DECLS([kIOMainPortDefault], [], [], [[#include <IOKit/IOKitLib.h>]])\nfi\n\n# ----------------------------------------------------------------------\n\n\n# ----------------------------------------------------------------------\n# Checks for typedefs, structures, and compiler characteristics.\n# ----------------------------------------------------------------------\n\nAC_TYPE_MBSTATE_T\nAC_TYPE_MODE_T\nAC_TYPE_OFF_T\nAC_TYPE_PID_T\nAC_TYPE_SIZE_T\nAC_TYPE_SSIZE_T\nAC_TYPE_UID_T\nAC_TYPE_UINT8_T\nAC_TYPE_UINT16_T\nAC_TYPE_UINT32_T\nAC_TYPE_UINT64_T\n\nAC_MSG_CHECKING(for alloc_size)\nold_CFLAGS=\"$CFLAGS\"\nCFLAGS=\"$CFLAGS -Wno-error -Werror=attributes\"\nAC_COMPILE_IFELSE(\n   [AC_LANG_SOURCE([[\n      /* Attribute supported in GCC 4.3 or later */\n      __attribute__((alloc_size(1))) char* my_alloc(int size) { return 0; }\n   ]])],\n   AC_DEFINE([HAVE_ATTR_ALLOC_SIZE], 1, [The alloc_size attribute is supported.])\n   AC_MSG_RESULT(yes),\n   AC_MSG_RESULT(no)\n)\nCFLAGS=\"$old_CFLAGS\"\n\nAC_MSG_CHECKING(for access)\nold_CFLAGS=\"$CFLAGS\"\nCFLAGS=\"$CFLAGS -Wno-error -Werror=attributes\"\nAC_COMPILE_IFELSE(\n   [AC_LANG_SOURCE([[\n      /* Attribute supported in GCC 10 or later */\n      __attribute__((access(read_only, 1, 2))) extern int foo(const char* str, unsigned len);\n   ]])],\n   AC_DEFINE([HAVE_ATTR_ACCESS], 1, [The access attribute is supported.])\n   AC_MSG_RESULT(yes),\n   AC_MSG_RESULT(no)\n)\nCFLAGS=\"$old_CFLAGS\"\n\nAC_MSG_CHECKING(for nonnull)\nold_CFLAGS=\"$CFLAGS\"\nCFLAGS=\"$CFLAGS -Wno-error -Werror=attributes\"\nAC_COMPILE_IFELSE(\n   [AC_LANG_SOURCE([[\n      /* Attribute supported in GCC 3.3 or later */\n      __attribute__((nonnull)) int my_strcmp(const char* a, const char* b);\n      __attribute__((nonnull(1))) long my_strtol(const char* str, char** endptr, int base);\n   ]])],\n   AC_DEFINE([HAVE_ATTR_NONNULL], 1, [The nonnull attribute is supported.])\n   AC_MSG_RESULT(yes),\n   AC_MSG_RESULT(no)\n)\nCFLAGS=\"$old_CFLAGS\"\n\nAC_MSG_CHECKING(for returns_nonnull)\nold_CFLAGS=\"$CFLAGS\"\nCFLAGS=\"$CFLAGS -Wno-error -Werror=attributes\"\nAC_COMPILE_IFELSE(\n   [AC_LANG_SOURCE([[\n      /* Attribute supported in GCC 4.9 or later */\n      __attribute__((returns_nonnull)) void* foo(void);\n   ]])],\n   AC_DEFINE([HAVE_ATTR_RETNONNULL], 1, [The returns_nonnull attribute is supported.])\n   AC_MSG_RESULT(yes),\n   AC_MSG_RESULT(no)\n)\nCFLAGS=\"$old_CFLAGS\"\n\nAC_MSG_CHECKING(for NaN support)\ndnl Note: AC_RUN_IFELSE does not try compiling the program at all when\ndnl $cross_compiling is 'yes'.\nAC_LINK_IFELSE(\n   [AC_LANG_PROGRAM(\n      [[\n#include <math.h>\n      ]], [[\n      double x = NAN;\n      /* Both should evaluate to false -> 0 (exit success) */\n      return isgreater(x, x) || isgreaterequal(x, x);\n      ]]\n   )],\n   [\n      flag_finite_math_only=unknown\n      if test \"$cross_compiling\" = yes; then\n         AC_COMPILE_IFELSE(\n            [AC_LANG_SOURCE([[\n/* __FINITE_MATH_ONLY__ is documented in Clang. */\n#if defined(__FINITE_MATH_ONLY__) && __FINITE_MATH_ONLY__\n#error \"should not enable -ffinite-math-only\"\n#endif\n            ]])],\n            [AC_MSG_RESULT([assume yes (cross compiling)])],\n            [flag_finite_math_only=yes]\n         )\n      elif ./conftest$EXEEXT >&AS_MESSAGE_LOG_FD; then\n         flag_finite_math_only=no\n         AC_MSG_RESULT(yes)\n      else\n         flag_finite_math_only=yes\n      fi\n      if test \"$flag_finite_math_only\" = yes; then\n         AC_MSG_RESULT(no)\n         AC_MSG_WARN([runtime behavior with NaN is not compliant - some functionality might break; consider using '-fno-finite-math-only'])\n      fi\n   ],\n   [\n      AC_MSG_RESULT(no)\n      AC_MSG_ERROR([cannot find required macros: NAN, isgreater() and isgreaterequal()])\n   ]\n)\n\nAC_MSG_CHECKING(for __builtin_ctz)\nAC_COMPILE_IFELSE(\n   [AC_LANG_PROGRAM(\n      [],\n      [[__builtin_ctz(1); /* Supported in GCC 3.4 or later */]]\n   )],\n   AC_DEFINE([HAVE_BUILTIN_CTZ], 1, [Define to 1 if the compiler supports '__builtin_ctz' function.])\n   AC_MSG_RESULT(yes),\n   AC_MSG_RESULT(no)\n)\n\n# ----------------------------------------------------------------------\n\n\n# ----------------------------------------------------------------------\n# Checks for generic library functions.\n# ----------------------------------------------------------------------\n\nAC_SEARCH_LIBS([ceil], [m], [], [AC_MSG_ERROR([cannot find required function ceil()])])\n\nif test \"$my_htop_platform\" = dragonflybsd; then\n   AC_SEARCH_LIBS([kvm_open], [kvm], [], [AC_MSG_ERROR([cannot find required function kvm_open()])])\n   AC_SEARCH_LIBS([getdevs], [devstat], [], [AC_MSG_ERROR([cannot find required function getdevs()])])\nfi\n\nif test \"$my_htop_platform\" = freebsd; then\n   if test \"$enable_static\" = yes; then\n      AC_SEARCH_LIBS([elf_version], [elf], [], [AC_MSG_ERROR([cannot find required function elf_version()])])\n   fi\n   AC_SEARCH_LIBS([kvm_open], [kvm], [], [AC_MSG_ERROR([cannot find required function kvm_open()])])\n   AC_SEARCH_LIBS([devstat_checkversion], [devstat], [], [AC_MSG_ERROR([cannot find required function devstat_checkversion()])])\nfi\n\nif test \"$my_htop_platform\" = linux; then\n   if test \"$enable_static\" != yes; then\n      AC_SEARCH_LIBS([dlopen], [dl dld], [], [AC_MSG_ERROR([cannot find required function dlopen()])])\n   fi\nfi\n\nif test \"$my_htop_platform\" = netbsd; then\n   AC_SEARCH_LIBS([kvm_open], [kvm], [], [AC_MSG_ERROR([cannot find required function kvm_open()])])\n   AC_SEARCH_LIBS([prop_dictionary_get], [prop], [], [AC_MSG_ERROR([cannot find required function prop_dictionary_get()])])\nfi\n\nif test \"$my_htop_platform\" = openbsd; then\n   AC_SEARCH_LIBS([kvm_open], [kvm], [], [AC_MSG_ERROR([cannot find required function kvm_open()])])\nfi\n\nif test \"$my_htop_platform\" = solaris; then\n   AC_SEARCH_LIBS([kstat_open], [kstat], [], [AC_MSG_ERROR([cannot find required function kstat_open()])])\n   AC_SEARCH_LIBS([Pgrab_error], [proc], [], [AC_MSG_ERROR([cannot find required function Pgrab_error()])])\n   AC_SEARCH_LIBS([free], [malloc], [], [AC_MSG_ERROR([cannot find required function free()])])\nfi\n\n# Optional Section\n\nAC_SEARCH_LIBS([clock_gettime], [rt])\n\nAC_CHECK_FUNCS([ \\\n   dladdr \\\n   faccessat \\\n   fstatat \\\n   host_get_clock_service \\\n   memfd_create \\\n   openat \\\n   readlinkat \\\n   sched_getscheduler \\\n   sched_setscheduler \\\n   strnlen \\\n])\n\n# strchrnul is available in macOS since 15.4, but the user may specify an older\n# macOS version ('-mmacos-version-min') to build for. We need to ensure it is\n# actually exposed in the header.\nhtop_save_CFLAGS=$CFLAGS\nCFLAGS=\"$CFLAGS -Werror\"\n\nAC_MSG_CHECKING([for strchrnul])\nAC_LINK_IFELSE(\n   [AC_LANG_PROGRAM(\n      [[\n#include <string.h>\n      ]], [[\n      static char ch;\n      char* ptr = strchrnul(&ch, 0);\n      return ptr != &ch; /* Should be 0 (exit success) */\n      ]]\n   )],\n   AC_DEFINE([HAVE_STRCHRNUL], [1], [Define to 1 if you have the 'strchrnul' function.])\n   AC_MSG_RESULT(yes),\n   AC_MSG_RESULT(no)\n)\n\nCFLAGS=$htop_save_CFLAGS\n\nif test \"$my_htop_platform\" = darwin; then\n   AC_CHECK_FUNCS([mach_timebase_info])\n\n   AC_CHECK_FUNCS(\n      [host_statistics64],\n      [AC_CHECK_TYPES([struct vm_statistics64], [], [], [[#include <mach/vm_statistics.h>]])],\n      []\n   )\nfi\n\nif test \"$my_htop_platform\" = pcp; then\n   AC_CHECK_FUNCS([pmLookupDescs])\nfi\n\nif test \"$my_htop_platform\" = linux && test \"x$enable_static\" = xyes; then\n   AC_CHECK_LIB([systemd], [sd_bus_open_system], [], [], [-lcap])\nfi\n\n# ----------------------------------------------------------------------\n\n\n# ----------------------------------------------------------------------\n# Checks for cross-platform features and flags.\n# ----------------------------------------------------------------------\n\ndnl PKG_PROG_PKG_CONFIG initializes $PKG_CONFIG and related variables.\ndnl If the macro is not called, some pkg-config checks might be skipped\ndnl and $PKG_CONFIG might be unset.\nm4_ifdef(\n   [PKG_PROG_PKG_CONFIG],\n   [\n      PKG_PROG_PKG_CONFIG()\n      pkg_m4_included=1 # Makefile might grep this keyword. Don't remove.\n   ], [\n      m4_warn(\n         [syntax],\n         [pkg.m4 is absent or older than version 0.16; this 'configure' would have incomplete pkg-config support]\n      )\n   ]\n)\n\nAC_ARG_ENABLE(\n   [unicode],\n   [AS_HELP_STRING(\n      [--enable-unicode],\n      [enable Unicode support @<:@default=yes@:>@]\n   )],\n   [],\n   [enable_unicode=yes]\n)\n\nAC_ARG_VAR([CURSES_CFLAGS], [C compiler flags for curses; this overrides auto detected values])\nAC_ARG_VAR([CURSES_LIBS], [linker flags for curses; this overrides auto detected values])\n\ncurses_pkg_names=\"ncurses6 ncurses5 ncurses ncursest6 ncursest5 ncursest curses\"\nif test \"x$enable_unicode\" = xyes; then\n   curses_pkg_names=\"ncursesw6 ncursesw5 ncursesw ncursestw6 ncursestw5 ncursestw $curses_pkg_names\"\nfi\n\nAC_ARG_WITH(\n   [curses],\n   [AS_HELP_STRING(\n      [--with-curses=NAME],\n      [select curses package NAME to link with; e.g. ncursesw6]\n   )],\n   [],\n   [with_curses=check]\n)\ncase $with_curses in\ncheck|yes)\n   : # No-op. Use default list.\n   ;;\nno)\n   AC_MSG_ERROR([bad value '$with_curses' for --with-curses option])\n   ;;\n*)\n   if test \"x${CURSES_CFLAGS+y}${CURSES_LIBS+y}\" = xyy; then\n      AC_MSG_WARN([ignoring --with-curses value due to override])\n   fi\n   curses_pkg_names=`echo \"x$with_curses\" | sed 's/^x\\(lib\\)\\{0,1\\}//'`\n   ;;\nesac\n\n# $1: C preprocessor and compiler flags for curses library\n# $2: linker flags for curses library\nhtop_check_curses_capability () {\n   htop_curses_cflags=${CURSES_CFLAGS-\"$1\"}\n   htop_curses_libs=${CURSES_LIBS-\"$2\"}\n\n   echo \"curses cflags${CURSES_CFLAGS+ (override)}: $htop_curses_cflags\" >&AS_MESSAGE_LOG_FD\n   echo \"curses libs${CURSES_LIBS+ (override)}: $htop_curses_libs\" >&AS_MESSAGE_LOG_FD\n\n   htop_msg_linker_flags=$htop_curses_libs\n   if test \"`echo x $htop_msg_linker_flags`\" = x; then\n      htop_msg_linker_flags=\"(no linker flags)\"\n   fi\n\n   htop_curses_status=0 # 0 for success; nonzero for failure\n\n   htop_save_CFLAGS=$CFLAGS\n   htop_save_LIBS=$LIBS\n   CFLAGS=\"$AM_CFLAGS $htop_curses_cflags $CFLAGS\"\n   LIBS=\"$htop_curses_libs $LIBS\"\n\n   # At this point we have not checked the name of curses header, so\n   # use forward declaration for the linking tests below.\n\n   # htop uses keypad(), but for ncurses implementation, the symbol is\n   # in \"-ltinfo\" and not \"-lncurses\".\n   # Check \"-ltinfo\" symbols first, as libncurses might require\n   # explicit \"-ltinfo\" to link (for internal dependency).\n   AC_MSG_CHECKING([for keypad in $htop_msg_linker_flags])\n   AC_LINK_IFELSE(\n      [AC_LANG_PROGRAM(\n         [[\n/* int keypad(WINDOW* win, bool enable); */\nint keypad(void* win, int enable);\n         ]], [[\n         static char dummy;\n         keypad((void*)&dummy, 0);\n         ]]\n      )],\n      [AC_MSG_RESULT(yes)],\n      [\n         AC_MSG_RESULT(no)\n         htop_curses_status=1\n      ]\n   )\n\n   # htop calls refresh(), which might be implemented as a macro.\n   # It is more reliable to test linking with doupdate(), which\n   # refresh() would call internally.\n   if test \"$htop_curses_status\" -eq 0; then\n      AC_MSG_CHECKING([for doupdate in $htop_msg_linker_flags])\n      AC_LINK_IFELSE(\n         [AC_LANG_PROGRAM(\n            [[\nint doupdate(void);\n            ]], [[\n            doupdate();\n            ]]\n         )],\n         [\n            AC_MSG_RESULT(yes)\n            htop_curses_capability=nonwide\n         ],\n         [\n            AC_MSG_RESULT(no)\n            htop_curses_status=1\n         ]\n      )\n   fi\n\n   # htop calls mvadd_wchnstr(), which might be implemented as a macro.\n   # It is more reliable to test linking with wadd_wchnstr().\n   if test \"x$htop_curses_status$enable_unicode\" = x0yes; then\n      AC_MSG_CHECKING([for wadd_wchnstr in $htop_msg_linker_flags])\n      AC_LINK_IFELSE(\n         [AC_LANG_PROGRAM(\n            [[\n/* int wadd_wchnstr(WINDOW* win, const cchar_t* wchstr, int n); */\nint wadd_wchnstr(void* win, const void* wchstr, int n);\n            ]], [[\n            static char dummy1;\n            static char dummy2;\n            wadd_wchnstr((void*)&dummy1, (void*)&dummy2, 0);\n            ]]\n         )],\n         [\n            AC_MSG_RESULT(yes)\n            htop_curses_capability=wide\n         ],\n         [\n            AC_MSG_RESULT(no)\n            htop_curses_status=1\n         ]\n      )\n   fi\n\n   if test \"$htop_curses_status\" -eq 0; then\n      AM_CFLAGS=\"$AM_CFLAGS $htop_curses_cflags\"\n      if test \"$htop_curses_capability\" = wide; then\n         AC_DEFINE([HAVE_LIBNCURSESW], 1, [libncursesw is present])\n      else\n         AC_DEFINE([HAVE_LIBNCURSES], 1, [libcurses is present])\n      fi\n   else\n      LIBS=$htop_save_LIBS\n   fi\n   CFLAGS=$htop_save_CFLAGS\n   return \"$htop_curses_status\"\n} # htop_check_curses_capability\n\nhtop_curses_capability=none\n\nif test \"x${CURSES_CFLAGS+y}${CURSES_LIBS+y}\" = xyy; then\n   curses_pkg_names=\"\"\n   htop_check_curses_capability \"$CURSES_CFLAGS\" \"$CURSES_LIBS\"\nfi\n\n# Prioritize $PKG_CONFIG over ncurses*-config, as users might need to\n# cross-compile htop.\nif test \"x$PKG_CONFIG\" != x; then\n   pkg_config_static_flag=\"\"\n   if test \"$enable_static\" = yes; then\n      pkg_config_static_flag=--static\n   fi\n\n   for curses_name in $curses_pkg_names; do\n      echo retrieving $curses_name information through $PKG_CONFIG >&AS_MESSAGE_LOG_FD\n      $PKG_CONFIG --exists --print-errors $curses_name >&AS_MESSAGE_LOG_FD 2>&AS_MESSAGE_LOG_FD || continue\n\n      : # Resets \"$?\" to 0\n      htop_config_cflags=`$PKG_CONFIG --cflags $pkg_config_static_flag $curses_name 2>/dev/null` || continue\n      : # Resets \"$?\" to 0\n      htop_config_libs=`$PKG_CONFIG --libs $pkg_config_static_flag $curses_name 2>/dev/null` || continue\n\n      AC_MSG_RESULT([found $curses_name information through $PKG_CONFIG])\n\n      if htop_check_curses_capability \"$htop_config_cflags\" \"$htop_config_libs\"; then\n         break\n      fi\n   done\nfi\n\ncase ${htop_curses_capability}-$enable_unicode in\nnone-*|nonwide-yes)\n   for curses_name in $curses_pkg_names; do\n      echo retrieving $curses_name information through ${curses_name}-config >&AS_MESSAGE_LOG_FD\n\n      ${curses_name}-config --cflags >/dev/null 2>&AS_MESSAGE_LOG_FD || continue\n\n      : # Resets \"$?\" to 0\n      htop_config_cflags=`${curses_name}-config --cflags 2>/dev/null` || continue\n      : # Resets \"$?\" to 0\n      htop_config_libs=`${curses_name}-config --libs 2>/dev/null` || continue\n\n      AC_MSG_RESULT([found $curses_name information through ${curses_name}-config])\n\n      if htop_check_curses_capability \"$htop_config_cflags\" \"$htop_config_libs\"; then\n         break\n      fi\n   done\n   ;;\nesac\n\ncase ${htop_curses_capability}-$enable_unicode in\nnone-*|nonwide-yes)\n   if test \"x$PKG_CONFIG\" = x; then (\n      # Friendly warning to advise user to install pkg-config.\n      # The local variables get discarded when done.\n      list=\"\"\n      echo \"\" >conftest.c\n      if test \"$cross_compiling\" != yes; then\n         # \"-print-multi-directory\" supported in GCC 3.0 or later\n         name=\"\"\n         if $CC $CPPFLAGS $CFLAGS -print-multi-directory -E conftest.c >/dev/null 2>&1; then\n            name=`$CC $CPPFLAGS $CFLAGS -print-multi-directory -E conftest.c 2>/dev/null |\n               sed '/^ *#/ d; s/^\\.$//; q'`\n         fi\n         list=\"/usr/lib${name}/pkgconfig $list\"\n         case $host_os in\n         darwin*)\n            list=\"/opt/homebrew/Library/Homebrew/os/mac/pkgconfig $list\"\n            ;;\n         freebsd*)\n            list=\"/usr/libdata/pkgconfig $list\"\n            ;;\n         netbsd*)\n            list=\"/usr/pkg/lib/pkgconfig $list\"\n            ;;\n         esac\n      fi\n      if test \"x$host_alias\" != x; then\n         list=\"/usr/lib/${host_alias}/pkgconfig $list\"\n      fi\n      # \"-print-multiarch\" supported in GCC 4.6 or later\n      if $CC $CPPFLAGS $CFLAGS -print-multiarch -E conftest.c >/dev/null 2>&1; then\n         name=`$CC $CPPFLAGS $CFLAGS -print-multiarch -E conftest.c 2>/dev/null |\n            sed '/^ *#/ d; q'`\n         if test \"x$name\" != x; then\n            list=\"/usr/lib/${name}/pkgconfig $list\"\n         fi\n      fi\n      rm -f conftest.*\n\n      for d in $list; do\n         result=`find \"$d\" -name '*curses*.pc' 2>/dev/null | sed '1 q'`\n         if test \"x$result\" != x; then\n            echo detected a .pc file: \"$result\" >&AS_MESSAGE_LOG_FD\n            AC_MSG_WARN([your system supports pkg-config; installing pkg-config is recommended before configuring htop])\n            m4_ifdef(\n               [PKG_PROG_PKG_CONFIG],\n               [],\n               [AC_MSG_WARN([this configure script would also need to be regenerated])]\n            )\n            break\n         fi\n      done\n   ) fi\n\n   # OpenBSD and Solaris are known to not provide '*curses*.pc' files.\n   AC_MSG_RESULT([no curses information found through '*-config' utilities; will guess the linker flags])\n   for curses_name in $curses_pkg_names; do\n      if htop_check_curses_capability \"\" \"-l$curses_name\"; then\n         break\n      fi\n      # For ncurses implementation, an extra terminfo library might be\n      # needed. Guess the terminfo library name based on the ncurses\n      # library file name (e.g. \"-ltinfow\" for \"-lncursesw\"), before\n      # trying the \"-ltinfo\" name.\n      tinfo_name=`echo x$curses_name | sed 's/^xncurses/tinfo/p; d'`\n      case x$tinfo_name in\n      x|xtinfo)\n         ;;\n      *)\n         if htop_check_curses_capability \"\" \"-l$curses_name -l$tinfo_name\"; then\n            break\n         fi\n         ;;\n      esac\n      if htop_check_curses_capability \"\" \"-l$curses_name -ltinfo\"; then\n         break\n      fi\n   done\n   ;;\nesac\n\ncase ${htop_curses_capability}-$enable_unicode in\nnonwide-yes)\n   AC_MSG_ERROR([cannot find required ncursesw library; you may want to use --disable-unicode])\n   ;;\nnone-*)\n   AC_MSG_ERROR([cannot find required curses/ncurses library])\n   ;;\nesac\n\nhtop_save_CFLAGS=$CFLAGS\nCFLAGS=\"$AM_CFLAGS $CFLAGS\"\n\nhave_curses_header=no\nhave_term_header=no\n\nif test \"x$enable_unicode\" = xyes; then\n   AC_CHECK_HEADERS(\n      [ \\\n         ncursesw/curses.h \\\n         ncurses/ncurses.h \\\n         ncurses/curses.h \\\n         ncurses.h \\\n      ], [\n         have_curses_header=yes\n         break\n      ]\n   )\n   AC_CHECK_HEADERS(\n      [ \\\n         ncursesw/term.h \\\n         ncurses/term.h \\\n         term.h \\\n      ], [\n         have_term_header=yes\n         break\n      ]\n   )\nelse\n   AC_CHECK_HEADERS(\n      [ \\\n         curses.h \\\n         ncurses/curses.h \\\n         ncurses/ncurses.h \\\n         ncurses.h \\\n      ], [\n         have_curses_header=yes\n         break\n      ]\n   )\n   AC_CHECK_HEADERS(\n      [ \\\n         ncurses/term.h \\\n         term.h \\\n      ], [\n         have_term_header=yes\n         break\n      ]\n   )\nfi\n\nif test \"$have_curses_header\" = no; then\n   AC_MSG_ERROR([cannot find required curses header file])\nelif test \"$have_term_header\" = no; then\n   AC_MSG_ERROR([cannot find required term header file])\nfi\n\nCFLAGS=\"-I$srcdir $CFLAGS\"\n\n# Check for things that might be macros.\n# \"stdscr\" is a macro in ncursest (reentrant version of ncurses).\nAC_MSG_CHECKING([whether the curses header works])\nAC_LINK_IFELSE(\n   [AC_LANG_PROGRAM(\n      [[\n/* Checks if the \"bool\" definition in curses is ISO C compatible */\n#include <stdbool.h>\n\n#include \"ProvideCurses.h\"\n      ]], [[\n      keypad(stdscr, false);\n\n      refresh();\n\n#if defined(HAVE_LIBNCURSESW)\n      {\n         static cchar_t dummy;\n         mvadd_wchnstr(0, 0, &dummy, 0);\n      }\n#endif\n      ]]\n   )],\n   [AC_MSG_RESULT(yes)],\n   [\n      AC_MSG_RESULT(no)\n      AC_MSG_FAILURE([there are problems with the curses header])\n   ]\n)\n\nCFLAGS=$htop_save_CFLAGS\n\nif test \"$enable_static\" = yes; then\n   AC_SEARCH_LIBS([Gpm_GetEvent], [gpm])\nfi\nif test \"$my_htop_platform\" = \"solaris\"; then\n   # On OmniOS /usr/include/sys/regset.h redefines ERR to 13 - \\r, breaking the Enter key.\n   # Since ncurses macros use the ERR macro, we cannot use another name.\n   AC_DEFINE([ERR], [(-1)], [Predefine ncurses macro.])\nfi\nAC_CHECK_FUNCS([set_escdelay])\nAC_CHECK_FUNCS([getmouse])\nAC_DEFINE([NCURSES_ENABLE_STDBOOL_H], [1], [Define to enable stdbool.h in ncurses])\n\n\nAC_ARG_ENABLE(\n   [affinity],\n   [AS_HELP_STRING(\n      [--enable-affinity],\n      [enable sched_setaffinity and sched_getaffinity for affinity support, conflicts with hwloc @<:@default=check@:>@]\n   )],\n   [],\n   [enable_affinity=check]\n)\nif test \"x$enable_affinity\" = xcheck; then\n   if test \"x$enable_hwloc\" = xyes; then\n      enable_affinity=no\n   else\n      AC_MSG_CHECKING([for usable sched_setaffinity])\n      AC_RUN_IFELSE(\n         [AC_LANG_PROGRAM(\n            [[\n               #include <sched.h>\n               #include <errno.h>\n               static cpu_set_t cpuset;\n            ]], [[\n               CPU_ZERO(&cpuset);\n               sched_setaffinity(0, sizeof(cpu_set_t), &cpuset);\n               if (errno == ENOSYS) return 1;\n            ]]\n         )],\n         [\n            enable_affinity=yes\n            AC_MSG_RESULT([yes])\n         ],\n         [\n            enable_affinity=no\n            AC_MSG_RESULT([no])\n         ],\n         [AC_MSG_RESULT([assume yes (cross compiling)])]\n      )\n   fi\nfi\nif test \"x$enable_affinity\" = xyes; then\n   if test \"x$enable_hwloc\" = xyes; then\n      AC_MSG_ERROR([--enable-hwloc and --enable-affinity are mutual exclusive. Specify at most one of them.])\n   fi\n   AC_DEFINE([HAVE_AFFINITY], [1], [Define if sched_setaffinity and sched_getaffinity are to be used.])\nfi\n\n\ndnl HTOP_PKG_CHECK_MODULES(VARIABLE-PREFIX, MODULES [, ACTION-IF-FOUND [, ACTION-IF-NOT-FOUND]])\ndnl This macro is a wrapper of PKG_CHECK_MODULES, which checks if\ndnl MODULES exist, and then sets VARIABLE-PREFIX_CFLAGS and\ndnl VARIABLE-PREFIX_LIBS from pkg-config --cflags and --libs output\ndnl respectively, and then runs ACTION-IF-FOUND. If pkg.m4 is absent or\ndnl pkg-config MODULES don't exist, this instead runs\ndnl ACTION-IF-NOT-FOUND. ACTION-IF-NOT-FOUND defaults to printing an\ndnl error message and exiting.\nAC_DEFUN([HTOP_PKG_CHECK_MODULES],\n   [m4_ifdef(\n      [PKG_PROG_PKG_CONFIG],\n      [PKG_CHECK_MODULES([$1], [$2], [$3], [$4])],\n      [m4_default(\n         [$4],\n         [AC_MSG_ERROR([pkg.m4 required to check for pkg-config modules '$2'; please regenerate configure script])]\n      )]\n   )]\n)\n\n# $1: Header file name\n# $2: List of directories to search, separated by spaces\n# $3: Additional include directives\nhtop_search_header_dir () {\n   htop_header_search_status=1 # 0 for success; nonzero for failure\n\n   htop_save_CPPFLAGS=$CPPFLAGS\n\n   AC_MSG_CHECKING([the include directory of $1])\n   for htop_header_dir in \"\" $2 ; do\n      htop_msg_header_dir=\"(default directories)\"\n      if test \"x$htop_header_dir\" != x; then\n         htop_msg_header_dir=$htop_header_dir\n         htop_header_dir=\" -I$htop_header_dir\"\n      fi\n      CPPFLAGS=\"$AM_CPPFLAGS$htop_header_dir $htop_save_CPPFLAGS\"\n      AC_COMPILE_IFELSE(\n         [AC_LANG_SOURCE([[\n$3\n#include <$1>\n         ]])],\n         [\n            AM_CPPFLAGS=\"$AM_CPPFLAGS$htop_header_dir\"\n            htop_header_search_status=0\n            break\n         ],\n         [htop_msg_header_dir=\"(not found)\"]\n      )\n   done\n   AC_MSG_RESULT([$htop_msg_header_dir])\n\n   CPPFLAGS=$htop_save_CPPFLAGS\n   return \"$htop_header_search_status\"\n} # htop_search_header_dir\n\nAC_ARG_ENABLE(\n   [unwind],\n   [AS_HELP_STRING(\n      [--enable-unwind],\n      [enable unwind support for printing backtraces; requires libunwind @<:@default=check@:>@]\n   )],\n   [],\n   [enable_unwind=check]\n)\ncase \"$enable_unwind\" in\n   no)\n      ;;\n   check|yes)\n      HTOP_PKG_CHECK_MODULES(LIBUNWIND, libunwind,\n         [],\n         [\n            if test \"$enable_static\" = yes; then\n               AC_CHECK_LIB([lzma], [lzma_index_buffer_decode])\n            fi\n            : \"${LIBUNWIND_LIBS='-lunwind'}\"\n         ]\n      )\n\n      htop_save_CFLAGS=$CFLAGS\n      CFLAGS=\"$AM_CFLAGS $LIBUNWIND_CFLAGS $CFLAGS\"\n\n      if htop_search_header_dir libunwind.h \"/usr/include/libunwind\"; then\n         AC_DEFINE([HAVE_LIBUNWIND_H], 1, [Define to 1 if you have the <libunwind.h> header file.])\n      elif test \"$enable_unwind\" = yes; then\n         AC_MSG_ERROR([cannot find required header file libunwind.h])\n      else\n         enable_unwind=no\n      fi\n\n      CFLAGS=$htop_save_CFLAGS\n      ;;\n   *)\n      AC_MSG_ERROR([bad value '$enable_unwind' for --enable-unwind])\n      ;;\nesac\n\nif test \"$enable_unwind\" != no; then\n   htop_save_CPPFLAGS=$CPPFLAGS\n   htop_save_CFLAGS=$CFLAGS\n   htop_save_LIBS=$LIBS\n   CPPFLAGS=\"$AM_CPPFLAGS $CPPFLAGS\"\n   CFLAGS=\"$AM_CFLAGS $LIBUNWIND_CFLAGS $CFLAGS\"\n   LIBS=\"$LIBUNWIND_LIBS $LIBS\"\n\n   # unw_init_local() is a macro in HP's implementation of libunwind\n   # (the most popular one, Mosberger et al.)\n   AC_MSG_CHECKING([whether $LIBUNWIND_LIBS supports local unwinding])\n   AC_LINK_IFELSE(\n      [AC_LANG_PROGRAM(\n         [[\n#define UNW_LOCAL_ONLY\n#include <libunwind.h>\n         ]], [[\n         /* If libunwind is built with remote unwinding only,\n            unw_init_local() will be a dummy function that returns\n            -UNW_EINVAL when called. */\n         unw_context_t context;\n         int ret = unw_getcontext(&context);\n         if (!ret) {\n            unw_cursor_t cursor;\n            ret = unw_init_local(&cursor, &context);\n         }\n         return (ret <= 0 && ret > -126 ? -ret : 1);\n         ]]\n      )],\n      [\n         libunwind_local_support=unknown\n         if test \"$cross_compiling\" = yes; then\n            AC_COMPILE_IFELSE(\n               [AC_LANG_SOURCE([[\n#include <libunwind.h>\n#ifdef UNW_REMOTE_ONLY\n#error \"local unwinding not supported\"\n#endif\n               ]])],\n               [AC_MSG_RESULT([assume yes (cross compiling)])],\n               [libunwind_local_support=no]\n            )\n         elif ./conftest$EXEEXT >&AS_MESSAGE_LOG_FD; then\n            libunwind_local_support=yes\n            AC_MSG_RESULT([yes])\n         else\n            libunwind_local_support=no\n         fi\n         if test \"$libunwind_local_support\" = no; then\n            AC_MSG_RESULT([no])\n            if test \"$enable_unwind\" = yes; then\n               AC_MSG_WARN([this build of libunwind might not support local unwinding])\n            else\n               LIBS=$htop_save_LIBS\n               enable_unwind=no\n            fi\n         fi\n      ],\n      [\n         AC_MSG_RESULT([no])\n         if test \"$enable_unwind\" = yes; then\n            AC_MSG_FAILURE([cannot link with libunwind])\n         else\n            LIBS=$htop_save_LIBS\n            enable_unwind=no\n         fi\n      ]\n   )\n\n   CPPFLAGS=$htop_save_CPPFLAGS\n   CFLAGS=$htop_save_CFLAGS\nfi\n\nif test \"$enable_unwind\" != no; then\n   AM_CFLAGS=\"$AM_CFLAGS $LIBUNWIND_CFLAGS\"\n   enable_unwind=yes\n   AC_DEFINE([HAVE_LOCAL_UNWIND], 1, [Define to 1 if local unwinding is enabled.])\nelse\n   # Fall back to backtrace(3) and add -lexecinfo if needed\n   AC_SEARCH_LIBS([backtrace], [execinfo])\n\n   AC_MSG_CHECKING([the return type of backtrace])\n   AC_COMPILE_IFELSE(\n      [AC_LANG_SOURCE([[\n#ifndef BACKTRACE_RETURN_TYPE\n#error \"BACKTRACE_RETURN_TYPE not defined\"\n#endif\n      ]])],\n      [AC_MSG_RESULT([user defined])],\n      [\n         htop_save_CFLAGS=$CFLAGS\n         CFLAGS=\"$AM_CFLAGS $CFLAGS -Werror\"\n\n         # Use 'typeof' if supported. Otherwise guess types from larger bit width\n         # to smaller.\n         # 'size_t' is used in FreeBSD, NetBSD and OpenBSD.\n         # 'int' is used in Linux (with glibc) and macOS.\n         # See also: Gnulib documentation of 'backtrace'\n         # (https://www.gnu.org/software/gnulib/manual/html_node/backtrace.html).\n         # GCC (< 15) had a bug that warns on a non-null pointer in 'typeof'.\n         # Workaround by passing an address of 1 instead.\n         for backtrace_return_type in \\\n            \"typeof(backtrace((void**)1, 0))\" \\\n            \"__typeof__(backtrace((void**)1, 0))\" \\\n            \"__typeof(backtrace((void**)1, 0))\" \\\n            size_t \\\n            int \\\n         ; do\n            AC_LINK_IFELSE(\n               [AC_LANG_PROGRAM(\n                  [[\n#include <execinfo.h>\n#include <stddef.h> /* For size_t */\n\n#define BACKTRACE_RETURN_TYPE $backtrace_return_type\n/* Assume the second argument of backtrace_symbols() uses the same type\n   as the return type of backtrace(). */\nchar** (*fn)(void* const*, BACKTRACE_RETURN_TYPE) = backtrace_symbols;\n                  ]], [[\n               static void* addr;\n               BACKTRACE_RETURN_TYPE nptrs = backtrace(&addr, 1);\n               (void)nptrs;\n                  ]]\n               )],\n               [\n                  AC_DEFINE_UNQUOTED([BACKTRACE_RETURN_TYPE], [$backtrace_return_type], [Defined to the return type of the 'backtrace' function.])\n                  break\n               ],\n               [backtrace_return_type=\"\"]\n            )\n         done\n\n         backtrace_return_type_msg=`echo \"x$backtrace_return_type\" | sed '\n            s/^x *//\n            s/^$/not available/\n            s/^\\(_*typeof_*\\) *( *backtrace( *( *void *\\* *\\* *) *1 *, *0 *) *) *$/automatic (using \\1)/'`\n         AC_MSG_RESULT([$backtrace_return_type_msg])\n\n         CFLAGS=$htop_save_CFLAGS\n      ]\n   )\nfi\n\n\nAC_ARG_ENABLE(\n   [hwloc],\n   [AS_HELP_STRING(\n      [--enable-hwloc],\n      [enable hwloc support for CPU affinity; disables affinity support; requires libhwloc @<:@default=no@:>@]\n   )],\n   [],\n   [enable_hwloc=no]\n)\ncase \"$enable_hwloc\" in\n   no)\n      ;;\n   yes)\n      HTOP_PKG_CHECK_MODULES(HWLOC, hwloc,\n         [\n            AM_CFLAGS=\"$AM_CFLAGS $HWLOC_CFLAGS\"\n            LIBS=\"$LIBS $HWLOC_LIBS\"\n            AC_DEFINE([HAVE_LIBHWLOC], [1], [Define to 1 if you have the 'hwloc' library (-lhwloc).])\n         ], [\n            AC_CHECK_LIB([hwloc], [hwloc_get_proc_cpubind], [], [AC_MSG_ERROR([cannot find required library libhwloc])])\n            AC_CHECK_HEADERS([hwloc.h], [], [AC_MSG_ERROR([cannot find require header file hwloc.h])])\n         ]\n      )\n      ;;\n   *)\n      AC_MSG_ERROR([bad value '$enable_hwloc' for --enable-hwloc])\n      ;;\nesac\n\n\nAC_ARG_WITH(\n   [config],\n   [AS_HELP_STRING(\n      [--with-config=DIR],\n      [configuration path @<:@default=/.config@:>@]\n   )],\n   [],\n   [with_config=\"/.config\"]\n)\ndnl Performance Co-Pilot configuration location to prevent overwrite\nif test \"$my_htop_platform\" = pcp -a \"$with_config\" = /.config; then\n    with_config=\"/.pcp\"\nelif test -z \"$with_config\"; then\n   AC_MSG_ERROR([bad empty value for --with-config option])\nfi\nAC_DEFINE_UNQUOTED([CONFIGDIR], [\"$with_config\"], [Configuration path.])\n\n# ----------------------------------------------------------------------\n\n\n# ----------------------------------------------------------------------\n# Checks for Linux features and flags.\n# ----------------------------------------------------------------------\n\nAC_ARG_WITH(\n   [proc],\n   [AS_HELP_STRING(\n      [--with-proc=DIR],\n      [location of a Linux-compatible proc filesystem @<:@default=/proc@:>@]\n   )],\n   [],\n   [with_proc=/proc]\n)\nif test -z \"$with_proc\"; then\n   AC_MSG_ERROR([bad empty value for --with-proc option])\nfi\nAC_DEFINE_UNQUOTED([PROCDIR], [\"$with_proc\"], [Path of proc filesystem.])\n\n\nAC_ARG_ENABLE(\n   [openvz],\n   [AS_HELP_STRING(\n      [--enable-openvz],\n      [enable OpenVZ support @<:@default=no@:>@]\n   )],\n   [],\n   [enable_openvz=no]\n)\nif test \"x$enable_openvz\" = xyes; then\n   AC_DEFINE([HAVE_OPENVZ], [1], [Define if openvz support enabled.])\nfi\n\n\nAC_ARG_ENABLE(\n   [vserver],\n   [AS_HELP_STRING(\n      [--enable-vserver],\n      [enable VServer support @<:@default=no@:>@]\n   )],\n   [],\n   [enable_vserver=no]\n)\nif test \"x$enable_vserver\" = xyes; then\n   AC_DEFINE([HAVE_VSERVER], [1], [Define if VServer support enabled.])\nfi\n\n\nAC_ARG_ENABLE(\n   [ancient_vserver],\n   [AS_HELP_STRING(\n      [--enable-ancient-vserver],\n      [enable ancient VServer support (implies --enable-vserver) @<:@default=no@:>@]\n   )],\n   [],\n   [enable_ancient_vserver=no]\n)\nif test \"x$enable_ancient_vserver\" = xyes; then\n   if test \"x$enable_vserver\" != xyes; then\n      enable_vserver=implied\n   fi\n   AC_DEFINE([HAVE_VSERVER], [1], [Define if VServer support enabled.])\n   AC_DEFINE([HAVE_ANCIENT_VSERVER], [1], [Define if ancient vserver support enabled.])\nfi\n\n\nAC_ARG_ENABLE(\n   [capabilities],\n   [AS_HELP_STRING(\n      [--enable-capabilities],\n      [enable Linux capabilities support; requires libcap @<:@default=check@:>@]\n   )],\n   [],\n   [enable_capabilities=check]\n)\ncase \"$enable_capabilities\" in\n   no)\n      ;;\n   check)\n      enable_capabilities=yes\n      AC_CHECK_LIB([cap], [cap_init], [], [enable_capabilities=no])\n      AC_CHECK_HEADERS([sys/capability.h], [], [enable_capabilities=no])\n      ;;\n   yes)\n      AC_CHECK_LIB([cap], [cap_init], [], [AC_MSG_ERROR([cannot find required library libcap])])\n      AC_CHECK_HEADERS([sys/capability.h], [], [AC_MSG_ERROR([cannot find required header file sys/capability.h])])\n      ;;\n   *)\n      AC_MSG_ERROR([bad value '$enable_capabilities' for --enable-capabilities])\n      ;;\nesac\n\n\n# $1: libnl-3 search path\nhtop_try_link_libnl3 () {\n   htop_save_LDFLAGS=$LDFLAGS\n   htop_save_LIBS=$LIBS\n\n   LIBS=\"-lnl-3 $LIBS\"\n   if test \"x$1\" != x; then\n      # New library path searched after what user has specified\n      LDFLAGS=\"$LDFLAGS -L$1\"\n   fi\n   AC_LINK_IFELSE(\n      [AC_LANG_PROGRAM(\n         [[\n/* struct nl_sock* nl_socket_alloc(void); */\nvoid* nl_socket_alloc(void);\n         ]], [[\n         void* sock = nl_socket_alloc();\n         ]]\n      )],\n      [htop_libnl3_link_succeed=yes],\n      [htop_libnl3_link_succeed=no]\n   )\n\n   LDFLAGS=$htop_save_LDFLAGS\n   LIBS=$htop_save_LIBS\n} # htop_try_link_libnl3\n\nAC_ARG_ENABLE(\n   [delayacct],\n   [AS_HELP_STRING(\n      [--enable-delayacct],\n      [enable Linux delay accounting support; requires libnl-3 and libnl-genl-3 @<:@default=check@:>@]\n   )],\n   [],\n   [enable_delayacct=check]\n)\ncase \"$enable_delayacct\" in\n   no|yes)\n      ;;\n   check)\n      if test \"$my_htop_platform\" != linux; then\n         enable_delayacct=no\n      elif test \"$enable_static\" = yes; then\n         enable_delayacct=no\n      fi\n      ;;\n   *)\n      AC_MSG_ERROR([bad value '$enable_delayacct' for --enable-delayacct])\n      ;;\nesac\n\ncase \"$enable_delayacct\" in\n   check|yes)\n      if test \"x${LIBNL3_CFLAGS+y}\" = x; then\n         HTOP_PKG_CHECK_MODULES(LIBNL3, libnl-3.0, [], [LIBNL3_CFLAGS=\"-I/usr/include/libnl3\"])\n      fi\n\n      htop_save_CFLAGS=$CFLAGS\n      # New include path searched after what user has specified\n      CFLAGS=\"$CFLAGS $LIBNL3_CFLAGS\"\n      AC_CHECK_HEADERS(\n         [netlink/attr.h netlink/handlers.h netlink/msg.h],\n         [],\n         [\n            if test \"$enable_delayacct\" = yes; then\n               AC_MSG_ERROR([cannot find required header files netlink/attr.h, netlink/handlers.h, netlink/msg.h])\n            fi\n            enable_delayacct=no\n         ]\n      )\n      CFLAGS=$htop_save_CFLAGS\n\n      if test \"$enable_delayacct\" != no; then\n         AC_MSG_CHECKING([the search path of libnl-3])\n\n         htop_libnl3_link_succeed=no\n         htop_try_link_libnl3 \"$LIBNL3_LIBDIR\"\n         if test \"$htop_libnl3_link_succeed${LIBNL3_LIBDIR+y}\" = no && test \"x$PKG_CONFIG\" != x; then\n            LIBNL3_LIBDIR=`$PKG_CONFIG --variable=libdir libnl-3.0 2>/dev/null`\n            if test \"x$LIBNL3_LIBDIR\" != x; then\n               htop_try_link_libnl3 \"$LIBNL3_LIBDIR\"\n            fi\n         fi\n\n         if test \"x$LIBNL3_LIBDIR\" = x; then\n            AC_MSG_RESULT([(default)])\n         else\n            # The path must end with a slash\n            LIBNL3_LIBDIR=`echo \"x$LIBNL3_LIBDIR\" | sed 's/^x//; s|/*$|/|'`\n            AC_MSG_RESULT([$LIBNL3_LIBDIR])\n         fi\n         AC_DEFINE_UNQUOTED([LIBNL3_LIBDIR], [\"$LIBNL3_LIBDIR\"], [libnl-3 search path; use the default search paths if empty])\n         if test \"$htop_libnl3_link_succeed\" = no; then\n            AC_MSG_WARN([libnl-3 binary currently not present; will be needed in htop runtime for delay accounting support])\n         fi\n\n         enable_delayacct=yes\n      fi\n      ;;\nesac\nif test \"$enable_delayacct\" = yes; then\n   AC_DEFINE([HAVE_DELAYACCT], [1], [Define if delay accounting support should be enabled.])\n   AM_CFLAGS=\"$AM_CFLAGS $LIBNL3_CFLAGS\"\nfi\nAM_CONDITIONAL([HAVE_DELAYACCT], [test \"$enable_delayacct\" = yes])\n\n\nAC_ARG_ENABLE(\n   [sensors],\n   [AS_HELP_STRING(\n      [--enable-sensors],\n      [enable libsensors support for reading temperature data; requires only libsensors headers at compile time, at runtime libsensors is loaded via dlopen @<:@default=check@:>@]\n   )],\n   [],\n   [enable_sensors=check]\n)\ncase \"$enable_sensors\" in\n   no)\n      ;;\n   check)\n      enable_sensors=yes\n      if test \"$enable_static\" = yes; then\n         AC_CHECK_LIB([sensors], [sensors_init], [], [enable_sensors=no])\n      fi\n      AC_CHECK_HEADERS([sensors/sensors.h], [], [enable_sensors=no])\n      ;;\n   yes)\n      if test \"$enable_static\" = yes; then\n         AC_CHECK_LIB([sensors], [sensors_init], [], [AC_MSG_ERROR([cannot find required library libsensors])])\n      fi\n      AC_CHECK_HEADERS([sensors/sensors.h], [], [AC_MSG_ERROR([cannot find required header file sensors/sensors.h])])\n      ;;\n   *)\n      AC_MSG_ERROR([bad value '$enable_sensors' for --enable-sensors])\n      ;;\nesac\nif test \"$enable_sensors\" = yes || test \"$my_htop_platform\" = freebsd; then\n   AC_DEFINE([BUILD_WITH_CPU_TEMP], [1], [Define if CPU temperature option should be enabled.])\nfi\n\n# ----------------------------------------------------------------------\n\n\n# ----------------------------------------------------------------------\n# Checks for compiler warnings.\n# ----------------------------------------------------------------------\n\nAM_CFLAGS=\"$AM_CFLAGS\\\n -Wall\\\n -Wcast-align\\\n -Wcast-qual\\\n -Wextra\\\n -Wfloat-equal\\\n -Wformat=2\\\n -Winit-self\\\n -Wmissing-format-attribute\\\n -Wmissing-noreturn\\\n -Wmissing-prototypes\\\n -Wpointer-arith\\\n -Wshadow\\\n -Wstrict-prototypes\\\n -Wundef\\\n -Wunused\\\n -Wwrite-strings\"\n\nm4_ifdef(\n   [AX_CHECK_COMPILE_FLAG],\n   [\n      dnl During the check of '-Wextra-semi-stmt' we must treat\n      dnl 'extra-semi-stmt' as a non-error because the AC_LANG_PROGRAM\n      dnl template will generate a warning of this (by design).\n      AX_CHECK_COMPILE_FLAG(\n         [-Wextra-semi-stmt],\n         [AM_CFLAGS=\"$AM_CFLAGS -Wextra-semi-stmt\"],\n         [],\n         [-Wno-error=extra-semi-stmt]\n      )\n\n      AX_CHECK_COMPILE_FLAG(\n         [-Wimplicit-int-conversion],\n         [AM_CFLAGS=\"$AM_CFLAGS -Wimplicit-int-conversion\"]\n      )\n      AX_CHECK_COMPILE_FLAG(\n         [-Wnull-dereference],\n         [AM_CFLAGS=\"$AM_CFLAGS -Wnull-dereference\"]\n      )\n   ], [\n      m4_warn(\n         [syntax],\n         [module m4/ax_check_compile_flag.m4 is missing]\n      )\n   ]\n)\n\nAC_ARG_ENABLE(\n   [werror],\n   [AS_HELP_STRING(\n      [--enable-werror],\n      [Treat warnings as errors @<:@default=no@:>@]\n   )],\n   [],\n   [enable_werror=no]\n)\nif test \"x$enable_werror\" = xyes; then\n   AM_CFLAGS=\"$AM_CFLAGS -Werror\"\nfi\n\nAC_ARG_ENABLE(\n   [debug],\n   [AS_HELP_STRING(\n      [--enable-debug],\n      [Enable compiling with maximum debug info, asserts and internal sanity checks @<:@default=no@:>@]\n   )],\n   [],\n   [enable_debug=no]\n)\nif test \"x$enable_debug\" != xyes; then\n   AM_CPPFLAGS=\"$AM_CPPFLAGS -DNDEBUG\"\n\n   AC_COMPILE_IFELSE(\n      [AC_LANG_SOURCE([[\n#ifdef __SUPPORT_SNAN__\n#error \"signaling NaN support not recommended\"\n#endif\n      ]])],\n      [:],\n      [\n         warning_msg=\"signaling NaN support is enabled; not recommended for htop\"\n         case \"$CC\" in\n         *gcc*)\n            warning_msg=\"$warning_msg (use '-fno-signaling-nans' compiler flag to disable)\"\n            ;;\n         esac\n         AC_MSG_WARN([$warning_msg])\n      ]\n   )\nelse\n   AM_CPPFLAGS=\"$AM_CPPFLAGS -ggdb3\"\nfi\n\n\nAC_SUBST([AM_CFLAGS])\nAC_SUBST([AM_CPPFLAGS])\n\n# ----------------------------------------------------------------------\n\n\n# ----------------------------------------------------------------------\n# We're done, let's go!\n# ----------------------------------------------------------------------\n\nAC_DEFINE_UNQUOTED([COPYRIGHT], [\"(C) 2004-2019 Hisham Muhammad. (C) 2020-2026 htop dev team.\"], [Copyright message.])\n\nAM_CONDITIONAL([HTOP_LINUX], [test \"$my_htop_platform\" = linux])\nAM_CONDITIONAL([HTOP_FREEBSD], [test \"$my_htop_platform\" = freebsd])\nAM_CONDITIONAL([HTOP_DRAGONFLYBSD], [test \"$my_htop_platform\" = dragonflybsd])\nAM_CONDITIONAL([HTOP_NETBSD], [test \"$my_htop_platform\" = netbsd])\nAM_CONDITIONAL([HTOP_OPENBSD], [test \"$my_htop_platform\" = openbsd])\nAM_CONDITIONAL([HTOP_DARWIN], [test \"$my_htop_platform\" = darwin])\nAM_CONDITIONAL([HTOP_SOLARIS], [test \"$my_htop_platform\" = solaris])\nAM_CONDITIONAL([HTOP_PCP], [test \"$my_htop_platform\" = pcp])\nAM_CONDITIONAL([HTOP_UNSUPPORTED], [test \"$my_htop_platform\" = unsupported])\n\nAC_SUBST(my_htop_platform)\nAC_CONFIG_FILES([Makefile htop.1])\nAC_OUTPUT\n\nif test \"$my_htop_platform\" = unsupported; then\n   echo \"\"\n   echo \"****************************************************************\"\n   echo \"WARNING! This platform is not currently supported by htop.\"\n   echo \"\"\n   echo \"The code will build, but it will produce a dummy version of htop\"\n   echo \"which shows no processes, using the files from the unsupported/\"\n   echo \"directory. This is meant to be a skeleton, to be used as a\"\n   echo \"starting point if you are porting htop to a new platform.\"\n   echo \"****************************************************************\"\n   echo \"\"\nfi\n\nAC_MSG_RESULT([\n  ${PACKAGE_NAME} ${VERSION}\n\n  platform:                  $my_htop_platform ($host_os)\n  (Linux) proc directory:    $with_proc\n  (Linux) openvz:            $enable_openvz\n  (Linux) vserver:           $enable_vserver\n  (Linux) ancient vserver:   $enable_ancient_vserver\n  (Linux) delay accounting:  $enable_delayacct\n  (Linux) sensors:           $enable_sensors\n  (Linux) capabilities:      $enable_capabilities\n  unicode:                   $enable_unicode\n  affinity:                  $enable_affinity\n  unwind:                    $enable_unwind\n  hwloc:                     $enable_hwloc\n  debug:                     $enable_debug\n  static:                    $enable_static\n])\n"
  },
  {
    "path": "darwin/DarwinMachine.c",
    "content": "/*\nhtop - DarwinMachine.c\n(C) 2014 Hisham H. Muhammad\n(C) 2023 htop dev team\nReleased under the GNU GPLv2+, see the COPYING file\nin the source distribution for its full text.\n*/\n\n#include \"darwin/DarwinMachine.h\"\n\n#include <errno.h>\n#include <stdbool.h>\n#include <stdio.h>\n#include <stdlib.h>\n#include <string.h>\n#include <unistd.h>\n#include <sys/mman.h>\n#include <sys/sysctl.h>\n#include <Availability.h>\n\n#include \"CRT.h\"\n#include \"Machine.h\"\n#include \"darwin/Platform.h\"\n#include \"darwin/PlatformHelpers.h\"\n#include \"generic/openzfs_sysctl.h\"\n#include \"zfs/ZfsArcStats.h\"\n\n// Compatibility for older SDKs.\n#if !defined(HAVE_DECL_KIOMAINPORTDEFAULT) || !HAVE_DECL_KIOMAINPORTDEFAULT\n#define kIOMainPortDefault kIOMasterPortDefault\n#endif\n\nstatic void DarwinMachine_getHostInfo(host_basic_info_data_t* p) {\n   mach_msg_type_number_t info_size = HOST_BASIC_INFO_COUNT;\n\n   if (0 != host_info(mach_host_self(), HOST_BASIC_INFO, (host_info_t)p, &info_size)) {\n      CRT_fatalError(\"Unable to retrieve host info\");\n   }\n}\n\nstatic void DarwinMachine_freeCPULoadInfo(processor_cpu_load_info_t* p) {\n   if (!p)\n      return;\n\n   if (!*p)\n      return;\n\n   if (0 != munmap(*p, vm_page_size)) {\n      CRT_fatalError(\"Unable to free old CPU load information\");\n   }\n\n   *p = NULL;\n}\n\nstatic unsigned int DarwinMachine_allocateCPULoadInfo(processor_cpu_load_info_t* p) {\n   mach_msg_type_number_t info_size = sizeof(processor_cpu_load_info_t);\n   natural_t cpu_count;\n\n   // TODO Improving the accuracy of the load counts would help a lot.\n   if (0 != host_processor_info(mach_host_self(), PROCESSOR_CPU_LOAD_INFO, &cpu_count, (processor_info_array_t*)p, &info_size)) {\n      CRT_fatalError(\"Unable to retrieve CPU info\");\n   }\n\n   return cpu_count;\n}\n\nstatic void DarwinMachine_getVMStats(DarwinMachine* this) {\n#ifdef HAVE_STRUCT_VM_STATISTICS64\n   mach_msg_type_number_t info_size = HOST_VM_INFO64_COUNT;\n\n   if (host_statistics64(mach_host_self(), HOST_VM_INFO64, (host_info_t)&this->vm_stats, &info_size) != 0) {\n      CRT_fatalError(\"Unable to retrieve VM statistics64\");\n   }\n#else\n   mach_msg_type_number_t info_size = HOST_VM_INFO_COUNT;\n\n   if (host_statistics(mach_host_self(), HOST_VM_INFO, (host_info_t)&this->vm_stats, &info_size) != 0) {\n      CRT_fatalError(\"Unable to retrieve VM statistics\");\n   }\n#endif\n}\n\nvoid Machine_scan(Machine* super) {\n   DarwinMachine* host = (DarwinMachine*) super;\n\n   /* Update the global data (CPU times and VM stats) */\n   DarwinMachine_freeCPULoadInfo(&host->prev_load);\n   host->prev_load = host->curr_load;\n   DarwinMachine_allocateCPULoadInfo(&host->curr_load);\n   DarwinMachine_getVMStats(host);\n   openzfs_sysctl_updateArcStats(&host->zfs);\n}\n\nMachine* Machine_new(UsersTable* usersTable, uid_t userId) {\n   DarwinMachine* this = xCalloc(1, sizeof(DarwinMachine));\n   Machine* super = &this->super;\n\n   Machine_init(super, usersTable, userId);\n\n   /* Initialize the CPU information */\n   super->activeCPUs = DarwinMachine_allocateCPULoadInfo(&this->prev_load);\n   super->existingCPUs = super->activeCPUs;\n   DarwinMachine_getHostInfo(&this->host_info);\n   DarwinMachine_allocateCPULoadInfo(&this->curr_load);\n\n   /* Initialize the VM statistics */\n   DarwinMachine_getVMStats(this);\n\n   /* Initialize the ZFS kstats, if zfs.kext loaded */\n   openzfs_sysctl_init(&this->zfs);\n   openzfs_sysctl_updateArcStats(&this->zfs);\n\n   this->GPUService = IOServiceGetMatchingService(kIOMainPortDefault, IOServiceMatching(\"IOGPU\"));\n   if (!this->GPUService) {\n      CRT_debug(\"Cannot initialize IOGPU service\");\n   }\n\n   return super;\n}\n\nvoid Machine_delete(Machine* super) {\n   DarwinMachine* this = (DarwinMachine*) super;\n\n   IOObjectRelease(this->GPUService);\n\n   DarwinMachine_freeCPULoadInfo(&this->prev_load);\n\n   Machine_done(super);\n   free(this);\n}\n\nbool Machine_isCPUonline(const Machine* host, unsigned int id) {\n   assert(id < host->existingCPUs);\n\n   // TODO: support offline CPUs and hot swapping\n   (void) host; (void) id;\n\n   return true;\n}\n"
  },
  {
    "path": "darwin/DarwinMachine.h",
    "content": "#ifndef HEADER_DarwinMachine\n#define HEADER_DarwinMachine\n/*\nhtop - DarwinMachine.h\n(C) 2014 Hisham H. Muhammad\nReleased under the GNU GPLv2+, see the COPYING file\nin the source distribution for its full text.\n*/\n\n#include <IOKit/IOKitLib.h>\n#include <mach/mach_host.h>\n#include <sys/sysctl.h>\n\n#include \"Machine.h\"\n#include \"zfs/ZfsArcStats.h\"\n\n\ntypedef struct DarwinMachine_ {\n   Machine super;\n\n   host_basic_info_data_t host_info;\n   vm_statistics64_data_t vm_stats;\n   processor_cpu_load_info_t prev_load;\n   processor_cpu_load_info_t curr_load;\n\n   io_service_t GPUService;\n\n   ZfsArcStats zfs;\n} DarwinMachine;\n\n#endif\n"
  },
  {
    "path": "darwin/DarwinProcess.c",
    "content": "/*\nhtop - DarwinProcess.c\n(C) 2015 Hisham H. Muhammad\nReleased under the GNU GPLv2+, see the COPYING file\nin the source distribution for its full text.\n*/\n\n#include \"config.h\" // IWYU pragma: keep\n\n#include \"darwin/DarwinProcess.h\"\n\n#include <libproc.h>\n#include <stdio.h>\n#include <stdlib.h>\n#include <string.h>\n#include <mach/mach.h>\n#include <sys/dirent.h>\n\n#include \"CRT.h\"\n#include \"Process.h\"\n#include \"darwin/DarwinMachine.h\"\n#include \"darwin/Platform.h\"\n\n\nconst ProcessFieldData Process_fields[LAST_PROCESSFIELD] = {\n   [0] = { .name = \"\", .title = NULL, .description = NULL, .flags = 0, },\n   [PID] = { .name = \"PID\", .title = \"PID\", .description = \"Process/thread ID\", .flags = 0, .pidColumn = true, },\n   [COMM] = { .name = \"Command\", .title = \"Command \", .description = \"Command line (insert as last column only)\", .flags = 0, },\n   [STATE] = { .name = \"STATE\", .title = \"S \", .description = \"Process state (S sleeping, R running, D disk, Z zombie, T traced, W paging)\", .flags = 0, },\n   [PPID] = { .name = \"PPID\", .title = \"PPID\", .description = \"Parent process ID\", .flags = 0, .pidColumn = true, },\n   [PGRP] = { .name = \"PGRP\", .title = \"PGRP\", .description = \"Process group ID\", .flags = 0, .pidColumn = true, },\n   [SESSION] = { .name = \"SESSION\", .title = \"SID\", .description = \"Process's session ID\", .flags = 0, .pidColumn = true, },\n   [TTY] = { .name = \"TTY\", .title = \"TTY      \", .description = \"Controlling terminal\", .flags = PROCESS_FLAG_TTY, },\n   [TPGID] = { .name = \"TPGID\", .title = \"TPGID\", .description = \"Process ID of the fg process group of the controlling terminal\", .flags = 0, .pidColumn = true, },\n   [MINFLT] = { .name = \"MINFLT\", .title = \"     MINFLT \", .description = \"Number of minor faults which have not required loading a memory page from disk\", .flags = 0, .defaultSortDesc = true, },\n   [MAJFLT] = { .name = \"MAJFLT\", .title = \"     MAJFLT \", .description = \"Number of major faults which have required loading a memory page from disk\", .flags = 0, .defaultSortDesc = true, },\n   [PRIORITY] = { .name = \"PRIORITY\", .title = \"PRI \", .description = \"Kernel's internal priority for the process\", .flags = 0, },\n   [NICE] = { .name = \"NICE\", .title = \" NI \", .description = \"Nice value (the higher the value, the more it lets other processes take priority)\", .flags = 0, },\n   [STARTTIME] = { .name = \"STARTTIME\", .title = \"START \", .description = \"Time the process was started\", .flags = 0, },\n   [ELAPSED] = { .name = \"ELAPSED\", .title = \"ELAPSED  \", .description = \"Time since the process was started\", .flags = 0, },\n   [PROCESSOR] = { .name = \"PROCESSOR\", .title = \"CPU \", .description = \"Id of the CPU the process last executed on\", .flags = 0, },\n   [M_VIRT] = { .name = \"M_VIRT\", .title = \" VIRT \", .description = \"Total program size in virtual memory\", .flags = 0, .defaultSortDesc = true, },\n   [M_RESIDENT] = { .name = \"M_RESIDENT\", .title = \"  RES \", .description = \"Resident set size, size of the text and data sections, plus stack usage\", .flags = 0, .defaultSortDesc = true, },\n   [ST_UID] = { .name = \"ST_UID\", .title = \"UID\", .description = \"User ID of the process owner\", .flags = 0, },\n   [PERCENT_CPU] = { .name = \"PERCENT_CPU\", .title = \" CPU%\", .description = \"Percentage of the CPU time the process used in the last sampling\", .flags = 0, .defaultSortDesc = true, .autoWidth = true, .autoTitleRightAlign = true, },\n   [PERCENT_NORM_CPU] = { .name = \"PERCENT_NORM_CPU\", .title = \"NCPU%\", .description = \"Normalized percentage of the CPU time the process used in the last sampling (normalized by cpu count)\", .flags = 0, .defaultSortDesc = true, .autoWidth = true, },\n   [PERCENT_MEM] = { .name = \"PERCENT_MEM\", .title = \"MEM% \", .description = \"Percentage of the memory the process is using, based on resident memory size\", .flags = 0, .defaultSortDesc = true, },\n   [USER] = { .name = \"USER\", .title = \"USER       \", .description = \"Username of the process owner (or user ID if name cannot be determined)\", .flags = 0, },\n   [TIME] = { .name = \"TIME\", .title = \"  TIME+  \", .description = \"Total time the process has spent in user and system time\", .flags = 0, .defaultSortDesc = true, },\n   [NLWP] = { .name = \"NLWP\", .title = \"NLWP \", .description = \"Number of threads in the process\", .flags = 0, },\n   [TGID] = { .name = \"TGID\", .title = \"TGID\", .description = \"Thread group ID (i.e. process ID)\", .flags = 0, .pidColumn = true, },\n   [PROC_EXE] = { .name = \"EXE\", .title = \"EXE             \", .description = \"Basename of exe of the process from /proc/[pid]/exe\", .flags = 0, },\n   [CWD] = { .name = \"CWD\", .title = \"CWD                       \", .description = \"The current working directory of the process\", .flags = PROCESS_FLAG_CWD, },\n   [TRANSLATED] = { .name = \"TRANSLATED\", .title = \"T \", .description = \"Translation info (T translated, N native)\", .flags = 0, },\n};\n\nProcess* DarwinProcess_new(const Machine* host) {\n   DarwinProcess* this = xCalloc(1, sizeof(DarwinProcess));\n   Object_setClass(this, Class(DarwinProcess));\n   Process_init(&this->super, host);\n\n   this->utime = 0;\n   this->stime = 0;\n   this->taskAccess = true;\n   this->translated = false;\n   this->super.state = UNKNOWN;\n\n   return (Process*)this;\n}\n\nvoid Process_delete(Object* cast) {\n   DarwinProcess* this = (DarwinProcess*) cast;\n   Process_done(&this->super);\n   // free platform-specific fields here\n   free(this);\n}\n\nstatic void DarwinProcess_rowWriteField(const Row* super, RichString* str, ProcessField field) {\n   const DarwinProcess* dp = (const DarwinProcess*) super;\n\n   char buffer[256]; buffer[255] = '\\0';\n   int attr = CRT_colors[DEFAULT_COLOR];\n   size_t n = sizeof(buffer) - 1;\n\n   switch (field) {\n   // add Platform-specific fields here\n   case TRANSLATED: xSnprintf(buffer, n, \"%c \", dp->translated ? 'T' : 'N'); break;\n   default:\n      Process_writeField(&dp->super, str, field);\n      return;\n   }\n\n   RichString_appendWide(str, attr, buffer);\n}\n\nstatic int DarwinProcess_compareByKey(const Process* v1, const Process* v2, ProcessField key) {\n   const DarwinProcess* p1 = (const DarwinProcess*)v1;\n   const DarwinProcess* p2 = (const DarwinProcess*)v2;\n\n   switch (key) {\n   // add Platform-specific fields here\n   case TRANSLATED:\n      return SPACESHIP_NUMBER(p1->translated, p2->translated);\n   default:\n      return Process_compareByKey_Base(v1, v2, key);\n   }\n}\n\nstatic void DarwinProcess_updateExe(pid_t pid, Process* proc) {\n   char path[PROC_PIDPATHINFO_MAXSIZE];\n\n   int r = proc_pidpath(pid, path, sizeof(path));\n   if (r <= 0)\n      return;\n\n   Process_updateExe(proc, path);\n}\n\nstatic void DarwinProcess_updateCwd(pid_t pid, Process* proc) {\n   struct proc_vnodepathinfo vpi;\n\n   int r = proc_pidinfo(pid, PROC_PIDVNODEPATHINFO, 0, &vpi, sizeof(vpi));\n   if (r <= 0) {\n      free(proc->procCwd);\n      proc->procCwd = NULL;\n      return;\n   }\n\n   if (!vpi.pvi_cdir.vip_path[0]) {\n      free(proc->procCwd);\n      proc->procCwd = NULL;\n      return;\n   }\n\n   free_and_xStrdup(&proc->procCwd, vpi.pvi_cdir.vip_path);\n}\n\nstatic void DarwinProcess_updateCmdLine(const struct kinfo_proc* k, Process* proc) {\n   Process_updateComm(proc, k->kp_proc.p_comm);\n\n   /* This function is from the old Mac version of htop. Originally from ps? */\n   int mib[3], argmax, nargs, c = 0;\n   size_t size;\n   char *procargs, *sp, *np, *cp;\n\n   /* Get the maximum process arguments size. */\n   mib[0] = CTL_KERN;\n   mib[1] = KERN_ARGMAX;\n\n   size = sizeof( argmax );\n   if ( sysctl( mib, 2, &argmax, &size, NULL, 0 ) == -1 ) {\n      goto ERROR_A;\n   }\n\n   /* Allocate space for the arguments. */\n   procargs = (char*)malloc(argmax);\n   if ( procargs == NULL ) {\n      goto ERROR_A;\n   }\n\n   /*\n    * Make a sysctl() call to get the raw argument space of the process.\n    * The layout is documented in start.s, which is part of the Csu\n    * project.  In summary, it looks like:\n    *\n    * /---------------\\ 0x00000000\n    * :               :\n    * :               :\n    * |---------------|\n    * | argc          |\n    * |---------------|\n    * | arg[0]        |\n    * |---------------|\n    * :               :\n    * :               :\n    * |---------------|\n    * | arg[argc - 1] |\n    * |---------------|\n    * | 0             |\n    * |---------------|\n    * | env[0]        |\n    * |---------------|\n    * :               :\n    * :               :\n    * |---------------|\n    * | env[n]        |\n    * |---------------|\n    * | 0             |\n    * |---------------| <-- Beginning of data returned by sysctl() is here.\n    * | argc          |\n    * |---------------|\n    * | exec_path     |\n    * |:::::::::::::::|\n    * |               |\n    * | String area.  |\n    * |               |\n    * |---------------| <-- Top of stack.\n    * :               :\n    * :               :\n    * \\---------------/ 0xffffffff\n    */\n   mib[0] = CTL_KERN;\n   mib[1] = KERN_PROCARGS2;\n   mib[2] = k->kp_proc.p_pid;\n\n   size = ( size_t ) argmax;\n   if ( sysctl( mib, 3, procargs, &size, NULL, 0 ) == -1 ) {\n      goto ERROR_B;\n   }\n\n   memcpy( &nargs, procargs, sizeof( nargs ) );\n   cp = procargs + sizeof( nargs );\n\n   /* Skip the saved exec_path. */\n   for ( ; cp < &procargs[size]; cp++ ) {\n      if ( *cp == '\\0' ) {\n         /* End of exec_path reached. */\n         break;\n      }\n   }\n   if ( cp == &procargs[size] ) {\n      goto ERROR_B;\n   }\n\n   /* Skip trailing '\\0' characters. */\n   for ( ; cp < &procargs[size]; cp++ ) {\n      if ( *cp != '\\0' ) {\n         /* Beginning of first argument reached. */\n         break;\n      }\n   }\n   if ( cp == &procargs[size] ) {\n      goto ERROR_B;\n   }\n   /* Save where the argv[0] string starts. */\n   sp = cp;\n\n   size_t end = 0;\n   for ( np = NULL; c < nargs && cp < &procargs[size]; cp++ ) {\n      if ( *cp == '\\0' ) {\n         c++;\n         if ( np != NULL ) {\n            /* Convert previous '\\0'. */\n            *np = ' ';\n         }\n         /* Note location of current '\\0'. */\n         np = cp;\n         if (end == 0) {\n            end = (size_t)(cp - sp);\n         }\n      }\n   }\n\n   /*\n    * sp points to the beginning of the arguments/environment string, and\n    * np should point to the '\\0' terminator for the string.\n    */\n   if ( np == NULL || np == sp ) {\n      /* Empty or unterminated string. */\n      goto ERROR_B;\n   }\n   if (end == 0) {\n      end = (size_t)(np - sp);\n   }\n\n   Process_updateCmdline(proc, sp, 0, end);\n\n   /* Clean up. */\n   free( procargs );\n\n   return;\n\nERROR_B:\n   free( procargs );\n\nERROR_A:\n   Process_updateCmdline(proc, k->kp_proc.p_comm, 0, strlen(k->kp_proc.p_comm));\n}\n\nstatic char* DarwinProcess_getDevname(dev_t dev) {\n   if (dev == NODEV) {\n      return NULL;\n   }\n   char buf[sizeof(\"/dev/\") + MAXNAMLEN];\n   char* name = devname_r(dev, S_IFCHR, buf, MAXNAMLEN);\n   if (name) {\n      return xStrdup(name);\n   }\n   return NULL;\n}\n\nvoid DarwinProcess_setFromKInfoProc(Process* proc, const struct kinfo_proc* ps, bool exists) {\n   DarwinProcess* dp = (DarwinProcess*)proc;\n   const Settings* settings = proc->super.host->settings;\n\n   const struct extern_proc* ep = &ps->kp_proc;\n\n   /* UNSET HERE :\n    *\n    * processor\n    * user (set at ProcessTable level)\n    * nlwp\n    * percent_cpu\n    * percent_mem\n    * m_virt\n    * m_resident\n    * minflt\n    * majflt\n    */\n\n   /* First, the \"immutable\" parts */\n   if (!exists) {\n      /* Set the PID/PGID/etc. */\n      Process_setPid(proc, ep->p_pid);\n      Process_setThreadGroup(proc, ep->p_pid);\n      Process_setParent(proc, ps->kp_eproc.e_ppid);\n      proc->pgrp = ps->kp_eproc.e_pgid;\n      proc->session = 0; /* TODO Get the session id */\n      proc->tpgid = ps->kp_eproc.e_tpgid;\n      proc->isKernelThread = false;\n      proc->isUserlandThread = false;\n      dp->translated = ps->kp_proc.p_flag & P_TRANSLATED;\n      proc->tty_nr = ps->kp_eproc.e_tdev;\n      proc->tty_name = NULL;\n\n      proc->starttime_ctime = ep->p_starttime.tv_sec;\n      Process_fillStarttimeBuffer(proc);\n\n      DarwinProcess_updateExe(ep->p_pid, proc);\n      DarwinProcess_updateCmdLine(ps, proc);\n\n      if (settings->ss->flags & PROCESS_FLAG_CWD) {\n         DarwinProcess_updateCwd(ep->p_pid, proc);\n      }\n   }\n\n   if (proc->tty_name == NULL && (dev_t)proc->tty_nr != NODEV) {\n      /* The call to devname() is extremely expensive (due to lstat)\n       * and represents ~95% of htop's CPU usage when there is high\n       * process turnover.\n       *\n       * To mitigate this we only fetch TTY information if the TTY\n       * field is enabled in the settings.\n       */\n      if (settings->ss->flags & PROCESS_FLAG_TTY) {\n         proc->tty_name = DarwinProcess_getDevname((dev_t)proc->tty_nr);\n         if (!proc->tty_name) {\n            /* devname failed: prevent us from calling it again */\n            proc->tty_nr = NODEV;\n         }\n      }\n   }\n\n   /* Mutable information */\n   proc->nice = ep->p_nice;\n   proc->priority = ep->p_priority;\n\n   proc->state = (ep->p_stat == SZOMB) ? ZOMBIE : UNKNOWN;\n\n   /* Make sure the updated flag is set */\n   proc->super.updated = true;\n}\n\nvoid DarwinProcess_setFromLibprocPidinfo(DarwinProcess* proc, DarwinProcessTable* dpt, double timeIntervalNS) {\n   struct proc_taskinfo pti;\n\n   if (PROC_PIDTASKINFO_SIZE != proc_pidinfo(Process_getPid(&proc->super), PROC_PIDTASKINFO, 0, &pti, PROC_PIDTASKINFO_SIZE)) {\n      proc->taskAccess = false;\n      return;\n   }\n\n   const DarwinMachine* dhost = (const DarwinMachine*) proc->super.super.host;\n\n   uint64_t total_existing_time_ns = proc->stime + proc->utime;\n   uint64_t user_time_ns = Platform_machTicksToNanoseconds(pti.pti_total_user);\n   uint64_t system_time_ns = Platform_machTicksToNanoseconds(pti.pti_total_system);\n   uint64_t total_current_time_ns = user_time_ns + system_time_ns;\n\n   if (total_existing_time_ns < total_current_time_ns) {\n      const uint64_t total_time_diff_ns = total_current_time_ns - total_existing_time_ns;\n      proc->super.percent_cpu = ((double)total_time_diff_ns / timeIntervalNS) * 100.0;\n   } else {\n      proc->super.percent_cpu = 0.0;\n   }\n   Process_updateCPUFieldWidths(proc->super.percent_cpu);\n\n   proc->super.state = pti.pti_numrunning > 0 ? RUNNING : SLEEPING;\n   // Convert from nanoseconds to hundredths of seconds\n   proc->super.time = total_current_time_ns / 10000000ULL;\n   proc->super.nlwp = pti.pti_threadnum;\n   proc->super.m_virt = pti.pti_virtual_size / ONE_K;\n   proc->super.m_resident = pti.pti_resident_size / ONE_K;\n   proc->super.majflt = pti.pti_faults;\n   proc->super.percent_mem = (double)pti.pti_resident_size * 100.0 / (double)dhost->host_info.max_mem;\n\n   proc->stime = system_time_ns;\n   proc->utime = user_time_ns;\n\n   dpt->super.kernelThreads += 0; /*pti.pti_threads_system;*/\n   dpt->super.userlandThreads += pti.pti_threadnum; /*pti.pti_threads_user;*/\n   dpt->super.totalTasks += pti.pti_threadnum;\n   dpt->super.runningTasks += pti.pti_numrunning;\n}\n\n/*\n * Scan threads for process state information.\n * Based on: http://stackoverflow.com/questions/6788274/ios-mac-cpu-usage-for-thread\n * and       https://github.com/max-horvath/htop-osx/blob/e86692e869e30b0bc7264b3675d2a4014866ef46/ProcessList.c\n */\nvoid DarwinProcess_scanThreads(DarwinProcess* dp, DarwinProcessTable* dpt) {\n   Process* proc = (Process*) dp;\n   kern_return_t ret;\n\n   if (!dp->taskAccess) {\n      return;\n   }\n\n   if (proc->state == ZOMBIE) {\n      return;\n   }\n\n   pid_t pid = Process_getPid(proc);\n\n   task_t task;\n   ret = task_for_pid(mach_task_self(), pid, &task);\n   if (ret != KERN_SUCCESS) {\n      // TODO: workaround for modern MacOS limits on task_for_pid()\n      if (ret != KERN_FAILURE)\n         CRT_debug(\"task_for_pid(%d) failed: %s\", pid, mach_error_string(ret));\n      dp->taskAccess = false;\n      return;\n   }\n\n   thread_array_t thread_list;\n   mach_msg_type_number_t thread_count;\n   ret = task_threads(task, &thread_list, &thread_count);\n   if (ret != KERN_SUCCESS) {\n      CRT_debug(\"task_threads(%d) failed: %s\", pid, mach_error_string(ret));\n      dp->taskAccess = false;\n      mach_port_deallocate(mach_task_self(), task);\n      return;\n   }\n\n   const bool hideUserlandThreads = dpt->super.super.host->settings->hideUserlandThreads;\n   bool isProcessStuck = false;\n\n   for (mach_msg_type_number_t i = 0; i < thread_count; i++) {\n\n      thread_identifier_info_data_t identifer_info;\n      mach_msg_type_number_t identifer_info_count = THREAD_IDENTIFIER_INFO_COUNT;\n      ret = thread_info(thread_list[i], THREAD_IDENTIFIER_INFO, (thread_info_t) &identifer_info, &identifer_info_count);\n      if (ret != KERN_SUCCESS) {\n         CRT_debug(\"thread_info(%d:%d) for identifier failed: %s\", pid, i, mach_error_string(ret));\n         continue;\n      }\n\n      uint64_t tid = identifer_info.thread_id;\n\n      bool preExisting;\n      Process *tprocess = ProcessTable_getProcess(&dpt->super, (pid_t)tid, &preExisting, DarwinProcess_new);\n      tprocess->super.updated = true;\n      dpt->super.totalTasks++;\n\n      if (hideUserlandThreads) {\n         tprocess->super.show = false;\n         continue;\n      }\n\n      pid_t tprocessPid = Process_getPid(tprocess);\n      assert(tprocessPid >= 0);\n      assert((uint64_t)tprocessPid == tid);\n      (void)tprocessPid;\n\n      Process_setParent(tprocess, pid);\n      Process_setThreadGroup(tprocess, pid);\n      tprocess->super.show       = true;\n      tprocess->isUserlandThread = true;\n      tprocess->st_uid           = proc->st_uid;\n      tprocess->user             = proc->user;\n\n#ifdef HAVE_THREAD_EXTENDED_INFO_DATA_T\n      thread_extended_info_data_t extended_info;\n      mach_msg_type_number_t extended_info_count = THREAD_EXTENDED_INFO_COUNT;\n      ret = thread_info(thread_list[i], THREAD_EXTENDED_INFO, (thread_info_t) &extended_info, &extended_info_count);\n      if (ret != KERN_SUCCESS) {\n         CRT_debug(\"thread_info(%d:%d) for extended failed: %s\", pid, i, mach_error_string(ret));\n         continue;\n      }\n\n      DarwinProcess* tdproc     = (DarwinProcess*)tprocess;\n      tdproc->super.percent_cpu = extended_info.pth_cpu_usage / 10.0;\n      tdproc->stime             = extended_info.pth_system_time;\n      tdproc->utime             = extended_info.pth_user_time;\n      tdproc->super.time        = (extended_info.pth_system_time + extended_info.pth_user_time) / 10000000;\n      tdproc->super.priority    = extended_info.pth_curpri;\n\n      if (extended_info.pth_run_state == TH_STATE_UNINTERRUPTIBLE) {\n         isProcessStuck |= true;\n         tdproc->super.state = UNINTERRUPTIBLE_WAIT;\n      }\n#endif\n\n      // TODO: depend on setting\n#ifdef HAVE_THREAD_EXTENDED_INFO_DATA_T\n      const char* name = extended_info.pth_name[0] != '\\0' ? extended_info.pth_name : proc->procComm;\n#else\n      // Not provided in thread_basic_info_data_t; fall back to the process name\n      const char* name = proc->procComm;\n#endif\n      Process_updateCmdline(tprocess, name, 0, name ? strlen(name) : 0);\n\n      if (!preExisting)\n         ProcessTable_add(&dpt->super, tprocess);\n   }\n\n   if (isProcessStuck) {\n      dp->super.state = UNINTERRUPTIBLE_WAIT;\n   }\n\n   vm_deallocate(mach_task_self(), (vm_address_t) thread_list, sizeof(thread_port_array_t) * thread_count);\n   mach_port_deallocate(mach_task_self(), task);\n}\n\n\nconst ProcessClass DarwinProcess_class = {\n   .super = {\n      .super = {\n         .extends = Class(Process),\n         .display = Row_display,\n         .delete = Process_delete,\n         .compare = Process_compare\n      },\n      .isHighlighted = Process_rowIsHighlighted,\n      .isVisible = Process_rowIsVisible,\n      .matchesFilter = Process_rowMatchesFilter,\n      .compareByParent = Process_compareByParent,\n      .sortKeyString = Process_rowGetSortKey,\n      .writeField = DarwinProcess_rowWriteField\n   },\n   .compareByKey = DarwinProcess_compareByKey\n};\n"
  },
  {
    "path": "darwin/DarwinProcess.h",
    "content": "#ifndef HEADER_DarwinProcess\n#define HEADER_DarwinProcess\n/*\nhtop - DarwinProcess.h\n(C) 2015 Hisham H. Muhammad\nReleased under the GNU GPLv2+, see the COPYING file\nin the source distribution for its full text.\n*/\n\n#include <sys/sysctl.h>\n\n#include \"Machine.h\"\n#include \"darwin/DarwinProcessTable.h\"\n\n\n#define PROCESS_FLAG_TTY 0x00000100\n\ntypedef struct DarwinProcess_ {\n   Process super;\n\n   uint64_t utime;\n   uint64_t stime;\n   bool taskAccess;\n   bool translated;\n} DarwinProcess;\n\nextern const ProcessClass DarwinProcess_class;\n\nextern const ProcessFieldData Process_fields[LAST_PROCESSFIELD];\n\nProcess* DarwinProcess_new(const Machine* settings);\n\nvoid Process_delete(Object* cast);\n\nvoid DarwinProcess_setFromKInfoProc(Process* proc, const struct kinfo_proc* ps, bool exists);\n\nvoid DarwinProcess_setFromLibprocPidinfo(DarwinProcess* proc, DarwinProcessTable* dpt, double timeIntervalNS);\n\n/*\n * Scan threads for process state information.\n * Based on: http://stackoverflow.com/questions/6788274/ios-mac-cpu-usage-for-thread\n * and       https://github.com/max-horvath/htop-osx/blob/e86692e869e30b0bc7264b3675d2a4014866ef46/ProcessList.c\n */\nvoid DarwinProcess_scanThreads(DarwinProcess* dp, DarwinProcessTable* dpt);\n\n#endif\n"
  },
  {
    "path": "darwin/DarwinProcessTable.c",
    "content": "/*\nhtop - DarwinProcessTable.c\n(C) 2014 Hisham H. Muhammad\nReleased under the GNU GPLv2+, see the COPYING file\nin the source distribution for its full text.\n*/\n\n#include \"darwin/DarwinProcessTable.h\"\n\n#include <errno.h>\n#include <libproc.h>\n#include <stdbool.h>\n#include <stdio.h>\n#include <stdlib.h>\n#include <string.h>\n#include <unistd.h>\n#include <utmpx.h>\n#include <sys/mman.h>\n#include <sys/sysctl.h>\n\n#include \"CRT.h\"\n#include \"ProcessTable.h\"\n#include \"darwin/DarwinMachine.h\"\n#include \"darwin/DarwinProcess.h\"\n#include \"darwin/Platform.h\"\n#include \"darwin/PlatformHelpers.h\"\n#include \"generic/openzfs_sysctl.h\"\n#include \"zfs/ZfsArcStats.h\"\n\n\nstatic struct kinfo_proc* ProcessTable_getKInfoProcs(size_t* count) {\n   int mib[4] = { CTL_KERN, KERN_PROC, KERN_PROC_ALL, 0 };\n   struct kinfo_proc* processes = NULL;\n\n   for (unsigned int retry = 0; retry < 4; retry++) {\n      size_t size = 0;\n      if (sysctl(mib, 4, NULL, &size, NULL, 0) < 0 || size == 0) {\n         CRT_fatalError(\"Unable to get size of kproc_infos\");\n      }\n\n      size += 16 * retry * retry * sizeof(struct kinfo_proc);\n      processes = xRealloc(processes, size);\n\n      if (sysctl(mib, 4, processes, &size, NULL, 0) == 0) {\n         *count = size / sizeof(struct kinfo_proc);\n         return processes;\n      }\n\n      if (errno != ENOMEM)\n         break;\n   }\n\n   CRT_fatalError(\"Unable to get kinfo_procs\");\n}\n\nProcessTable* ProcessTable_new(Machine* host, Hashtable* pidMatchList) {\n   DarwinProcessTable* this = xCalloc(1, sizeof(DarwinProcessTable));\n   Object_setClass(this, Class(ProcessTable));\n\n   ProcessTable* super = &this->super;\n   ProcessTable_init(super, Class(DarwinProcess), host, pidMatchList);\n\n   return super;\n}\n\nvoid ProcessTable_delete(Object* cast) {\n   DarwinProcessTable* this = (DarwinProcessTable*) cast;\n   ProcessTable_done(&this->super);\n   free(this);\n}\n\nvoid ProcessTable_goThroughEntries(ProcessTable* super) {\n   const Machine* host = super->super.host;\n   const DarwinMachine* dhost = (const DarwinMachine*) host;\n   DarwinProcessTable* dpt = (DarwinProcessTable*) super;\n   bool preExisting = true;\n   struct kinfo_proc* ps;\n   size_t count;\n   DarwinProcess* proc;\n\n   /* Get the time difference */\n   dpt->global_diff = 0;\n   for (unsigned int i = 0; i < host->existingCPUs; ++i) {\n      for (size_t j = 0; j < CPU_STATE_MAX; ++j) {\n         dpt->global_diff += dhost->curr_load[i].cpu_ticks[j] - dhost->prev_load[i].cpu_ticks[j];\n      }\n   }\n\n   const double time_interval_ns = Platform_schedulerTicksToNanoseconds(dpt->global_diff) / (double) host->activeCPUs;\n\n   /* We use kinfo_procs for initial data since :\n    *\n    * 1) They always succeed.\n    * 2) They contain the basic information.\n    *\n    * We attempt to fill-in additional information with libproc.\n    */\n   ps = ProcessTable_getKInfoProcs(&count);\n\n   for (size_t i = 0; i < count; ++i) {\n      proc = (DarwinProcess*)ProcessTable_getProcess(super, ps[i].kp_proc.p_pid, &preExisting, DarwinProcess_new);\n\n      DarwinProcess_setFromKInfoProc(&proc->super, &ps[i], preExisting);\n      DarwinProcess_setFromLibprocPidinfo(proc, dpt, time_interval_ns);\n\n      // Deduce further process states not covered in the libproc call above\n      if (ps[i].kp_proc.p_stat == SZOMB) {\n         proc->super.state = ZOMBIE;\n      } else if (ps[i].kp_proc.p_stat == SSTOP) {\n         proc->super.state = STOPPED;\n      }\n\n      if (proc->super.st_uid != ps[i].kp_eproc.e_ucred.cr_uid) {\n         proc->super.st_uid = ps[i].kp_eproc.e_ucred.cr_uid;\n         proc->super.user = UsersTable_getRef(host->usersTable, proc->super.st_uid);\n      }\n\n      // Disabled for High Sierra due to bug in macOS High Sierra\n      bool isScanThreadSupported = !Platform_KernelVersionIsBetween((KernelVersion) {17, 0, 0}, (KernelVersion) {17, 5, 0});\n\n      if (isScanThreadSupported) {\n         DarwinProcess_scanThreads(proc, dpt);\n      }\n\n      super->totalTasks += 1;\n\n      if (!preExisting) {\n         ProcessTable_add(super, &proc->super);\n      }\n   }\n\n   free(ps);\n}\n"
  },
  {
    "path": "darwin/DarwinProcessTable.h",
    "content": "#ifndef HEADER_DarwinProcessTable\n#define HEADER_DarwinProcessTable\n/*\nhtop - DarwinProcessTable.h\n(C) 2014 Hisham H. Muhammad\nReleased under the GNU GPLv2+, see the COPYING file\nin the source distribution for its full text.\n*/\n\n#include <mach/mach_host.h>\n#include <sys/sysctl.h>\n\n#include \"ProcessTable.h\"\n\n\ntypedef struct DarwinProcessTable_ {\n   ProcessTable super;\n\n   uint64_t global_diff;\n} DarwinProcessTable;\n\n#endif\n"
  },
  {
    "path": "darwin/Platform.c",
    "content": "/*\nhtop - darwin/Platform.c\n(C) 2014 Hisham H. Muhammad\n(C) 2015 David C. Hunt\nReleased under the GNU GPLv2+, see the COPYING file\nin the source distribution for its full text.\n*/\n\n#include \"config.h\" // IWYU pragma: keep\n\n#include \"darwin/Platform.h\"\n\n#include <assert.h>\n#include <errno.h>\n#include <math.h>\n#include <stdlib.h>\n#include <unistd.h>\n#include <net/if_types.h>\n#include <net/route.h>\n#include <sys/socket.h>\n#include <mach/port.h>\n#include <net/if.h> // After `sys/socket.h` for struct `sockaddr` (for iOS6 SDK)\n\n#include <CoreFoundation/CFBase.h>\n#include <CoreFoundation/CFDictionary.h>\n#include <CoreFoundation/CFNumber.h>\n#include <CoreFoundation/CFString.h>\n#include <CoreFoundation/CoreFoundation.h>\n\n#include <IOKit/IOKitLib.h>\n#include <IOKit/IOTypes.h>\n#include <IOKit/ps/IOPowerSources.h>\n#include <IOKit/ps/IOPSKeys.h>\n#include <IOKit/storage/IOBlockStorageDriver.h>\n\n#include \"ClockMeter.h\"\n#include \"CPUMeter.h\"\n#include \"CRT.h\"\n#include \"DateTimeMeter.h\"\n#include \"FileDescriptorMeter.h\"\n#include \"GPUMeter.h\"\n#include \"HostnameMeter.h\"\n#include \"LoadAverageMeter.h\"\n#include \"Macros.h\"\n#include \"MemoryMeter.h\"\n#include \"MemorySwapMeter.h\"\n#include \"ProcessLocksScreen.h\"\n#include \"SwapMeter.h\"\n#include \"SysArchMeter.h\"\n#include \"TasksMeter.h\"\n#include \"UptimeMeter.h\"\n#include \"darwin/DarwinMachine.h\"\n#include \"darwin/PlatformHelpers.h\"\n#include \"generic/fdstat_sysctl.h\"\n#include \"zfs/ZfsArcMeter.h\"\n#include \"zfs/ZfsCompressedArcMeter.h\"\n\n#ifdef HAVE_HOST_GET_CLOCK_SERVICE\n#include <mach/clock.h>\n#include <mach/mach.h>\n#endif\n\n#ifdef HAVE_MACH_MACH_TIME_H\n#include <mach/mach_time.h>\n#endif\n\n\nconst ScreenDefaults Platform_defaultScreens[] = {\n   {\n      .name = \"Main\",\n      .columns = \"PID USER PRIORITY NICE M_VIRT M_RESIDENT STATE PERCENT_CPU PERCENT_MEM TIME Command\",\n      .sortKey = \"PERCENT_CPU\",\n   },\n};\n\nconst unsigned int Platform_numberOfDefaultScreens = ARRAYSIZE(Platform_defaultScreens);\n\nconst SignalItem Platform_signals[] = {\n   { .name = \" 0 Cancel\",    .number =  0 },\n   { .name = \" 1 SIGHUP\",    .number =  1 },\n   { .name = \" 2 SIGINT\",    .number =  2 },\n   { .name = \" 3 SIGQUIT\",   .number =  3 },\n   { .name = \" 4 SIGILL\",    .number =  4 },\n   { .name = \" 5 SIGTRAP\",   .number =  5 },\n   { .name = \" 6 SIGABRT\",   .number =  6 },\n   { .name = \" 6 SIGIOT\",    .number =  6 },\n   { .name = \" 7 SIGEMT\",    .number =  7 },\n   { .name = \" 8 SIGFPE\",    .number =  8 },\n   { .name = \" 9 SIGKILL\",   .number =  9 },\n   { .name = \"10 SIGBUS\",    .number = 10 },\n   { .name = \"11 SIGSEGV\",   .number = 11 },\n   { .name = \"12 SIGSYS\",    .number = 12 },\n   { .name = \"13 SIGPIPE\",   .number = 13 },\n   { .name = \"14 SIGALRM\",   .number = 14 },\n   { .name = \"15 SIGTERM\",   .number = 15 },\n   { .name = \"16 SIGURG\",    .number = 16 },\n   { .name = \"17 SIGSTOP\",   .number = 17 },\n   { .name = \"18 SIGTSTP\",   .number = 18 },\n   { .name = \"19 SIGCONT\",   .number = 19 },\n   { .name = \"20 SIGCHLD\",   .number = 20 },\n   { .name = \"21 SIGTTIN\",   .number = 21 },\n   { .name = \"22 SIGTTOU\",   .number = 22 },\n   { .name = \"23 SIGIO\",     .number = 23 },\n   { .name = \"24 SIGXCPU\",   .number = 24 },\n   { .name = \"25 SIGXFSZ\",   .number = 25 },\n   { .name = \"26 SIGVTALRM\", .number = 26 },\n   { .name = \"27 SIGPROF\",   .number = 27 },\n   { .name = \"28 SIGWINCH\",  .number = 28 },\n   { .name = \"29 SIGINFO\",   .number = 29 },\n   { .name = \"30 SIGUSR1\",   .number = 30 },\n   { .name = \"31 SIGUSR2\",   .number = 31 },\n};\n\nconst unsigned int Platform_numberOfSignals = ARRAYSIZE(Platform_signals);\n\nenum {\n   MEMORY_CLASS_WIRED = 0,\n   MEMORY_CLASS_SPECULATIVE,\n   MEMORY_CLASS_ACTIVE,\n   MEMORY_CLASS_PURGEABLE,\n   MEMORY_CLASS_COMPRESSED,\n   MEMORY_CLASS_INACTIVE,\n}; // N.B. the chart will display categories in this order\n\nconst MemoryClass Platform_memoryClasses[] = {\n    [MEMORY_CLASS_WIRED] = { .label = \"wired\", .countsAsUsed = true, .countsAsCache = false, .color = MEMORY_1 }, // pages wired down to physical memory (kernel)\n   [MEMORY_CLASS_SPECULATIVE] = { .label = \"speculative\", .countsAsUsed = true, .countsAsCache = true, .color = MEMORY_2 }, // readahead optimization caches\n   [MEMORY_CLASS_ACTIVE] = { .label = \"active\", .countsAsUsed = true, .countsAsCache = false, .color = MEMORY_3 }, // userland pages actively being used\n   [MEMORY_CLASS_PURGEABLE] = { .label = \"purgeable\", .countsAsUsed = false, .countsAsCache = true, .color = MEMORY_4 }, // userland pages voluntarily marked \"discardable\" by apps\n   [MEMORY_CLASS_COMPRESSED] = { .label = \"compressed\", .countsAsUsed = true, .countsAsCache = false, .color = MEMORY_5 }, // userland pages being compressed (means memory pressure++)\n   [MEMORY_CLASS_INACTIVE] = { .label = \"inactive\", .countsAsUsed = true, .countsAsCache = true, .color = MEMORY_6 }, // pages no longer used; macOS counts them as \"used\" anyway...\n};\n\nconst unsigned int Platform_numberOfMemoryClasses = ARRAYSIZE(Platform_memoryClasses);\n\nconst MeterClass* const Platform_meterTypes[] = {\n   &CPUMeter_class,\n   &ClockMeter_class,\n   &DateMeter_class,\n   &DateTimeMeter_class,\n   &LoadAverageMeter_class,\n   &LoadMeter_class,\n   &MemoryMeter_class,\n   &SwapMeter_class,\n   &MemorySwapMeter_class,\n   &TasksMeter_class,\n   &BatteryMeter_class,\n   &HostnameMeter_class,\n   &SysArchMeter_class,\n   &UptimeMeter_class,\n   &SecondsUptimeMeter_class,\n   &AllCPUsMeter_class,\n   &AllCPUs2Meter_class,\n   &AllCPUs4Meter_class,\n   &AllCPUs8Meter_class,\n   &LeftCPUsMeter_class,\n   &RightCPUsMeter_class,\n   &LeftCPUs2Meter_class,\n   &RightCPUs2Meter_class,\n   &LeftCPUs4Meter_class,\n   &RightCPUs4Meter_class,\n   &LeftCPUs8Meter_class,\n   &RightCPUs8Meter_class,\n   &ZfsArcMeter_class,\n   &ZfsCompressedArcMeter_class,\n   &DiskIORateMeter_class,\n   &DiskIOTimeMeter_class,\n   &DiskIOMeter_class,\n   &NetworkIOMeter_class,\n   &FileDescriptorMeter_class,\n   &GPUMeter_class,\n   &BlankMeter_class,\n   NULL\n};\n\nstatic uint64_t Platform_nanosecondsPerMachTickNumer = 1;\nstatic uint64_t Platform_nanosecondsPerMachTickDenom = 1;\n\nstatic double Platform_nanosecondsPerSchedulerTick = -1;\n\nstatic mach_port_t iokit_port; // the mach port used to initiate communication with IOKit\n\nstatic void Platform_calculateNanosecondsPerMachTick(uint64_t* numer, uint64_t* denom) {\n   // Check if we can determine the timebase used on this system.\n\n#ifdef __x86_64__\n   /* WORKAROUND for `mach_timebase_info` giving incorrect values on M1 under Rosetta 2.\n    *    rdar://FB9546856 http://www.openradar.appspot.com/FB9546856\n    *\n    *    We don't know exactly what feature/attribute of the M1 chip causes this mistake under Rosetta 2.\n    *    Until we have more Apple ARM chips to compare against, the best we can do is special-case\n    *    the \"Apple M1\" chip specifically when running under Rosetta 2.\n    *\n    *    Rosetta 2 only supports x86-64, so skip this workaround when building for other architectures.\n    */\n\n   bool isRunningUnderRosetta2 = Platform_isRunningTranslated();\n\n   // Kernel versions >= 20.0.0 (macOS 11.0 AKA Big Sur) affected\n   bool isBuggedVersion = 0 <= Platform_CompareKernelVersion((KernelVersion) {20, 0, 0});\n\n   if (isRunningUnderRosetta2 && isBuggedVersion) {\n      // In this case `mach_timebase_info` provides the wrong value, so we hard-code the correct factor,\n      // as determined from `mach_timebase_info` as if the process was running natively.\n      *numer = 125;\n      *denom = 3;\n      return;\n   }\n#endif\n\n#ifdef HAVE_MACH_TIMEBASE_INFO\n   mach_timebase_info_data_t info = { 0 };\n   if (mach_timebase_info(&info) == KERN_SUCCESS) {\n      *numer = info.numer;\n      *denom = info.denom;\n      return;\n   }\n#endif\n\n   // No info on actual timebase found; assume timebase in nanoseconds.\n   *numer = 1;\n   *denom = 1;\n}\n\n// Converts ticks in the Mach \"timebase\" to nanoseconds.\n// See `mach_timebase_info`, as used to define the `Platform_nanosecondsPerMachTick` constant.\nuint64_t Platform_machTicksToNanoseconds(uint64_t mach_ticks) {\n   uint64_t ticks_quot = mach_ticks / Platform_nanosecondsPerMachTickDenom;\n   uint64_t ticks_rem  = mach_ticks % Platform_nanosecondsPerMachTickDenom;\n\n   uint64_t part1 = ticks_quot * Platform_nanosecondsPerMachTickNumer;\n\n   // When Platform_nanosecondsPerMachTickDenom * Platform_nanosecondsPerMachTickNumer is less than 2^64, ticks_rem *\n   // Platform_nanosecondsPerMachTickNumer will be less than 2^64 as well, i.e. never overflows.\n   uint64_t part2 = (ticks_rem * Platform_nanosecondsPerMachTickNumer) / Platform_nanosecondsPerMachTickDenom;\n\n   return part1 + part2;\n}\n\nbool Platform_init(void) {\n   Platform_calculateNanosecondsPerMachTick(&Platform_nanosecondsPerMachTickNumer, &Platform_nanosecondsPerMachTickDenom);\n\n   // Determine the number of scheduler clock ticks per second\n   errno = 0;\n   long scheduler_ticks_per_sec = sysconf(_SC_CLK_TCK);\n\n   if (errno || scheduler_ticks_per_sec < 1) {\n      CRT_fatalError(\"Unable to retrieve clock tick rate\");\n   }\n\n   const double nanos_per_sec = 1e9;\n   Platform_nanosecondsPerSchedulerTick = nanos_per_sec / scheduler_ticks_per_sec;\n\n   return true;\n}\n\n// Converts \"scheduler ticks\" to nanoseconds.\n// See `sysconf(_SC_CLK_TCK)`, as used to define the `Platform_nanosecondsPerSchedulerTick` constant.\ndouble Platform_schedulerTicksToNanoseconds(const double scheduler_ticks) {\n   return scheduler_ticks * Platform_nanosecondsPerSchedulerTick;\n}\n\nvoid Platform_done(void) {\n   /* no platform-specific cleanup needed */\n}\n\nvoid Platform_setBindings(Htop_Action* keys) {\n   /* no platform-specific key bindings */\n   (void) keys;\n}\n\nint Platform_getUptime(void) {\n   struct timeval bootTime, currTime;\n   int mib[2] = { CTL_KERN, KERN_BOOTTIME };\n   size_t size = sizeof(bootTime);\n\n   int err = sysctl(mib, 2, &bootTime, &size, NULL, 0);\n   if (err) {\n      return -1;\n   }\n   gettimeofday(&currTime, NULL);\n\n   return (int) difftime(currTime.tv_sec, bootTime.tv_sec);\n}\n\nvoid Platform_getLoadAverage(double* one, double* five, double* fifteen) {\n   double results[3];\n\n   if (3 == getloadavg(results, 3)) {\n      *one = results[0];\n      *five = results[1];\n      *fifteen = results[2];\n   } else {\n      *one = 0;\n      *five = 0;\n      *fifteen = 0;\n   }\n}\n\npid_t Platform_getMaxPid(void) {\n   /* http://opensource.apple.com/source/xnu/xnu-2782.1.97/bsd/sys/proc_internal.hh */\n   return 99999;\n}\n\nstatic double Platform_setCPUAverageValues(Meter* mtr) {\n   const Machine* host = mtr->host;\n   unsigned int activeCPUs = host->activeCPUs;\n   double sumNice = 0.0;\n   double sumNormal = 0.0;\n   double sumKernel = 0.0;\n   double sumPercent = 0.0;\n   for (unsigned int i = 1; i <= host->existingCPUs; i++) {\n      sumPercent += Platform_setCPUValues(mtr, i);\n      sumNice    += mtr->values[CPU_METER_NICE];\n      sumNormal  += mtr->values[CPU_METER_NORMAL];\n      sumKernel  += mtr->values[CPU_METER_KERNEL];\n   }\n   mtr->values[CPU_METER_NICE]   = sumNice   / activeCPUs;\n   mtr->values[CPU_METER_NORMAL] = sumNormal / activeCPUs;\n   mtr->values[CPU_METER_KERNEL] = sumKernel / activeCPUs;\n   return sumPercent / activeCPUs;\n}\n\ndouble Platform_setCPUValues(Meter* mtr, unsigned int cpu) {\n\n   if (cpu == 0) {\n      return Platform_setCPUAverageValues(mtr);\n   }\n\n   const DarwinMachine* dhost = (const DarwinMachine*) mtr->host;\n   const processor_cpu_load_info_t prev = &dhost->prev_load[cpu - 1];\n   const processor_cpu_load_info_t curr = &dhost->curr_load[cpu - 1];\n   double total = 0;\n\n   /* Take the sums */\n   for (size_t i = 0; i < CPU_STATE_MAX; ++i) {\n      total += (double)curr->cpu_ticks[i] - (double)prev->cpu_ticks[i];\n   }\n\n   if (total > 1e-6) {\n      mtr->values[CPU_METER_NICE]\n         = ((double)curr->cpu_ticks[CPU_STATE_NICE] - (double)prev->cpu_ticks[CPU_STATE_NICE]) * 100.0 / total;\n      mtr->values[CPU_METER_NORMAL]\n         = ((double)curr->cpu_ticks[CPU_STATE_USER] - (double)prev->cpu_ticks[CPU_STATE_USER]) * 100.0 / total;\n      mtr->values[CPU_METER_KERNEL]\n         = ((double)curr->cpu_ticks[CPU_STATE_SYSTEM] - (double)prev->cpu_ticks[CPU_STATE_SYSTEM]) * 100.0 / total;\n   } else {\n      mtr->values[CPU_METER_NICE] = 0.0;\n      mtr->values[CPU_METER_NORMAL] = 0.0;\n      mtr->values[CPU_METER_KERNEL] = 0.0;\n   }\n\n   mtr->curItems = 3;\n\n   /* Convert to percent and return */\n   total = mtr->values[CPU_METER_NICE] + mtr->values[CPU_METER_NORMAL] + mtr->values[CPU_METER_KERNEL];\n\n   mtr->values[CPU_METER_FREQUENCY] = NAN;\n   mtr->values[CPU_METER_TEMPERATURE] = NAN;\n\n   return CLAMP(total, 0.0, 100.0);\n}\n\nvoid Platform_setGPUValues(Meter* mtr, double* totalUsage, unsigned long long* totalGPUTimeDiff) {\n   const Machine* host = mtr->host;\n   const DarwinMachine* dhost = (const DarwinMachine *)host;\n\n   assert(*totalGPUTimeDiff == -1ULL);\n   (void)totalGPUTimeDiff;\n\n   mtr->curItems = 1;\n   mtr->values[0] = NAN;\n\n   if (!dhost->GPUService)\n      return;\n\n   static uint64_t prevMonotonicMs;\n\n   // Ensure there is a small time interval between the creation of the\n   // CF property tables. If this function is called in quick successions\n   // (e.g. for multiple meter instances), we might get \"0% utilization\"\n   // as a result.\n   if (host->monotonicMs <= prevMonotonicMs) {\n      mtr->values[0] = *totalUsage;\n      return;\n   }\n\n   CFDictionaryRef perfStats = IORegistryEntryCreateCFProperty(dhost->GPUService, CFSTR(\"PerformanceStatistics\"), kCFAllocatorDefault, kNilOptions);\n   if (!perfStats)\n      return;\n\n   assert(CFGetTypeID(perfStats) == CFDictionaryGetTypeID());\n\n   CFNumberRef deviceUtil = CFDictionaryGetValue(perfStats, CFSTR(\"Device Utilization %\"));\n   if (!deviceUtil)\n      goto cleanup;\n\n   int device = -1;\n   CFNumberGetValue(deviceUtil, kCFNumberIntType, &device);\n   *totalUsage = (double)device;\n\n   prevMonotonicMs = host->monotonicMs;\n\ncleanup:\n   CFRelease(perfStats);\n\n   mtr->values[0] = *totalUsage;\n}\n\nvoid Platform_setMemoryValues(Meter* mtr) {\n   const DarwinMachine* dhost = (const DarwinMachine*) mtr->host;\n   const Settings* settings = mtr->host->settings;\n   double page_K = (double)vm_page_size / (double)1024;\n\n#ifdef HAVE_STRUCT_VM_STATISTICS64\n   const struct vm_statistics64* vm = &dhost->vm_stats;\n   #ifdef HAVE_STRUCT_VM_STATISTICS64_EXTERNAL_PAGE_COUNT\n   const natural_t external_page_count = vm->external_page_count;\n   #else\n   const natural_t external_page_count = 0;\n   #endif\n   #ifdef HAVE_STRUCT_VM_STATISTICS64_COMPRESSOR_PAGE_COUNT\n   const natural_t compressor_page_count = vm->compressor_page_count;\n   #else\n   const natural_t compressor_page_count = 0;\n   #endif\n#else\n   const struct vm_statistics* vm = &dhost->vm_stats;\n   const natural_t external_page_count = 0;\n   const natural_t compressor_page_count = 0;\n#endif // HAVE_STRUCT_VM_STATISTICS64\n\n   mtr->total = dhost->host_info.max_mem / 1024;\n   mtr->values[MEMORY_CLASS_WIRED]       = page_K * vm->wire_count;\n   if (settings->showCachedMemory) {\n      mtr->values[MEMORY_CLASS_SPECULATIVE] = page_K * vm->speculative_count;\n      mtr->values[MEMORY_CLASS_ACTIVE]      = page_K * (vm->active_count - vm->purgeable_count - external_page_count); // external pages are pages swapped out\n      mtr->values[MEMORY_CLASS_PURGEABLE]   = page_K * vm->purgeable_count; // purgeable pages are flagged in the active pages\n   }\n   else { // if showCachedMemory is disabled, merge speculative and purgeable into the active pages\n      mtr->values[MEMORY_CLASS_SPECULATIVE] = 0;\n      mtr->values[MEMORY_CLASS_ACTIVE]      = page_K * (vm->speculative_count + vm->active_count - external_page_count); // external pages are pages swapped out\n      mtr->values[MEMORY_CLASS_PURGEABLE]   = 0;\n   }\n   mtr->values[MEMORY_CLASS_COMPRESSED]  = page_K * compressor_page_count;\n   mtr->values[MEMORY_CLASS_INACTIVE]    = page_K * vm->inactive_count; // for some reason macOS counts inactive pages in the \"used\" memory...\n}\n\nvoid Platform_setSwapValues(Meter* mtr) {\n   int mib[2] = {CTL_VM, VM_SWAPUSAGE};\n   struct xsw_usage swapused;\n   size_t swlen = sizeof(swapused);\n   sysctl(mib, 2, &swapused, &swlen, NULL, 0);\n\n   mtr->total = swapused.xsu_total / 1024;\n   mtr->values[SWAP_METER_USED] = swapused.xsu_used / 1024;\n}\n\nvoid Platform_setZfsArcValues(Meter* this) {\n   const DarwinMachine* dhost = (const DarwinMachine*) this->host;\n\n   ZfsArcMeter_readStats(this, &dhost->zfs);\n}\n\nvoid Platform_setZfsCompressedArcValues(Meter* this) {\n   const DarwinMachine* dhost = (const DarwinMachine*) this->host;\n\n   ZfsCompressedArcMeter_readStats(this, &dhost->zfs);\n}\n\nchar* Platform_getProcessEnv(pid_t pid) {\n   char* env = NULL;\n\n   int argmax;\n   size_t bufsz = sizeof(argmax);\n\n   int mib[3];\n   mib[0] = CTL_KERN;\n   mib[1] = KERN_ARGMAX;\n   if (sysctl(mib, 2, &argmax, &bufsz, 0, 0) == 0) {\n      char* buf = xMalloc(argmax);\n      if (buf) {\n         mib[0] = CTL_KERN;\n         mib[1] = KERN_PROCARGS2;\n         mib[2] = pid;\n         bufsz = argmax;\n         if (sysctl(mib, 3, buf, &bufsz, 0, 0) == 0) {\n            if (bufsz > sizeof(int)) {\n               char *p = buf, *endp = buf + bufsz;\n               int argc = *(int*)(void*)p;\n               p += sizeof(int);\n\n               // skip exe\n               p = strchr(p, 0) + 1;\n\n               // skip padding\n               while (!*p && p < endp)\n                  ++p;\n\n               // skip argv\n               for (; argc-- && p < endp; p = strrchr(p, 0) + 1)\n                  ;\n\n               // skip padding\n               while (!*p && p < endp)\n                  ++p;\n\n               size_t size = endp - p;\n               env = xMalloc(size + 2);\n               memcpy(env, p, size);\n               env[size] = 0;\n               env[size + 1] = 0;\n            }\n         }\n         free(buf);\n      }\n   }\n\n   return env;\n}\n\nFileLocks_ProcessData* Platform_getProcessLocks(pid_t pid) {\n   (void)pid;\n   return NULL;\n}\n\nvoid Platform_getFileDescriptors(double* used, double* max) {\n   Generic_getFileDescriptors_sysctl(used, max);\n}\n\nbool Platform_getDiskIO(DiskIOData* data) {\n\n   io_iterator_t drive_list;\n\n   /* Get the list of all drives */\n   if (IOServiceGetMatchingServices(iokit_port, IOServiceMatching(\"IOBlockStorageDriver\"), &drive_list))\n      return false;\n\n   uint64_t read_sum = 0, write_sum = 0, timeSpend_sum = 0;\n   uint64_t numDisks = 0;\n\n   io_registry_entry_t drive;\n   while ((drive = IOIteratorNext(drive_list)) != 0) {\n      CFMutableDictionaryRef properties_tmp = NULL;\n\n      /* Get the properties of this drive */\n      if (IORegistryEntryCreateCFProperties(drive, &properties_tmp, kCFAllocatorDefault, 0)) {\n         IOObjectRelease(drive);\n         IOObjectRelease(drive_list);\n         return false;\n      }\n\n      if (!properties_tmp) {\n         IOObjectRelease(drive);\n         continue;\n      }\n\n      CFDictionaryRef properties = properties_tmp;\n\n      /* Get the statistics of this drive */\n      CFDictionaryRef statistics = (CFDictionaryRef) CFDictionaryGetValue(properties, CFSTR(kIOBlockStorageDriverStatisticsKey));\n\n      if (!statistics) {\n         CFRelease(properties);\n         IOObjectRelease(drive);\n         continue;\n      }\n\n      numDisks++;\n\n      CFNumberRef number;\n      uint64_t value;\n\n      /* Get bytes read */\n      number = (CFNumberRef) CFDictionaryGetValue(statistics, CFSTR(kIOBlockStorageDriverStatisticsBytesReadKey));\n      if (number != 0) {\n         CFNumberGetValue(number, kCFNumberSInt64Type, &value);\n         read_sum += value;\n      }\n\n      /* Get bytes written */\n      number = (CFNumberRef) CFDictionaryGetValue(statistics, CFSTR(kIOBlockStorageDriverStatisticsBytesWrittenKey));\n      if (number != 0) {\n         CFNumberGetValue(number, kCFNumberSInt64Type, &value);\n         write_sum += value;\n      }\n\n      /* Get total read time (in ns) */\n      number = (CFNumberRef) CFDictionaryGetValue(statistics, CFSTR(kIOBlockStorageDriverStatisticsTotalReadTimeKey));\n      if (number != 0) {\n         CFNumberGetValue(number, kCFNumberSInt64Type, &value);\n         timeSpend_sum += value;\n      }\n\n      /* Get total write time (in ns) */\n      number = (CFNumberRef) CFDictionaryGetValue(statistics, CFSTR(kIOBlockStorageDriverStatisticsTotalWriteTimeKey));\n      if (number != 0) {\n         CFNumberGetValue(number, kCFNumberSInt64Type, &value);\n         timeSpend_sum += value;\n      }\n\n      CFRelease(properties);\n      IOObjectRelease(drive);\n   }\n\n   data->totalBytesRead = read_sum;\n   data->totalBytesWritten = write_sum;\n   data->totalMsTimeSpend = timeSpend_sum / 1e6; /* Convert from ns to ms */\n   data->numDisks = numDisks;\n\n   if (drive_list)\n      IOObjectRelease(drive_list);\n\n   return true;\n}\n\n/* Caution: Given that interfaces are dynamic, and it is not possible to get statistics on interfaces that no longer exist,\n   if some interface disappears between the time of two samples, the values of the second sample may be lower than those of\n   the first one. */\nbool Platform_getNetworkIO(NetworkIOData* data) {\n   int mib[6] = {CTL_NET,\n      PF_ROUTE, /* routing messages */\n      0, /* protocol number, currently always 0 */\n      0, /* select all address families */\n      NET_RT_IFLIST2, /* interface list with addresses */\n      0};\n\n   for (size_t retry = 0; retry < 4; retry++) {\n      size_t len = 0;\n\n      /* Determine len */\n      if (sysctl(mib, ARRAYSIZE(mib), NULL, &len, NULL, 0) < 0 || len == 0)\n         return false;\n\n      len += 16 * retry * retry * sizeof(struct if_msghdr2);\n      char *buf = xMalloc(len);\n\n      if (sysctl(mib, ARRAYSIZE(mib), buf, &len, NULL, 0) < 0) {\n         free(buf);\n         if (errno == ENOMEM && retry < 3)\n            continue;\n         else\n            return false;\n      }\n\n      uint64_t bytesReceived_sum = 0, packetsReceived_sum = 0, bytesTransmitted_sum = 0, packetsTransmitted_sum = 0;\n\n      for (char *next = buf; next < buf + len;) {\n         void *tmp = (void*) next;\n         struct if_msghdr *ifm = (struct if_msghdr*) tmp;\n\n         next += ifm->ifm_msglen;\n\n         if (ifm->ifm_type != RTM_IFINFO2)\n            continue;\n\n         struct if_msghdr2 *ifm2 = (struct if_msghdr2*) ifm;\n\n         if (ifm2->ifm_data.ifi_type != IFT_LOOP) { /* do not count loopback traffic */\n            bytesReceived_sum += ifm2->ifm_data.ifi_ibytes;\n            packetsReceived_sum += ifm2->ifm_data.ifi_ipackets;\n            bytesTransmitted_sum += ifm2->ifm_data.ifi_obytes;\n            packetsTransmitted_sum += ifm2->ifm_data.ifi_opackets;\n         }\n      }\n\n      data->bytesReceived = bytesReceived_sum;\n      data->packetsReceived = packetsReceived_sum;\n      data->bytesTransmitted = bytesTransmitted_sum;\n      data->packetsTransmitted = packetsTransmitted_sum;\n\n      free(buf);\n   }\n\n   return true;\n}\n\nvoid Platform_getBattery(double* percent, ACPresence* isOnAC) {\n   *percent = NAN;\n   *isOnAC = AC_ERROR;\n\n   CFArrayRef list = NULL;\n\n   CFTypeRef power_sources = IOPSCopyPowerSourcesInfo();\n   if (!power_sources)\n      goto cleanup;\n\n   list = IOPSCopyPowerSourcesList(power_sources);\n   if (!list)\n      goto cleanup;\n\n   double cap_current = 0.0;\n   double cap_max = 0.0;\n\n   /* Get the battery */\n   size_t len = CFArrayGetCount(list);\n   for (size_t i = 0; i < len; ++i) {\n      CFDictionaryRef power_source = IOPSGetPowerSourceDescription(power_sources, CFArrayGetValueAtIndex(list, i)); /* GET rule */\n\n      if (!power_source)\n         continue;\n\n      CFStringRef power_type = CFDictionaryGetValue(power_source, CFSTR(kIOPSTransportTypeKey)); /* GET rule */\n\n      if (kCFCompareEqualTo != CFStringCompare(power_type, CFSTR(kIOPSInternalType), 0))\n         continue;\n\n      /* Determine the AC state */\n      CFStringRef power_state = CFDictionaryGetValue(power_source, CFSTR(kIOPSPowerSourceStateKey));\n\n      if (*isOnAC != AC_PRESENT)\n         *isOnAC = (kCFCompareEqualTo == CFStringCompare(power_state, CFSTR(kIOPSACPowerValue), 0)) ? AC_PRESENT : AC_ABSENT;\n\n      /* Get the percentage remaining */\n      double tmp;\n      CFNumberGetValue(CFDictionaryGetValue(power_source, CFSTR(kIOPSCurrentCapacityKey)), kCFNumberDoubleType, &tmp);\n      cap_current += tmp;\n      CFNumberGetValue(CFDictionaryGetValue(power_source, CFSTR(kIOPSMaxCapacityKey)), kCFNumberDoubleType, &tmp);\n      cap_max += tmp;\n   }\n\n   if (cap_max > 0.0)\n      *percent = 100.0 * cap_current / cap_max;\n\ncleanup:\n   if (list)\n      CFRelease(list);\n\n   if (power_sources)\n      CFRelease(power_sources);\n}\n\nvoid Platform_gettime_monotonic(uint64_t* msec) {\n\n#ifdef HAVE_HOST_GET_CLOCK_SERVICE\n\n   clock_serv_t cclock;\n   mach_timespec_t mts;\n\n   host_get_clock_service(mach_host_self(), SYSTEM_CLOCK, &cclock);\n   clock_get_time(cclock, &mts);\n   mach_port_deallocate(mach_task_self(), cclock);\n\n   *msec = ((uint64_t)mts.tv_sec * 1000) + ((uint64_t)mts.tv_nsec / 1000000);\n\n#else\n\n   Generic_gettime_monotonic(msec);\n\n#endif\n\n}\n\nstatic void Platform_getOSRelease(char* buffer, size_t bufferLen) {\n   static const CFStringRef osfiles[] = {\n#ifdef OSRELEASEFILE\n      CFSTR(OSRELEASEFILE) /* Custom path for testing; undefined by default */,\n#endif\n      CFSTR(\"/System/Library/CoreServices/SystemVersion.plist\"),\n   };\n\n   if (!bufferLen)\n      return;\n\n   CFPropertyListRef plist = NULL;\n   for (size_t i = 0; i < ARRAYSIZE(osfiles); i++) {\n      CFURLRef url = CFURLCreateWithFileSystemPath(kCFAllocatorDefault, osfiles[i], kCFURLPOSIXPathStyle, /*isDirectory*/false);\n      if (!url)\n         continue;\n\n      CFReadStreamRef stream = CFReadStreamCreateWithFile(kCFAllocatorDefault, url);\n      CFRelease(url);\n\n      if (!stream)\n         continue;\n\n      bool canRead = CFReadStreamOpen(stream);\n      if (canRead) {\n         plist = CFPropertyListCreateWithStream(kCFAllocatorDefault, stream, 0, kCFPropertyListImmutable, NULL, NULL);\n         CFReadStreamClose(stream);\n      }\n      CFRelease(stream);\n\n      if (canRead)\n         break;\n   }\n\n   if (!plist)\n      goto fail;\n\n   CFStringRef str = NULL;\n   if (CFGetTypeID(plist) == CFDictionaryGetTypeID()) {\n      CFDictionaryRef dict = (CFDictionaryRef)plist;\n\n      CFStringRef productName = CFDictionaryGetValue(dict, CFSTR(\"ProductName\"));\n      CFStringRef productVersion = CFDictionaryGetValue(dict, CFSTR(\"ProductVersion\"));\n      CFStringRef separator = productName && productVersion ? CFSTR(\" \") : CFSTR(\"\");\n\n      if (!productName)\n         productName = CFSTR(\"\");\n      if (!productVersion)\n         productVersion = CFSTR(\"\");\n\n      str = CFStringCreateWithFormat(kCFAllocatorDefault, NULL, CFSTR(\"%@%@%@\"), productName, separator, productVersion);\n   }\n   CFRelease(plist);\n\n   if (!str)\n      goto fail;\n\n   bool ok = CFStringGetCString(str, buffer, bufferLen, kCFStringEncodingUTF8);\n   CFRelease(str);\n\n   if (ok)\n      return;\n\nfail:\n   buffer[0] = '\\0';\n}\n\nconst char* Platform_getRelease(void) {\n   return Generic_unameRelease(Platform_getOSRelease);\n}\n"
  },
  {
    "path": "darwin/Platform.h",
    "content": "#ifndef HEADER_Platform\n#define HEADER_Platform\n/*\nhtop - darwin/Platform.h\n(C) 2014 Hisham H. Muhammad\n(C) 2015 David C. Hunt\nReleased under the GNU GPLv2+, see the COPYING file\nin the source distribution for its full text.\n*/\n\n#include <stdbool.h>\n#include <sys/types.h>\n\n#include \"Action.h\"\n#include \"BatteryMeter.h\"\n#include \"CPUMeter.h\"\n#include \"DiskIOMeter.h\"\n#include \"Hashtable.h\"\n#include \"MemoryMeter.h\"\n#include \"NetworkIOMeter.h\"\n#include \"ProcessLocksScreen.h\"\n#include \"SignalsPanel.h\"\n#include \"CommandLine.h\"\n#include \"darwin/DarwinProcess.h\"\n#include \"generic/gettime.h\"\n#include \"generic/hostname.h\"\n#include \"generic/uname.h\"\n\n\nextern const ScreenDefaults Platform_defaultScreens[];\n\nextern const unsigned int Platform_numberOfDefaultScreens;\n\nextern const SignalItem Platform_signals[];\n\nextern const unsigned int Platform_numberOfSignals;\n\nextern const MemoryClass Platform_memoryClasses[];\n\nextern const unsigned int Platform_numberOfMemoryClasses;\n\nextern const MeterClass* const Platform_meterTypes[];\n\nbool Platform_init(void);\n\n// Converts ticks in the Mach \"timebase\" to nanoseconds.\n// See `mach_timebase_info`, as used to define the `Platform_nanosecondsPerMachTick*` constants.\nuint64_t Platform_machTicksToNanoseconds(uint64_t mach_ticks);\n\n// Converts \"scheduler ticks\" to nanoseconds.\n// See `sysconf(_SC_CLK_TCK)`, as used to define the `Platform_nanosecondsPerSchedulerTick` constant.\ndouble Platform_schedulerTicksToNanoseconds(const double scheduler_ticks);\n\nvoid Platform_done(void);\n\nvoid Platform_setBindings(Htop_Action* keys);\n\nint Platform_getUptime(void);\n\nvoid Platform_getLoadAverage(double* one, double* five, double* fifteen);\n\npid_t Platform_getMaxPid(void);\n\ndouble Platform_setCPUValues(Meter* mtr, unsigned int cpu);\n\nvoid Platform_setGPUValues(Meter* mtr, double* totalUsage, unsigned long long* totalGPUTimeDiff);\n\nvoid Platform_setMemoryValues(Meter* mtr);\n\nvoid Platform_setSwapValues(Meter* mtr);\n\nvoid Platform_setZfsArcValues(Meter* this);\n\nvoid Platform_setZfsCompressedArcValues(Meter* this);\n\nchar* Platform_getProcessEnv(pid_t pid);\n\nFileLocks_ProcessData* Platform_getProcessLocks(pid_t pid);\n\nvoid Platform_getFileDescriptors(double* used, double* max);\n\nbool Platform_getDiskIO(DiskIOData* data);\n\nbool Platform_getNetworkIO(NetworkIOData* data);\n\nvoid Platform_getBattery(double* percent, ACPresence* isOnAC);\n\nstatic inline void Platform_getHostname(char* buffer, size_t size) {\n   Generic_hostname(buffer, size);\n}\n\nconst char* Platform_getRelease(void);\n\nstatic inline const char* Platform_getFailedState(void) {\n   return NULL;\n}\n\n#define PLATFORM_LONG_OPTIONS\n\nstatic inline void Platform_longOptionsUsage(ATTR_UNUSED const char* name) { }\n\nstatic inline CommandLineStatus Platform_getLongOption(ATTR_UNUSED int opt, ATTR_UNUSED int argc, ATTR_UNUSED char** argv) {\n   return STATUS_ERROR_EXIT;\n}\n\nstatic inline void Platform_gettime_realtime(struct timeval* tv, uint64_t* msec) {\n   Generic_gettime_realtime(tv, msec);\n}\n\nvoid Platform_gettime_monotonic(uint64_t* msec);\n\nstatic inline Hashtable* Platform_dynamicMeters(void) {\n   return NULL;\n}\n\nstatic inline void Platform_dynamicMetersDone(ATTR_UNUSED Hashtable* table) { }\n\nstatic inline void Platform_dynamicMeterInit(ATTR_UNUSED Meter* meter) { }\n\nstatic inline void Platform_dynamicMeterUpdateValues(ATTR_UNUSED Meter* meter) { }\n\nstatic inline void Platform_dynamicMeterDisplay(ATTR_UNUSED const Meter* meter, ATTR_UNUSED RichString* out) { }\n\nstatic inline Hashtable* Platform_dynamicColumns(void) {\n   return NULL;\n}\n\nstatic inline void Platform_dynamicColumnsDone(ATTR_UNUSED Hashtable* table) { }\n\nstatic inline const char* Platform_dynamicColumnName(ATTR_UNUSED unsigned int key) {\n   return NULL;\n}\n\nstatic inline bool Platform_dynamicColumnWriteField(ATTR_UNUSED const Process* proc, ATTR_UNUSED RichString* str, ATTR_UNUSED unsigned int key) {\n   return false;\n}\n\nstatic inline Hashtable* Platform_dynamicScreens(void) {\n   return NULL;\n}\n\nstatic inline void Platform_defaultDynamicScreens(ATTR_UNUSED Settings* settings) { }\n\nstatic inline void Platform_addDynamicScreen(ATTR_UNUSED ScreenSettings* ss) { }\n\nstatic inline void Platform_addDynamicScreenAvailableColumns(ATTR_UNUSED Panel* availableColumns, ATTR_UNUSED const char* screen) { }\n\nstatic inline void Platform_dynamicScreensDone(ATTR_UNUSED Hashtable* screens) { }\n\n#endif\n"
  },
  {
    "path": "darwin/PlatformHelpers.c",
    "content": "/*\nhtop - darwin/PlatformHelpers.c\n(C) 2018 Pierre Malhaire, 2020-2021 htop dev team, 2021 Alexander Momchilov\nReleased under the GNU GPLv2+, see the COPYING file\nin the source distribution for its full text.\n*/\n\n#include \"darwin/PlatformHelpers.h\"\n\n#include <errno.h>\n#include <unistd.h>\n#include <stdio.h>\n#include <string.h>\n#include <sys/sysctl.h>\n\n#include \"CRT.h\"\n\n#ifdef HAVE_MACH_MACH_TIME_H\n#include <mach/mach_time.h>\n#endif\n\n\nvoid Platform_GetKernelVersion(KernelVersion* k) {\n   static KernelVersion cachedKernelVersion;\n\n   if (!cachedKernelVersion.major) {\n      // just in case it fails someday\n      cachedKernelVersion = (KernelVersion) { -1, -1, -1 };\n      char str[256] = {0};\n      size_t size = sizeof(str);\n      int ret = sysctlbyname(\"kern.osrelease\", str, &size, NULL, 0);\n      if (ret == 0) {\n         sscanf(str, \"%hd.%hd.%hd\", &cachedKernelVersion.major, &cachedKernelVersion.minor, &cachedKernelVersion.patch);\n      }\n   }\n   memcpy(k, &cachedKernelVersion, sizeof(cachedKernelVersion));\n}\n\nint Platform_CompareKernelVersion(KernelVersion v) {\n   struct KernelVersion actualVersion;\n   Platform_GetKernelVersion(&actualVersion);\n\n   if (actualVersion.major != v.major) {\n      return actualVersion.major - v.major;\n   }\n   if (actualVersion.minor != v.minor) {\n      return actualVersion.minor - v.minor;\n   }\n   if (actualVersion.patch != v.patch) {\n      return actualVersion.patch - v.patch;\n   }\n\n   return 0;\n}\n\nbool Platform_KernelVersionIsBetween(KernelVersion lowerBound, KernelVersion upperBound) {\n   return 0 <= Platform_CompareKernelVersion(lowerBound)\n      && Platform_CompareKernelVersion(upperBound) < 0;\n}\n\nvoid Platform_getCPUBrandString(char* cpuBrandString, size_t cpuBrandStringSize) {\n   if (sysctlbyname(\"machdep.cpu.brand_string\", cpuBrandString, &cpuBrandStringSize, NULL, 0) == -1) {\n      fprintf(stderr,\n         \"WARN: Unable to determine the CPU brand string.\\n\"\n         \"errno: %i, %s\\n\", errno, strerror(errno));\n\n      String_safeStrncpy(cpuBrandString, \"UNKNOWN!\", cpuBrandStringSize);\n   }\n}\n\n// Adapted from https://developer.apple.com/documentation/apple-silicon/about-the-rosetta-translation-environment\nbool Platform_isRunningTranslated(void) {\n   int ret = 0;\n   size_t size = sizeof(ret);\n   errno = 0;\n   if (sysctlbyname(\"sysctl.proc_translated\", &ret, &size, NULL, 0) == -1) {\n      if (errno == ENOENT)\n         return false;\n\n      fprintf(stderr,\n         \"WARN: Could not determine if this process was running in a translation environment like Rosetta 2.\\n\"\n         \"Assuming that we're not.\\n\"\n         \"errno: %i, %s\\n\", errno, strerror(errno));\n\n      return false;\n   }\n   return ret;\n}\n"
  },
  {
    "path": "darwin/PlatformHelpers.h",
    "content": "#ifndef HEADER_PlatformHelpers\n#define HEADER_PlatformHelpers\n/*\nhtop - darwin/PlatformHelpers.h\n(C) 2018 Pierre Malhaire, 2020-2022 htop dev team, 2021 Alexander Momchilov\nReleased under the GNU GPLv2+, see the COPYING file\nin the source distribution for its full text.\n*/\n\n#include <stdbool.h>\n#include <sys/types.h>\n\n\ntypedef struct KernelVersion {\n   short int major;\n   short int minor;\n   short int patch;\n} KernelVersion;\n\nvoid Platform_GetKernelVersion(KernelVersion* k);\n\n/* compare the given os version with the one installed returns:\n0 if equals the installed version\npositive value if less than the installed version\nnegative value if more than the installed version\n*/\nint Platform_CompareKernelVersion(KernelVersion v);\n\n// lowerBound <= currentVersion < upperBound\nbool Platform_KernelVersionIsBetween(KernelVersion lowerBound, KernelVersion upperBound);\n\nvoid Platform_getCPUBrandString(char* cpuBrandString, size_t cpuBrandStringSize);\n\nbool Platform_isRunningTranslated(void);\n\n#endif\n"
  },
  {
    "path": "darwin/ProcessField.h",
    "content": "#ifndef HEADER_DarwinProcessField\n#define HEADER_DarwinProcessField\n/*\nhtop - darwin/ProcessField.h\n(C) 2020 htop dev team\nReleased under the GNU GPLv2+, see the COPYING file\nin the source distribution for its full text.\n*/\n\n\n#define PLATFORM_PROCESS_FIELDS  \\\n   TRANSLATED = 100,             \\\n                                 \\\n   DUMMY_BUMP_FIELD = CWD,       \\\n   // End of list\n\n\n#endif /* HEADER_DarwinProcessField */\n"
  },
  {
    "path": "docs/ai-contributions-policy.md",
    "content": "# AI-Assisted Contributions Policy for htop\nv1.0, 2026-02-18\n\n1.  You **MAY** use AI assistance for contributing to htop, as long as you follow the principles described below.\n\n2.  **Accountability**:\n\n    You **MUST** take the responsibility for your contribution.\n\n    Contributing to htop means vouching for the quality, license compliance, and utility of your submission.\n\n    All contributions, whether from a human author or assisted by large language models (LLMs) or other generative AI tools, must meet the project’s standards for inclusion.\n\n    The contributor is always the author and is fully accountable for the entirety of these contributions.\n\n3.  **Transparency**:\n\n    You **MUST** disclose the use of AI tools when the significant part of the contribution is taken from a tool without changes.\n\n    You **SHOULD** disclose the other uses of AI tools, where it might be useful.\n\n    Routine use of assistive tools for correcting grammar and spelling, or for clarifying language, does not require disclosure.\n\n    -   Disclosures are made where authorship is normally indicated. For contributions tracked in git, the recommended method is an `Assisted-by:` commit message trailer.\n\n    -   Examples:\n\n        -   `Assisted-by: generic LLM chatbot`\n\n        -   `Assisted-by: ChatGPTv5`\n\n    For contributions outside git commits, disclosure may include document preambles, design file metadata or `Assisted-by:` lines on issues.\n\n4.  **Contribution Evaluation**:\n\n    AI tools may be used to assist human reviewers by providing analysis and suggestions.\n\n    You **MUST NOT** use AI as the sole or final arbiter in making a substantive or subjective judgment on a contribution.\n\n    This does not prohibit the use of automated tooling for objective technical validation, such as CI/CD pipelines, automated testing, or spam filtering.\n\n    The final accountability for accepting a contribution, even if implemented by an automated system, always rests with the human contributor who authorizes the action.\n\nThis policy has been adapted for htop use from the Fedora policy v1.0 published at <https://docs.fedoraproject.org/en-US/council/policy/ai-contribution-policy/>.\n\nThe key words “MAY”, “MUST”, “MUST NOT”, and “SHOULD” in this document are to be interpreted as described in [RFC 2119](https://datatracker.ietf.org/doc/html/rfc2119).\n"
  },
  {
    "path": "docs/styleguide.md",
    "content": "htop coding style guide\n=======================\n\nNaming conventions\n------------------\n\nNames are important to convey what all those things inside the project are for.\nFilenames for source code traditionally used camel-case naming with the first letter written in uppercase.\nThe file extension is always lowercase.\n\nThe only exceptions here are `htop.c` and `pcp-htop.c`, which contain the main entrance points into the code.\n\nFolders for e.g. platform-specific code or complex features spawning multiple files are written in lowercase, e.g. `linux`, `freebsd`, `zfs`.\n\nInside files, the naming somewhat depends on the context.\nFunction names should include a camel-case prefix before the actual name, separated by an underscore.\nWhile this prefix usually coincides with the module name, this is not required, yet strongly advised.\nOne important exception to this rule are the memory management and the string utility functions from `XUtils.h`.\n\nVariable names inside functions should be short and precise.\nUsing `i` for some loop counter is totally fine, using `someCounterValueForThisSimpleLoop` is not.\nOn the other hand, when you need to hold global storage try to keep this local to your module, i.e. declare such variables `static` within the C source file.\nOnly if your variable really needs to be visible for the whole project (which is really rare) it deserves a declaration in the header, marked `extern`.\n\nFile content structure\n----------------------\n\nThe content within each file is usually structured according to the following loose template:\n\n* Copyright declaration\n* Inclusion of used headers\n* Necessary data structures and forward declarations\n* Static module-private function implementations\n* Externally visible function implementations\n* Externally visible constant structures (pseudo-OOP definitions)\n\nFor header files header guards based on `#ifdef` should be used.\nThese header guards are historically placed **before** the Copyright declaration.\nStick to that for consistency please.\n\nExample:\n\n```c\n#ifndef HEADER_FILENAME\n#define HEADER_FILENAME\n/*\nhtop - Filename.h\n(C) 2021 htop dev team\nReleased under the GNU GPLv2+, see the COPYING file\nin the source distribution for its full text.\n*/\n```\n\nImport and use of headers\n-------------------------\n\nWe use the GPLv2+ as a shorthand indication that we release `htop` under the GNU Public license version 2 but are totally fine with users opting to apply the \"any later version\" clause.\n\nEvery file should import headers for all symbols it's using.\nThus when using a symbol from a header, even if that symbol is already imported by something else you use, you should declare an import for that header.\nDoing so allows for easier restructuring of the code when things need to be moved around.\nIf you are unsure if all necessary headers are included you might use IWYU to tell you.\n\nThe list of includes should be the first thing in the file, after the copyright comment and be followed by two blank lines.\nThe include list should be in the following order, with each group separated by one blank line:\n\n1. `include \"config.h\" // IWYU pragma: keep` if the global configuration\n    from `automake`&`autoconfigure` or any of the feature guards for C library headers\n    (like `__GNU_SOURCE`) are required, optional otherwise. Beware of the IWYU comment.\n2. Accompanying module header file (for C source files only, missing inside headers)\n3. List of used system headers (non-conditional includes)\n4. List of used program headers\n5. Conditionally included header files, system headers first\n\nThe list of headers should be sorted with includes from subdirectories following after files inside their parent directory.\nThus `unistd.h` sorts before `sys/time.h`.\n\nWhen `XUtils.h` is used by the module itself or any of its included headers, the C source file must include `config.h` in the manner noted above.\nFailure to do so will cause a compilation error (sanity check inside `XUtils.h`) or may result in other, hard-to-debug compilation issues.\nThe include for `config.h` is only ever placed in the C source file and never in any header file.\nFor further details see PR #1337 in our issue tracker.\n\nSymbol Exports\n--------------\n\nExports of symbols should be used sparingly.\nThus unless a function you write is intended to become public API of a module you should mark it as `static`.\nIf a function should be public API an appropriate declaration for that function has to be placed in the accompanying header file.\n\nPlease avoid function-like macros, in particular when exporting them in a header file.\nThey have several downsides (re-evaluation of arguments, syntactic escapes, weak typing) for which usually a better alternative like an actual function exists.\nFurthermore when using function-like `define`s you may need to mark certain headers for IWYU so tracking of used symbols works.\n\nMemory Management\n-----------------\n\nWhen allocating memory make sure to free resources properly.\nFor allocation this project uses a set of tiny wrappers around the common functions `malloc`, `calloc` and `realloc` named `xMalloc`, `xCalloc` and `xRealloc`.\nThese functions check that memory allocation worked and error out on failure.\n\nAllocation functions assert the amount of memory requested is non-zero.\nTrying to allocate 0 bytes of memory is an error.\nPlease use the explicit value `NULL` in this case and handle it in your code accordingly.\n\nIf the allocated block of memory is intended to hold an array of values, you should use the alternate functions `xReallocArray` and `xReallocArrayZero` instead.\n\nWorking with Strings\n--------------------\n\nIt is strongly encouraged to use the functions starting with `String_` from `XUtils.h` for working with zero-terminated strings as these make the API easier to use and are intended to make the intent of your code easier to grasp.\n\nThus instead of `!strcmp(foo, \"foo\")` it's preferred to use `String_eq(foo, \"foo\")` instead.\nWhile sometimes a bit more to type, this helps a lot with making the code easier to follow.\n\nStyling the code\n----------------\n\nNow for the style details that can mostly be automated: Indentation, spacing and bracing.\nWhile there is no definitive code style we use, a set of rules loosely enforced has evolved.\n\nIndentation in the code is done by three (3) spaces. No tabs are used. Ever.\n\nBefore and after keywords should be a space, e.g. `if (condition)` and `do { … } while (condition);`.\n\nAfter opening and before closing braces a new line should be started.\nContent of such encoded blocks should be indented one level further than their enclosing block.\n\nIf a line of source code becomes too long, or when structuring it into multiple parts for clarity, the continuation line should be indented one more level than the first line it continues:\n\n```c\nif (very_long_condition &&\n   another_very_complex_expression &&\n   something_else_to_check) {\n   // Code follows as normal ...\n} else {\n\n}\n```\n\nBraces around simple single code statements (return, break, continue, goto, trivial assignments) are usually left out.\n\n```c\nif (answer)\n   return 42;\n```\n\nIf it helps readability (with several unrelated if statements in a row) or to avoid dangling-else situations braces can be added.\n\nControl flow statements and the instruction making up their body should not be put on a single line,\ni.e. after the condition of an if statement a new line should be inserted and the body indented accordingly.\n\n```c\nif (answer)\n   return 42;\nelse if (again)\n   continue;\nelse\n   break;\n```\n\nWhen the statements that form control flow constructs are complex (e.g. more than just a simple assignment or jump) or need explanatory comments you should use braces.\nIf any block of such a statement uses braces then all blocks of that statement must have braces too.\n\n```c\nif ((fd = open(filename, O_RDONLY)) >= 0 &&\n   (amtRead = read(buffer, sizeof(buffer))) > 0) {\n   // Parse the information further ...\n   metric = handleBufferContent(buffer, amtRead);\n} else {\n   metric = -1;\n}\n\nif (fd >= 0)\n   close(fd);\n```\n\nWhile the existing code base isn't fully consistent with this code style yet it is strongly recommended that new code follows these rules.\nAdapting surrounding code near places you need to touch is encouraged.\nTry to split off such changes into a separate, clean-up only commit to reduce noise while reviewing your changes.\n\nWhen writing your code consistency with the surrounding codebase is favoured.\n\nDon't shy away from leaving (single) blank lines to separate different groups of related statements.\nThey can be a great asset to structure the flow of a method.\n\n```c\n   int stuff = 0;\n\n   // If asked for gives only half the answer ...\n   if (param)\n      stuff = 21;\n\n   // Compute the answer\n   stuff %= 2;\n   stuff *= 4;\n   stuff *= 5;\n   stuff += !!stuff;\n   stuff *= 2;\n\n   return stuff;\n```\n\nIf you want to automate formatting your code, the following command gives you a good baseline of how it should look:\n\n```bash\nastyle -r -xb -s3 -p -xg -c -k1 -W1 -H \\*.c \\*.h\n```\n\nWorking with System APIs\n------------------------\n\nPlease try to be considerate when using modern platform features.\nWhile they usually provide quite a performance gain or make your life easier, it is beneficial if `htop` runs on rather ancient systems.\nThus when you want to use such features you should try to have an alternative available that works as a fallback.\n\nAn example for this are functions like `fstatat` on Linux that extend the kernel API on modern systems.\nBut even though it has been around for over a decade you are asked to provide a POSIX alternative like emulating such calls by `fstat` if this is doable.\nIf an alternative cannot be provided you should gracefully downgrade. That could make a feature that requires this shiny API unavailable on systems that lack support for that API. Make this case visually clear to the user.\n\nIn general, code written for the project should be able to compile on any C99-compliant compiler.\n\nWriting documentation\n---------------------\n\nThe primary user documentation should be the man file which you can find in `htop.1.in`.\n\nAdditional documentation, like this file, should be written in gh-style markdown.\nMake each sentence one line.\nMarkdown will combine these in output formats.\nIt does only insert a paragraph if you insert a blank line into the source file.\nThis way git can better diff and present the changes when documentation is altered.\n\nDocumentation files reside in the `docs/` directory and have a `.md` extension.\n\nWriting pull-requests (PRs)\n---------------------------\n\nWhen writing your PR or patch, the set of patches should contain the minimal changes required.\nEach patch in itself should ideally be self-contained and runable.\nThe commit comment should be descriptive (`Updated Foo.c` is not), explain what the changes are and describe why they were made.\nWhile in trivial cases a short subject suffices, more complex changes might warrant a longer description and explanation of the rationale behind the changes.\n\nA PR should not contain any merge commits.\nTo follow the upstream branch of your PR rebase your work instead.\n\nAvoid small commits that just fix typos that another of your commits introduced.\nInstead squash those changes in the appropriate commit that introduced that mistake.\nGit offers `git commit --fixup=<commit>` and `git rebase -i --autosquash` to help you with this.\n\nYour final PR should contain a minimal set of reasonably sized commits that by themselves are easy to review.\nIf you open a PR you need to follow up to resolve any comments/change requests.\nOtherwise it may be closed without merging.\n\nRebase early. Rebase often.\n\nPlease check the [AI-Assisted Contributions Policy](ai-contributions-policy.md) for requirements and mandatory disclosure in your commits and PRs if you use AI assistance in creating your work.\n"
  },
  {
    "path": "dragonflybsd/DragonFlyBSDMachine.c",
    "content": "/*\nhtop - DragonFlyBSDMachine.c\n(C) 2014 Hisham H. Muhammad\n(C) 2017 Diederik de Groot\nReleased under the GNU GPLv2+, see the COPYING file\nin the source distribution for its full text.\n*/\n\n#include \"dragonflybsd/DragonFlyBSDMachine.h\"\n\n#include <fcntl.h>\n#include <limits.h>\n#include <stddef.h>\n#include <stdlib.h>\n#include <string.h>\n#include <unistd.h>\n#include <sys/types.h>\n#include <sys/sysctl.h>\n#include <sys/user.h>\n#include <sys/param.h>\n\n#include \"CRT.h\"\n#include \"Macros.h\"\n\n#include \"dragonflybsd/DragonFlyBSDProcess.h\"\n\n\nstatic int MIB_hw_physmem[2];\nstatic int MIB_vm_stats_vm_v_page_count[4];\n\nstatic int MIB_vm_stats_vm_v_wire_count[4];\nstatic int MIB_vm_stats_vm_v_active_count[4];\nstatic int MIB_vm_stats_vm_v_cache_count[4];\nstatic int MIB_vm_stats_vm_v_inactive_count[4];\n\nstatic int MIB_vfs_bufspace[2];\n\nstatic int MIB_kern_cp_time[2];\nstatic int MIB_kern_cp_times[2];\n\nMachine* Machine_new(UsersTable* usersTable, uid_t userId) {\n   size_t len;\n   char errbuf[_POSIX2_LINE_MAX];\n   DragonFlyBSDMachine* this = xCalloc(1, sizeof(DragonFlyBSDMachine));\n   Machine* super = &this->super;\n\n   Machine_init(super, usersTable, userId);\n\n   // physical memory in system: hw.physmem\n   // physical page size: hw.pagesize\n   // usable pagesize : vm.stats.vm.v_page_size\n   len = 2; sysctlnametomib(\"hw.physmem\", MIB_hw_physmem, &len);\n\n   len = sizeof(this->pageSize);\n   if (sysctlbyname(\"vm.stats.vm.v_page_size\", &this->pageSize, &len, NULL, 0) == -1)\n      CRT_fatalError(\"Cannot get pagesize by sysctl\");\n   this->pageSizeKb = this->pageSize / ONE_K;\n\n   // usable page count vm.stats.vm.v_page_count\n   // actually usable memory : vm.stats.vm.v_page_count * vm.stats.vm.v_page_size\n   len = 4; sysctlnametomib(\"vm.stats.vm.v_page_count\", MIB_vm_stats_vm_v_page_count, &len);\n\n   len = 4; sysctlnametomib(\"vm.stats.vm.v_wire_count\", MIB_vm_stats_vm_v_wire_count, &len);\n   len = 4; sysctlnametomib(\"vm.stats.vm.v_active_count\", MIB_vm_stats_vm_v_active_count, &len);\n   len = 4; sysctlnametomib(\"vm.stats.vm.v_cache_count\", MIB_vm_stats_vm_v_cache_count, &len);\n   len = 4; sysctlnametomib(\"vm.stats.vm.v_inactive_count\", MIB_vm_stats_vm_v_inactive_count, &len);\n\n   len = 2; sysctlnametomib(\"vfs.bufspace\", MIB_vfs_bufspace, &len);\n\n   int cpus = 1;\n   len = sizeof(cpus);\n   if (sysctlbyname(\"hw.ncpu\", &cpus, &len, NULL, 0) != 0) {\n      cpus = 1;\n   }\n\n   size_t sizeof_cp_time_array = sizeof(unsigned long) * CPUSTATES;\n   len = 2; sysctlnametomib(\"kern.cp_time\", MIB_kern_cp_time, &len);\n   this->cp_time_o = xCalloc(CPUSTATES, sizeof(unsigned long));\n   this->cp_time_n = xCalloc(CPUSTATES, sizeof(unsigned long));\n   len = sizeof_cp_time_array;\n\n   // fetch initial single (or average) CPU clicks from kernel\n   sysctl(MIB_kern_cp_time, 2, this->cp_time_o, &len, NULL, 0);\n\n   // on smp box, fetch rest of initial CPU's clicks\n   if (cpus > 1) {\n      len = 2; sysctlnametomib(\"kern.cp_times\", MIB_kern_cp_times, &len);\n      this->cp_times_o = xCalloc(cpus, sizeof_cp_time_array);\n      this->cp_times_n = xCalloc(cpus, sizeof_cp_time_array);\n      len = cpus * sizeof_cp_time_array;\n      sysctl(MIB_kern_cp_times, 2, this->cp_times_o, &len, NULL, 0);\n   }\n\n   super->existingCPUs = MAXIMUM(cpus, 1);\n   // TODO: support offline CPUs and hot swapping\n   super->activeCPUs = super->existingCPUs;\n\n   if (cpus == 1 ) {\n      this->cpus = xRealloc(this->cpus, sizeof(CPUData));\n   } else {\n      // on smp we need CPUs + 1 to store averages too (as kernel kindly provides that as well)\n      this->cpus = xRealloc(this->cpus, (super->existingCPUs + 1) * sizeof(CPUData));\n   }\n\n   len = sizeof(this->kernelFScale);\n   if (sysctlbyname(\"kern.fscale\", &this->kernelFScale, &len, NULL, 0) == -1 || this->kernelFScale <= 0) {\n      //sane default for kernel provided CPU percentage scaling, at least on x86 machines, in case this sysctl call failed\n      this->kernelFScale = 2048;\n   }\n\n   this->kd = kvm_openfiles(NULL, \"/dev/null\", NULL, 0, errbuf);\n   if (this->kd == NULL) {\n      CRT_fatalError(\"kvm_openfiles() failed\");\n   }\n\n   return super;\n}\n\nvoid Machine_delete(Machine* super) {\n   DragonFlyBSDMachine* this = (DragonFlyBSDMachine*) super;\n\n   Machine_done(super);\n\n   if (this->kd) {\n      kvm_close(this->kd);\n   }\n\n   if (this->jails) {\n      Hashtable_delete(this->jails);\n   }\n\n   free(this->cp_time_o);\n   free(this->cp_time_n);\n   free(this->cp_times_o);\n   free(this->cp_times_n);\n   free(this->cpus);\n\n   free(this);\n}\n\nstatic void DragonFlyBSDMachine_scanCPUTime(Machine* super) {\n   const DragonFlyBSDMachine* this = (DragonFlyBSDMachine*) super;\n\n   unsigned int cpus   = super->existingCPUs;  // actual CPU count\n   unsigned int maxcpu = cpus;              // max iteration (in case we have average + smp)\n   int cp_times_offset;\n\n   assert(cpus > 0);\n\n   size_t sizeof_cp_time_array;\n\n   unsigned long* cp_time_n; // old clicks state\n   unsigned long* cp_time_o; // current clicks state\n\n   unsigned long cp_time_d[CPUSTATES];\n   double        cp_time_p[CPUSTATES];\n\n   // get averages or single CPU clicks\n   sizeof_cp_time_array = sizeof(unsigned long) * CPUSTATES;\n   sysctl(MIB_kern_cp_time, 2, this->cp_time_n, &sizeof_cp_time_array, NULL, 0);\n\n   // get rest of CPUs\n   if (cpus > 1) {\n      // on smp systems DragonFlyBSD kernel concats all CPU states into one long array in\n      // kern.cp_times sysctl OID\n      // we store averages in dfpl->cpus[0], and actual cores after that\n      maxcpu = cpus + 1;\n      sizeof_cp_time_array = cpus * sizeof(unsigned long) * CPUSTATES;\n      sysctl(MIB_kern_cp_times, 2, this->cp_times_n, &sizeof_cp_time_array, NULL, 0);\n   }\n\n   for (unsigned int i = 0; i < maxcpu; i++) {\n      if (cpus == 1) {\n         // single CPU box\n         cp_time_n = this->cp_time_n;\n         cp_time_o = this->cp_time_o;\n      } else {\n         if (i == 0 ) {\n            // average\n            cp_time_n = this->cp_time_n;\n            cp_time_o = this->cp_time_o;\n         } else {\n            // specific smp cores\n            cp_times_offset = i - 1;\n            cp_time_n = this->cp_times_n + (cp_times_offset * CPUSTATES);\n            cp_time_o = this->cp_times_o + (cp_times_offset * CPUSTATES);\n         }\n      }\n\n      // diff old vs new\n      unsigned long long total_o = 0;\n      unsigned long long total_n = 0;\n      unsigned long long total_d = 0;\n      for (int s = 0; s < CPUSTATES; s++) {\n         cp_time_d[s] = cp_time_n[s] - cp_time_o[s];\n         total_o += cp_time_o[s];\n         total_n += cp_time_n[s];\n      }\n\n      // totals\n      total_d = total_n - total_o;\n      if (total_d < 1 ) {\n         total_d = 1;\n      }\n\n      // save current state as old and calc percentages\n      for (int s = 0; s < CPUSTATES; ++s) {\n         cp_time_o[s] = cp_time_n[s];\n         cp_time_p[s] = ((double)cp_time_d[s]) / ((double)total_d) * 100;\n      }\n\n      CPUData* cpuData = &(this->cpus[i]);\n      cpuData->userPercent      = cp_time_p[CP_USER];\n      cpuData->nicePercent      = cp_time_p[CP_NICE];\n      cpuData->systemPercent    = cp_time_p[CP_SYS];\n      cpuData->irqPercent       = cp_time_p[CP_INTR];\n      cpuData->systemAllPercent = cp_time_p[CP_SYS] + cp_time_p[CP_INTR];\n      // this one is not really used, but we store it anyway\n      cpuData->idlePercent      = cp_time_p[CP_IDLE];\n   }\n}\n\nstatic void DragonFlyBSDMachine_scanMemoryInfo(Machine* super) {\n   DragonFlyBSDMachine* this = (DragonFlyBSDMachine*) super;\n\n   // @etosan:\n   // memory counter relationships seem to be these:\n   //  total = active + wired + inactive + cache + free\n   //  htop_used (unavail to anybody) = active + wired\n   //  htop_cache (for cache meter)   = buffers + cache\n   //  user_free (avail to procs)     = buffers + inactive + cache + free\n\n   u_long totalMem;\n   unsigned long long int memActive, memWire, memInactive, memCache;\n   long long int buffersMem;\n   size_t len;\n\n   // total memory\n   len = sizeof(totalMem);\n   if ((sysctl(MIB_hw_physmem, 2, &(totalMem), &len, NULL, 0) == 0) && (totalMem > 0))\n      super->totalMem = totalMem / 1024;\n   else\n      super->totalMem = 0;\n\n   // \"active\" pages\n   len = sizeof(memActive);\n   if ((sysctl(MIB_vm_stats_vm_v_active_count, 4, &(memActive), &len, NULL, 0) == 0) && (memActive > 0))\n      this->activeMem = memActive * this->pageSizeKb;\n   else\n      this->activeMem = 0;\n\n   // \"wired\" pages\n   len = sizeof(memWire);\n   if ((sysctl(MIB_vm_stats_vm_v_wire_count, 4, &(memWire), &len, NULL, 0) == 0) && (memWire > 0))\n      this->wiredMem = memWire * this->pageSizeKb;\n   else\n      this->wiredMem = 0;\n\n   // \"inactive\" pages\n   len = sizeof(memInactive);\n   if ((sysctl(MIB_vm_stats_vm_v_inactive_count, 4, &(memInactive), &len, NULL, 0) == 0) && (memInactive > 0))\n      this->inactiveMem = memInactive * this->pageSizeKb;\n   else\n      this->inactiveMem = 0;\n\n   // \"cache\" pages\n   len = sizeof(memCache);\n   if ((sysctl(MIB_vm_stats_vm_v_cache_count, 4, &(memCache), &len, NULL, 0) == 0) && (memCache > 0))\n      this->cacheMem = memCache * this->pageSizeKb;\n   else\n      this->cacheMem = 0;\n\n   // \"buffers\" pages (separate read, should be deducted from 'wired')\n   len = sizeof(buffersMem);\n   if ((sysctl(MIB_vfs_bufspace, 2, &(buffersMem), &len, NULL, 0) == 0) && (buffersMem > 0))\n      this->buffersMem = buffersMem / 1024;\n   else\n      this->buffersMem = 0;\n   this->wiredMem -= this->buffersMem; // subtract (NB: \"buffers\" can't be larger than \"wired\")\n\n   // swap\n   struct kvm_swap swap[16];\n   int nswap = kvm_getswapinfo(this->kd, swap, ARRAYSIZE(swap), 0);\n   super->totalSwap = 0;\n   super->usedSwap = 0;\n   for (int i = 0; i < nswap; i++) {\n      super->totalSwap += swap[i].ksw_total;\n      super->usedSwap += swap[i].ksw_used;\n   }\n   super->totalSwap *= this->pageSizeKb;\n   super->usedSwap *= this->pageSizeKb;\n}\n\nstatic void DragonFlyBSDMachine_scanJails(DragonFlyBSDMachine* this) {\n   size_t len;\n   char* jails; /* Jail list */\n   char* curpos;\n   char* nextpos;\n\n   if (sysctlbyname(\"jail.list\", NULL, &len, NULL, 0) == -1) {\n      CRT_fatalError(\"initial sysctlbyname / jail.list failed\");\n   }\n\nretry:\n   if (len == 0)\n      return;\n\n   jails = xMalloc(len);\n\n   if (sysctlbyname(\"jail.list\", jails, &len, NULL, 0) == -1) {\n      if (errno == ENOMEM) {\n         free(jails);\n         goto retry;\n      }\n      CRT_fatalError(\"sysctlbyname / jail.list failed\");\n   }\n\n   if (this->jails) {\n      Hashtable_delete(this->jails);\n   }\n\n   this->jails = Hashtable_new(20, true);\n   curpos = jails;\n   while (curpos) {\n      int jailid;\n      char* str_hostname;\n\n      nextpos = strchr(curpos, '\\n');\n      if (nextpos) {\n         *nextpos++ = 0;\n      }\n\n      jailid = atoi(strtok(curpos, \" \"));\n      str_hostname = strtok(NULL, \" \");\n\n      char* jname = (char*) (Hashtable_get(this->jails, jailid));\n      if (jname == NULL) {\n         jname = xStrdup(str_hostname);\n         Hashtable_put(this->jails, jailid, jname);\n      }\n\n      curpos = nextpos;\n   }\n\n   free(jails);\n}\n\nchar* DragonFlyBSDMachine_readJailName(const DragonFlyBSDMachine* host, int jailid) {\n   char* hostname;\n   char* jname;\n\n   if (jailid != 0 && host->jails && (hostname = (char*)Hashtable_get(host->jails, jailid))) {\n      jname = xStrdup(hostname);\n   } else {\n      jname = xStrdup(\"-\");\n   }\n\n   return jname;\n}\n\nvoid Machine_scan(Machine* super) {\n   DragonFlyBSDMachine* this = (DragonFlyBSDMachine*) super;\n\n   DragonFlyBSDMachine_scanMemoryInfo(super);\n   DragonFlyBSDMachine_scanCPUTime(super);\n   DragonFlyBSDMachine_scanJails(this);\n}\n\nbool Machine_isCPUonline(const Machine* host, unsigned int id) {\n   assert(id < host->existingCPUs);\n   (void)host; (void)id;\n\n   // TODO: Support detecting online / offline CPUs.\n   return true;\n}\n"
  },
  {
    "path": "dragonflybsd/DragonFlyBSDMachine.h",
    "content": "#ifndef HEADER_DragonFlyBSDMachine\n#define HEADER_DragonFlyBSDMachine\n/*\nhtop - DragonFlyBSDMachine.h\n(C) 2014 Hisham H. Muhammad\n(C) 2017 Diederik de Groot\nReleased under the GNU GPLv2+, see the COPYING file\nin the source distribution for its full text.\n*/\n\n#include <sys/types.h>  // required for kvm.h\n#include <kvm.h>\n#include <osreldate.h>\n#include <stdbool.h>\n#include <sys/jail.h>\n#include <sys/param.h>\n#include <sys/resource.h>\n#include <sys/uio.h>\n\n#include \"Hashtable.h\"\n#include \"Machine.h\"\n#include \"ProcessTable.h\"\n#include \"UsersTable.h\"\n\n\ntypedef struct CPUData_ {\n   double userPercent;\n   double nicePercent;\n   double systemPercent;\n   double irqPercent;\n   double idlePercent;\n   double systemAllPercent;\n} CPUData;\n\ntypedef struct DragonFlyBSDMachine_ {\n   Machine super;\n   kvm_t* kd;\n\n   Hashtable* jails;\n\n   int pageSize;\n   int pageSizeKb;\n   int kernelFScale;\n\n   memory_t wiredMem;\n   memory_t buffersMem;\n   memory_t activeMem;\n   memory_t inactiveMem;\n   memory_t cacheMem;\n\n   CPUData* cpus;\n\n   unsigned long* cp_time_o;\n   unsigned long* cp_time_n;\n\n   unsigned long* cp_times_o;\n   unsigned long* cp_times_n;\n} DragonFlyBSDMachine;\n\nchar* DragonFlyBSDMachine_readJailName(const DragonFlyBSDMachine* host, int jailid);\n\n#endif\n"
  },
  {
    "path": "dragonflybsd/DragonFlyBSDProcess.c",
    "content": "/*\nhtop - dragonflybsd/DragonFlyBSDProcess.c\n(C) 2015 Hisham H. Muhammad\n(C) 2017 Diederik de Groot\nReleased under the GNU GPLv2+, see the COPYING file\nin the source distribution for its full text.\n*/\n\n#include \"dragonflybsd/DragonFlyBSDProcess.h\"\n\n#include <stdlib.h>\n#include <string.h>\n#include <unistd.h>\n#include <sys/syscall.h>\n\n#include \"CRT.h\"\n\n#include \"dragonflybsd/Platform.h\"\n\n\nconst ProcessFieldData Process_fields[LAST_PROCESSFIELD] = {\n   [0] = { .name = \"\", .title = NULL, .description = NULL, .flags = 0, },\n   [PID] = { .name = \"PID\", .title = \"PID\", .description = \"Process/thread ID\", .flags = 0, .pidColumn = true, },\n   [COMM] = { .name = \"Command\", .title = \"Command \", .description = \"Command line (insert as last column only)\", .flags = 0, },\n   [STATE] = { .name = \"STATE\", .title = \"S \", .description = \"Process state (S sleeping (<20s), I Idle, Q Queued for Run, R running, D disk, Z zombie, T traced, W paging, B Blocked, A AskedPage, C Core, J Jailed)\", .flags = 0, },\n   [PPID] = { .name = \"PPID\", .title = \"PPID\", .description = \"Parent process ID\", .flags = 0, .pidColumn = true, },\n   [PGRP] = { .name = \"PGRP\", .title = \"PGRP\", .description = \"Process group ID\", .flags = 0, .pidColumn = true, },\n   [SESSION] = { .name = \"SESSION\", .title = \"SID\", .description = \"Process's session ID\", .flags = 0, .pidColumn = true, },\n   [TTY] = { .name = \"TTY\", .title = \"TTY      \", .description = \"Controlling terminal\", .flags = 0, },\n   [TPGID] = { .name = \"TPGID\", .title = \"TPGID\", .description = \"Process ID of the fg process group of the controlling terminal\", .flags = 0, .pidColumn = true, },\n   [MINFLT] = { .name = \"MINFLT\", .title = \"     MINFLT \", .description = \"Number of minor faults which have not required loading a memory page from disk\", .flags = 0, .defaultSortDesc = true, },\n   [MAJFLT] = { .name = \"MAJFLT\", .title = \"     MAJFLT \", .description = \"Number of major faults which have required loading a memory page from disk\", .flags = 0, .defaultSortDesc = true, },\n   [PRIORITY] = { .name = \"PRIORITY\", .title = \"PRI \", .description = \"Kernel's internal priority for the process\", .flags = 0, },\n   [NICE] = { .name = \"NICE\", .title = \" NI \", .description = \"Nice value (the higher the value, the more it lets other processes take priority)\", .flags = 0, },\n   [STARTTIME] = { .name = \"STARTTIME\", .title = \"START \", .description = \"Time the process was started\", .flags = 0, },\n   [ELAPSED] = { .name = \"ELAPSED\", .title = \"ELAPSED  \", .description = \"Time since the process was started\", .flags = 0, },\n   [PROCESSOR] = { .name = \"PROCESSOR\", .title = \"CPU \", .description = \"Id of the CPU the process last executed on\", .flags = 0, },\n   [M_VIRT] = { .name = \"M_VIRT\", .title = \" VIRT \", .description = \"Total program size in virtual memory\", .flags = 0, .defaultSortDesc = true, },\n   [M_RESIDENT] = { .name = \"M_RESIDENT\", .title = \"  RES \", .description = \"Resident set size, size of the text and data sections, plus stack usage\", .flags = 0, .defaultSortDesc = true, },\n   [ST_UID] = { .name = \"ST_UID\", .title = \"UID\", .description = \"User ID of the process owner\", .flags = 0, },\n   [PERCENT_CPU] = { .name = \"PERCENT_CPU\", .title = \" CPU%\", .description = \"Percentage of the CPU time the process used in the last sampling\", .flags = 0, .defaultSortDesc = true, .autoWidth = true, .autoTitleRightAlign = true, },\n   [PERCENT_NORM_CPU] = { .name = \"PERCENT_NORM_CPU\", .title = \"NCPU%\", .description = \"Normalized percentage of the CPU time the process used in the last sampling (normalized by cpu count)\", .flags = 0, .defaultSortDesc = true, .autoWidth = true, },\n   [PERCENT_MEM] = { .name = \"PERCENT_MEM\", .title = \"MEM% \", .description = \"Percentage of the memory the process is using, based on resident memory size\", .flags = 0, .defaultSortDesc = true, },\n   [USER] = { .name = \"USER\", .title = \"USER       \", .description = \"Username of the process owner (or user ID if name cannot be determined)\", .flags = 0, },\n   [TIME] = { .name = \"TIME\", .title = \"  TIME+  \", .description = \"Total time the process has spent in user and system time\", .flags = 0, .defaultSortDesc = true, },\n   [NLWP] = { .name = \"NLWP\", .title = \"NLWP \", .description = \"Number of threads in the process\", .flags = 0, },\n   [TGID] = { .name = \"TGID\", .title = \"TGID\", .description = \"Thread group ID (i.e. process ID)\", .flags = 0, .pidColumn = true, },\n   [PROC_COMM] = { .name = \"COMM\", .title = \"COMM            \", .description = \"comm string of the process\", .flags = 0, },\n   [PROC_EXE] = { .name = \"EXE\", .title = \"EXE             \", .description = \"Basename of exe of the process\", .flags = 0, },\n   [CWD] = { .name = \"CWD\", .title = \"CWD                       \", .description = \"The current working directory of the process\", .flags = PROCESS_FLAG_CWD, },\n   [JID] = { .name = \"JID\", .title = \"JID\", .description = \"Jail prison ID\", .flags = 0, .pidColumn = true, },\n   [JAIL] = { .name = \"JAIL\", .title = \"JAIL        \", .description = \"Jail prison name\", .flags = 0, },\n};\n\nProcess* DragonFlyBSDProcess_new(const Machine* host) {\n   DragonFlyBSDProcess* this = xCalloc(1, sizeof(DragonFlyBSDProcess));\n   Object_setClass(this, Class(DragonFlyBSDProcess));\n   Process_init(&this->super, host);\n   return (Process*)this;\n}\n\nvoid Process_delete(Object* cast) {\n   DragonFlyBSDProcess* this = (DragonFlyBSDProcess*) cast;\n   Process_done((Process*)cast);\n   free(this->jname);\n   free(this);\n}\n\nstatic void DragonFlyBSDProcess_rowWriteField(const Row* super, RichString* str, ProcessField field) {\n   const Process* this = (const Process*) super;\n   const DragonFlyBSDProcess* fp = (const DragonFlyBSDProcess*) super;\n\n   char buffer[256]; buffer[255] = '\\0';\n   int attr = CRT_colors[DEFAULT_COLOR];\n   size_t n = sizeof(buffer) - 1;\n\n   switch (field) {\n   // add Platform-specific fields here\n   case PID: xSnprintf(buffer, n, \"%*d \", Process_pidDigits, Process_isKernelThread(this) ? -1 : Process_getPid(this)); break;\n   case JID: xSnprintf(buffer, n, \"%*d \", Process_pidDigits, fp->jid); break;\n   case JAIL: Row_printLeftAlignedField(str, attr, fp->jname, 11); return;\n   default:\n      Process_writeField(&fp->super, str, field);\n      return;\n   }\n\n   RichString_appendWide(str, attr, buffer);\n}\n\nstatic int DragonFlyBSDProcess_compareByKey(const Process* v1, const Process* v2, ProcessField key) {\n   const DragonFlyBSDProcess* p1 = (const DragonFlyBSDProcess*)v1;\n   const DragonFlyBSDProcess* p2 = (const DragonFlyBSDProcess*)v2;\n\n   switch (key) {\n   // add Platform-specific fields here\n   case JID:\n      return SPACESHIP_NUMBER(p1->jid, p2->jid);\n   case JAIL:\n      return SPACESHIP_NULLSTR(p1->jname, p2->jname);\n   default:\n      return Process_compareByKey_Base(v1, v2, key);\n   }\n}\n\nconst ProcessClass DragonFlyBSDProcess_class = {\n   .super = {\n      .super = {\n         .extends = Class(Process),\n         .display = Row_display,\n         .delete = Process_delete,\n         .compare = Process_compare\n      },\n      .isHighlighted = Process_rowIsHighlighted,\n      .isVisible = Process_rowIsVisible,\n      .matchesFilter = Process_rowMatchesFilter,\n      .compareByParent = Process_compareByParent,\n      .sortKeyString = Process_rowGetSortKey,\n      .writeField = DragonFlyBSDProcess_rowWriteField\n   },\n   .compareByKey = DragonFlyBSDProcess_compareByKey\n};\n"
  },
  {
    "path": "dragonflybsd/DragonFlyBSDProcess.h",
    "content": "#ifndef HEADER_DragonFlyBSDProcess\n#define HEADER_DragonFlyBSDProcess\n/*\nhtop - dragonflybsd/DragonFlyBSDProcess.h\n(C) 2015 Hisham H. Muhammad\n(C) 2017 Diederik de Groot\nReleased under the GNU GPLv2+, see the COPYING file\nin the source distribution for its full text.\n*/\n\n#include <stdbool.h>\n\n#include \"Object.h\"\n#include \"Process.h\"\n#include \"Machine.h\"\n\n\ntypedef struct DragonFlyBSDProcess_ {\n   Process super;\n   int   jid;\n   char* jname;\n} DragonFlyBSDProcess;\n\nextern const ProcessClass DragonFlyBSDProcess_class;\n\nextern const ProcessFieldData Process_fields[LAST_PROCESSFIELD];\n\nProcess* DragonFlyBSDProcess_new(const Machine* host);\n\nvoid Process_delete(Object* cast);\n\n#endif\n"
  },
  {
    "path": "dragonflybsd/DragonFlyBSDProcessTable.c",
    "content": "/*\nhtop - DragonFlyBSDProcessTable.c\n(C) 2014 Hisham H. Muhammad\n(C) 2017 Diederik de Groot\nReleased under the GNU GPLv2+, see the COPYING file\nin the source distribution for its full text.\n*/\n\n#include \"dragonflybsd/DragonFlyBSDProcessTable.h\"\n\n#include <fcntl.h>\n#include <limits.h>\n#include <stddef.h>\n#include <stdlib.h>\n#include <string.h>\n#include <unistd.h>\n#include <sys/types.h>\n#include <sys/stat.h>\n#include <sys/sysctl.h>\n#include <sys/user.h>\n#include <sys/param.h>\n\n#include \"CRT.h\"\n#include \"Macros.h\"\n\n#include \"dragonflybsd/DragonFlyBSDMachine.h\"\n#include \"dragonflybsd/DragonFlyBSDProcess.h\"\n\n\nProcessTable* ProcessTable_new(Machine* host, Hashtable* pidMatchList) {\n   DragonFlyBSDProcessTable* this = xCalloc(1, sizeof(DragonFlyBSDProcessTable));\n   Object_setClass(this, Class(ProcessTable));\n\n   ProcessTable* super = (ProcessTable*) this;\n   ProcessTable_init(super, Class(DragonFlyBSDProcess), host, pidMatchList);\n\n   return super;\n}\n\nvoid ProcessTable_delete(Object* cast) {\n   DragonFlyBSDProcessTable* this = (DragonFlyBSDProcessTable*) cast;\n   ProcessTable_done(&this->super);\n   free(this);\n}\n\n//static void DragonFlyBSDProcessTable_updateExe(const struct kinfo_proc* kproc, Process* proc) {\n//   const int mib[] = { CTL_KERN, KERN_PROC, KERN_PROC_PATHNAME, kproc->kp_pid };\n//   char buffer[2048];\n//   size_t size = sizeof(buffer);\n//   if (sysctl(mib, 4, buffer, &size, NULL, 0) != 0) {\n//      Process_updateExe(proc, NULL);\n//      return;\n//   }\n//\n//   /* Kernel threads return an empty buffer */\n//   if (buffer[0] == '\\0') {\n//      Process_updateExe(proc, NULL);\n//      return;\n//   }\n//\n//   Process_updateExe(proc, buffer);\n//}\n\nstatic void DragonFlyBSDProcessTable_updateExe(const struct kinfo_proc* kproc, Process* proc) {\n   if (Process_isKernelThread(proc))\n      return;\n\n   char path[32];\n   xSnprintf(path, sizeof(path), \"/proc/%d/file\", kproc->kp_pid);\n\n   char target[PATH_MAX];\n   ssize_t ret = readlink(path, target, sizeof(target) - 1);\n   if (ret <= 0)\n      return;\n\n   target[ret] = '\\0';\n   Process_updateExe(proc, target);\n}\n\nstatic void DragonFlyBSDProcessTable_updateCwd(const struct kinfo_proc* kproc, Process* proc) {\n   const int mib[] = { CTL_KERN, KERN_PROC, KERN_PROC_CWD, kproc->kp_pid };\n   char buffer[2048];\n   size_t size = sizeof(buffer);\n   if (sysctl(mib, 4, buffer, &size, NULL, 0) != 0) {\n      free(proc->procCwd);\n      proc->procCwd = NULL;\n      return;\n   }\n\n   /* Kernel threads return an empty buffer */\n   if (buffer[0] == '\\0') {\n      free(proc->procCwd);\n      proc->procCwd = NULL;\n      return;\n   }\n\n   free_and_xStrdup(&proc->procCwd, buffer);\n}\n\nstatic void DragonFlyBSDProcessTable_updateProcessName(kvm_t* kd, const struct kinfo_proc* kproc, Process* proc) {\n   Process_updateComm(proc, kproc->kp_comm);\n\n   char** argv = kvm_getargv(kd, kproc, 0);\n   if (!argv || !argv[0]) {\n      Process_updateCmdline(proc, kproc->kp_comm, 0, strlen(kproc->kp_comm));\n      return;\n   }\n\n   size_t len = 0;\n   for (size_t i = 0; argv[i]; i++) {\n      len += strlen(argv[i]) + 1;\n   }\n\n   char* cmdline = xMalloc(len);\n\n   char* at = cmdline;\n   size_t end = 0;\n   for (size_t i = 0; argv[i]; i++) {\n      at = stpcpy(at, argv[i]);\n      if (end == 0) {\n         end = (size_t)(at - cmdline);\n      }\n      *at++ = ' ';\n   }\n   at--;\n   *at = '\\0';\n\n   Process_updateCmdline(proc, cmdline, 0, end);\n\n   free(cmdline);\n}\n\nvoid ProcessTable_goThroughEntries(ProcessTable* super) {\n   const Machine* host = super->super.host;\n   const DragonFlyBSDMachine* dhost = (const DragonFlyBSDMachine*) host;\n   const Settings* settings = host->settings;\n\n   bool hideKernelThreads = settings->hideKernelThreads;\n   bool hideUserlandThreads = settings->hideUserlandThreads;\n\n   int count = 0;\n\n   const struct kinfo_proc* kprocs = kvm_getprocs(dhost->kd, KERN_PROC_ALL | (!hideUserlandThreads ? KERN_PROC_FLAG_LWP : 0), 0, &count);\n\n   for (int i = 0; i < count; i++) {\n      const struct kinfo_proc* kproc = &kprocs[i];\n      bool preExisting = false;\n      bool ATTR_UNUSED isIdleProcess = false;\n\n      // note: dragonflybsd kernel processes all have the same pid, so we misuse the kernel thread address to give them a unique identifier\n      Process* proc = ProcessTable_getProcess(super, kproc->kp_ktaddr ? (pid_t)kproc->kp_ktaddr : kproc->kp_pid, &preExisting, DragonFlyBSDProcess_new);\n      DragonFlyBSDProcess* dfp = (DragonFlyBSDProcess*) proc;\n\n      if (!preExisting) {\n         dfp->jid = kproc->kp_jailid;\n         if (kproc->kp_ktaddr && kproc->kp_flags & P_SYSTEM) {\n            // dfb kernel threads all have the same pid, so we misuse the kernel thread address to give them a unique identifier\n            Process_setPid(proc, (pid_t)kproc->kp_ktaddr);\n            proc->isKernelThread = true;\n         } else {\n            Process_setPid(proc, kproc->kp_pid);\t\t// process ID\n            proc->isKernelThread = false;\n         }\n         proc->isUserlandThread = kproc->kp_nthreads > 1;\n         Process_setParent(proc, kproc->kp_ppid); // parent process id\n         proc->tpgid = kproc->kp_tpgid;\t\t// tty process group id\n         //Process_setThreadGroup(proc, kproc->kp_lwp.kl_tid);\t// thread group id\n         Process_setThreadGroup(proc, kproc->kp_pid);\n         proc->pgrp = kproc->kp_pgid;\t\t// process group id\n         proc->session = kproc->kp_sid;\n         proc->st_uid = kproc->kp_uid;\t\t// user ID\n         proc->processor = kproc->kp_lwp.kl_origcpu;\n         proc->starttime_ctime = kproc->kp_start.tv_sec;\n         Process_fillStarttimeBuffer(proc);\n         proc->user = UsersTable_getRef(host->usersTable, proc->st_uid);\n\n         proc->tty_nr = kproc->kp_tdev; // control terminal device number\n         const char* name = (kproc->kp_tdev != NODEV) ? devname(kproc->kp_tdev, S_IFCHR) : NULL;\n         if (!name) {\n            free(proc->tty_name);\n            proc->tty_name = NULL;\n         } else {\n            free_and_xStrdup(&proc->tty_name, name);\n         }\n\n         DragonFlyBSDProcessTable_updateExe(kproc, proc);\n         DragonFlyBSDProcessTable_updateProcessName(dhost->kd, kproc, proc);\n\n         if (settings->ss->flags & PROCESS_FLAG_CWD) {\n            DragonFlyBSDProcessTable_updateCwd(kproc, proc);\n         }\n\n         ProcessTable_add(super, proc);\n\n         dfp->jname = DragonFlyBSDMachine_readJailName(dhost, kproc->kp_jailid);\n      } else {\n         proc->processor = kproc->kp_lwp.kl_cpuid;\n         if (dfp->jid != kproc->kp_jailid) {\t// process can enter jail anytime\n            dfp->jid = kproc->kp_jailid;\n            free(dfp->jname);\n            dfp->jname = DragonFlyBSDMachine_readJailName(dhost, kproc->kp_jailid);\n         }\n         // if there are reapers in the system, process can get reparented anytime\n         Process_setParent(proc, kproc->kp_ppid);\n         if (proc->st_uid != kproc->kp_uid) {\t// some processes change users (eg. to lower privs)\n            proc->st_uid = kproc->kp_uid;\n            proc->user = UsersTable_getRef(host->usersTable, proc->st_uid);\n         }\n         if (settings->updateProcessNames) {\n            DragonFlyBSDProcessTable_updateProcessName(dhost->kd, kproc, proc);\n         }\n      }\n\n      proc->m_virt = kproc->kp_vm_map_size / ONE_K;\n      proc->m_resident = kproc->kp_vm_rssize * dhost->pageSizeKb;\n      proc->nlwp = kproc->kp_nthreads;\t\t// number of lwp thread\n      proc->time = (kproc->kp_lwp.kl_uticks + kproc->kp_lwp.kl_sticks + kproc->kp_lwp.kl_iticks) / 10000;\n\n      proc->percent_cpu = 100.0 * ((double)kproc->kp_lwp.kl_pctcpu / (double)dhost->kernelFScale);\n      proc->percent_mem = 100.0 * proc->m_resident / (double)(host->totalMem);\n      Process_updateCPUFieldWidths(proc->percent_cpu);\n\n      if (proc->percent_cpu > 0.1) {\n         // system idle process should own all CPU time left regardless of CPU count\n         if (String_eq(\"idle\", kproc->kp_comm)) {\n            isIdleProcess = true;\n         }\n      }\n\n      if (kproc->kp_lwp.kl_pid != -1)\n         proc->priority = kproc->kp_lwp.kl_prio;\n      else\n         proc->priority = -kproc->kp_lwp.kl_tdprio;\n\n      switch (kproc->kp_lwp.kl_rtprio.type) {\n         case RTP_PRIO_REALTIME:\n            proc->nice = PRIO_MIN - 1 - RTP_PRIO_MAX + kproc->kp_lwp.kl_rtprio.prio;\n            break;\n         case RTP_PRIO_IDLE:\n            proc->nice = PRIO_MAX + 1 + kproc->kp_lwp.kl_rtprio.prio;\n            break;\n         case RTP_PRIO_THREAD:\n            proc->nice = PRIO_MIN - 1 - RTP_PRIO_MAX - kproc->kp_lwp.kl_rtprio.prio;\n            break;\n         default:\n            proc->nice = kproc->kp_nice;\n            break;\n      }\n\n      // would be nice if we could store multiple states in proc->state (as enum) and have writeField render them\n      /* Taken from: https://github.com/DragonFlyBSD/DragonFlyBSD/blob/c163a4d7ee9c6857ee4e04a3a2cbb50c3de29da1/sys/sys/proc_common.h */\n      switch (kproc->kp_stat) {\n         case SIDL:\n            proc->state = IDLE;\n            isIdleProcess = true;\n            break;\n         case SACTIVE:\n            switch (kproc->kp_lwp.kl_stat) {\n               case LSSLEEP:\n                  if (kproc->kp_lwp.kl_flags & LWP_SINTR) {          // interruptible wait short/long\n                     if (kproc->kp_lwp.kl_slptime >= MAXSLP) {\n                        proc->state = IDLE;\n                        isIdleProcess = true;\n                     } else {\n                        proc->state = SLEEPING;\n                     }\n                  } else if (kproc->kp_lwp.kl_tdflags & TDF_SINTR) { // interruptible lwkt wait\n                     proc->state = SLEEPING;\n                  } else if (kproc->kp_paddr) {                      // uninterruptible wait\n                     proc->state = UNINTERRUPTIBLE_WAIT;\n                  } else {                                           // uninterruptible lwkt wait\n                     proc->state = UNINTERRUPTIBLE_WAIT;\n                  }\n                  break;\n               case LSRUN:\n                  if (kproc->kp_lwp.kl_stat == LSRUN) {\n                     if (!(kproc->kp_lwp.kl_tdflags & (TDF_RUNNING | TDF_RUNQ))) {\n                        proc->state = QUEUED;\n                     } else {\n                        proc->state = RUNNING;\n                     }\n                  }\n                  break;\n               case LSSTOP:\n                  proc->state = STOPPED;\n                  break;\n               default:\n                  proc->state = PAGING;\n                  break;\n            }\n            break;\n         case SSTOP:\n            proc->state = STOPPED;\n            break;\n         case SZOMB:\n            proc->state = ZOMBIE;\n            break;\n         case SCORE:\n            proc->state = BLOCKED;\n            break;\n         default:\n            proc->state = UNKNOWN;\n      }\n\n      if (kproc->kp_flags & P_SWAPPEDOUT)\n         proc->state = SLEEPING;\n      if (kproc->kp_flags & P_TRACED)\n         proc->state = TRACED;\n      if (kproc->kp_flags & P_JAILED)\n         proc->state = TRACED;\n\n      if (Process_isKernelThread(proc))\n         super->kernelThreads++;\n\n      super->totalTasks++;\n\n      if (proc->state == RUNNING)\n         super->runningTasks++;\n\n      proc->super.show = ! ((hideKernelThreads && Process_isKernelThread(proc)) || (hideUserlandThreads && Process_isUserlandThread(proc)));\n      proc->super.updated = true;\n   }\n}\n"
  },
  {
    "path": "dragonflybsd/DragonFlyBSDProcessTable.h",
    "content": "#ifndef HEADER_DragonFlyBSDProcessTable\n#define HEADER_DragonFlyBSDProcessTable\n/*\nhtop - DragonFlyBSDProcessTable.h\n(C) 2014 Hisham H. Muhammad\n(C) 2017 Diederik de Groot\nReleased under the GNU GPLv2+, see the COPYING file\nin the source distribution for its full text.\n*/\n\n#include <stdbool.h>\n#include <sys/param.h>\n\n#include \"ProcessTable.h\"\n\n\ntypedef struct DragonFlyBSDProcessTable_ {\n   ProcessTable super;\n} DragonFlyBSDProcessTable;\n\n#endif\n"
  },
  {
    "path": "dragonflybsd/Platform.c",
    "content": "/*\nhtop - dragonflybsd/Platform.c\n(C) 2014 Hisham H. Muhammad\n(C) 2017 Diederik de Groot\nReleased under the GNU GPLv2+, see the COPYING file\nin the source distribution for its full text.\n*/\n\n#include \"config.h\" // IWYU pragma: keep\n\n#include \"dragonflybsd/Platform.h\"\n\n#include <devstat.h>\n#include <errno.h>\n#include <ifaddrs.h>\n#include <math.h>\n#include <time.h>\n#include <sys/resource.h>\n#include <sys/socket.h>\n#include <sys/sysctl.h>\n#include <sys/time.h>\n#include <sys/types.h>\n#include <vm/vm_param.h>\n\n#include \"ClockMeter.h\"\n#include \"CPUMeter.h\"\n#include \"DateTimeMeter.h\"\n#include \"FileDescriptorMeter.h\"\n#include \"HostnameMeter.h\"\n#include \"LoadAverageMeter.h\"\n#include \"Macros.h\"\n#include \"MemoryMeter.h\"\n#include \"MemorySwapMeter.h\"\n#include \"ProcessTable.h\"\n#include \"SwapMeter.h\"\n#include \"SysArchMeter.h\"\n#include \"TasksMeter.h\"\n#include \"UptimeMeter.h\"\n#include \"XUtils.h\"\n#include \"dragonflybsd/DragonFlyBSDMachine.h\"\n#include \"dragonflybsd/DragonFlyBSDProcess.h\"\n#include \"dragonflybsd/DragonFlyBSDProcessTable.h\"\n#include \"generic/fdstat_sysctl.h\"\n\n\nconst ScreenDefaults Platform_defaultScreens[] = {\n   {\n      .name = \"Main\",\n      .columns = \"PID USER PRIORITY NICE M_VIRT M_RESIDENT STATE PERCENT_CPU PERCENT_MEM TIME Command\",\n      .sortKey = \"PERCENT_CPU\",\n   },\n};\n\nconst unsigned int Platform_numberOfDefaultScreens = ARRAYSIZE(Platform_defaultScreens);\n\nconst SignalItem Platform_signals[] = {\n   { .name = \" 0 Cancel\",    .number =  0 },\n   { .name = \" 1 SIGHUP\",    .number =  1 },\n   { .name = \" 2 SIGINT\",    .number =  2 },\n   { .name = \" 3 SIGQUIT\",   .number =  3 },\n   { .name = \" 4 SIGILL\",    .number =  4 },\n   { .name = \" 5 SIGTRAP\",   .number =  5 },\n   { .name = \" 6 SIGABRT\",   .number =  6 },\n   { .name = \" 7 SIGEMT\",    .number =  7 },\n   { .name = \" 8 SIGFPE\",    .number =  8 },\n   { .name = \" 9 SIGKILL\",   .number =  9 },\n   { .name = \"10 SIGBUS\",    .number = 10 },\n   { .name = \"11 SIGSEGV\",   .number = 11 },\n   { .name = \"12 SIGSYS\",    .number = 12 },\n   { .name = \"13 SIGPIPE\",   .number = 13 },\n   { .name = \"14 SIGALRM\",   .number = 14 },\n   { .name = \"15 SIGTERM\",   .number = 15 },\n   { .name = \"16 SIGURG\",    .number = 16 },\n   { .name = \"17 SIGSTOP\",   .number = 17 },\n   { .name = \"18 SIGTSTP\",   .number = 18 },\n   { .name = \"19 SIGCONT\",   .number = 19 },\n   { .name = \"20 SIGCHLD\",   .number = 20 },\n   { .name = \"21 SIGTTIN\",   .number = 21 },\n   { .name = \"22 SIGTTOU\",   .number = 22 },\n   { .name = \"23 SIGIO\",     .number = 23 },\n   { .name = \"24 SIGXCPU\",   .number = 24 },\n   { .name = \"25 SIGXFSZ\",   .number = 25 },\n   { .name = \"26 SIGVTALRM\", .number = 26 },\n   { .name = \"27 SIGPROF\",   .number = 27 },\n   { .name = \"28 SIGWINCH\",  .number = 28 },\n   { .name = \"29 SIGINFO\",   .number = 29 },\n   { .name = \"30 SIGUSR1\",   .number = 30 },\n   { .name = \"31 SIGUSR2\",   .number = 31 },\n   { .name = \"32 SIGTHR\",    .number = 32 },\n   { .name = \"33 SIGLIBRT\",  .number = 33 },\n};\n\nconst unsigned int Platform_numberOfSignals = ARRAYSIZE(Platform_signals);\n\nenum {\n   MEMORY_CLASS_WIRED = 0,\n   MEMORY_CLASS_BUFFERS,\n   MEMORY_CLASS_ACTIVE,\n   MEMORY_CLASS_CACHE,\n   MEMORY_CLASS_INACTIVE,\n}; // N.B. the chart will display categories in this order\n\nconst MemoryClass Platform_memoryClasses[] = {\n   [MEMORY_CLASS_WIRED] = { .label = \"wired\", .countsAsUsed = true, .countsAsCache = false, .color = MEMORY_1 },\n   [MEMORY_CLASS_BUFFERS] = { .label = \"buffers\", .countsAsUsed = true, .countsAsCache = false, .color = MEMORY_2 },\n   [MEMORY_CLASS_ACTIVE] = { .label = \"active\", .countsAsUsed = true, .countsAsCache = false, .color = MEMORY_3 },\n   [MEMORY_CLASS_CACHE] = { .label = \"cache\", .countsAsUsed = false, .countsAsCache = true, .color = MEMORY_4 },\n   [MEMORY_CLASS_INACTIVE] = { .label = \"inactive\", .countsAsUsed = false, .countsAsCache = true, .color = MEMORY_5 },\n};\n\nconst unsigned int Platform_numberOfMemoryClasses = ARRAYSIZE(Platform_memoryClasses);\n\nconst MeterClass* const Platform_meterTypes[] = {\n   &CPUMeter_class,\n   &ClockMeter_class,\n   &DateMeter_class,\n   &DateTimeMeter_class,\n   &LoadAverageMeter_class,\n   &LoadMeter_class,\n   &MemoryMeter_class,\n   &MemorySwapMeter_class,\n   &SwapMeter_class,\n   &TasksMeter_class,\n   &UptimeMeter_class,\n   &SecondsUptimeMeter_class,\n   &BatteryMeter_class,\n   &HostnameMeter_class,\n   &SysArchMeter_class,\n   &AllCPUsMeter_class,\n   &AllCPUs2Meter_class,\n   &AllCPUs4Meter_class,\n   &AllCPUs8Meter_class,\n   &LeftCPUsMeter_class,\n   &RightCPUsMeter_class,\n   &LeftCPUs2Meter_class,\n   &RightCPUs2Meter_class,\n   &LeftCPUs4Meter_class,\n   &RightCPUs4Meter_class,\n   &LeftCPUs8Meter_class,\n   &RightCPUs8Meter_class,\n   &DiskIORateMeter_class,\n   &DiskIOTimeMeter_class,\n   &DiskIOMeter_class,\n   &NetworkIOMeter_class,\n   &FileDescriptorMeter_class,\n   &BlankMeter_class,\n   NULL\n};\n\nbool Platform_init(void) {\n   /* no platform-specific setup needed */\n   return true;\n}\n\nvoid Platform_done(void) {\n   /* no platform-specific cleanup needed */\n}\n\nvoid Platform_setBindings(Htop_Action* keys) {\n   /* no platform-specific key bindings */\n   (void) keys;\n}\n\nint Platform_getUptime(void) {\n   struct timeval bootTime, currTime;\n   int mib[2] = { CTL_KERN, KERN_BOOTTIME };\n   size_t size = sizeof(bootTime);\n\n   int err = sysctl(mib, 2, &bootTime, &size, NULL, 0);\n   if (err) {\n      return -1;\n   }\n   gettimeofday(&currTime, NULL);\n\n   return (int) difftime(currTime.tv_sec, bootTime.tv_sec);\n}\n\nvoid Platform_getLoadAverage(double* one, double* five, double* fifteen) {\n   struct loadavg loadAverage;\n   int mib[2] = { CTL_VM, VM_LOADAVG };\n   size_t size = sizeof(loadAverage);\n\n   int err = sysctl(mib, 2, &loadAverage, &size, NULL, 0);\n   if (err) {\n      *one = 0;\n      *five = 0;\n      *fifteen = 0;\n      return;\n   }\n   *one     = (double) loadAverage.ldavg[0] / loadAverage.fscale;\n   *five    = (double) loadAverage.ldavg[1] / loadAverage.fscale;\n   *fifteen = (double) loadAverage.ldavg[2] / loadAverage.fscale;\n}\n\npid_t Platform_getMaxPid(void) {\n   int maxPid;\n   size_t size = sizeof(maxPid);\n   int err = sysctlbyname(\"kern.pid_max\", &maxPid, &size, NULL, 0);\n   if (err) {\n      return 999999;\n   }\n   return maxPid;\n}\n\ndouble Platform_setCPUValues(Meter* this, unsigned int cpu) {\n   const Machine* host = this->host;\n   const DragonFlyBSDMachine* dhost = (const DragonFlyBSDMachine*) host;\n   unsigned int cpus = host->activeCPUs;\n   const CPUData* cpuData;\n\n   if (cpus == 1) {\n      // single CPU box has everything in fpl->cpus[0]\n      cpuData = &(dhost->cpus[0]);\n   } else {\n      cpuData = &(dhost->cpus[cpu]);\n   }\n\n   double  percent;\n   double* v = this->values;\n\n   v[CPU_METER_NICE]   = cpuData->nicePercent;\n   v[CPU_METER_NORMAL] = cpuData->userPercent;\n   if (host->settings->detailedCPUTime) {\n      v[CPU_METER_KERNEL]  = cpuData->systemPercent;\n      v[CPU_METER_IRQ]     = cpuData->irqPercent;\n      this->curItems = 4;\n   } else {\n      v[CPU_METER_KERNEL] = cpuData->systemAllPercent;\n      this->curItems = 3;\n   }\n\n   percent = sumPositiveValues(v, this->curItems);\n   percent = MINIMUM(percent, 100.0);\n\n   v[CPU_METER_FREQUENCY] = NAN;\n   v[CPU_METER_TEMPERATURE] = NAN;\n\n   return percent;\n}\n\nvoid Platform_setMemoryValues(Meter* this) {\n   const Machine* host = this->host;\n   const DragonFlyBSDMachine* fhost = (const DragonFlyBSDMachine*) host;\n   const Settings* settings = host->settings;\n\n   this->total = host->totalMem;\n   if (settings->showCachedMemory) {\n      this->values[MEMORY_CLASS_WIRED]    = fhost->wiredMem;\n      this->values[MEMORY_CLASS_BUFFERS]  = fhost->buffersMem;\n   } else { // if showCachedMemory is disabled, merge buffers into the wired pages\n      this->values[MEMORY_CLASS_WIRED]    = fhost->wiredMem + fhost->buffersMem;\n      this->values[MEMORY_CLASS_BUFFERS]  = 0;\n   }\n   this->values[MEMORY_CLASS_ACTIVE]   = fhost->activeMem;\n   this->values[MEMORY_CLASS_CACHE]    = fhost->cacheMem;\n   this->values[MEMORY_CLASS_INACTIVE] = fhost->inactiveMem;\n}\n\nvoid Platform_setSwapValues(Meter* this) {\n   const Machine* host = this->host;\n   this->total = host->totalSwap;\n   this->values[SWAP_METER_USED] = host->usedSwap;\n}\n\nchar* Platform_getProcessEnv(pid_t pid) {\n   // TODO\n   (void)pid;  // prevent unused warning\n   return NULL;\n}\n\nFileLocks_ProcessData* Platform_getProcessLocks(pid_t pid) {\n   (void)pid;\n   return NULL;\n}\n\nvoid Platform_getFileDescriptors(double* used, double* max) {\n   Generic_getFileDescriptors_sysctl(used, max);\n}\n\nbool Platform_getDiskIO(DiskIOData* data) {\n   struct statinfo dev_stats = { 0 };\n   struct device_selection* dev_sel = NULL;\n   int n_selected, n_selections;\n   long sel_gen;\n\n   dev_stats.dinfo = xCalloc(1, sizeof(struct devinfo));\n\n   int ret = getdevs(&dev_stats);\n   if (ret < 0) {\n      CRT_debug(\"getdevs() failed [%d]: %s\", ret, strerror(errno));\n      free(dev_stats.dinfo);\n      return false;\n   }\n\n   ret = selectdevs(&dev_sel, &n_selected, &n_selections, &sel_gen,\n         dev_stats.dinfo->generation, dev_stats.dinfo->devices, dev_stats.dinfo->numdevs,\n         NULL, 0, NULL, 0, DS_SELECT_ONLY, dev_stats.dinfo->numdevs, 1);\n   if (ret < 0) {\n      CRT_debug(\"selectdevs() failed [%d]: %s\", ret, strerror(errno));\n      free(dev_stats.dinfo);\n      return false;\n   }\n\n   uint64_t bytesReadSum = 0;\n   uint64_t bytesWriteSum = 0;\n   uint64_t busyMsTimeSum = 0;\n   uint64_t numDisks = 0;\n\n   for (int i = 0; i < dev_stats.dinfo->numdevs; i++) {\n      const struct devstat* device = &dev_stats.dinfo->devices[dev_sel[i].position];\n\n      switch (device->device_type & DEVSTAT_TYPE_MASK) {\n      case DEVSTAT_TYPE_DIRECT:\n      case DEVSTAT_TYPE_SEQUENTIAL:\n      case DEVSTAT_TYPE_WORM:\n      case DEVSTAT_TYPE_CDROM:\n      case DEVSTAT_TYPE_OPTICAL:\n      case DEVSTAT_TYPE_CHANGER:\n      case DEVSTAT_TYPE_STORARRAY:\n      case DEVSTAT_TYPE_FLOPPY:\n         break;\n      default:\n         continue;\n      }\n\n      bytesReadSum  += device->bytes_read;\n      bytesWriteSum += device->bytes_written;\n      busyMsTimeSum += (device->busy_time.tv_sec * 1000 + device->busy_time.tv_usec / 1000);\n      numDisks++;\n   }\n\n   data->totalBytesRead = bytesReadSum;\n   data->totalBytesWritten = bytesWriteSum;\n   data->totalMsTimeSpend = busyMsTimeSum;\n   data->numDisks = numDisks;\n\n   free(dev_stats.dinfo);\n   return true;\n}\n\nbool Platform_getNetworkIO(NetworkIOData* data) {\n   struct ifaddrs* ifaddrs = NULL;\n\n   if (getifaddrs(&ifaddrs) != 0)\n      return false;\n\n   for (const struct ifaddrs* ifa = ifaddrs; ifa; ifa = ifa->ifa_next) {\n      if (!ifa->ifa_addr)\n         continue;\n      if (ifa->ifa_addr->sa_family != AF_LINK)\n         continue;\n      if (ifa->ifa_flags & IFF_LOOPBACK)\n         continue;\n\n      const struct if_data* ifd = (const struct if_data*)ifa->ifa_data;\n\n      data->bytesReceived += ifd->ifi_ibytes;\n      data->packetsReceived += ifd->ifi_ipackets;\n      data->bytesTransmitted += ifd->ifi_obytes;\n      data->packetsTransmitted += ifd->ifi_opackets;\n   }\n\n   freeifaddrs(ifaddrs);\n   return true;\n}\n\nvoid Platform_getBattery(double* percent, ACPresence* isOnAC) {\n   int life;\n   size_t life_len = sizeof(life);\n   if (sysctlbyname(\"hw.acpi.battery.life\", &life, &life_len, NULL, 0) == -1)\n      *percent = NAN;\n   else\n      *percent = life;\n\n   int acline;\n   size_t acline_len = sizeof(acline);\n   if (sysctlbyname(\"hw.acpi.acline\", &acline, &acline_len, NULL, 0) == -1)\n      *isOnAC = AC_ERROR;\n   else\n      *isOnAC = acline == 0 ? AC_ABSENT : AC_PRESENT;\n}\n"
  },
  {
    "path": "dragonflybsd/Platform.h",
    "content": "#ifndef HEADER_Platform\n#define HEADER_Platform\n/*\nhtop - dragonflybsd/Platform.h\n(C) 2014 Hisham H. Muhammad\n(C) 2017 Diederik de Groot\nReleased under the GNU GPLv2+, see the COPYING file\nin the source distribution for its full text.\n*/\n\n#include <stdbool.h>\n#include <stddef.h>\n#include <stdint.h>\n#include <sys/types.h>\n\n#include \"Action.h\"\n#include \"BatteryMeter.h\"\n#include \"DiskIOMeter.h\"\n#include \"Hashtable.h\"\n#include \"Macros.h\"\n#include \"MemoryMeter.h\"\n#include \"NetworkIOMeter.h\"\n#include \"Process.h\"\n#include \"ProcessLocksScreen.h\"\n#include \"SignalsPanel.h\"\n#include \"CommandLine.h\"\n#include \"generic/gettime.h\"\n#include \"generic/hostname.h\"\n#include \"generic/uname.h\"\n\n\nextern const ScreenDefaults Platform_defaultScreens[];\n\nextern const unsigned int Platform_numberOfDefaultScreens;\n\nextern const SignalItem Platform_signals[];\n\nextern const unsigned int Platform_numberOfSignals;\n\nextern const MemoryClass Platform_memoryClasses[];\n\nextern const unsigned int Platform_numberOfMemoryClasses;\n\nextern const MeterClass* const Platform_meterTypes[];\n\nbool Platform_init(void);\n\nvoid Platform_done(void);\n\nvoid Platform_setBindings(Htop_Action* keys);\n\nint Platform_getUptime(void);\n\nvoid Platform_getLoadAverage(double* one, double* five, double* fifteen);\n\npid_t Platform_getMaxPid(void);\n\ndouble Platform_setCPUValues(Meter* this, unsigned int cpu);\n\nvoid Platform_setMemoryValues(Meter* this);\n\nvoid Platform_setSwapValues(Meter* this);\n\nchar* Platform_getProcessEnv(pid_t pid);\n\nFileLocks_ProcessData* Platform_getProcessLocks(pid_t pid);\n\nvoid Platform_getFileDescriptors(double* used, double* max);\n\nbool Platform_getDiskIO(DiskIOData* data);\n\nbool Platform_getNetworkIO(NetworkIOData* data);\n\nvoid Platform_getBattery(double* percent, ACPresence* isOnAC);\n\nstatic inline void Platform_getHostname(char* buffer, size_t size) {\n   Generic_hostname(buffer, size);\n}\n\nstatic inline const char* Platform_getRelease(void) {\n   return Generic_uname();\n}\n\nstatic inline const char* Platform_getFailedState(void) {\n   return NULL;\n}\n\n#define PLATFORM_LONG_OPTIONS\n\nstatic inline void Platform_longOptionsUsage(ATTR_UNUSED const char* name) { }\n\nstatic inline CommandLineStatus Platform_getLongOption(ATTR_UNUSED int opt, ATTR_UNUSED int argc, ATTR_UNUSED char** argv) {\n   return STATUS_ERROR_EXIT;\n}\n\nstatic inline void Platform_gettime_realtime(struct timeval* tv, uint64_t* msec) {\n   Generic_gettime_realtime(tv, msec);\n}\n\nstatic inline void Platform_gettime_monotonic(uint64_t* msec) {\n   Generic_gettime_monotonic(msec);\n}\n\nstatic inline Hashtable* Platform_dynamicMeters(void) {\n   return NULL;\n}\n\nstatic inline void Platform_dynamicMetersDone(ATTR_UNUSED Hashtable* table) { }\n\nstatic inline void Platform_dynamicMeterInit(ATTR_UNUSED Meter* meter) { }\n\nstatic inline void Platform_dynamicMeterUpdateValues(ATTR_UNUSED Meter* meter) { }\n\nstatic inline void Platform_dynamicMeterDisplay(ATTR_UNUSED const Meter* meter, ATTR_UNUSED RichString* out) { }\n\nstatic inline Hashtable* Platform_dynamicColumns(void) {\n   return NULL;\n}\n\nstatic inline void Platform_dynamicColumnsDone(ATTR_UNUSED Hashtable* table) { }\n\nstatic inline const char* Platform_dynamicColumnName(ATTR_UNUSED unsigned int key) {\n   return NULL;\n}\n\nstatic inline bool Platform_dynamicColumnWriteField(ATTR_UNUSED const Process* proc, ATTR_UNUSED RichString* str, ATTR_UNUSED unsigned int key) {\n   return false;\n}\n\nstatic inline Hashtable* Platform_dynamicScreens(void) {\n   return NULL;\n}\n\nstatic inline void Platform_defaultDynamicScreens(ATTR_UNUSED Settings* settings) { }\n\nstatic inline void Platform_addDynamicScreen(ATTR_UNUSED ScreenSettings* ss) { }\n\nstatic inline void Platform_addDynamicScreenAvailableColumns(ATTR_UNUSED Panel* availableColumns, ATTR_UNUSED const char* screen) { }\n\nstatic inline void Platform_dynamicScreensDone(ATTR_UNUSED Hashtable* screens) { }\n\n#endif\n"
  },
  {
    "path": "dragonflybsd/ProcessField.h",
    "content": "#ifndef HEADER_DragonFlyBSDProcessField\n#define HEADER_DragonFlyBSDProcessField\n/*\nhtop - dragonflybsd/ProcessField.h\n(C) 2020 htop dev team\nReleased under the GNU GPLv2+, see the COPYING file\nin the source distribution for its full text.\n*/\n\n\n#define PLATFORM_PROCESS_FIELDS  \\\n   JID = 100,                    \\\n   JAIL = 101,                   \\\n                                 \\\n   DUMMY_BUMP_FIELD = CWD,       \\\n   // End of list\n\n\n#endif /* HEADER_DragonFlyBSDProcessField */\n"
  },
  {
    "path": "freebsd/FreeBSDMachine.c",
    "content": "/*\nhtop - FreeBSDMachine.c\n(C) 2014 Hisham H. Muhammad\nReleased under the GNU GPLv2+, see the COPYING file\nin the source distribution for its full text.\n*/\n\n#include \"config.h\" // IWYU pragma: keep\n\n#include \"freebsd/FreeBSDMachine.h\"\n\n#include <assert.h>\n#include <limits.h>\n#include <math.h>\n#include <stdlib.h>\n#include <string.h>\n#include <sys/_iovec.h>\n#include <sys/errno.h>\n#include <sys/param.h> // needs to be included before <sys/jail.h> for MAXPATHLEN\n#include <sys/jail.h>\n#include <sys/priority.h>\n#include <sys/proc.h>\n#include <sys/resource.h>\n#include <sys/sysctl.h>\n#include <sys/time.h>\n#include <sys/types.h>\n#include <sys/user.h>\n#include <sys/vmmeter.h>\n\n#include \"CRT.h\"\n#include \"Macros.h\"\n#include \"Object.h\"\n#include \"Scheduling.h\"\n#include \"Settings.h\"\n#include \"XUtils.h\"\n#include \"generic/openzfs_sysctl.h\"\n#include \"zfs/ZfsArcStats.h\"\n\n\nstatic int MIB_hw_physmem[2];\nstatic int MIB_vm_stats_vm_v_page_count[4];\n\nstatic int MIB_vm_stats_vm_v_wire_count[4];\nstatic int MIB_vm_stats_vm_v_active_count[4];\nstatic int MIB_vm_stats_vm_v_laundry_count[4];\nstatic int MIB_vm_stats_vm_v_inactive_count[4];\n\nstatic int MIB_vfs_bufspace[2];\n\nstatic int MIB_kern_cp_time[2];\nstatic int MIB_kern_cp_times[2];\n\nMachine* Machine_new(UsersTable* usersTable, uid_t userId) {\n   FreeBSDMachine* this = xCalloc(1, sizeof(FreeBSDMachine));\n   Machine* super = &this->super;\n   char errbuf[_POSIX2_LINE_MAX];\n   size_t len;\n\n   Machine_init(super, usersTable, userId);\n\n   // physical memory in system: hw.physmem\n   // physical page size: hw.pagesize\n   // usable pagesize : vm.stats.vm.v_page_size\n   len = 2; sysctlnametomib(\"hw.physmem\", MIB_hw_physmem, &len);\n\n   len = sizeof(this->pageSize);\n   if (sysctlbyname(\"vm.stats.vm.v_page_size\", &this->pageSize, &len, NULL, 0) == -1)\n      CRT_fatalError(\"Cannot get pagesize by sysctl\");\n   this->pageSizeKb = this->pageSize / ONE_K;\n\n   // usable page count vm.stats.vm.v_page_count\n   // actually usable memory : vm.stats.vm.v_page_count * vm.stats.vm.v_page_size\n   len = 4; sysctlnametomib(\"vm.stats.vm.v_page_count\", MIB_vm_stats_vm_v_page_count, &len);\n\n   len = 4; sysctlnametomib(\"vm.stats.vm.v_wire_count\", MIB_vm_stats_vm_v_wire_count, &len);\n   len = 4; sysctlnametomib(\"vm.stats.vm.v_active_count\", MIB_vm_stats_vm_v_active_count, &len);\n   len = 4; sysctlnametomib(\"vm.stats.vm.v_laundry_count\", MIB_vm_stats_vm_v_laundry_count, &len);\n   len = 4; sysctlnametomib(\"vm.stats.vm.v_inactive_count\", MIB_vm_stats_vm_v_inactive_count, &len);\n\n   len = 2; sysctlnametomib(\"vfs.bufspace\", MIB_vfs_bufspace, &len);\n\n   openzfs_sysctl_init(&this->zfs);\n   openzfs_sysctl_updateArcStats(&this->zfs);\n\n   int smp = 0;\n   len = sizeof(smp);\n\n   if (sysctlbyname(\"kern.smp.active\", &smp, &len, NULL, 0) != 0 || len != sizeof(smp)) {\n      smp = 0;\n   }\n\n   int cpus = 1;\n   len = sizeof(cpus);\n\n   if (smp) {\n      int err = sysctlbyname(\"kern.smp.cpus\", &cpus, &len, NULL, 0);\n      if (err) {\n         cpus = 1;\n      }\n   } else {\n      cpus = 1;\n   }\n\n   size_t sizeof_cp_time_array = sizeof(unsigned long) * CPUSTATES;\n   len = 2; sysctlnametomib(\"kern.cp_time\", MIB_kern_cp_time, &len);\n   this->cp_time_o = xCalloc(CPUSTATES, sizeof(unsigned long));\n   this->cp_time_n = xCalloc(CPUSTATES, sizeof(unsigned long));\n   len = sizeof_cp_time_array;\n\n   // fetch initial single (or average) CPU clicks from kernel\n   sysctl(MIB_kern_cp_time, 2, this->cp_time_o, &len, NULL, 0);\n\n   // on smp box, fetch rest of initial CPU's clicks\n   if (cpus > 1) {\n      len = 2; sysctlnametomib(\"kern.cp_times\", MIB_kern_cp_times, &len);\n      this->cp_times_o = xCalloc(cpus, sizeof_cp_time_array);\n      this->cp_times_n = xCalloc(cpus, sizeof_cp_time_array);\n      len = cpus * sizeof_cp_time_array;\n      sysctl(MIB_kern_cp_times, 2, this->cp_times_o, &len, NULL, 0);\n   }\n\n   super->existingCPUs = MAXIMUM(cpus, 1);\n   // TODO: support offline CPUs and hot swapping\n   super->activeCPUs = super->existingCPUs;\n\n   if (cpus == 1 ) {\n      this->cpus = xRealloc(this->cpus, sizeof(CPUData));\n   } else {\n      // on smp we need CPUs + 1 to store averages too (as kernel kindly provides that as well)\n      this->cpus = xRealloc(this->cpus, (super->existingCPUs + 1) * sizeof(CPUData));\n   }\n\n   len = sizeof(this->kernelFScale);\n   if (sysctlbyname(\"kern.fscale\", &this->kernelFScale, &len, NULL, 0) == -1 || this->kernelFScale <= 0) {\n      //sane default for kernel provided CPU percentage scaling, at least on x86 machines, in case this sysctl call failed\n      this->kernelFScale = 2048;\n   }\n\n   this->kd = kvm_openfiles(NULL, \"/dev/null\", NULL, 0, errbuf);\n   if (this->kd == NULL) {\n      CRT_fatalError(\"kvm_openfiles() failed\");\n   }\n\n   return super;\n}\n\nvoid Machine_delete(Machine* super) {\n   FreeBSDMachine* this = (FreeBSDMachine*) super;\n\n   Machine_done(super);\n\n   if (this->kd) {\n      kvm_close(this->kd);\n   }\n\n   free(this->cp_time_o);\n   free(this->cp_time_n);\n   free(this->cp_times_o);\n   free(this->cp_times_n);\n   free(this->cpus);\n\n   free(this);\n}\n\nstatic inline void FreeBSDMachine_scanCPU(Machine* super) {\n   const FreeBSDMachine* this = (FreeBSDMachine*) super;\n\n   unsigned int cpus   = super->existingCPUs; // actual CPU count\n   unsigned int maxcpu = cpus;             // max iteration (in case we have average + smp)\n   int cp_times_offset;\n\n   assert(cpus > 0);\n\n   size_t sizeof_cp_time_array;\n\n   unsigned long* cp_time_n; // old clicks state\n   unsigned long* cp_time_o; // current clicks state\n\n   unsigned long cp_time_d[CPUSTATES];\n   double        cp_time_p[CPUSTATES];\n\n   // get averages or single CPU clicks\n   sizeof_cp_time_array = sizeof(unsigned long) * CPUSTATES;\n   sysctl(MIB_kern_cp_time, 2, this->cp_time_n, &sizeof_cp_time_array, NULL, 0);\n\n   // get rest of CPUs\n   if (cpus > 1) {\n      // on smp systems FreeBSD kernel concats all CPU states into one long array in\n      // kern.cp_times sysctl OID\n      // we store averages in this->cpus[0], and actual cores after that\n      maxcpu = cpus + 1;\n      sizeof_cp_time_array = cpus * sizeof(unsigned long) * CPUSTATES;\n      sysctl(MIB_kern_cp_times, 2, this->cp_times_n, &sizeof_cp_time_array, NULL, 0);\n   }\n\n   for (unsigned int i = 0; i < maxcpu; i++) {\n      if (cpus == 1) {\n         // single CPU box\n         cp_time_n = this->cp_time_n;\n         cp_time_o = this->cp_time_o;\n      } else {\n         if (i == 0 ) {\n            // average\n            cp_time_n = this->cp_time_n;\n            cp_time_o = this->cp_time_o;\n         } else {\n            // specific smp cores\n            cp_times_offset = i - 1;\n            cp_time_n = this->cp_times_n + (cp_times_offset * CPUSTATES);\n            cp_time_o = this->cp_times_o + (cp_times_offset * CPUSTATES);\n         }\n      }\n\n      // diff old vs new\n      unsigned long long total_o = 0;\n      unsigned long long total_n = 0;\n      unsigned long long total_d = 0;\n      for (int s = 0; s < CPUSTATES; s++) {\n         cp_time_d[s] = cp_time_n[s] - cp_time_o[s];\n         total_o += cp_time_o[s];\n         total_n += cp_time_n[s];\n      }\n\n      // totals\n      total_d = total_n - total_o;\n      if (total_d < 1 ) {\n         total_d = 1;\n      }\n\n      // save current state as old and calc percentages\n      for (int s = 0; s < CPUSTATES; ++s) {\n         cp_time_o[s] = cp_time_n[s];\n         cp_time_p[s] = ((double)cp_time_d[s]) / ((double)total_d) * 100;\n      }\n\n      CPUData* cpuData = &(this->cpus[i]);\n      cpuData->userPercent      = cp_time_p[CP_USER];\n      cpuData->nicePercent      = cp_time_p[CP_NICE];\n      cpuData->systemPercent    = cp_time_p[CP_SYS];\n      cpuData->irqPercent       = cp_time_p[CP_INTR];\n      cpuData->systemAllPercent = cp_time_p[CP_SYS] + cp_time_p[CP_INTR];\n      // this one is not really used\n      //cpuData->idlePercent      = cp_time_p[CP_IDLE];\n\n      cpuData->temperature = NAN;\n      cpuData->frequency = NAN;\n\n      const int coreId = (cpus == 1) ? 0 : ((int)i - 1);\n      if (coreId < 0)\n         continue;\n\n      // TODO: test with hyperthreading and multi-cpu systems\n      if (super->settings->showCPUTemperature) {\n         int temperature;\n         size_t len = sizeof(temperature);\n         char mibBuffer[32];\n         xSnprintf(mibBuffer, sizeof(mibBuffer), \"dev.cpu.%d.temperature\", coreId);\n         int r = sysctlbyname(mibBuffer, &temperature, &len, NULL, 0);\n         if (r == 0)\n            cpuData->temperature = (double)(temperature - 2732) / 10.0; // convert from deci-Kelvin to Celsius\n      }\n\n      // TODO: test with hyperthreading and multi-cpu systems\n      if (super->settings->showCPUFrequency) {\n         int frequency;\n         size_t len = sizeof(frequency);\n         char mibBuffer[32];\n         xSnprintf(mibBuffer, sizeof(mibBuffer), \"dev.cpu.%d.freq\", coreId);\n         int r = sysctlbyname(mibBuffer, &frequency, &len, NULL, 0);\n         if (r == 0)\n            cpuData->frequency = frequency; // keep in MHz\n      }\n   }\n\n   // calculate max temperature and avg frequency for average meter and\n   // propagate frequency to all cores if only supplied for CPU 0\n   if (cpus > 1) {\n      if (super->settings->showCPUTemperature) {\n         double maxTemp = -HUGE_VAL;\n         for (unsigned int i = 1; i < maxcpu; i++) {\n            if (isgreater(this->cpus[i].temperature, maxTemp)) {\n               maxTemp = this->cpus[i].temperature;\n               this->cpus[0].temperature = maxTemp;\n            }\n         }\n      }\n\n      if (super->settings->showCPUFrequency) {\n         const double coreZeroFreq = this->cpus[1].frequency;\n         double freqSum = coreZeroFreq;\n         if (isNonnegative(coreZeroFreq)) {\n            for (unsigned int i = 2; i < maxcpu; i++) {\n               if (!isNonnegative(this->cpus[i].frequency))\n                  this->cpus[i].frequency = coreZeroFreq;\n\n               freqSum += this->cpus[i].frequency;\n            }\n\n            this->cpus[0].frequency = freqSum / (maxcpu - 1);\n         }\n      }\n   }\n}\n\nstatic void FreeBSDMachine_scanMemoryInfo(Machine* super) {\n   FreeBSDMachine* this = (FreeBSDMachine*) super;\n\n   // comment by Pierre-Marie Baty <pm@pmbaty.com>\n   //\n   // FreeBSD has the following memory classes:\n   //    active:   userland pages currently mapped to physical memory (i.e. in use)\n   //    inactive: userland pages that are no longer active, can be (re)allocated to processes\n   //    laundry:  userland pages that were just released, now being flushed, will become inactive\n   //    wired:    kernel pages currently mapped to physical memory, cannot be paged out nor swapped\n   //       buffers: subcategory of 'wired' corresponding to the filesystem caches\n   //    free:     pages that haven't been allocated yet, or have been released\n   //\n   // With ZFS, the ARC area is NOT counted in the 'buffers' class, but is still counted in the 'wired'\n   // class. The ARC total must thus be subtracted from the 'wired' class AND added to the 'buffer' class,\n   // so that the result (ARC being shown in buffersMem) is consistent with what ZFS users would expect.\n   // This adjustment is done in Platform_setMemoryValues() in freebsd/Platform.c.\n\n   u_long totalMem;\n   u_int memActive, memWire, memInactive, memLaundry;\n   long buffersMem;\n   size_t len;\n\n   // total memory\n   len = sizeof(totalMem);\n   if ((sysctl(MIB_hw_physmem, 2, &(totalMem), &len, NULL, 0) == 0) && (totalMem > 0))\n      super->totalMem = totalMem / 1024;\n   else\n      super->totalMem = 0;\n\n   // \"active\" pages\n   len = sizeof(memActive);\n   if ((sysctl(MIB_vm_stats_vm_v_active_count, 4, &(memActive), &len, NULL, 0) == 0) && (memActive > 0))\n      this->activeMem = memActive * this->pageSizeKb;\n   else\n      this->activeMem = 0;\n\n   // \"wired\" pages\n   len = sizeof(memWire);\n   if ((sysctl(MIB_vm_stats_vm_v_wire_count, 4, &(memWire), &len, NULL, 0) == 0) && (memWire > 0))\n      this->wiredMem = memWire * this->pageSizeKb;\n   else\n      this->wiredMem = 0;\n\n   // \"inactive\" pages\n   len = sizeof(memInactive);\n   if ((sysctl(MIB_vm_stats_vm_v_inactive_count, 4, &(memInactive), &len, NULL, 0) == 0) && (memInactive > 0))\n      this->inactiveMem = memInactive * this->pageSizeKb;\n   else\n      this->inactiveMem = 0;\n\n   // \"laundry\" pages\n   len = sizeof(memLaundry);\n   if ((sysctl(MIB_vm_stats_vm_v_laundry_count, 4, &(memLaundry), &len, NULL, 0) == 0) && (memLaundry > 0))\n      this->laundryMem = memLaundry * this->pageSizeKb;\n   else\n      this->laundryMem = 0;\n\n   // \"buffers\" pages (separate read, should be deducted from 'wired')\n   len = sizeof(buffersMem);\n   if ((sysctl(MIB_vfs_bufspace, 2, &(buffersMem), &len, NULL, 0) == 0) && (buffersMem > 0))\n      this->buffersMem = buffersMem / 1024;\n   else\n      this->buffersMem = 0;\n   this->wiredMem -= this->buffersMem; // subtract (NB: \"buffers\" can't be larger than \"wired\")\n\n   // NOTE: it is wrong in FreeBSD to represent the \"shared\" memory as a memory class by itself.\n   // The only page classes exposed by the kernel are \"active\", \"inactive\", \"wired\", \"laundry\" and \"free\".\n   // The \"shared\" memory can be obtained from another sysctl, but there is no simple way\n   // in FreeBSD to determine which page classe(s) this \"shared\" memory should be subtracted from.\n\n   // swap\n   struct kvm_swap swap[16];\n   int nswap = kvm_getswapinfo(this->kd, swap, ARRAYSIZE(swap), 0);\n   super->totalSwap = 0;\n   super->usedSwap = 0;\n   for (int i = 0; i < nswap; i++) {\n      super->totalSwap += swap[i].ksw_total;\n      super->usedSwap += swap[i].ksw_used;\n   }\n   super->totalSwap *= this->pageSizeKb;\n   super->usedSwap *= this->pageSizeKb;\n}\n\nvoid Machine_scan(Machine* super) {\n   FreeBSDMachine* this = (FreeBSDMachine*) super;\n\n   openzfs_sysctl_updateArcStats(&this->zfs);\n   FreeBSDMachine_scanMemoryInfo(super);\n   FreeBSDMachine_scanCPU(super);\n}\n\nbool Machine_isCPUonline(const Machine* host, unsigned int id) {\n   assert(id < host->existingCPUs);\n\n   // TODO: support offline CPUs and hot swapping\n   (void) host; (void) id;\n\n   return true;\n}\n"
  },
  {
    "path": "freebsd/FreeBSDMachine.h",
    "content": "#ifndef HEADER_FreeBSDMachine\n#define HEADER_FreeBSDMachine\n/*\nhtop - FreeBSDMachine.h\n(C) 2014 Hisham H. Muhammad\nReleased under the GNU GPLv2+, see the COPYING file\nin the source distribution for its full text.\n*/\n\n#include <kvm.h>\n#include <stdbool.h>\n#include <sys/types.h>\n\n#include \"Hashtable.h\"\n#include \"Machine.h\"\n#include \"UsersTable.h\"\n#include \"zfs/ZfsArcStats.h\"\n\n\ntypedef struct CPUData_ {\n   double userPercent;\n   double nicePercent;\n   double systemPercent;\n   double irqPercent;\n   double systemAllPercent;\n\n   double frequency;\n   double temperature;\n} CPUData;\n\ntypedef struct FreeBSDMachine_ {\n   Machine super;\n   kvm_t* kd;\n\n   int pageSize;\n   int pageSizeKb;\n   int kernelFScale;\n\n   memory_t wiredMem;\n   memory_t buffersMem;\n   memory_t activeMem;\n   memory_t laundryMem;\n   memory_t inactiveMem;\n   memory_t arcMem;\n\n   ZfsArcStats zfs;\n\n   CPUData* cpus;\n\n   unsigned long* cp_time_o;\n   unsigned long* cp_time_n;\n\n   unsigned long* cp_times_o;\n   unsigned long* cp_times_n;\n\n} FreeBSDMachine;\n\n#endif\n"
  },
  {
    "path": "freebsd/FreeBSDProcess.c",
    "content": "/*\nhtop - FreeBSDProcess.c\n(C) 2015 Hisham H. Muhammad\nReleased under the GNU GPLv2+, see the COPYING file\nin the source distribution for its full text.\n*/\n\n#include \"config.h\" // IWYU pragma: keep\n\n#include \"freebsd/FreeBSDProcess.h\"\n\n#include <stdlib.h>\n\n#include \"CRT.h\"\n#include \"Macros.h\"\n#include \"Process.h\"\n#include \"RichString.h\"\n#include \"Scheduling.h\"\n#include \"XUtils.h\"\n\n\nconst char* const nodevStr = \"nodev\";\n\nconst ProcessFieldData Process_fields[LAST_PROCESSFIELD] = {\n   [0] = { .name = \"\", .title = NULL, .description = NULL, .flags = 0, },\n   [PID] = { .name = \"PID\", .title = \"PID\", .description = \"Process/thread ID\", .flags = 0, .pidColumn = true, },\n   [COMM] = { .name = \"Command\", .title = \"Command \", .description = \"Command line (insert as last column only)\", .flags = 0, },\n   [STATE] = { .name = \"STATE\", .title = \"S \", .description = \"Process state (S sleeping, R running, D disk, Z zombie, T traced, W paging)\", .flags = 0, },\n   [PPID] = { .name = \"PPID\", .title = \"PPID\", .description = \"Parent process ID\", .flags = 0, .pidColumn = true, },\n   [PGRP] = { .name = \"PGRP\", .title = \"PGRP\", .description = \"Process group ID\", .flags = 0, .pidColumn = true, },\n   [SESSION] = { .name = \"SESSION\", .title = \"SID\", .description = \"Process's session ID\", .flags = 0, .pidColumn = true, },\n   [TTY] = { .name = \"TTY\", .title = \"TTY      \", .description = \"Controlling terminal\", .flags = 0, },\n   [TPGID] = { .name = \"TPGID\", .title = \"TPGID\", .description = \"Process ID of the fg process group of the controlling terminal\", .flags = 0, .pidColumn = true, },\n   [MAJFLT] = { .name = \"MAJFLT\", .title = \"     MAJFLT \", .description = \"Number of copy-on-write faults\", .flags = 0, .defaultSortDesc = true, },\n   [PRIORITY] = { .name = \"PRIORITY\", .title = \"PRI \", .description = \"Kernel's internal priority for the process\", .flags = 0, },\n   [NICE] = { .name = \"NICE\", .title = \" NI \", .description = \"Nice value (the higher the value, the more it lets other processes take priority)\", .flags = 0, },\n   [STARTTIME] = { .name = \"STARTTIME\", .title = \"START \", .description = \"Time the process was started\", .flags = 0, },\n   [ELAPSED] = { .name = \"ELAPSED\", .title = \"ELAPSED  \", .description = \"Time since the process was started\", .flags = 0, },\n   [PROCESSOR] = { .name = \"PROCESSOR\", .title = \"CPU \", .description = \"Id of the CPU the process last executed on\", .flags = 0, },\n   [M_VIRT] = { .name = \"M_VIRT\", .title = \" VIRT \", .description = \"Total program size in virtual memory\", .flags = 0, .defaultSortDesc = true, },\n   [M_RESIDENT] = { .name = \"M_RESIDENT\", .title = \"  RES \", .description = \"Resident set size, size of the text and data sections, plus stack usage\", .flags = 0, .defaultSortDesc = true, },\n   [ST_UID] = { .name = \"ST_UID\", .title = \"UID\", .description = \"User ID of the process owner\", .flags = 0, },\n   [PERCENT_CPU] = { .name = \"PERCENT_CPU\", .title = \" CPU%\", .description = \"Percentage of the CPU time the process used in the last sampling\", .flags = 0, .defaultSortDesc = true, .autoWidth = true, .autoTitleRightAlign = true, },\n   [PERCENT_NORM_CPU] = { .name = \"PERCENT_NORM_CPU\", .title = \"NCPU%\", .description = \"Normalized percentage of the CPU time the process used in the last sampling (normalized by cpu count)\", .flags = 0, .defaultSortDesc = true, .autoWidth = true, },\n   [PERCENT_MEM] = { .name = \"PERCENT_MEM\", .title = \"MEM% \", .description = \"Percentage of the memory the process is using, based on resident memory size\", .flags = 0, .defaultSortDesc = true, },\n   [USER] = { .name = \"USER\", .title = \"USER       \", .description = \"Username of the process owner (or user ID if name cannot be determined)\", .flags = 0, },\n   [TIME] = { .name = \"TIME\", .title = \"  TIME+  \", .description = \"Total time the process has spent in user and system time\", .flags = 0, .defaultSortDesc = true, },\n   [NLWP] = { .name = \"NLWP\", .title = \"NLWP \", .description = \"Number of threads in the process\", .flags = 0, .defaultSortDesc = true, },\n   [TGID] = { .name = \"TGID\", .title = \"TGID\", .description = \"Thread group ID (i.e. process ID)\", .flags = 0, .pidColumn = true, },\n   [PROC_COMM] = { .name = \"COMM\", .title = \"COMM            \", .description = \"comm string of the process\", .flags = 0, },\n   [PROC_EXE] = { .name = \"EXE\", .title = \"EXE             \", .description = \"Basename of exe of the process\", .flags = 0, },\n   [CWD] = { .name = \"CWD\", .title = \"CWD                       \", .description = \"The current working directory of the process\", .flags = PROCESS_FLAG_CWD, },\n#ifdef SCHEDULER_SUPPORT\n   [SCHEDULERPOLICY] = { .name = \"SCHEDULERPOLICY\", .title = \"SCHED \", .description = \"Current scheduling policy of the process\", .flags = PROCESS_FLAG_SCHEDPOL, },\n#endif\n   [JID] = { .name = \"JID\", .title = \"JID\", .description = \"Jail prison ID\", .flags = 0, .pidColumn = true, },\n   [JAIL] = { .name = \"JAIL\", .title = \"JAIL        \", .description = \"Jail prison name\", .flags = 0, },\n   [SCHEDCLASS] = { .name = \"SCHEDCLASS\", .title = \"SC\", .description = \"Scheduling Class (Timesharing, Realtime, Idletime)\", .flags = 0, },\n   [EMULATION] = { .name = \"EMULATION\", .title = \"EMULATION        \", .description = \"System call emulation environment (ABI)\", .flags = 0, },\n};\n\nProcess* FreeBSDProcess_new(const Machine* machine) {\n   FreeBSDProcess* this = xCalloc(1, sizeof(FreeBSDProcess));\n   Object_setClass(this, Class(FreeBSDProcess));\n   Process_init(&this->super, machine);\n   return (Process*)this;\n}\n\nvoid Process_delete(Object* cast) {\n   FreeBSDProcess* this = (FreeBSDProcess*) cast;\n   Process_done((Process*)cast);\n   free(this->emul);\n   free(this->jname);\n   free(this);\n}\n\nstatic const char FreeBSD_schedclassChars[MAX_SCHEDCLASS] = {\n   [SCHEDCLASS_UNKNOWN] = '?',     // Something went wrong or the base system has a new scheduling class\n   [SCHEDCLASS_INTR_THREAD] = '-', // interrupt thread, these have special handling of priority\n   [SCHEDCLASS_IDLE] = 'i',        // idletime scheduling\n   [SCHEDCLASS_TIMESHARE] = ' ',   // timesharing process scheduling (regular processes are timeshared)\n   [SCHEDCLASS_REALTIME] = 'r',    // realtime scheduling\n};\n\nstatic void FreeBSDProcess_rowWriteField(const Row* super, RichString* str, ProcessField field) {\n   const FreeBSDProcess* fp = (const FreeBSDProcess*) super;\n\n   char buffer[256]; buffer[255] = '\\0';\n   char sched_class;\n   int attr = CRT_colors[DEFAULT_COLOR];\n   size_t n = sizeof(buffer) - 1;\n\n   switch (field) {\n   // add FreeBSD-specific fields here\n   case JID: xSnprintf(buffer, n, \"%*d \", Process_pidDigits, fp->jid); break;\n\n   case JAIL:\n      Row_printLeftAlignedField(str, attr, fp->jname ? fp->jname : \"N/A\", 11);\n      return;\n\n   case EMULATION:\n      Row_printLeftAlignedField(str, attr, fp->emul ? fp->emul : \"N/A\", 16);\n      return;\n\n   case SCHEDCLASS:\n      assert(0 <= fp->sched_class && fp->sched_class < ARRAYSIZE(FreeBSD_schedclassChars));\n      sched_class = FreeBSD_schedclassChars[fp->sched_class];\n      assert(sched_class);\n      xSnprintf(buffer, n, \" %c\", sched_class);\n      break;\n\n   default:\n      Process_writeField(&fp->super, str, field);\n      return;\n   }\n\n   RichString_appendWide(str, attr, buffer);\n}\n\nstatic int FreeBSDProcess_compareByKey(const Process* v1, const Process* v2, ProcessField key) {\n   const FreeBSDProcess* p1 = (const FreeBSDProcess*)v1;\n   const FreeBSDProcess* p2 = (const FreeBSDProcess*)v2;\n\n   switch (key) {\n   // add FreeBSD-specific fields here\n   case JID:\n      return SPACESHIP_NUMBER(p1->jid, p2->jid);\n   case JAIL:\n      return SPACESHIP_NULLSTR(p1->jname, p2->jname);\n   case EMULATION:\n      return SPACESHIP_NULLSTR(p1->emul, p2->emul);\n   case SCHEDCLASS:\n      return SPACESHIP_NUMBER(p1->sched_class, p2->sched_class);\n   default:\n      return Process_compareByKey_Base(v1, v2, key);\n   }\n}\n\nconst ProcessClass FreeBSDProcess_class = {\n   .super = {\n      .super = {\n         .extends = Class(Process),\n         .display = Row_display,\n         .delete = Process_delete,\n         .compare = Process_compare\n      },\n      .isHighlighted = Process_rowIsHighlighted,\n      .isVisible = Process_rowIsVisible,\n      .matchesFilter = Process_rowMatchesFilter,\n      .compareByParent = Process_compareByParent,\n      .sortKeyString = Process_rowGetSortKey,\n      .writeField = FreeBSDProcess_rowWriteField\n   },\n   .compareByKey = FreeBSDProcess_compareByKey\n};\n"
  },
  {
    "path": "freebsd/FreeBSDProcess.h",
    "content": "#ifndef HEADER_FreeBSDProcess\n#define HEADER_FreeBSDProcess\n/*\nhtop - FreeBSDProcess.h\n(C) 2015 Hisham H. Muhammad\nReleased under the GNU GPLv2+, see the COPYING file\nin the source distribution for its full text.\n*/\n\n#include <stdbool.h>\n\n#include \"Object.h\"\n#include \"Process.h\"\n#include \"Machine.h\"\n\ntypedef enum {\n   SCHEDCLASS_UNKNOWN = 0,\n\n   SCHEDCLASS_INTR_THREAD, /* interrupt thread */\n   SCHEDCLASS_REALTIME,\n   SCHEDCLASS_TIMESHARE, /* Regular scheduling */\n   SCHEDCLASS_IDLE,\n\n   MAX_SCHEDCLASS,\n} FreeBSDSchedClass;\n\ntypedef struct FreeBSDProcess_ {\n   Process super;\n   int   jid;\n   char* jname;\n   char* emul;\n   FreeBSDSchedClass sched_class;\n} FreeBSDProcess;\n\nextern const ProcessClass FreeBSDProcess_class;\n\nextern const ProcessFieldData Process_fields[LAST_PROCESSFIELD];\n\nProcess* FreeBSDProcess_new(const Machine* host);\n\nvoid Process_delete(Object* cast);\n\n#endif\n"
  },
  {
    "path": "freebsd/FreeBSDProcessTable.c",
    "content": "/*\nhtop - FreeBSDProcessTable.c\n(C) 2014 Hisham H. Muhammad\nReleased under the GNU GPLv2+, see the COPYING file\nin the source distribution for its full text.\n*/\n\n#include \"config.h\" // IWYU pragma: keep\n\n#include \"freebsd/FreeBSDProcessTable.h\"\n\n#include <assert.h>\n#include <limits.h>\n#include <math.h>\n#include <stdlib.h>\n#include <string.h>\n#include <unistd.h>\n#include <sys/_iovec.h>\n#include <sys/errno.h>\n#include <sys/param.h> // needs to be included before <sys/jail.h> for MAXPATHLEN\n#include <sys/jail.h>\n#include <sys/priority.h>\n#include <sys/proc.h>\n#include <sys/resource.h>\n#include <sys/stat.h>\n#include <sys/sysctl.h>\n#include <sys/time.h>\n#include <sys/types.h>\n#include <sys/user.h>\n#include <sys/vmmeter.h>\n\n#include \"CRT.h\"\n#include \"Macros.h\"\n#include \"Object.h\"\n#include \"Process.h\"\n#include \"ProcessTable.h\"\n#include \"Scheduling.h\"\n#include \"Settings.h\"\n#include \"XUtils.h\"\n\n#include \"freebsd/FreeBSDMachine.h\"\n#include \"freebsd/FreeBSDProcess.h\"\n\n\nProcessTable* ProcessTable_new(Machine* host, Hashtable* pidMatchList) {\n   FreeBSDProcessTable* this = xCalloc(1, sizeof(FreeBSDProcessTable));\n   Object_setClass(this, Class(ProcessTable));\n   this->osreldate = getosreldate();\n\n   ProcessTable* super = &this->super;\n   ProcessTable_init(super, Class(FreeBSDProcess), host, pidMatchList);\n\n   return super;\n}\n\nvoid ProcessTable_delete(Object* cast) {\n   FreeBSDProcessTable* this = (FreeBSDProcessTable*) cast;\n   ProcessTable_done(&this->super);\n   free(this);\n}\n\nstatic void FreeBSDProcessTable_updateExe(const struct kinfo_proc* kproc, Process* proc) {\n   if (Process_isKernelThread(proc)) {\n      Process_updateExe(proc, NULL);\n      return;\n   }\n\n   const int mib[] = { CTL_KERN, KERN_PROC, KERN_PROC_PATHNAME, kproc->ki_pid };\n   char buffer[2048];\n   size_t size = sizeof(buffer);\n   if (sysctl(mib, 4, buffer, &size, NULL, 0) != 0) {\n      Process_updateExe(proc, NULL);\n      return;\n   }\n\n   Process_updateExe(proc, buffer);\n}\n\nstatic void FreeBSDProcessTable_updateCwd(const struct kinfo_proc* kproc, Process* proc) {\n#ifdef KERN_PROC_CWD\n   const int mib[] = { CTL_KERN, KERN_PROC, KERN_PROC_CWD, kproc->ki_pid };\n   char buffer[2048];\n   size_t size = sizeof(buffer);\n   if (sysctl(mib, 4, buffer, &size, NULL, 0) != 0) {\n      free(proc->procCwd);\n      proc->procCwd = NULL;\n      return;\n   }\n\n   /* Kernel threads return an empty buffer */\n   if (buffer[0] == '\\0') {\n      free(proc->procCwd);\n      proc->procCwd = NULL;\n      return;\n   }\n\n   free_and_xStrdup(&proc->procCwd, buffer);\n#else\n   proc->procCwd = NULL;\n#endif\n}\n\nstatic void FreeBSDProcessTable_updateProcessName(kvm_t* kd, const struct kinfo_proc* kproc, Process* proc) {\n   Process_updateComm(proc, kproc->ki_comm);\n\n   char** argv = kvm_getargv(kd, kproc, 0);\n   if (!argv || !argv[0]) {\n      Process_updateCmdline(proc, kproc->ki_comm, 0, strlen(kproc->ki_comm));\n      return;\n   }\n\n   size_t len = 0;\n   for (size_t i = 0; argv[i]; i++) {\n      len += strlen(argv[i]) + 1;\n   }\n\n   char* cmdline = xMalloc(len);\n   char* at = cmdline;\n   size_t end = 0;\n   for (size_t i = 0; argv[i]; i++) {\n      at = stpcpy(at, argv[i]);\n      if (end == 0) {\n         end = (size_t)(at - cmdline);\n      }\n      *at++ = ' ';\n   }\n   at--;\n   *at = '\\0';\n\n   Process_updateCmdline(proc, cmdline, 0, end);\n\n   free(cmdline);\n}\n\nstatic char* FreeBSDProcessTable_readJailName(const struct kinfo_proc* kproc) {\n   if (kproc->ki_jid == 0)\n      return xStrdup(\"-\");\n\n   char jnamebuf[MAXHOSTNAMELEN] = {0};\n   struct iovec jiov[4];\n\nIGNORE_WCASTQUAL_BEGIN\n   *(const void**)&jiov[0].iov_base = \"jid\";\n   jiov[0].iov_len = sizeof(\"jid\");\n   jiov[1].iov_base = (void*) &kproc->ki_jid;\n   jiov[1].iov_len = sizeof(kproc->ki_jid);\n   *(const void**)&jiov[2].iov_base = \"name\";\n   jiov[2].iov_len = sizeof(\"name\");\n   jiov[3].iov_base = jnamebuf;\n   jiov[3].iov_len = sizeof(jnamebuf);\nIGNORE_WCASTQUAL_END\n\n   int jid = jail_get(jiov, 4, 0);\n   if (jid == kproc->ki_jid)\n      return xStrdup(jnamebuf);\n\n   return NULL;\n}\n\nvoid ProcessTable_goThroughEntries(ProcessTable* super) {\n   const FreeBSDProcessTable* this = (const FreeBSDProcessTable*)super;\n   const Machine* host = super->super.host;\n   const FreeBSDMachine* fhost = (const FreeBSDMachine*) host;\n   const Settings* settings = host->settings;\n   bool hideKernelThreads = settings->hideKernelThreads;\n   bool hideUserlandThreads = settings->hideUserlandThreads;\n\n   int count = 0;\n   const struct kinfo_proc* kprocs = kvm_getprocs(fhost->kd, KERN_PROC_PROC, 0, &count);\n\n   for (int i = 0; i < count; i++) {\n      const struct kinfo_proc* kproc = &kprocs[i];\n      bool preExisting = false;\n      Process* proc = ProcessTable_getProcess(super, kproc->ki_pid, &preExisting, FreeBSDProcess_new);\n      FreeBSDProcess* fp = (FreeBSDProcess*) proc;\n\n      if (!preExisting) {\n         fp->jid = kproc->ki_jid;\n         Process_setPid(proc, kproc->ki_pid);\n         Process_setThreadGroup(proc, kproc->ki_pid);\n         Process_setParent(proc, kproc->ki_ppid);\n         proc->isKernelThread = kproc->ki_pid != 1 && (kproc->ki_flag & P_SYSTEM);\n         proc->isUserlandThread = false;\n         proc->tpgid = kproc->ki_tpgid;\n         proc->session = kproc->ki_sid;\n         proc->pgrp = kproc->ki_pgid;\n         proc->st_uid = kproc->ki_uid;\n         proc->starttime_ctime = kproc->ki_start.tv_sec;\n         if (proc->starttime_ctime < 0) {\n            proc->starttime_ctime = host->realtimeMs / 1000;\n         }\n         Process_fillStarttimeBuffer(proc);\n         proc->user = UsersTable_getRef(host->usersTable, proc->st_uid);\n         ProcessTable_add(super, proc);\n\n         FreeBSDProcessTable_updateExe(kproc, proc);\n         FreeBSDProcessTable_updateProcessName(fhost->kd, kproc, proc);\n\n         if (settings->ss->flags & PROCESS_FLAG_CWD) {\n            FreeBSDProcessTable_updateCwd(kproc, proc);\n         }\n\n         fp->jname = FreeBSDProcessTable_readJailName(kproc);\n\n         proc->tty_nr = kproc->ki_tdev;\n         const char* name = (kproc->ki_tdev != NODEV) ? devname(kproc->ki_tdev, S_IFCHR) : NULL;\n         if (!name) {\n            free(proc->tty_name);\n            proc->tty_name = NULL;\n         } else {\n            free_and_xStrdup(&proc->tty_name, name);\n         }\n      } else {\n         if (fp->jid != kproc->ki_jid) {\n            // process can enter jail anytime\n            fp->jid = kproc->ki_jid;\n            free(fp->jname);\n            fp->jname = FreeBSDProcessTable_readJailName(kproc);\n         }\n         // if there are reapers in the system, process can get reparented anytime\n         Process_setParent(proc, kproc->ki_ppid);\n         if (proc->st_uid != kproc->ki_uid) {\n            // some processes change users (eg. to lower privs)\n            proc->st_uid = kproc->ki_uid;\n            proc->user = UsersTable_getRef(host->usersTable, proc->st_uid);\n         }\n         if (settings->updateProcessNames) {\n            FreeBSDProcessTable_updateProcessName(fhost->kd, kproc, proc);\n         }\n      }\n\n      free_and_xStrdup(&fp->emul, kproc->ki_emul);\n\n      // from FreeBSD source /src/usr.bin/top/machine.c\n      proc->m_virt = kproc->ki_size / ONE_K;\n      proc->m_resident = kproc->ki_rssize * fhost->pageSizeKb;\n      proc->nlwp = kproc->ki_numthreads;\n      proc->time = (kproc->ki_runtime + 5000) / 10000;\n\n      proc->percent_cpu = 100.0 * ((double)kproc->ki_pctcpu / (double)fhost->kernelFScale);\n      proc->percent_mem = 100.0 * proc->m_resident / (double)(host->totalMem);\n      Process_updateCPUFieldWidths(proc->percent_cpu);\n\n      if (kproc->ki_stat == SRUN && kproc->ki_oncpu != NOCPU) {\n         proc->processor = kproc->ki_oncpu;\n      } else {\n         proc->processor = kproc->ki_lastcpu;\n      }\n\n      proc->majflt = kproc->ki_cow;\n\n      proc->priority = kproc->ki_pri.pri_level -\n          /* Reference point, as used by system's top(1) and ps(1). */\n          (this->osreldate >= 1500048 ? PUSER : PZERO);\n\n      switch (PRI_BASE(kproc->ki_pri.pri_class)) {\n         /* Handling of the below is explained in the FreeBSD base system in:\n          * /usr/src/usr.bin/top/machine.c (function format_nice) */\n         case PRI_ITHD:\n            fp->sched_class = SCHEDCLASS_INTR_THREAD;\n            proc->nice = 0;\n            break;\n\n         case PRI_REALTIME:\n            fp->sched_class = SCHEDCLASS_REALTIME;\n\n            /* Different for KPROCs and user procs */\n            if (kproc->ki_flag & P_KPROC) {\n               proc->nice = kproc->ki_pri.pri_native - PRI_MIN_REALTIME;\n            } else {\n               proc->nice = kproc->ki_pri.pri_user - PRI_MIN_REALTIME;\n            }\n            break;\n\n         case PRI_IDLE:\n            fp->sched_class = SCHEDCLASS_IDLE;\n\n            /* Different for KPROCs and user procs */\n            if (kproc->ki_flag & P_KPROC) {\n               proc->nice = kproc->ki_pri.pri_native - PRI_MIN_IDLE;\n            } else {\n               proc->nice = kproc->ki_pri.pri_user - PRI_MIN_IDLE;\n            }\n            break;\n\n         case PRI_TIMESHARE:\n            fp->sched_class = SCHEDCLASS_TIMESHARE;\n            proc->nice = kproc->ki_nice - NZERO;\n            break;\n\n         default:\n            fp->sched_class = SCHEDCLASS_UNKNOWN;\n            proc->nice = PROCESS_NICE_UNKNOWN;\n            break;\n      }\n\n      /* Taken from: https://github.com/freebsd/freebsd-src/blob/1ad2d87778970582854082bcedd2df0394fd4933/sys/sys/proc.h#L851 */\n      switch (kproc->ki_stat) {\n         case SIDL:   proc->state = IDLE; break;\n         case SRUN:   proc->state = RUNNING; break;\n         case SSLEEP: proc->state = SLEEPING; break;\n         case SSTOP:  proc->state = STOPPED; break;\n         case SZOMB:  proc->state = ZOMBIE; break;\n         case SWAIT:  proc->state = WAITING; break;\n         case SLOCK:  proc->state = BLOCKED; break;\n         default:     proc->state = UNKNOWN;\n      }\n\n      if (Process_isKernelThread(proc))\n         super->kernelThreads++;\n\n#ifdef SCHEDULER_SUPPORT\n      if (settings->ss->flags & PROCESS_FLAG_SCHEDPOL)\n         Scheduling_readProcessPolicy(proc);\n#endif\n\n      proc->super.show = ! ((hideKernelThreads && Process_isKernelThread(proc)) || (hideUserlandThreads && Process_isUserlandThread(proc)));\n\n      super->totalTasks++;\n      if (proc->state == RUNNING)\n         super->runningTasks++;\n      proc->super.updated = true;\n   }\n}\n"
  },
  {
    "path": "freebsd/FreeBSDProcessTable.h",
    "content": "#ifndef HEADER_FreeBSDProcessTable\n#define HEADER_FreeBSDProcessTable\n/*\nhtop - FreeBSDProcessTable.h\n(C) 2014 Hisham H. Muhammad\nReleased under the GNU GPLv2+, see the COPYING file\nin the source distribution for its full text.\n*/\n\n#include <stdbool.h>\n#include <sys/types.h>\n\n#include \"Hashtable.h\"\n#include \"ProcessTable.h\"\n#include \"UsersTable.h\"\n\ntypedef struct FreeBSDProcessTable_ {\n   ProcessTable super;\n\n   int osreldate;\n} FreeBSDProcessTable;\n\n#endif\n"
  },
  {
    "path": "freebsd/Platform.c",
    "content": "/*\nhtop - freebsd/Platform.c\n(C) 2014 Hisham H. Muhammad\nReleased under the GNU GPLv2+, see the COPYING file\nin the source distribution for its full text.\n*/\n\n#include \"config.h\" // IWYU pragma: keep\n\n#include \"freebsd/Platform.h\"\n\n#include <devstat.h>\n#include <math.h>\n#include <stdint.h>\n#include <stdlib.h>\n#include <time.h>\n#include <net/if.h>\n#include <net/if_mib.h>\n#include <sys/_types.h>\n#include <sys/devicestat.h>\n#include <sys/param.h>\n#include <sys/resource.h>\n#include <sys/socket.h>\n#include <sys/sysctl.h>\n#include <sys/time.h>\n#include <sys/types.h>\n#include <vm/vm_param.h>\n\n#include \"CPUMeter.h\"\n#include \"ClockMeter.h\"\n#include \"DateTimeMeter.h\"\n#include \"DiskIOMeter.h\"\n#include \"FileDescriptorMeter.h\"\n#include \"HostnameMeter.h\"\n#include \"LoadAverageMeter.h\"\n#include \"Machine.h\"\n#include \"Macros.h\"\n#include \"MemoryMeter.h\"\n#include \"MemorySwapMeter.h\"\n#include \"Meter.h\"\n#include \"NetworkIOMeter.h\"\n#include \"Settings.h\"\n#include \"SwapMeter.h\"\n#include \"SysArchMeter.h\"\n#include \"TasksMeter.h\"\n#include \"UptimeMeter.h\"\n#include \"XUtils.h\"\n#include \"freebsd/FreeBSDMachine.h\"\n#include \"generic/fdstat_sysctl.h\"\n#include \"zfs/ZfsArcMeter.h\"\n#include \"zfs/ZfsCompressedArcMeter.h\"\n\nconst ScreenDefaults Platform_defaultScreens[] = {\n   {\n      .name = \"Main\",\n      .columns = \"PID USER PRIORITY NICE M_VIRT M_RESIDENT STATE PERCENT_CPU PERCENT_MEM TIME Command\",\n      .sortKey = \"PERCENT_CPU\",\n   },\n};\n\nconst unsigned int Platform_numberOfDefaultScreens = ARRAYSIZE(Platform_defaultScreens);\n\nconst SignalItem Platform_signals[] = {\n   { .name = \" 0 Cancel\",    .number =  0 },\n   { .name = \" 1 SIGHUP\",    .number =  1 },\n   { .name = \" 2 SIGINT\",    .number =  2 },\n   { .name = \" 3 SIGQUIT\",   .number =  3 },\n   { .name = \" 4 SIGILL\",    .number =  4 },\n   { .name = \" 5 SIGTRAP\",   .number =  5 },\n   { .name = \" 6 SIGABRT\",   .number =  6 },\n   { .name = \" 7 SIGEMT\",    .number =  7 },\n   { .name = \" 8 SIGFPE\",    .number =  8 },\n   { .name = \" 9 SIGKILL\",   .number =  9 },\n   { .name = \"10 SIGBUS\",    .number = 10 },\n   { .name = \"11 SIGSEGV\",   .number = 11 },\n   { .name = \"12 SIGSYS\",    .number = 12 },\n   { .name = \"13 SIGPIPE\",   .number = 13 },\n   { .name = \"14 SIGALRM\",   .number = 14 },\n   { .name = \"15 SIGTERM\",   .number = 15 },\n   { .name = \"16 SIGURG\",    .number = 16 },\n   { .name = \"17 SIGSTOP\",   .number = 17 },\n   { .name = \"18 SIGTSTP\",   .number = 18 },\n   { .name = \"19 SIGCONT\",   .number = 19 },\n   { .name = \"20 SIGCHLD\",   .number = 20 },\n   { .name = \"21 SIGTTIN\",   .number = 21 },\n   { .name = \"22 SIGTTOU\",   .number = 22 },\n   { .name = \"23 SIGIO\",     .number = 23 },\n   { .name = \"24 SIGXCPU\",   .number = 24 },\n   { .name = \"25 SIGXFSZ\",   .number = 25 },\n   { .name = \"26 SIGVTALRM\", .number = 26 },\n   { .name = \"27 SIGPROF\",   .number = 27 },\n   { .name = \"28 SIGWINCH\",  .number = 28 },\n   { .name = \"29 SIGINFO\",   .number = 29 },\n   { .name = \"30 SIGUSR1\",   .number = 30 },\n   { .name = \"31 SIGUSR2\",   .number = 31 },\n   { .name = \"32 SIGTHR\",    .number = 32 },\n   { .name = \"33 SIGLIBRT\",  .number = 33 },\n};\n\nconst unsigned int Platform_numberOfSignals = ARRAYSIZE(Platform_signals);\n\nenum {\n   MEMORY_CLASS_WIRED = 0,\n   MEMORY_CLASS_BUFFERS,\n   MEMORY_CLASS_ACTIVE,\n   MEMORY_CLASS_LAUNDRY,\n   MEMORY_CLASS_INACTIVE,\n   MEMORY_CLASS_ARC,\n}; // N.B. the chart will display categories in this order\n\nconst MemoryClass Platform_memoryClasses[] = {\n   [MEMORY_CLASS_WIRED] = { .label = \"wired\", .countsAsUsed = true, .countsAsCache = false, .color = MEMORY_1 },\n   [MEMORY_CLASS_BUFFERS] = { .label = \"buffers\", .countsAsUsed = true, .countsAsCache = false, .color = MEMORY_2 },\n   [MEMORY_CLASS_ACTIVE] = { .label = \"active\", .countsAsUsed = true, .countsAsCache = false, .color = MEMORY_3 },\n   [MEMORY_CLASS_LAUNDRY] = { .label = \"laundry\", .countsAsUsed = true, .countsAsCache = false, .color = MEMORY_4 },\n   [MEMORY_CLASS_INACTIVE] = { .label = \"inactive\", .countsAsUsed = false, .countsAsCache = true, .color = MEMORY_5 },\n   [MEMORY_CLASS_ARC] = { .label = \"ARC\", .countsAsUsed = false, .countsAsCache = true, .color = MEMORY_6 },\n};\n\nconst unsigned int Platform_numberOfMemoryClasses = ARRAYSIZE(Platform_memoryClasses);\n\nconst MeterClass* const Platform_meterTypes[] = {\n   &CPUMeter_class,\n   &ClockMeter_class,\n   &DateMeter_class,\n   &DateTimeMeter_class,\n   &LoadAverageMeter_class,\n   &LoadMeter_class,\n   &MemoryMeter_class,\n   &SwapMeter_class,\n   &MemorySwapMeter_class,\n   &TasksMeter_class,\n   &UptimeMeter_class,\n   &SecondsUptimeMeter_class,\n   &BatteryMeter_class,\n   &HostnameMeter_class,\n   &SysArchMeter_class,\n   &AllCPUsMeter_class,\n   &AllCPUs2Meter_class,\n   &AllCPUs4Meter_class,\n   &AllCPUs8Meter_class,\n   &LeftCPUsMeter_class,\n   &RightCPUsMeter_class,\n   &LeftCPUs2Meter_class,\n   &RightCPUs2Meter_class,\n   &LeftCPUs4Meter_class,\n   &RightCPUs4Meter_class,\n   &LeftCPUs8Meter_class,\n   &RightCPUs8Meter_class,\n   &BlankMeter_class,\n   &ZfsArcMeter_class,\n   &ZfsCompressedArcMeter_class,\n   &DiskIORateMeter_class,\n   &DiskIOTimeMeter_class,\n   &DiskIOMeter_class,\n   &FileDescriptorMeter_class,\n   &NetworkIOMeter_class,\n   NULL\n};\n\nbool Platform_init(void) {\n   /* no platform-specific setup needed */\n   return true;\n}\n\nvoid Platform_done(void) {\n   /* no platform-specific cleanup needed */\n}\n\nvoid Platform_setBindings(Htop_Action* keys) {\n   /* no platform-specific key bindings */\n   (void) keys;\n}\n\nint Platform_getUptime(void) {\n   struct timeval bootTime, currTime;\n   const int mib[2] = { CTL_KERN, KERN_BOOTTIME };\n   size_t size = sizeof(bootTime);\n\n   int err = sysctl(mib, 2, &bootTime, &size, NULL, 0);\n   if (err) {\n      return -1;\n   }\n   gettimeofday(&currTime, NULL);\n\n   return (int) difftime(currTime.tv_sec, bootTime.tv_sec);\n}\n\nvoid Platform_getLoadAverage(double* one, double* five, double* fifteen) {\n   struct loadavg loadAverage;\n   const int mib[2] = { CTL_VM, VM_LOADAVG };\n   size_t size = sizeof(loadAverage);\n\n   int err = sysctl(mib, 2, &loadAverage, &size, NULL, 0);\n   if (err) {\n      *one = 0;\n      *five = 0;\n      *fifteen = 0;\n      return;\n   }\n   *one     = (double) loadAverage.ldavg[0] / loadAverage.fscale;\n   *five    = (double) loadAverage.ldavg[1] / loadAverage.fscale;\n   *fifteen = (double) loadAverage.ldavg[2] / loadAverage.fscale;\n}\n\npid_t Platform_getMaxPid(void) {\n   int maxPid;\n   size_t size = sizeof(maxPid);\n   int err = sysctlbyname(\"kern.pid_max\", &maxPid, &size, NULL, 0);\n   if (err) {\n      return 99999;\n   }\n   return maxPid;\n}\n\ndouble Platform_setCPUValues(Meter* this, unsigned int cpu) {\n   const Machine* host = this->host;\n   const FreeBSDMachine* fhost = (const FreeBSDMachine*) host;\n   unsigned int cpus = host->activeCPUs;\n\n   // single CPU box has everything in fhost->cpus[0]\n   const CPUData* cpuData = cpus == 1 ? &fhost->cpus[0] : &fhost->cpus[cpu];\n\n   double  percent;\n   double* v = this->values;\n\n   v[CPU_METER_NICE]   = cpuData->nicePercent;\n   v[CPU_METER_NORMAL] = cpuData->userPercent;\n   if (host->settings->detailedCPUTime) {\n      v[CPU_METER_KERNEL]  = cpuData->systemPercent;\n      v[CPU_METER_IRQ]     = cpuData->irqPercent;\n      this->curItems = 4;\n      percent = v[CPU_METER_NICE] + v[CPU_METER_NORMAL] + v[CPU_METER_KERNEL] + v[CPU_METER_IRQ];\n   } else {\n      v[CPU_METER_KERNEL] = cpuData->systemAllPercent;\n      this->curItems = 3;\n      percent = v[CPU_METER_NICE] + v[CPU_METER_NORMAL] + v[CPU_METER_KERNEL];\n   }\n\n   percent = CLAMP(percent, 0.0, 100.0);\n\n   v[CPU_METER_FREQUENCY] = cpuData->frequency;\n   v[CPU_METER_TEMPERATURE] = cpuData->temperature;\n\n   return percent;\n}\n\nvoid Platform_setMemoryValues(Meter* this) {\n   const Machine* host = this->host;\n   const FreeBSDMachine* fhost = (const FreeBSDMachine*) host;\n\n   this->total = host->totalMem;\n   if (host->settings->showCachedMemory) {\n      this->values[MEMORY_CLASS_WIRED]    = fhost->wiredMem;\n      this->values[MEMORY_CLASS_BUFFERS]  = fhost->buffersMem;\n   } else { // if showCachedMemory is disabled, merge buffers into the wired pages\n      this->values[MEMORY_CLASS_WIRED]    = fhost->wiredMem + fhost->buffersMem;\n      this->values[MEMORY_CLASS_BUFFERS]  = 0;\n   }\n   this->values[MEMORY_CLASS_ACTIVE]   = fhost->activeMem;\n   this->values[MEMORY_CLASS_LAUNDRY]  = fhost->laundryMem;\n   this->values[MEMORY_CLASS_INACTIVE] = fhost->inactiveMem;\n\n   if (fhost->zfs.enabled) {\n      // ZFS does not shrink below the value of zfs_arc_min.\n      unsigned long long int shrinkableSize = 0;\n      if (fhost->zfs.size > fhost->zfs.min)\n         shrinkableSize = fhost->zfs.size - fhost->zfs.min;\n      this->values[MEMORY_CLASS_ARC] = shrinkableSize;\n   } else {\n      this->values[MEMORY_CLASS_ARC] = 0;\n   }\n}\n\nvoid Platform_setSwapValues(Meter* this) {\n   const Machine* host = this->host;\n\n   this->total = host->totalSwap;\n   this->values[SWAP_METER_USED] = host->usedSwap;\n}\n\nvoid Platform_setZfsArcValues(Meter* this) {\n   const FreeBSDMachine* fhost = (const FreeBSDMachine*) this->host;\n\n   ZfsArcMeter_readStats(this, &fhost->zfs);\n}\n\nvoid Platform_setZfsCompressedArcValues(Meter* this) {\n   const FreeBSDMachine* fhost = (const FreeBSDMachine*) this->host;\n\n   ZfsCompressedArcMeter_readStats(this, &fhost->zfs);\n}\n\nchar* Platform_getProcessEnv(pid_t pid) {\n   const int mib[4] = { CTL_KERN, KERN_PROC, KERN_PROC_ENV, pid };\n\n   size_t capacity = ARG_MAX;\n   char* env = xMalloc(capacity);\n\n   int err = sysctl(mib, 4, env, &capacity, NULL, 0);\n   if (err || capacity == 0) {\n      free(env);\n      return NULL;\n   }\n\n   if (env[capacity - 1] || env[capacity - 2]) {\n      env = xRealloc(env, capacity + 2);\n      env[capacity] = 0;\n      env[capacity + 1] = 0;\n   }\n\n   return env;\n}\n\nFileLocks_ProcessData* Platform_getProcessLocks(pid_t pid) {\n   (void)pid;\n   return NULL;\n}\n\nvoid Platform_getFileDescriptors(double* used, double* max) {\n   Generic_getFileDescriptors_sysctl(used, max);\n}\n\nbool Platform_getDiskIO(DiskIOData* data) {\n\n   if (devstat_checkversion(NULL) < 0)\n      return false;\n\n   // use static to plug memory leak; see #841\n   static struct devinfo info = { 0 };\n   struct statinfo current = { .dinfo = &info };\n\n   // get number of devices\n   if (devstat_getdevs(NULL, &current) < 0)\n      return false;\n\n   int count = current.dinfo->numdevs;\n\n   uint64_t bytesReadSum = 0, bytesWriteSum = 0, timeSpendSum = 0;\n   uint64_t numDisks = 0;\n\n   // get data\n   for (int i = 0; i < count; i++) {\n      uint64_t bytes_read, bytes_write;\n      long double busy_time;\n\n      devstat_compute_statistics(&current.dinfo->devices[i],\n                                 NULL,\n                                 1.0,\n                                 DSM_TOTAL_BYTES_READ, &bytes_read,\n                                 DSM_TOTAL_BYTES_WRITE, &bytes_write,\n                                 DSM_TOTAL_BUSY_TIME, &busy_time,\n                                 DSM_NONE);\n\n      bytesReadSum += bytes_read;\n      bytesWriteSum += bytes_write;\n      timeSpendSum += 1000 * busy_time;\n      numDisks++;\n   }\n\n   data->totalBytesRead = bytesReadSum;\n   data->totalBytesWritten = bytesWriteSum;\n   data->totalMsTimeSpend = timeSpendSum;\n   data->numDisks = numDisks;\n   return true;\n}\n\nbool Platform_getNetworkIO(NetworkIOData* data) {\n   // get number of interfaces\n   int count;\n   size_t countLen = sizeof(count);\n   const int countMib[] = { CTL_NET, PF_LINK, NETLINK_GENERIC, IFMIB_SYSTEM, IFMIB_IFCOUNT };\n\n   int r = sysctl(countMib, ARRAYSIZE(countMib), &count, &countLen, NULL, 0);\n   if (r < 0)\n      return false;\n\n   for (int i = 1; i <= count; i++) {\n      struct ifmibdata ifmd;\n      size_t ifmdLen = sizeof(ifmd);\n\n      const int dataMib[] = { CTL_NET, PF_LINK, NETLINK_GENERIC, IFMIB_IFDATA, i, IFDATA_GENERAL };\n\n      r = sysctl(dataMib, ARRAYSIZE(dataMib), &ifmd, &ifmdLen, NULL, 0);\n      if (r < 0)\n         continue;\n\n      if (ifmd.ifmd_flags & IFF_LOOPBACK)\n         continue;\n\n      data->bytesReceived += ifmd.ifmd_data.ifi_ibytes;\n      data->packetsReceived += ifmd.ifmd_data.ifi_ipackets;\n      data->bytesTransmitted += ifmd.ifmd_data.ifi_obytes;\n      data->packetsTransmitted += ifmd.ifmd_data.ifi_opackets;\n   }\n\n   return true;\n}\n\nvoid Platform_getBattery(double* percent, ACPresence* isOnAC) {\n   int life;\n   size_t life_len = sizeof(life);\n   if (sysctlbyname(\"hw.acpi.battery.life\", &life, &life_len, NULL, 0) == -1)\n      *percent = NAN;\n   else\n      *percent = life;\n\n   int acline;\n   size_t acline_len = sizeof(acline);\n   if (sysctlbyname(\"hw.acpi.acline\", &acline, &acline_len, NULL, 0) == -1)\n      *isOnAC = AC_ERROR;\n   else\n      *isOnAC = acline == 0 ? AC_ABSENT : AC_PRESENT;\n}\n"
  },
  {
    "path": "freebsd/Platform.h",
    "content": "#ifndef HEADER_Platform\n#define HEADER_Platform\n/*\nhtop - freebsd/Platform.h\n(C) 2014 Hisham H. Muhammad\nReleased under the GNU GPLv2+, see the COPYING file\nin the source distribution for its full text.\n*/\n\n#include <stdbool.h>\n#include <sys/types.h>\n\n#include \"Action.h\"\n#include \"BatteryMeter.h\"\n#include \"DiskIOMeter.h\"\n#include \"Hashtable.h\"\n#include \"MemoryMeter.h\"\n#include \"NetworkIOMeter.h\"\n#include \"Process.h\"\n#include \"ProcessLocksScreen.h\"\n#include \"SignalsPanel.h\"\n#include \"CommandLine.h\"\n#include \"generic/gettime.h\"\n#include \"generic/hostname.h\"\n#include \"generic/uname.h\"\n\n\nextern const ScreenDefaults Platform_defaultScreens[];\n\nextern const unsigned int Platform_numberOfDefaultScreens;\n\nextern const SignalItem Platform_signals[];\n\nextern const unsigned int Platform_numberOfSignals;\n\nextern const MemoryClass Platform_memoryClasses[];\n\nextern const unsigned int Platform_numberOfMemoryClasses;\n\nextern const MeterClass* const Platform_meterTypes[];\n\nbool Platform_init(void);\n\nvoid Platform_done(void);\n\nvoid Platform_setBindings(Htop_Action* keys);\n\nint Platform_getUptime(void);\n\nvoid Platform_getLoadAverage(double* one, double* five, double* fifteen);\n\npid_t Platform_getMaxPid(void);\n\ndouble Platform_setCPUValues(Meter* this, unsigned int cpu);\n\nvoid Platform_setMemoryValues(Meter* this);\n\nvoid Platform_setSwapValues(Meter* this);\n\nvoid Platform_setZfsArcValues(Meter* this);\n\nvoid Platform_setZfsCompressedArcValues(Meter* this);\n\nchar* Platform_getProcessEnv(pid_t pid);\n\nFileLocks_ProcessData* Platform_getProcessLocks(pid_t pid);\n\nvoid Platform_getFileDescriptors(double* used, double* max);\n\nbool Platform_getDiskIO(DiskIOData* data);\n\nbool Platform_getNetworkIO(NetworkIOData* data);\n\nvoid Platform_getBattery(double* percent, ACPresence* isOnAC);\n\nstatic inline void Platform_getHostname(char* buffer, size_t size) {\n   Generic_hostname(buffer, size);\n}\n\nstatic inline const char* Platform_getRelease(void) {\n   return Generic_uname();\n}\n\nstatic inline const char* Platform_getFailedState(void) {\n   return NULL;\n}\n\n#define PLATFORM_LONG_OPTIONS\n\nstatic inline void Platform_longOptionsUsage(ATTR_UNUSED const char* name) { }\n\nstatic inline CommandLineStatus Platform_getLongOption(ATTR_UNUSED int opt, ATTR_UNUSED int argc, ATTR_UNUSED char** argv) {\n   return STATUS_ERROR_EXIT;\n}\n\nstatic inline void Platform_gettime_realtime(struct timeval* tv, uint64_t* msec) {\n   Generic_gettime_realtime(tv, msec);\n}\n\nstatic inline void Platform_gettime_monotonic(uint64_t* msec) {\n   Generic_gettime_monotonic(msec);\n}\n\nstatic inline Hashtable* Platform_dynamicMeters(void) {\n   return NULL;\n}\n\nstatic inline void Platform_dynamicMetersDone(ATTR_UNUSED Hashtable* table) { }\n\nstatic inline void Platform_dynamicMeterInit(ATTR_UNUSED Meter* meter) { }\n\nstatic inline void Platform_dynamicMeterUpdateValues(ATTR_UNUSED Meter* meter) { }\n\nstatic inline void Platform_dynamicMeterDisplay(ATTR_UNUSED const Meter* meter, ATTR_UNUSED RichString* out) { }\n\nstatic inline Hashtable* Platform_dynamicColumns(void) {\n   return NULL;\n}\n\nstatic inline const char* Platform_dynamicColumnName(ATTR_UNUSED unsigned int key) {\n   return NULL;\n}\n\nstatic inline void Platform_dynamicColumnsDone(ATTR_UNUSED Hashtable* table) { }\n\nstatic inline bool Platform_dynamicColumnWriteField(ATTR_UNUSED const Process* proc, ATTR_UNUSED RichString* str, ATTR_UNUSED unsigned int key) {\n   return false;\n}\n\nstatic inline Hashtable* Platform_dynamicScreens(void) {\n   return NULL;\n}\n\nstatic inline void Platform_defaultDynamicScreens(ATTR_UNUSED Settings* settings) { }\n\nstatic inline void Platform_addDynamicScreen(ATTR_UNUSED ScreenSettings* ss) { }\n\nstatic inline void Platform_addDynamicScreenAvailableColumns(ATTR_UNUSED Panel* availableColumns, ATTR_UNUSED const char* screen) { }\n\nstatic inline void Platform_dynamicScreensDone(ATTR_UNUSED Hashtable* screens) { }\n\n#endif\n"
  },
  {
    "path": "freebsd/ProcessField.h",
    "content": "#ifndef HEADER_FreeBSDProcessField\n#define HEADER_FreeBSDProcessField\n/*\nhtop - freebsd/ProcessField.h\n(C) 2020 htop dev team\nReleased under the GNU GPLv2+, see the COPYING file\nin the source distribution for its full text.\n*/\n\n\n#define PLATFORM_PROCESS_FIELDS  \\\n   JID = 100,                    \\\n   JAIL = 101,                   \\\n   EMULATION = 102,              \\\n   SCHEDCLASS = 103,             \\\n                                 \\\n   DUMMY_BUMP_FIELD = CWD,       \\\n   // End of list\n\n\n#endif /* HEADER_FreeBSDProcessField */\n"
  },
  {
    "path": "generic/fdstat_sysctl.c",
    "content": "/*\nhtop - generic/fdstat_sysctl.c\n(C) 2022-2023 htop dev team\nReleased under the GNU GPLv2+, see the COPYING file\nin the source distribution for its full text.\n*/\n\n#include \"config.h\" // IWYU pragma: keep\n\n#include \"generic/fdstat_sysctl.h\"\n\n#include <math.h>\n#include <stddef.h>\n#include <stdint.h>\n\n#include <sys/types.h> // Shitty FreeBSD upstream headers\n#include <sys/sysctl.h>\n\n\nstatic void Generic_getFileDescriptors_sysctl_internal(\n   const char* sysctlname_maxfiles,\n   const char* sysctlname_numfiles,\n   size_t size_header,\n   size_t size_entry,\n   double* used,\n   double* max\n) {\n   *used = NAN;\n   *max = 65536;\n\n   int max_fd, open_fd;\n   size_t len;\n\n   len = sizeof(max_fd);\n   if (sysctlname_maxfiles && sysctlbyname(sysctlname_maxfiles, &max_fd, &len, NULL, 0) == 0) {\n      if (max_fd) {\n         *max = max_fd;\n      } else {\n         *max = NAN;\n      }\n   }\n\n   len = sizeof(open_fd);\n   if (sysctlname_numfiles && sysctlbyname(sysctlname_numfiles, &open_fd, &len, NULL, 0) == 0) {\n      *used = open_fd;\n      return;\n   }\n\n   // If no sysctl arc available, try to guess from the file table size at kern.file\n   // The size per entry differs per OS, thus skip if we don't know:\n   if (!size_entry)\n      return;\n\n   len = 0;\n   if (sysctlbyname(\"kern.file\", NULL, &len, NULL, 0) < 0)\n      return;\n\n   if (len < size_header)\n      return;\n\n   *used = (len - size_header) / size_entry;\n}\n\nvoid Generic_getFileDescriptors_sysctl(double* used, double* max) {\n#if defined(HTOP_DARWIN)\n   Generic_getFileDescriptors_sysctl_internal(\n      \"kern.maxfiles\", \"kern.num_files\", 0, 0, used, max);\n#elif defined(HTOP_DRAGONFLYBSD)\n   Generic_getFileDescriptors_sysctl_internal(\n      \"kern.maxfiles\", \"kern.openfiles\", 0, 0, used, max);\n#elif defined(HTOP_FREEBSD)\n   Generic_getFileDescriptors_sysctl_internal(\n      \"kern.maxfiles\", \"kern.openfiles\", 0, 0, used, max);\n#elif defined(HTOP_NETBSD)\n   Generic_getFileDescriptors_sysctl_internal(\n      \"kern.maxfiles\", NULL, 0, sizeof(struct kinfo_file), used, max);\n#else\n#error Unknown platform: Please implement proper way to query open/max file information\n#endif\n}\n"
  },
  {
    "path": "generic/fdstat_sysctl.h",
    "content": "#ifndef HEADER_fdstat_sysctl\n#define HEADER_fdstat_sysctl\n/*\nhtop - generic/fdstat_sysctl.h\n(C) 2022-2023 htop dev team\nReleased under the GNU GPLv2+, see the COPYING file\nin the source distribution for its full text.\n*/\n\n\nvoid Generic_getFileDescriptors_sysctl(double* used, double* max);\n\n#endif\n"
  },
  {
    "path": "generic/gettime.c",
    "content": "/*\nhtop - generic/gettime.c\n(C) 2021 htop dev team\nReleased under the GNU GPLv2+, see the COPYING file\nin the source distribution for its full text.\n*/\n\n#include \"config.h\"  // IWYU pragma: keep\n\n#include \"generic/gettime.h\"\n\n#include <string.h>\n#include <time.h>\n\n\nvoid Generic_gettime_realtime(struct timeval* tvp, uint64_t* msec) {\n\n#if defined(HAVE_CLOCK_GETTIME)\n\n   struct timespec ts;\n   if (clock_gettime(CLOCK_REALTIME, &ts) == 0) {\n      tvp->tv_sec = ts.tv_sec;\n      tvp->tv_usec = (suseconds_t)(ts.tv_nsec / 1000);\n      *msec = ((uint64_t)ts.tv_sec * 1000) + ((uint64_t)ts.tv_nsec / 1000000);\n   } else {\n      memset(tvp, 0, sizeof(struct timeval));\n      *msec = 0;\n   }\n\n#else /* lower resolution gettimeofday(2) is always available */\n\n   struct timeval tv;\n   if (gettimeofday(&tv, NULL) == 0) {\n      *tvp = tv; /* struct copy */\n      *msec = ((uint64_t)tv.tv_sec * 1000) + ((uint64_t)tv.tv_usec / 1000);\n   } else {\n      memset(tvp, 0, sizeof(struct timeval));\n      *msec = 0;\n   }\n\n#endif\n}\n\nvoid Generic_gettime_monotonic(uint64_t* msec) {\n#if defined(HAVE_CLOCK_GETTIME)\n\n   struct timespec ts;\n   if (clock_gettime(CLOCK_MONOTONIC, &ts) == 0)\n      *msec = ((uint64_t)ts.tv_sec * 1000) + ((uint64_t)ts.tv_nsec / 1000000);\n   else\n      *msec = 0;\n\n#else /* lower resolution gettimeofday() should be always available */\n\n   struct timeval tv;\n   if (gettimeofday(&tv, NULL) == 0)\n      *msec = ((uint64_t)tv.tv_sec * 1000) + ((uint64_t)tv.tv_usec / 1000);\n   else\n      *msec = 0;\n\n#endif\n}\n"
  },
  {
    "path": "generic/gettime.h",
    "content": "#ifndef HEADER_gettime\n#define HEADER_gettime\n/*\nhtop - generic/gettime.h\n(C) 2021 htop dev team\nReleased under the GNU GPLv2+, see the COPYING file\nin the source distribution for its full text.\n*/\n\n#include <stdint.h>\n#include <sys/time.h>\n\n\nvoid Generic_gettime_realtime(struct timeval* tvp, uint64_t* msec);\n\nvoid Generic_gettime_monotonic(uint64_t* msec);\n\n#endif\n"
  },
  {
    "path": "generic/hostname.c",
    "content": "/*\nhtop - generic/hostname.c\n(C) 2021 htop dev team\nReleased under the GNU GPLv2+, see the COPYING file\nin the source distribution for its full text.\n*/\n\n#include \"config.h\"  // IWYU pragma: keep\n\n#include \"generic/hostname.h\"\n\n#include <unistd.h>\n\n\nvoid Generic_hostname(char* buffer, size_t size) {\n   gethostname(buffer, size - 1);\n   buffer[size - 1] = '\\0';\n}\n"
  },
  {
    "path": "generic/hostname.h",
    "content": "#ifndef HEADER_hostname\n#define HEADER_hostname\n/*\nhtop - generic/hostname.h\n(C) 2021 htop dev team\nReleased under the GNU GPLv2+, see the COPYING file\nin the source distribution for its full text.\n*/\n\n#include <stddef.h>\n\n\nvoid Generic_hostname(char* buffer, size_t size);\n\n#endif\n"
  },
  {
    "path": "generic/openzfs_sysctl.c",
    "content": "/*\nhtop - generic/openzfs_sysctl.c\n(C) 2014 Hisham H. Muhammad\nReleased under the GNU GPLv2+, see the COPYING file\nin the source distribution for its full text.\n*/\n\n#include \"generic/openzfs_sysctl.h\"\n\n#include <stdlib.h>\n#include <sys/types.h> // IWYU pragma: keep\n#include <sys/sysctl.h> // needs <sys/types.h> for u_int with gcc\n\n#include \"zfs/ZfsArcStats.h\"\n\n\nstatic int MIB_kstat_zfs_misc_arcstats_size[5];\nstatic int MIB_kstat_zfs_misc_arcstats_c_min[5];\nstatic int MIB_kstat_zfs_misc_arcstats_c_max[5];\nstatic int MIB_kstat_zfs_misc_arcstats_mfu_size[5];\nstatic int MIB_kstat_zfs_misc_arcstats_mru_size[5];\nstatic int MIB_kstat_zfs_misc_arcstats_anon_size[5];\nstatic int MIB_kstat_zfs_misc_arcstats_hdr_size[5];\nstatic int MIB_kstat_zfs_misc_arcstats_other_size[5];\nstatic int MIB_kstat_zfs_misc_arcstats_compressed_size[5];\nstatic int MIB_kstat_zfs_misc_arcstats_uncompressed_size[5];\n\nvoid openzfs_sysctl_init(ZfsArcStats* stats) {\n   size_t len;\n   unsigned long long int arcSize;\n\n   len = sizeof(arcSize);\n   if (sysctlbyname(\"kstat.zfs.misc.arcstats.size\", &arcSize, &len, NULL, 0) == 0 && arcSize != 0) {\n      stats->enabled = 1;\n\n      len = 5;\n      sysctlnametomib(\"kstat.zfs.misc.arcstats.size\", MIB_kstat_zfs_misc_arcstats_size, &len);\n\n      sysctlnametomib(\"kstat.zfs.misc.arcstats.c_min\", MIB_kstat_zfs_misc_arcstats_c_min, &len);\n      sysctlnametomib(\"kstat.zfs.misc.arcstats.c_max\", MIB_kstat_zfs_misc_arcstats_c_max, &len);\n      sysctlnametomib(\"kstat.zfs.misc.arcstats.mfu_size\", MIB_kstat_zfs_misc_arcstats_mfu_size, &len);\n      sysctlnametomib(\"kstat.zfs.misc.arcstats.mru_size\", MIB_kstat_zfs_misc_arcstats_mru_size, &len);\n      sysctlnametomib(\"kstat.zfs.misc.arcstats.anon_size\", MIB_kstat_zfs_misc_arcstats_anon_size, &len);\n      sysctlnametomib(\"kstat.zfs.misc.arcstats.hdr_size\", MIB_kstat_zfs_misc_arcstats_hdr_size, &len);\n      sysctlnametomib(\"kstat.zfs.misc.arcstats.other_size\", MIB_kstat_zfs_misc_arcstats_other_size, &len);\n\n      if (sysctlnametomib(\"kstat.zfs.misc.arcstats.compressed_size\", MIB_kstat_zfs_misc_arcstats_compressed_size, &len) == 0) {\n         stats->isCompressed = 1;\n         sysctlnametomib(\"kstat.zfs.misc.arcstats.uncompressed_size\", MIB_kstat_zfs_misc_arcstats_uncompressed_size, &len);\n      } else {\n         stats->isCompressed = 0;\n      }\n   } else {\n      stats->enabled = 0;\n   }\n}\n\nvoid openzfs_sysctl_updateArcStats(ZfsArcStats* stats) {\n   size_t len;\n\n   if (stats->enabled) {\n      len = sizeof(stats->size);\n      sysctl(MIB_kstat_zfs_misc_arcstats_size, 5, &(stats->size), &len, NULL, 0);\n      stats->size /= 1024;\n\n      len = sizeof(stats->min);\n      sysctl(MIB_kstat_zfs_misc_arcstats_c_min, 5, &(stats->min), &len, NULL, 0);\n      stats->min /= 1024;\n\n      len = sizeof(stats->max);\n      sysctl(MIB_kstat_zfs_misc_arcstats_c_max, 5, &(stats->max), &len, NULL, 0);\n      stats->max /= 1024;\n\n      len = sizeof(stats->MFU);\n      sysctl(MIB_kstat_zfs_misc_arcstats_mfu_size, 5, &(stats->MFU), &len, NULL, 0);\n      stats->MFU /= 1024;\n\n      len = sizeof(stats->MRU);\n      sysctl(MIB_kstat_zfs_misc_arcstats_mru_size, 5, &(stats->MRU), &len, NULL, 0);\n      stats->MRU /= 1024;\n\n      len = sizeof(stats->anon);\n      sysctl(MIB_kstat_zfs_misc_arcstats_anon_size, 5, &(stats->anon), &len, NULL, 0);\n      stats->anon /= 1024;\n\n      len = sizeof(stats->header);\n      sysctl(MIB_kstat_zfs_misc_arcstats_hdr_size, 5, &(stats->header), &len, NULL, 0);\n      stats->header /= 1024;\n\n      len = sizeof(stats->other);\n      sysctl(MIB_kstat_zfs_misc_arcstats_other_size, 5, &(stats->other), &len, NULL, 0);\n      stats->other /= 1024;\n\n      if (stats->isCompressed) {\n         len = sizeof(stats->compressed);\n         sysctl(MIB_kstat_zfs_misc_arcstats_compressed_size, 5, &(stats->compressed), &len, NULL, 0);\n         stats->compressed /= 1024;\n\n         len = sizeof(stats->uncompressed);\n         sysctl(MIB_kstat_zfs_misc_arcstats_uncompressed_size, 5, &(stats->uncompressed), &len, NULL, 0);\n         stats->uncompressed /= 1024;\n      }\n   }\n}\n"
  },
  {
    "path": "generic/openzfs_sysctl.h",
    "content": "#ifndef HEADER_openzfs_sysctl\n#define HEADER_openzfs_sysctl\n/*\nhtop - generic/openzfs_sysctl.h\n(C) 2014 Hisham H. Muhammad\nReleased under the GNU GPLv2+, see the COPYING file\nin the source distribution for its full text.\n*/\n\n#include \"zfs/ZfsArcStats.h\"\n\n\nvoid openzfs_sysctl_init(ZfsArcStats* stats);\n\nvoid openzfs_sysctl_updateArcStats(ZfsArcStats* stats);\n\n#endif\n"
  },
  {
    "path": "generic/uname.c",
    "content": "/*\nhtop - generic/uname.c\n(C) 2021 htop dev team\nReleased under the GNU GPLv2+, see the COPYING file\nin the source distribution for its full text.\n*/\n\n#include \"config.h\"  // IWYU pragma: keep\n\n#include \"generic/uname.h\"\n\n#include <stdbool.h>\n#include <stdio.h>\n#include <string.h>\n\n#include \"Macros.h\"\n#include \"XUtils.h\"\n\n#ifdef HAVE_SYS_UTSNAME_H\n#include <sys/utsname.h>\n#endif\n\n\nstatic void parseOSRelease(char* buffer, size_t bufferLen) {\n   static const char* const osfiles[] = {\n#ifdef OSRELEASEFILE\n      OSRELEASEFILE /* Custom path for testing; undefined by default */,\n#endif\n      \"/etc/os-release\",\n      \"/usr/lib/os-release\",\n   };\n\n   if (!bufferLen)\n      return;\n\n   FILE* fp = NULL;\n   for (size_t i = 0; i < ARRAYSIZE(osfiles); i++) {\n      fp = fopen(osfiles[i], \"r\");\n      if (fp)\n         break;\n   }\n   if (!fp) {\n      buffer[0] = '\\0';\n      return;\n   }\n\n   char name[64] = {'\\0'};\n   char version[64] = {'\\0'};\n   char lineBuffer[256];\n   while (fgets(lineBuffer, sizeof(lineBuffer), fp)) {\n      if (String_startsWith(lineBuffer, \"PRETTY_NAME=\\\"\")) {\n         const char* start = lineBuffer + strlen(\"PRETTY_NAME=\\\"\");\n         const char* stop = strrchr(lineBuffer, '\"');\n         if (!stop || stop <= start)\n            continue;\n         String_safeStrncpy(buffer, start, MINIMUM(bufferLen, (size_t)(stop - start + 1)));\n         fclose(fp);\n         return;\n      }\n      if (String_startsWith(lineBuffer, \"NAME=\\\"\")) {\n         const char* start = lineBuffer + strlen(\"NAME=\\\"\");\n         const char* stop = strrchr(lineBuffer, '\"');\n         if (!stop || stop <= start)\n            continue;\n         String_safeStrncpy(name, start, MINIMUM(sizeof(name), (size_t)(stop - start + 1)));\n         continue;\n      }\n      if (String_startsWith(lineBuffer, \"VERSION=\\\"\")) {\n         const char* start = lineBuffer + strlen(\"VERSION=\\\"\");\n         const char* stop = strrchr(lineBuffer, '\"');\n         if (!stop || stop <= start)\n            continue;\n         String_safeStrncpy(version, start, MINIMUM(sizeof(version), (size_t)(stop - start + 1)));\n         continue;\n      }\n   }\n   fclose(fp);\n\n   snprintf(buffer, bufferLen, \"%s%s%s\", name, name[0] && version[0] ? \" \" : \"\", version);\n}\n\nconst char* Generic_unameRelease(Platform_FetchReleaseFunction fetchRelease) {\n   static char savedString[\n      /* uname structure fields - manpages recommend sizeof */\n      sizeof(((struct utsname*)0)->sysname) +\n      sizeof(((struct utsname*)0)->release) +\n      sizeof(((struct utsname*)0)->machine) +\n      16/*markup*/ +\n      128/*distro*/] = \"No information\";\n   static bool loaded_data = false;\n\n   if (!loaded_data) {\n      struct utsname uname_info;\n      int uname_result = uname(&uname_info);\n\n      char distro[128];\n      fetchRelease(distro, sizeof(distro));\n\n      if (uname_result == 0) {\n         size_t written = xSnprintf(savedString, sizeof(savedString), \"%s %s [%s]\", uname_info.sysname, uname_info.release, uname_info.machine);\n         if (distro[0] && sizeof(savedString) > written && !String_contains_i(savedString, distro, false))\n            snprintf(savedString + written, sizeof(savedString) - written, \" @ %s\", distro);\n      } else if (distro[0]) {\n         snprintf(savedString, sizeof(savedString), \"%s\", distro);\n      }\n\n      loaded_data = true;\n   }\n\n   return savedString;\n}\n\nconst char* Generic_uname(void) {\n   return Generic_unameRelease(parseOSRelease);\n}\n"
  },
  {
    "path": "generic/uname.h",
    "content": "#ifndef HEADER_uname\n#define HEADER_uname\n/*\nhtop - generic/uname.h\n(C) 2021 htop dev team\nReleased under the GNU GPLv2+, see the COPYING file\nin the source distribution for its full text.\n*/\n\n#include <stddef.h>\n\ntypedef void (*Platform_FetchReleaseFunction)(char* buffer, size_t length);\n\nconst char* Generic_unameRelease(Platform_FetchReleaseFunction fetchRelease);\n\nconst char* Generic_uname(void);\n\n#endif\n"
  },
  {
    "path": "htop.1.in",
    "content": ".TH \"HTOP\" \"1\" \"2026\" \"@PACKAGE_STRING@\" \"User Commands\"\n.SH \"NAME\"\nhtop, pcp-htop \\- interactive process viewer\n.SH \"SYNOPSIS\"\n.B htop\n.RB [ \\-dCFhpustvH ]\n.br\n.B pcp-htop\n.RB [ \\-dCFhpustvH ]\n.RB [ \\-\\-host/-h\\ host ]\n.SH \"DESCRIPTION\"\n.B htop\nis a cross-platform ncurses-based process viewer.\n.LP\nIt is similar to\n.BR top ,\nbut allows you to scroll vertically and horizontally, and interact using\na pointing device (mouse).\nYou can observe all processes running on the system, along with their\ncommand line arguments, as well as view them in a tree format, select\nmultiple processes and act on them all at once.\n.LP\nTasks related to processes (killing, renicing) can be done without\nentering their PIDs.\n.LP\n.B pcp-htop\nis a version of\n.B htop\nbuilt using the Performance Co-Pilot (PCP) Metrics API (see \\c\n.BR PCPIntro (1),\n.BR PMAPI (3)),\nallowing to extend\n.B htop\nto display values from arbitrary metrics.\nSee the section below titled\n.B \"CONFIG FILES\"\nfor further details.\n.br\n.SH \"COMMAND-LINE OPTIONS\"\nMandatory arguments to long options are mandatory for short options too.\n.TP\n\\fB\\-d \\-\\-delay=DELAY\\fR\nDelay between updates, in tenths of a second. If the delay value is\nless than 1, it is increased to 1, i.e. 1/10 second. If the delay value\nis greater than 100, it is decreased to 100, i.e. 10 seconds.\n.TP\n\\fB\\-C \\-\\-no-color \\-\\-no-colour\\fR\nStart\n.B htop\nin monochrome mode\n.TP\n\\fB\\-F \\-\\-filter=FILTER\nFilter processes by terms matching the commands. The terms are matched\ncase-insensitive and as fixed strings (not regexs). You can separate multiple terms with \"|\".\n.TP\n\\fB\\-\\-no-function-bar\\fR\nHide the function bar\n.TP\n\\fB\\-h \\-\\-help\nDisplay a help message and exit\n.TP\n\\fB\\-p \\-\\-pid=PID,PID...\\fR\nShow only the given PIDs\n.TP\n\\fB\\-s \\-\\-sort\\-key COLUMN\\fR\nSort by this column (use \\-\\-sort\\-key help for a column list).\nThis will force a list view unless you specify -t at the same time.\nSorting in tree mode applies to the direct children of each process.\n.TP\n\\fB\\-u \\-\\-user[=USERNAME|UID]\\fR\nShow only the processes of a given user, or self if omitted\n.TP\n\\fB\\-U \\-\\-no-unicode\\fR\nDo not use unicode but ASCII characters for graph meters\n.TP\n\\fB\\-M \\-\\-no-mouse\\fR\nDisable support of mouse control\n.TP\n\\fB\\-\\-no-meters\\fR\nHide graph meters\n.TP\n\\fB\\-\\-readonly\\fR\nDisable all system and process changing features\n.TP\n\\fB\\-V \\-\\-version\nOutput version information and exit\n.TP\n\\fB\\-t \\-\\-tree\nShow processes in tree view. This can be used to force a tree view when\nrequesting a sort order with -s.\n.TP\n\\fB\\-H \\-\\-highlight-changes=DELAY\\fR\nHighlight new and old processes\n.TP\n\\fB\\-\\-drop-capabilities[=off|basic|strict]\\fR\nLinux only; this option needs to have been enabled at compile-time and\nrequires libcap support at runtime.\n.br\nDrop unneeded Linux capabilities.\nIn strict mode features like killing, changing process priorities and reading\nprocess delay accounting information will not work due to fewer capabilities\nbeing held.\n.SH \"INTERACTIVE COMMANDS\"\nThe following commands are supported while in\n.BR htop :\n.TP 5\n.B Tab, Shift-Tab\nSelect the next / the previous screen tab to display.\nYou can enable showing the screen tab names in the Setup screen (F2).\n.TP\n.B Up, Alt-k\nSelect (highlight) the previous process in the process list. Scroll the list\nif necessary.\n.TP\n.B Down, Alt-j\nSelect (highlight) the next process in the process list. Scroll the list if\nnecessary.\n.TP\n.B Left, Alt-h\nScroll the process list left.\n.TP\n.B Right, Alt-l\nScroll the process list right.\n.TP\n.B PgUp, PgDn\nScroll the process list up or down one window.\n.TP\n.B Home\nScroll to the top of the process list and select the first process.\n.TP\n.B End\nScroll to the bottom of the process list and select the last process.\n.TP\n.B Ctrl-A, ^\nScroll left to the beginning of the process entry (i.e. beginning of line).\n.TP\n.B Ctrl-E, $\nScroll right to the end of the process entry (i.e. end of line).\n.TP\n.B Space\nTag or untag a process. Commands that can operate on multiple processes,\nlike \"kill\", will then apply over the list of tagged processes, instead\nof the currently highlighted one.\n.TP\n.B c\nTag the current process and its children. Commands that can operate on multiple\nprocesses, like \"kill\", will then apply over the list of tagged processes,\ninstead of the currently highlighted one.\n.TP\n.B U\nUntag all processes (remove all tags added with the Space or c keys).\n.TP\n.B s\nTrace process system calls: if strace(1) is installed, pressing this key\nwill attach it to the currently selected process, presenting a live\nupdate of system calls issued by the process.\n.TP\n.B l\nDisplay open files for a process: if lsof(1) is installed, pressing this key\nwill display the list of file descriptors opened by the process.\n.TP\n.B w\nDisplay the command line of the selected process in a separate screen, wrapped\nonto multiple lines as needed.\n.TP\n.B x\nDisplay the active file locks of the selected process in a separate screen.\n.TP\n.B F1, h, ?\nGo to the help screen\n.TP\n.B F2, S\nGo to the setup screen, where you can configure the meters displayed at the top\nof the screen, set various display options, choose among color schemes, and\nselect which columns are displayed, in which order.\n.TP\n.B F3, /\nIncrementally search the command lines of all the displayed processes. The\ncurrently selected (highlighted) command will update as you type. While in\nsearch mode, pressing F3 will cycle through matching occurrences.\nPressing Shift-F3 will cycle backwards.\n\nAlternatively the search can be started by simply typing the command\nyou are looking for, although for the first character normal key\nbindings take precedence.\n.TP\n.B F4, \\\\\\\\\nIncremental process filtering: type in part of a process command line and\nonly processes whose names match will be shown. To cancel filtering,\nenter the Filter option again and press Esc.\nThe matching is done case-insensitive. Terms are fixed strings (no regex).\nYou can separate multiple terms with \"|\".\n.TP\n.B F5, t\nTree view: organize processes by parenthood, and layout the relations\nbetween them as a tree. Toggling the key will switch between tree and\nyour previously selected sort view. Selecting a sort view will exit\ntree view.\n.TP\n.B F6, <, >\nSelects a field for sorting, also accessible through < and >.\nThe current sort field is indicated by a highlight in the header.\n.TP\n.B F7, ]\nIncrease the selected process's priority (subtract from 'nice' value).\nThis can only be done by the superuser.\n.TP\n.B F8, [\nDecrease the selected process's priority (add to 'nice' value)\n.TP\n.B Shift-F7, }\nIncrease the selected process's autogroup priority (subtract from autogroup 'nice' value).\nThis can only be done by the superuser.\n.TP\n.B Shift-F8, {\nDecrease the selected process's autogroup priority (add to autogroup 'nice' value)\n.TP\n.B F9, k\n\"Kill\" process: sends a signal which is selected in a menu, to one or a group\nof processes. If processes were tagged, sends the signal to all tagged processes.\nIf none is tagged, sends to the currently selected process.\n.TP\n.B F10, q\nQuit\n.TP\n.B I\nInvert the sort order: if sort order is increasing, switch to decreasing, and\nvice-versa.\n.TP\n.B +, \\-, *\nWhen in tree view mode, expand or collapse subtree. When a subtree is collapsed\na \"+\" sign shows to the left of the process name.\nPressing \"*\" will expand or collapse all children of PIDs without parents, so\ntypically PID 1 (init) and PID 2 (kthreadd on Linux, if kernel threads are shown).\n.TP\n.B a (on multiprocessor machines)\nSet CPU affinity: mark which CPUs a process is allowed to use.\n.TP\n.B u\nShow only processes owned by a specified user.\n.TP\n.B N\nSort by PID.\n.TP\n.B M\nSort by memory usage (top compatibility key).\n.TP\n.B P\nSort by processor usage (top compatibility key).\n.TP\n.B T\nSort by time (top compatibility key).\n.TP\n.B F\n\"Follow\" process: if the sort order causes the currently selected process\nto move in the list, make the selection bar follow it. This is useful for\nmonitoring a process: this way, you can keep a process always visible on\nscreen. When a movement key is used, \"follow\" loses effect.\n.TP\n.B K\nHide kernel threads: prevent the threads belonging the kernel to be\ndisplayed in the process list. (This is a toggle key.)\n.TP\n.B H\nHide user threads: on systems that represent them differently than ordinary\nprocesses (such as recent NPTL-based systems), this can hide threads from\nuserspace processes in the process list. (This is a toggle key.)\n.TP\n.B O\nHide containerized processes: prevent processes running in a container\nfrom being displayed in the process list. (This is a toggle key.)\n.TP\n.B p\nShow full paths to running programs, where applicable. (This is a toggle key.)\n.TP\n.B Z\nPause/resume process updates.\n.TP\n.B m\nMerge exe, comm and cmdline, where applicable. (This is a toggle key.)\n.TP\n.B Ctrl-L\nRefresh: redraw screen and recalculate values.\n.TP\n.B Numbers\nPID search: type in process ID and the selection highlight will be moved to it.\n.PD\n.SH \"COLUMNS\"\nThe following columns can display data about each process. A value of '\\-' in\nall the rows indicates that a column is unsupported on your system, or\ncurrently unimplemented in\n.BR htop .\nThe names below are the ones used in the\n\"Available Columns\" section of the setup screen. If a different name is\nshown in\n.BR htop 's\nmain screen, it is shown below in parenthesis.\n.TP 5\n.B Command\nThe full command line of the process (i.e. program name and arguments).\n\nIf the option 'Merge exe, comm and cmdline in Command' (toggled by the 'm' key)\nis active, the executable path (/proc/[pid]/exe) and the command name\n(/proc/[pid]/comm) are also shown merged with the command line, if available.\n\nThe program basename is highlighted if set in the configuration. Additional\nhighlighting can be configured for stale executables (cf. EXE column below).\n\nThe Command column should be the last column in each screen as can get very long\nand profits from being able to extend its length dynamically.\n\nAdditional color highlighting:\n.RS\n.IP \\[bu] 2\nMagenta: regular process command names\n.IP \\[bu] 2\nBold blue: userland thread names\n.IP \\[bu] 2\nBold gray: kernel threads or processes with no command name\n.RE\n.TP\n.B COMM\nThe command name of the process obtained from /proc/[pid]/comm, if readable.\n\nRequires Linux kernel 2.6.33 or newer.\n.TP\n.B EXE\nThe abbreviated basename of the executable of the process, obtained from\n/proc/[pid]/exe, if readable. htop is able to read this file on linux for ALL\nthe processes only if it has the capability CAP_SYS_PTRACE or root privileges.\n\nThe basename is marked in red if the executable used to run the process has\nbeen replaced or deleted on disk since the process started. The information is\nobtained by processing the contents of /proc/[pid]/exe.\n\nFurthermore the basename is marked in yellow if any library is reported as having\nbeen replaced or deleted on disk since it was last loaded. The information is\nobtained by processing the contents of /proc/[pid]/maps.\n\nWhen deciding the color the replacement of the main executable always takes\nprecedence over replacement of any other library. If only the memory map indicates\na replacement of the main executable, this will show as if any other library had\nbeen replaced or deleted.\n\nThis additional color markup can be configured in the \"Display Options\" section of\nthe setup screen.\n\nDisplaying EXE requires CAP_SYS_PTRACE and PTRACE_MODE_READ_FSCRED.\n.TP\n.B PID\nThe process ID.\n.TP\n.B STATE (S)\nThe state of the process:\n   \\fBS\\fR for sleeping\n   \\fBI\\fR for idle (longer inactivity than sleeping on platforms that distinguish)\n   \\fBR\\fR for running\n   \\fBD\\fR for disk sleep (uninterruptible)\n   \\fBZ\\fR for zombie (waiting for parent to read its exit status)\n   \\fBT\\fR for traced or suspended (e.g by SIGTSTP)\n   \\fBW\\fR for paging\n.TP\n.B PPID\nThe parent process ID.\n.TP\n.B PGRP\nThe process's group ID.\n.TP\n.B SESSION (SID)\nThe process's session ID.\n.TP\n.B TTY\nThe controlling terminal of the process.\n.TP\n.B TPGID\nThe process ID of the foreground process group of the controlling terminal.\n.TP\n.B MINFLT\nThe number of page faults happening in the main memory.\n.TP\n.B CMINFLT\nThe number of minor faults for the process's waited-for children (see MINFLT above).\n.TP\n.B MAJFLT\nThe number of page faults happening out of the main memory.\n.TP\n.B CMAJFLT\nThe number of major faults for the process's waited-for children (see MAJFLT above).\n.TP\n.B UTIME (UTIME+)\nThe user CPU time, which is the amount of time the process has spent executing\non the CPU in user mode (i.e. everything but system calls), measured in clock\nticks.\n.TP\n.B STIME (STIME+)\nThe system CPU time, which is the amount of time the kernel has spent\nexecuting system calls on behalf of the process, measured in clock ticks.\n.TP\n.B CUTIME (CUTIME+)\nThe children's user CPU time, which is the amount of time the process's\nwaited-for children have spent executing in user mode (see UTIME above).\n.TP\n.B CSTIME (CSTIME+)\nThe children's system CPU time, which is the amount of time the kernel has spent\nexecuting system calls on behalf of all the process's waited-for children (see\nSTIME above).\n.TP\n.B PRIORITY (PRI)\nThe kernel's internal priority for the process, usually just its nice value\nplus twenty. Different for real-time processes.\n.TP\n.B NICE (NI)\nThe nice value of a process, from 19 (low priority) to -20 (high priority). A\nhigh value means the process is being nice, letting others have a higher\nrelative priority. The usual OS permission restrictions for adjusting priority apply.\n.TP\n.B STARTTIME (START)\nThe time the process was started.\n.TP\n.B PROCESSOR (CPU)\nThe ID of the CPU the process last executed on.\n.TP\n.B M_VIRT (VIRT)\nThe size of the virtual memory of the process.\n.TP\n.B M_RESIDENT (RES)\nThe resident set size (text + data + stack) of the process (i.e. the size of the\nprocess's used physical memory).\n.TP\n.B M_SHARE (SHR)\nThe size of the process's shared pages.\n.TP\n.B M_TRS (CODE)\nThe text resident set size of the process (i.e. the size of the process's\nexecutable instructions).\n.TP\n.B M_DRS (DATA)\nThe data resident set size (data + stack) of the process (i.e. the size of anything\nexcept the process's executable instructions).\n.TP\n.B M_LRS (LIB)\nThe library size of the process.\n.TP\n.B M_SWAP (SWAP)\nThe size of the process's swapped pages.\n.TP\n.B M_PSS (PSS)\nThe proportional set size, same as M_RESIDENT but each page is divided by the\nnumber of processes sharing it.\n.TP\n.B M_M_PSSWP (PSSWP)\nThe proportional swap share of this mapping, unlike M_SWAP this does not take\ninto account swapped out page of underlying shmem objects.\n.TP\n.B ST_UID (UID)\nThe user ID of the process owner.\n.TP\n.B PERCENT_CPU (CPU%)\nThe percentage of the CPU time that the process is currently using.\nThis is the default way to represent CPU usage in Linux. Each process can\nconsume up to 100% which means the full capacity of the core it is running\non. This is sometimes called \"Irix mode\" e.g. in\n.BR top (1).\n.TP\n.B PERCENT_NORM_CPU (NCPU%)\nThe percentage of the CPU time that the process is currently using normalized\nby CPU count. This is sometimes called \"Solaris mode\" e.g. in\n.BR top (1).\n.TP\n.B PERCENT_MEM (MEM%)\nThe percentage of memory the process is currently using (based on the process's\nresident memory size, see M_RESIDENT above).\n.TP\n.B USER\nThe username of the process owner, or the user ID if the name can't be\ndetermined.\n\nOn Linux the username is highlighted if the process has elevated privileges,\ni.e. if it has been started from binaries with file capabilities set or\nretained Linux capabilities, via the ambient set, after switching from the\nroot user.\n\nAdditional color highlighting:\n.RS\n.IP \\[bu] 2\nMagenta: elevated privilege processes\n.IP \\[bu] 2\nBold gray: processes owned by other users\n.IP \\[bu] 2\nWhite (default): processes owned by the current user\n.RE\n.TP\n.B TIME (TIME+)\nThe time, measured in clock ticks that the process has spent in user and system\ntime (see UTIME, STIME above).\n.TP\n.B NLWP\nThe number of Light-Weight Processes (=threads) in the process.\n.TP\n.B TGID\nThe thread group ID.\n.TP\n.B CTID\nOpenVZ container ID, a.k.a virtual environment ID.\n.TP\n.B VPID\nOpenVZ process ID.\n.TP\n.B VXID\nVServer process ID.\n.TP\n.B RCHAR (RD_CHAR)\nThe number of bytes the process has read.\n.TP\n.B WCHAR (WR_CHAR)\nThe number of bytes the process has written.\n.TP\n.B SYSCR (RD_SYSC)\nThe number of read(2) syscalls for the process.\n.TP\n.B SYSCW (WR_SYSC)\nThe number of write(2) syscalls for the process.\n.TP\n.B RBYTES (IO_RBYTES)\nBytes of read(2) I/O for the process.\n.TP\n.B WBYTES (IO_WBYTES)\nBytes of write(2) I/O for the process.\n.TP\n.B CNCLWB (IO_CANCEL)\nBytes of cancelled write(2) I/O.\n.TP\n.B IO_READ_RATE (DISK READ)\nThe I/O rate of read(2) in bytes per second, for the process.\n.TP\n.B IO_WRITE_RATE (DISK WRITE)\nThe I/O rate of write(2) in bytes per second, for the process.\n.TP\n.B IO_RATE (DISK R/W)\nThe I/O rate, IO_READ_RATE + IO_WRITE_RATE (see above).\n.TP\n.B CGROUP\nWhich cgroup the process is in. For a shortened view see the CCGROUP column below.\n.TP\n.B CCGROUP\nShortened view of the cgroup name that the process is in.\nThis performs some pattern-based replacements to shorten the displayed string and thus condense the information.\n   \\fB/*.slice\\fR is shortened to \\fB/[*]\\fR (exceptions below)\n   \\fB/system.slice\\fR is shortened to \\fB/[S]\\fR\n   \\fB/user.slice\\fR is shortened to \\fB/[U]\\fR\n   \\fB/user-*.slice\\fR is shortened to \\fB/[U:*]\\fR (directly preceding \\fB/[U]\\fR before dropped)\n   \\fB/machine.slice\\fR is shortened to \\fB/[M]\\fR\n   \\fB/machine-*.scope\\fR is shortened to \\fB/[SNC:*]\\fR (SNC: systemd nspawn container), uppercase for the monitor\n   \\fB/lxc.monitor.*\\fR is shortened to \\fB/[LXC:*]\\fR\n   \\fB/lxc.payload.*\\fR is shortened to \\fB/[lxc:*]\\fR\n   \\fB/*.scope\\fR is shortened to \\fB/!*\\fR\n   \\fB/*.service\\fR is shortened to \\fB/*\\fR (suffix removed)\n\nEncountered escape sequences (e.g. from systemd) inside the cgroup name are not decoded.\n.TP\n.B OOM\nOOM killer score.\n.TP\n.B CTXT\nIncremental sum of voluntary and nonvoluntary context switches.\n.TP\n.B IO_PRIORITY (IO)\nThe I/O scheduling class followed by the priority if the class supports it:\n   \\fBR\\fR for Realtime\n   \\fBB\\fR for Best-effort\n   \\fBid\\fR for Idle\n.TP\n.B PERCENT_CPU_DELAY (CPUD%)\nThe percentage of time spent waiting for a CPU (while runnable). Requires CAP_NET_ADMIN.\n.TP\n.B PERCENT_IO_DELAY (IOD%)\nThe percentage of time spent waiting for the completion of synchronous block I/O. Requires CAP_NET_ADMIN.\n.TP\n.B PERCENT_SWAP_DELAY (SWAPD%)\nThe percentage of time spent swapping in pages. Requires CAP_NET_ADMIN.\n.TP\n.B AGRP\nThe autogroup identifier for the process. Requires Linux CFS to be enabled.\n.TP\n.B ANI\nThe autogroup nice value for the process autogroup. Requires Linux CFS to be enabled.\n.TP\n.B All other flags\nCurrently unsupported (always displays '-').\n.SH \"EXTERNAL LIBRARIES\"\nWhile\n.B htop\ndepends on most of the libraries it uses at build time there are two\nnoteworthy exceptions to this rule. These exceptions both relate to\ndata displayed in meters displayed in the header of\n.B htop\nand were intentionally created as optional runtime dependencies instead.\nThese exceptions are described below:\n.TP\n.B libsystemd\nThe bindings for libsystemd are used in the SystemD meter to determine\nthe number of active services and the overall system state. Looking for\nthe functions to determine these information at runtime allows for\nbuilds to support these meters without forcing the package manager\nto install these libraries on systems that otherwise don't use systemd.\n\nSummary: no build time dependency, optional runtime dependency on\n.B libsystemd\nvia dynamic loading, with\n.B systemctl(1)\nfallback.\n.TP\n.B libsensors\nThe bindings for libsensors are used for the CPU temperature readings\nin the CPU usage meters if displaying the temperature is enabled through\nthe setup screen. In order for\n.B htop\nto show these temperatures correctly though, a proper configuration\nof libsensors through its usual configuration files is assumed and that\nall CPU cores correspond to temperature sensors from the\n.B coretemp\ndriver with core 0 corresponding to a sensor labelled \"Core 0\". The\npackage temperature may be given as \"Package id 0\". If missing it is\ninferred as the maximum value from the available per-core readings.\n\nSummary: build time dependency on\n.B libsensors(3)\nC header files, optional runtime dependency on\n.B libsensors(3)\nvia dynamic loading.\n.SH \"CONFIG FILES\"\nBy default\n.B htop\nreads its configuration from the XDG-compliant path\n.IR ~/.config/htop/htoprc .\nThe configuration file is overwritten upon clean exit by\n.BR htop 's\nin-program Setup configuration, so it should not be hand-edited.\nIf no user configuration exists\n.B htop\ntries to read the system-wide configuration from\n.I @sysconfdir@/htoprc\nand as a last resort, falls back to its hard coded defaults.\n.LP\nYou may override the location of the configuration file using the $HTOPRC\nenvironment variable (so you can have multiple configurations for different\nmachines that share the same home directory, for example).\n.LP\nThe\n.B pcp-htop\nutility makes use of\n.I htoprc\nin a similar way.\nHowever,\n.B pcp-htop\nreads its configuration from a path more conventionally used by\nPerformance Co-Pilot tools,\n.IR ~/.pcp/htop/htoprc ,\nin order to provide separate configuration when both\n.B htop\nand\n.B pcp-htop\nare installed and in use.\n.B pcp-htop\nsupports additional configuration files below the same directory\nallowing new meters, columns and screen tabs to be added via the\nSetup screen (F2).\nThis displays additional Available Meters, Available Column and\nScreen Tabs for each meter, column or screen configuration file.\n.LP\nThese\n.B pcp-htop\nconfiguration files are read once at startup.\nThe format of these files is described in detail in the\n.BR pcp-htop (5)\nmanual page.\n.LP\nThis functionality makes available many thousands of Performance\nCo-Pilot metrics for display by\n.BR pcp-htop ,\nas well as the ability to display custom metrics added at individual sites.\nApplications and services instrumented using the OpenMetrics format\n.B https://openmetrics.io\ncan also be displayed by\n.B pcp-htop\nif the\n.BR pmdaopenmetrics (1)\ncomponent is configured.\n.LP\nThe configuration for both\n.B htop\nand\n.B pcp-htop\nis only saved when a clean exit is performed. Sending any signal will cause\n.I all configuration changes to be lost.\n.SH \"MEMORY SIZES\"\nMemory sizes in\n.B htop\nare displayed in a human-readable form.\nSizes are printed in powers of 1024 using binary IEC units.\nIf no suffix is shown the units are implicitly K as in KiB (kibibyte, 1 KiB = 1024 bytes).\n.LP\nThe decision to use this convention was made in order to conserve screen\nspace and make memory size representations consistent throughout\n.B htop\nas allocations are granular to full memory pages (4 KiB for most platforms).\n.SH \"SEE ALSO\"\n.BR proc (5),\n.BR top (1),\n.BR free (1),\n.BR ps (1),\n.BR uptime (1)\nand\n.BR limits.conf (5).\n.SH \"SEE ALSO FOR PCP\"\n.BR pmdaopenmetrics (1),\n.BR PCPIntro (1),\n.BR PMAPI (3),\nand\n.BR pcp-htop (5).\n.SH \"AUTHORS\"\n.B htop\nwas originally developed by Hisham Muhammad.\nNowadays it is maintained by the community at <htop@groups.io>.\n.LP\n.B pcp-htop\nis maintained as a collaboration between the <htop@groups.io> and <pcp@groups.io>\ncommunities, and forms part of the Performance Co-Pilot suite of tools.\n.SH \"COPYRIGHT\"\nCopyright \\(co 2004-2019 Hisham Muhammad.\n.br\nCopyright \\(co 2020-2026 htop dev team.\n.LP\nLicense GPLv2+: GNU General Public License version 2 or, at your option, any later version.\n.LP\nThis is free software: you are free to change and redistribute it.\nThere is NO WARRANTY, to the extent permitted by law.\n"
  },
  {
    "path": "htop.c",
    "content": "/*\nhtop - htop.c\n(C) 2004-2011 Hisham H. Muhammad\n(C) 2020-2021 htop dev team\nReleased under the GNU GPLv2+, see the COPYING file\nin the source distribution for its full text.\n*/\n\n#include \"config.h\" // IWYU pragma: keep\n\n#include \"CommandLine.h\"\n\n\nconst char* program = PACKAGE;\n\nint main(int argc, char** argv) {\n   return CommandLine_run(argc, argv);\n}\n"
  },
  {
    "path": "htop.desktop",
    "content": "[Desktop Entry]\nType=Application\nVersion=1.0\nName=Htop\nGenericName=Process Viewer\nGenericName[ca]=Visualitzador de processos\nGenericName[da]=Procesfremviser\nGenericName[de]=Prozessanzeige\nGenericName[en_GB]=Process Viewer\nGenericName[es]=Visor de procesos\nGenericName[fi]=Prosessikatselin\nGenericName[fr]=Visualiseur de processus\nGenericName[gl]=Visor de procesos\nGenericName[hy]=Պրոցեսների դիտարկիչ\nGenericName[it]=Visore dei processi\nGenericName[ja]=プロセスビューア\nGenericName[ko]=프로세스 뷰어\nGenericName[nb]=Prosessviser\nGenericName[nl]=Viewer van processen\nGenericName[nn]=Prosessvisar\nGenericName[pl]=Przeglądarka procesów\nGenericName[pt]=Visualizador de Processos\nGenericName[pt_BR]=Visualizador de processos\nGenericName[ru]=Монитор процессов\nGenericName[sk]=Prehliadač procesov\nGenericName[sl]=Pregledovalnik opravil\nGenericName[sr@ijekavian]=Приказивач процеса\nGenericName[sr@ijekavianlatin]=Prikazivač procesa\nGenericName[sr@latin]=Prikazivač procesa\nGenericName[sr]=Приказивач процеса\nGenericName[sv]=Processvisning\nGenericName[tr]=Süreç Görüntüleyici\nGenericName[uk]=Перегляд процесів\nGenericName[zh_CN]=进程查看器\nGenericName[zh_TW]=行程檢視器\nComment=Show System Processes\nComment[ca]=Visualitzeu els processos del sistema\nComment[da]=Vis systemprocesser\nComment[de]=Systemprozesse anzeigen\nComment[en_GB]=Show System Processes\nComment[es]=Mostrar procesos del sistema\nComment[fi]=Katsele järjestelmän prosesseja\nComment[fr]=Affiche les processus système\nComment[gl]=Mostrar os procesos do sistema.\nComment[hy]=Դիտել համակարգում առկա պրոցեսները\nComment[it]=Mostra processi di sistema\nComment[ja]=実行中のプロセスを表示\nComment[ko]=시스템 프로세스 보기\nComment[nb]=Vis systemprosesser\nComment[nl]=Systeemprocessen tonen\nComment[nn]=Vis systemprosessar\nComment[pl]=Pokaż procesy systemowe\nComment[pt]=Mostrar os Processos do Sistema\nComment[pt_BR]=Mostra os processos do sistema\nComment[ru]=Просмотр списка процессов в системе\nComment[sk]=Zobraziť systémové procesy\nComment[sl]=Prikaz sistemskih opravil\nComment[sr@ijekavian]=Приказ системских процеса\nComment[sr@ijekavianlatin]=Prikaz sistemskih procesa\nComment[sr@latin]=Prikaz sistemskih procesa\nComment[sr]=Приказ системских процеса\nComment[sv]=Visa systemprocesser\nComment[tr]=Sistem Süreçlerini Göster\nComment[uk]=Перегляд системних процесів\nComment[zh_CN]=显示系统进程\nComment[zh_TW]=顯示系統行程\nIcon=htop\nExec=htop\nTerminal=true\nCategories=System;Monitor;ConsoleOnly;\nKeywords=system;process;task\n"
  },
  {
    "path": "iwyu/htop.imp",
    "content": "[\n    { include: [\"<curses.h>\", \"private\", \"\\\"ProvideCurses.h\\\"\", \"public\"] },\n    { include: [\"<ncurses.h>\", \"private\", \"\\\"ProvideCurses.h\\\"\", \"public\"] },\n    { include: [\"<ncurses/curses.h>\", \"private\", \"\\\"ProvideCurses.h\\\"\", \"public\"] },\n    { include: [\"<ncurses/ncurses.h>\", \"private\", \"\\\"ProvideCurses.h\\\"\", \"public\"] },\n\n    { include: [\"<term.h>\", \"private\", \"\\\"ProvideTerm.h\\\"\", \"public\"] },\n    { include: [\"<ncurses/term.h>\", \"private\", \"\\\"ProvideTerm.h\\\"\", \"public\"] },\n    { include: [\"<ncursesw/term.h>\", \"private\", \"\\\"ProvideTerm.h\\\"\", \"public\"] },\n\n    { include: [\"<libunwind-x86_64.h>\", \"private\", \"<libunwind.h>\", \"public\"] },\n    { include: [\"\\\"ibunwind-x86_64.h\\\"\", \"private\", \"<libunwind.h>\", \"public\"] },\n\n    { include: [\"<bits/types/struct_tm.h>\", \"private\", \"<time.h>\", \"public\"] },\n\n    { include: [\"<bits/getopt_core.h>\", \"private\", \"<unistd.h>\", \"public\"] },\n\n    { include: [\"<sys/dirent.h>\", \"private\", \"<dirent.h>\", \"public\"] },\n\n    { include: [\"<sys/signal.h>\", \"private\", \"<signal.h>\", \"public\"] },\n\n    { include: [\"<sys/_stdarg.h>\", \"private\", \"<stdarg.h>\", \"public\"] },\n\n    { include: [\"<sys/limits.h>\", \"private\", \"<limits.h>\", \"public\"] },\n\n    { include: [\"<x86/_inttypes.h>\", \"private\", \"<inttypes.h>\", \"public\"] },\n\n    { include: [\"<linux/capability.h>\", \"private\", \"<sys/capability.h>\", \"public\"] },\n\n    { include: [\"<bits/mman-shared.h>\", \"private\", \"<sys/mman.h>\", \"public\"] },\n\n    { include: [\"<bits/types/struct_sched_param.h>\", \"private\", \"<sched.h>\", \"public\"] },\n]\n"
  },
  {
    "path": "iwyu/run_iwyu.sh",
    "content": "#!/bin/sh\n\nSCRIPT=$(readlink -f \"$0\")\nSCRIPTDIR=$(dirname \"$SCRIPT\")\nSOURCEDIR=\"$SCRIPTDIR/..\"\n\nPKG_NL3=$(pkg-config --cflags libnl-3.0)\n\nIWYU=${IWYU:-iwyu}\n\ncd \"$SOURCEDIR\" || exit\n\n./configure CC=clang CXX=clang++ --enable-silent-rules\nmake clean\nmake -k -s CC=\"$IWYU\" CFLAGS=\"-Xiwyu --no_comments -Xiwyu --no_fwd_decl -Xiwyu --mapping_file='$SCRIPTDIR/htop.imp' $PKG_NL3\"\n"
  },
  {
    "path": "linux/CGroupUtils.c",
    "content": "/*\nhtop - CGroupUtils.c\n(C) 2021 htop dev team\nReleased under the GNU GPLv2+, see the COPYING file\nin the source distribution for its full text.\n*/\n\n#include \"config.h\" // IWYU pragma: keep\n\n#include \"linux/CGroupUtils.h\"\n\n#include <stdbool.h>\n#include <stddef.h>\n#include <stdlib.h>\n#include <string.h>\n\n#include \"Macros.h\"\n#include \"XUtils.h\"\n\n\nstatic const char* str_slice_suffix = \".slice\";\nstatic const char* str_system_slice = \"system.slice\";\nstatic const char* str_user_slice = \"user.slice\";\nstatic const char* str_machine_slice = \"machine.slice\";\nstatic const char* str_user_slice_prefix = \"/user-\";\nstatic const char* str_system_slice_prefix = \"/system-\";\n\nstatic const char* str_lxc_monitor_legacy = \"lxc.monitor\";\nstatic const char* str_lxc_payload_legacy = \"lxc.payload\";\nstatic const char* str_lxc_monitor_prefix = \"lxc.monitor.\";\nstatic const char* str_lxc_payload_prefix = \"lxc.payload.\";\n\nstatic const char* str_nspawn_scope_prefix = \"machine-\";\nstatic const char* str_nspawn_monitor_label = \"/supervisor\";\nstatic const char* str_nspawn_payload_label = \"/payload\";\n\nstatic const char* str_snap_scope_prefix = \"snap.\";\nstatic const char* str_pod_scope_prefix = \"libpod-\";\nstatic const char* str_docker_scope_prefix = \"docker-\";\n\nstatic const char* str_service_suffix = \".service\";\nstatic const char* str_scope_suffix = \".scope\";\n\ntypedef struct StrBuf_state {\n   char* buf;\n   size_t size;\n   size_t pos;\n} StrBuf_state;\n\ntypedef bool (*StrBuf_putc_t)(StrBuf_state* p, char c);\n\nstatic bool StrBuf_putc_count(StrBuf_state* p, ATTR_UNUSED char c) {\n   p->pos++;\n   return true;\n}\n\nstatic bool StrBuf_putc_write(StrBuf_state* p, char c) {\n   if (p->pos >= p->size)\n      return false;\n\n   p->buf[p->pos] = c;\n   p->pos++;\n   return true;\n}\n\nstatic bool StrBuf_putsn(StrBuf_state* p, StrBuf_putc_t w, const char* s, size_t count) {\n   for (; count; count--)\n      if (!w(p, *s++))\n         return false;\n\n   return true;\n}\n\nstatic bool StrBuf_putsz(StrBuf_state* p, StrBuf_putc_t w, const char* s) {\n   while (*s)\n      if (!w(p, *s++))\n         return false;\n\n   return true;\n}\n\nstatic bool Label_checkEqual(const char* labelStart, size_t labelLen, const char* expected) {\n   return labelLen == strlen(expected) && String_startsWith(labelStart, expected);\n}\n\nstatic bool Label_checkPrefix(const char* labelStart, size_t labelLen, const char* expected) {\n   return labelLen > strlen(expected) && String_startsWith(labelStart, expected);\n}\n\nstatic bool Label_checkSuffix(const char* labelStart, size_t labelLen, const char* expected) {\n   return labelLen > strlen(expected) && String_startsWith(labelStart + labelLen - strlen(expected), expected);\n}\n\nstatic bool CGroup_filterName_internal(const char* cgroup, StrBuf_state* s, StrBuf_putc_t w) {\n   while (*cgroup) {\n      if ('/' == *cgroup) {\n         while ('/' == *cgroup)\n            cgroup++;\n\n         if (!w(s, '/'))\n            return false;\n\n         continue;\n      }\n\n      const char* labelStart = cgroup;\n      const char* nextSlash = String_strchrnul(labelStart, '/');\n      const size_t labelLen = nextSlash - labelStart;\n\n      if (Label_checkEqual(labelStart, labelLen, str_system_slice)) {\n         cgroup = nextSlash;\n\n         if (!StrBuf_putsz(s, w, \"[S]\"))\n            return false;\n\n         if (String_startsWith(cgroup, str_system_slice_prefix)) {\n            cgroup = String_strchrnul(cgroup + 1, '/');\n            continue;\n         }\n\n         continue;\n      }\n\n      if (Label_checkEqual(labelStart, labelLen, str_machine_slice)) {\n         cgroup = nextSlash;\n\n         if (!StrBuf_putsz(s, w, \"[M]\"))\n            return false;\n\n         continue;\n      }\n\n      if (Label_checkEqual(labelStart, labelLen, str_user_slice)) {\n         cgroup = nextSlash;\n\n         if (!StrBuf_putsz(s, w, \"[U]\"))\n            return false;\n\n         if (!String_startsWith(cgroup, str_user_slice_prefix))\n            continue;\n\n         const char* userSliceSlash = String_strchrnul(cgroup + strlen(str_user_slice_prefix), '/');\n         const char* sliceSpec = userSliceSlash - strlen(str_slice_suffix);\n\n         if (!String_startsWith(sliceSpec, str_slice_suffix))\n            continue;\n\n         const size_t sliceNameLen = sliceSpec - (cgroup + strlen(str_user_slice_prefix));\n\n         s->pos--;\n         if (!w(s, ':'))\n            return false;\n\n         if (!StrBuf_putsn(s, w, cgroup + strlen(str_user_slice_prefix), sliceNameLen))\n            return false;\n\n         if (!w(s, ']'))\n            return false;\n\n         cgroup = userSliceSlash;\n\n         continue;\n      }\n\n      if (Label_checkSuffix(labelStart, labelLen, str_slice_suffix)) {\n         const size_t sliceNameLen = labelLen - strlen(str_slice_suffix);\n\n         if (!w(s, '['))\n            return false;\n\n         if (!StrBuf_putsn(s, w, cgroup, sliceNameLen))\n            return false;\n\n         if (!w(s, ']'))\n            return false;\n\n         cgroup = nextSlash;\n\n         continue;\n      }\n\n      if (Label_checkPrefix(labelStart, labelLen, str_lxc_payload_prefix)) {\n         const size_t cgroupNameLen = labelLen - strlen(str_lxc_payload_prefix);\n\n         if (!StrBuf_putsz(s, w, \"[lxc:\"))\n            return false;\n\n         if (!StrBuf_putsn(s, w, cgroup + strlen(str_lxc_payload_prefix), cgroupNameLen))\n            return false;\n\n         if (!w(s, ']'))\n            return false;\n\n         cgroup = nextSlash;\n\n         continue;\n      }\n\n      if (Label_checkPrefix(labelStart, labelLen, str_lxc_monitor_prefix)) {\n         const size_t cgroupNameLen = labelLen - strlen(str_lxc_monitor_prefix);\n\n         if (!StrBuf_putsz(s, w, \"[LXC:\"))\n            return false;\n\n         if (!StrBuf_putsn(s, w, cgroup + strlen(str_lxc_monitor_prefix), cgroupNameLen))\n            return false;\n\n         if (!w(s, ']'))\n            return false;\n\n         cgroup = nextSlash;\n\n         continue;\n      }\n\n      // LXC legacy cgroup naming\n      if (Label_checkEqual(labelStart, labelLen, str_lxc_monitor_legacy) ||\n         Label_checkEqual(labelStart, labelLen, str_lxc_payload_legacy)) {\n         bool isMonitor = Label_checkEqual(labelStart, labelLen, str_lxc_monitor_legacy);\n\n         labelStart = nextSlash;\n         while (*labelStart == '/')\n            labelStart++;\n\n         nextSlash = String_strchrnul(labelStart, '/');\n         if (nextSlash - labelStart > 0) {\n            if (!StrBuf_putsz(s, w, isMonitor ? \"[LXC:\" : \"[lxc:\"))\n               return false;\n\n            if (!StrBuf_putsn(s, w, labelStart, nextSlash - labelStart))\n               return false;\n\n            if (!w(s, ']'))\n               return false;\n\n            cgroup = nextSlash;\n            continue;\n         }\n\n         labelStart = cgroup;\n         nextSlash = labelStart + labelLen;\n      }\n\n      if (Label_checkSuffix(labelStart, labelLen, str_service_suffix)) {\n         const size_t serviceNameLen = labelLen - strlen(str_service_suffix);\n\n         if (String_startsWith(cgroup, \"user@\")) {\n            cgroup = nextSlash;\n\n            while (*cgroup == '/')\n               cgroup++;\n\n            continue;\n         }\n\n         if (!StrBuf_putsn(s, w, cgroup, serviceNameLen))\n            return false;\n\n         cgroup = nextSlash;\n\n         continue;\n      }\n\n      if (Label_checkSuffix(labelStart, labelLen, str_scope_suffix)) {\n         const size_t scopeNameLen = labelLen - strlen(str_scope_suffix);\n\n         if (Label_checkPrefix(labelStart, scopeNameLen, str_nspawn_scope_prefix)) {\n            const size_t machineScopeNameLen = scopeNameLen - strlen(str_nspawn_scope_prefix);\n\n            const bool is_monitor = String_startsWith(nextSlash, str_nspawn_monitor_label);\n\n            if (!StrBuf_putsz(s, w, is_monitor ? \"[SNC:\" : \"[snc:\"))\n               return false;\n\n            if (!StrBuf_putsn(s, w, cgroup + strlen(str_nspawn_scope_prefix), machineScopeNameLen))\n               return false;\n\n            if (!w(s, ']'))\n               return false;\n\n            cgroup = nextSlash;\n            if (String_startsWith(nextSlash, str_nspawn_monitor_label))\n               cgroup += strlen(str_nspawn_monitor_label);\n            else if (String_startsWith(nextSlash, str_nspawn_payload_label))\n               cgroup += strlen(str_nspawn_payload_label);\n\n            continue;\n         } else if (Label_checkPrefix(labelStart, scopeNameLen, str_snap_scope_prefix)) {\n            const char* nextDot = String_strchrnul(labelStart + strlen(str_snap_scope_prefix), '.');\n\n            if (!StrBuf_putsz(s, w, \"!snap:\"))\n               return false;\n\n            if (nextDot >= labelStart + scopeNameLen) {\n               nextDot = labelStart + scopeNameLen;\n            }\n\n            if (!StrBuf_putsn(s, w, labelStart + strlen(str_snap_scope_prefix), nextDot - (labelStart + strlen(str_snap_scope_prefix))))\n               return false;\n\n            cgroup = nextSlash;\n\n            continue;\n         } else if (Label_checkPrefix(labelStart, scopeNameLen, str_pod_scope_prefix)) {\n            const char* nextDot = String_strchrnul(labelStart + strlen(str_pod_scope_prefix), '.');\n\n            if (!StrBuf_putsz(s, w, \"!pod:\"))\n               return false;\n\n            if (nextDot >= labelStart + scopeNameLen) {\n               nextDot = labelStart + scopeNameLen;\n            }\n\n            if (!StrBuf_putsn(s, w, labelStart + strlen(str_pod_scope_prefix),\n               MINIMUM( nextDot - (labelStart + strlen(str_pod_scope_prefix)), 12)))\n               return false;\n\n            cgroup = nextSlash;\n\n            continue;\n         } else if (Label_checkPrefix(labelStart, scopeNameLen, str_docker_scope_prefix)) {\n            const char* nextDot = String_strchrnul(labelStart + strlen(str_docker_scope_prefix), '.');\n\n            if (!StrBuf_putsz(s, w, \"!docker:\"))\n               return false;\n\n            if (nextDot >= labelStart + scopeNameLen) {\n               nextDot = labelStart + scopeNameLen;\n            }\n\n            if (!StrBuf_putsn(s, w, labelStart + strlen(str_docker_scope_prefix),\n               MINIMUM( nextDot - (labelStart + strlen(str_docker_scope_prefix)), 12)))\n               return false;\n\n            cgroup = nextSlash;\n\n            continue;\n         }\n\n         if (!w(s, '!'))\n            return false;\n\n         if (!StrBuf_putsn(s, w, cgroup, scopeNameLen))\n            return false;\n\n         cgroup = nextSlash;\n\n         continue;\n      }\n\n      // Default behavior: Copy the full label\n      cgroup = labelStart;\n\n      if (!StrBuf_putsn(s, w, cgroup, labelLen))\n         return false;\n\n      cgroup = nextSlash;\n   }\n\n   return true;\n}\n\nchar* CGroup_filterName(const char* cgroup) {\n   StrBuf_state s = {\n      .buf = NULL,\n      .size = 0,\n      .pos = 0,\n   };\n\n   if (!CGroup_filterName_internal(cgroup, &s, StrBuf_putc_count)) {\n      return NULL;\n   }\n\n   s.buf = xCalloc(s.pos + 1, sizeof(char));\n   s.size = s.pos;\n   s.pos = 0;\n\n   if (!CGroup_filterName_internal(cgroup, &s, StrBuf_putc_write)) {\n      free(s.buf);\n      return NULL;\n   }\n\n   s.buf[s.size] = '\\0';\n   return s.buf;\n}\n\nstatic bool CGroup_filterContainer_internal(const char* cgroup, StrBuf_state* s, StrBuf_putc_t w) {\n   while (*cgroup) {\n      if ('/' == *cgroup) {\n         while ('/' == *cgroup)\n            cgroup++;\n\n         continue;\n      }\n\n      const char* labelStart = cgroup;\n      const char* nextSlash = String_strchrnul(labelStart, '/');\n      const size_t labelLen = nextSlash - labelStart;\n\n      if (Label_checkPrefix(labelStart, labelLen, str_lxc_payload_prefix)) {\n         const size_t cgroupNameLen = labelLen - strlen(str_lxc_payload_prefix);\n\n         if (!StrBuf_putsz(s, w, \"/lxc:\"))\n            return false;\n\n         if (!StrBuf_putsn(s, w, cgroup + strlen(str_lxc_payload_prefix), cgroupNameLen))\n            return false;\n\n         cgroup = nextSlash;\n\n         continue;\n      }\n\n      // LXC legacy cgroup naming\n      if (Label_checkEqual(labelStart, labelLen, str_lxc_payload_legacy)) {\n         labelStart = nextSlash;\n         while (*labelStart == '/')\n            labelStart++;\n\n         nextSlash = String_strchrnul(labelStart, '/');\n         if (nextSlash - labelStart > 0) {\n            if (!StrBuf_putsz(s, w, \"/lxc:\"))\n               return false;\n\n            if (!StrBuf_putsn(s, w, labelStart, nextSlash - labelStart))\n               return false;\n\n            cgroup = nextSlash;\n            continue;\n         }\n\n         labelStart = cgroup;\n         nextSlash = labelStart + labelLen;\n      }\n\n      if (Label_checkSuffix(labelStart, labelLen, str_scope_suffix)) {\n         const size_t scopeNameLen = labelLen - strlen(str_scope_suffix);\n\n         if (Label_checkPrefix(labelStart, scopeNameLen, str_nspawn_scope_prefix)) {\n            const size_t machineScopeNameLen = scopeNameLen - strlen(str_nspawn_scope_prefix);\n\n            const bool is_monitor = String_startsWith(nextSlash, str_nspawn_monitor_label);\n\n            if (!is_monitor) {\n               if (!StrBuf_putsz(s, w, \"/snc:\"))\n                  return false;\n\n               if (!StrBuf_putsn(s, w, cgroup + strlen(str_nspawn_scope_prefix), machineScopeNameLen))\n                  return false;\n            }\n\n            cgroup = nextSlash;\n            if (String_startsWith(nextSlash, str_nspawn_monitor_label))\n               cgroup += strlen(str_nspawn_monitor_label);\n            else if (String_startsWith(nextSlash, str_nspawn_payload_label))\n               cgroup += strlen(str_nspawn_payload_label);\n\n            continue;\n         } else if (Label_checkPrefix(labelStart, scopeNameLen, str_pod_scope_prefix)) {\n            const char* nextDot = String_strchrnul(labelStart + strlen(str_pod_scope_prefix), '.');\n\n            if (!StrBuf_putsz(s, w, \"/pod:\"))\n               return false;\n\n            if (nextDot >= labelStart + scopeNameLen) {\n               nextDot = labelStart + scopeNameLen;\n            }\n\n            if (!StrBuf_putsn(s, w, labelStart + strlen(str_pod_scope_prefix),\n               MINIMUM( nextDot - (labelStart + strlen(str_pod_scope_prefix)), 12)))\n               return false;\n\n            cgroup = nextSlash;\n\n            continue;\n         } else if (Label_checkPrefix(labelStart, scopeNameLen, str_docker_scope_prefix)) {\n            const char* nextDot = String_strchrnul(labelStart + strlen(str_docker_scope_prefix), '.');\n\n            if (!StrBuf_putsz(s, w, \"!docker:\"))\n               return false;\n\n            if (nextDot >= labelStart + scopeNameLen) {\n               nextDot = labelStart + scopeNameLen;\n            }\n\n            if (!StrBuf_putsn(s, w, labelStart + strlen(str_docker_scope_prefix),\n               MINIMUM( nextDot - (labelStart + strlen(str_docker_scope_prefix)), 12)))\n               return false;\n\n            cgroup = nextSlash;\n\n            continue;\n         }\n\n         cgroup = nextSlash;\n\n         continue;\n      }\n\n      cgroup = nextSlash;\n   }\n\n   return true;\n}\n\nchar* CGroup_filterContainer(const char* cgroup) {\n   StrBuf_state s = {\n      .buf = NULL,\n      .size = 0,\n      .pos = 0,\n   };\n\n   if (!CGroup_filterContainer_internal(cgroup, &s, StrBuf_putc_count)) {\n      return NULL;\n   }\n\n   if (!s.pos) {\n      return xStrdup(\"/\");\n   }\n\n   s.buf = xCalloc(s.pos + 1, sizeof(char));\n   s.size = s.pos;\n   s.pos = 0;\n\n   if (!CGroup_filterContainer_internal(cgroup, &s, StrBuf_putc_write)) {\n      free(s.buf);\n      return NULL;\n   }\n\n   s.buf[s.size] = '\\0';\n   return s.buf;\n}\n"
  },
  {
    "path": "linux/CGroupUtils.h",
    "content": "#ifndef HEADER_CGroupUtils\n#define HEADER_CGroupUtils\n/*\nhtop - CGroupUtils.h\n(C) 2021 htop dev team\nReleased under the GNU GPLv2+, see the COPYING file\nin the source distribution for its full text.\n*/\n\n\nchar* CGroup_filterName(const char* cgroup);\nchar* CGroup_filterContainer(const char* cgroup);\n\n#endif /* HEADER_CGroupUtils */\n"
  },
  {
    "path": "linux/Compat.c",
    "content": "/*\nhtop - linux/Compat.c\n(C) 2020 htop dev team\nReleased under the GNU GPLv2+, see the COPYING file\nin the source distribution for its full text.\n*/\n\n#include \"config.h\" // IWYU pragma: keep\n\n#include \"linux/Compat.h\"\n\n#include <errno.h>\n#include <fcntl.h> // IWYU pragma: keep\n#include <limits.h>\n#include <unistd.h>\n#include <sys/stat.h>\n#include <sys/types.h> // IWYU pragma: keep\n\n#include \"XUtils.h\" // IWYU pragma: keep\n\n\n/* GNU/Hurd does not have PATH_MAX in limits.h */\n#ifndef PATH_MAX\n# define PATH_MAX 4096\n#endif\n\n\nint Compat_faccessat(int dirfd,\n                     const char* pathname,\n                     int mode,\n                     int flags) {\n   int ret;\n\n#ifdef HAVE_FACCESSAT\n\n   // Implementation note: AT_SYMLINK_NOFOLLOW unsupported on FreeBSD, fallback to lstat in that case\n\n   errno = 0;\n\n   ret = faccessat(dirfd, pathname, mode, flags);\n   if (!ret || errno != EINVAL)\n      return ret;\n\n#endif\n\n   // Error out on unsupported configurations\n   if (dirfd != (int)AT_FDCWD || mode != F_OK) {\n      errno = EINVAL;\n      return -1;\n   }\n\n   // Fallback to stat(2)/lstat(2) depending on flags\n   struct stat sb;\n   if (flags) {\n      ret = lstat(pathname, &sb);\n   } else {\n      ret = stat(pathname, &sb);\n   }\n\n   return ret;\n}\n\nint Compat_fstatat(int dirfd,\n                   const char* dirpath,\n                   const char* pathname,\n                   struct stat* statbuf,\n                   int flags) {\n\n#ifdef HAVE_FSTATAT\n\n   (void)dirpath;\n\n   return fstatat(dirfd, pathname, statbuf, flags);\n\n#else\n\n   (void)dirfd;\n\n   char path[4096];\n   xSnprintf(path, sizeof(path), \"%s/%s\", dirpath, pathname);\n\n   if (flags & AT_SYMLINK_NOFOLLOW)\n      return lstat(path, statbuf);\n\n   return stat(path, statbuf);\n\n#endif\n}\n\n#ifndef HAVE_OPENAT\n\nint Compat_openat(const char* dirpath,\n                  const char* pathname,\n                  int flags) {\n\n   char path[4096];\n   xSnprintf(path, sizeof(path), \"%s/%s\", dirpath, pathname);\n\n   return open(path, flags);\n}\n\n#endif /* !HAVE_OPENAT */\n\nssize_t Compat_readlinkat(int dirfd,\n                          const char* dirpath,\n                          const char* pathname,\n                          char* buf,\n                          size_t bufsize) {\n\n#ifdef HAVE_READLINKAT\n\n   (void)dirpath;\n\n   return readlinkat(dirfd, pathname, buf, bufsize);\n\n#else\n\n   (void)dirfd;\n\n   char path[4096];\n   xSnprintf(path, sizeof(path), \"%s/%s\", dirpath, pathname);\n\n   return readlink(path, buf, bufsize);\n\n#endif\n}\n\nssize_t Compat_readlink(openat_arg_t dirfd,\n                        const char* pathname,\n                        char* buf,\n                        size_t bufsize) {\n\n#ifdef HAVE_OPENAT\n\n   char fdPath[32];\n   xSnprintf(fdPath, sizeof(fdPath), \"/proc/self/fd/%d\", dirfd);\n\n   char dirPath[PATH_MAX + 1];\n   ssize_t r = readlink(fdPath, dirPath, sizeof(dirPath) - 1);\n   if (r < 0)\n      return r;\n\n   dirPath[r] = '\\0';\n\n   char linkPath[PATH_MAX + 1];\n   xSnprintf(linkPath, sizeof(linkPath), \"%s/%s\", dirPath, pathname);\n\n#else\n\n   char linkPath[PATH_MAX + 1];\n   xSnprintf(linkPath, sizeof(linkPath), \"%s/%s\", dirfd, pathname);\n\n#endif /* HAVE_OPENAT */\n\n   return readlink(linkPath, buf, bufsize);\n}\n\nATTR_ACCESS3_W(2, 3)\nstatic ssize_t readfd_internal(int fd, void* buffer, size_t count) {\n   if (!count) {\n      close(fd);\n      return -EINVAL;\n   }\n\n   ssize_t alreadyRead = 0;\n   count--; // reserve one for null-terminator\n\n   for (;;) {\n      ssize_t res = read(fd, buffer, count);\n      if (res == -1) {\n         if (errno == EINTR)\n            continue;\n\n         close(fd);\n         *((char*)buffer) = '\\0';\n         return -errno;\n      }\n\n      if (res > 0) {\n         assert((size_t)res <= count);\n\n         buffer = ((char*)buffer) + res;\n         count -= (size_t)res;\n         alreadyRead += res;\n      }\n\n      if (count == 0 || res == 0) {\n         close(fd);\n         *((char*)buffer) = '\\0';\n         return alreadyRead;\n      }\n   }\n}\n\nssize_t Compat_readfile(const char* pathname, void* buffer, size_t count) {\n   int fd = open(pathname, O_RDONLY);\n   if (fd < 0)\n      return -errno;\n\n   return readfd_internal(fd, buffer, count);\n}\n\nssize_t Compat_readfileat(openat_arg_t dirfd, const char* pathname, void* buffer, size_t count) {\n   int fd = Compat_openat(dirfd, pathname, O_RDONLY);\n   if (fd < 0)\n      return -errno;\n\n   return readfd_internal(fd, buffer, count);\n}\n"
  },
  {
    "path": "linux/Compat.h",
    "content": "#ifndef HEADER_Compat\n#define HEADER_Compat\n/*\nhtop - linux/Compat.h\n(C) 2020 htop dev team\nReleased under the GNU GPLv2+, see the COPYING file\nin the source distribution for its full text.\n*/\n\n#include <fcntl.h>\n#include <stddef.h> // IWYU pragma: keep\n#include <unistd.h>\n#include <sys/stat.h> // IWYU pragma: keep\n\n#include \"Macros.h\"\n\n\nint Compat_faccessat(int dirfd,\n                     const char* pathname,\n                     int mode,\n                     int flags);\n\nint Compat_fstatat(int dirfd,\n                   const char* dirpath,\n                   const char* pathname,\n                   struct stat* statbuf,\n                   int flags);\n\n#ifdef HAVE_OPENAT\n\ntypedef int openat_arg_t;\n\nstatic inline void Compat_openatArgClose(openat_arg_t dirfd) {\n   close(dirfd);\n}\n\nstatic inline int Compat_openat(openat_arg_t dirfd, const char* pathname, int flags) {\n   return openat(dirfd, pathname, flags);\n}\n\n#else /* HAVE_OPENAT */\n\ntypedef const char* openat_arg_t;\n\nstatic inline void Compat_openatArgClose(openat_arg_t dirpath) {\n   (void)dirpath;\n}\n\nint Compat_openat(openat_arg_t dirpath, const char* pathname, int flags);\n\n#endif /* HAVE_OPENAT */\n\nssize_t Compat_readlinkat(int dirfd,\n                          const char* dirpath,\n                          const char* pathname,\n                          char* buf,\n                          size_t bufsize);\n\nssize_t Compat_readlink(openat_arg_t dirfd,\n                        const char* pathname,\n                        char* buf,\n                        size_t bufsize);\n\nATTR_NONNULL ATTR_ACCESS3_W(2, 3)\nssize_t Compat_readfile(const char* pathname, void* buffer, size_t count);\n\nATTR_NONNULL ATTR_ACCESS3_W(3, 4)\nssize_t Compat_readfileat(openat_arg_t dirfd, const char* pathname, void* buffer, size_t count);\n\n#endif /* HEADER_Compat */\n"
  },
  {
    "path": "linux/GPU.c",
    "content": "/*\nhtop - GPU.c\n(C) 2023 htop dev team\nReleased under the GNU GPLv2+, see the COPYING file\nin the source distribution for its full text.\n*/\n\n#include \"config.h\" // IWYU pragma: keep\n\n#include \"linux/GPU.h\"\n\n#include <assert.h>\n#include <ctype.h>\n#include <dirent.h>\n#include <errno.h>\n#include <sys/types.h>\n\n#include \"linux/Compat.h\"\n#include \"linux/LinuxMachine.h\"\n\n\ntypedef unsigned long long int ClientID;\n#define INVALID_CLIENT_ID ((ClientID)-1)\n\n\ntypedef struct ClientInfo_ {\n   char* pdev;\n   ClientID id;\n   struct ClientInfo_* next;\n} ClientInfo;\n\nenum section_state {\n   SECST_UNKNOWN,\n   SECST_DUPLICATE,\n   SECST_NEW,\n};\n\nstatic bool is_duplicate_client(const ClientInfo* parsed, ClientID id, const char* pdev) {\n   for (; parsed; parsed = parsed->next) {\n      if (id == parsed->id && String_eq_nullable(pdev, parsed->pdev)) {\n         return true;\n      }\n   }\n\n   return false;\n}\n\nstatic void update_machine_gpu(LinuxProcessTable* lpt, unsigned long long int time, const char* engine, size_t engine_len) {\n   Machine* host = lpt->super.super.host;\n   LinuxMachine* lhost = (LinuxMachine*) host;\n   GPUEngineData** engineData = &lhost->gpuEngineData;\n\n   while (*engineData) {\n      if (strncmp((*engineData)->key, engine, engine_len) == 0 && (*engineData)->key[engine_len] == '\\0')\n         break;\n\n      engineData = &((*engineData)->next);\n   }\n\n   if (!*engineData) {\n      GPUEngineData* newData = xMalloc(sizeof(*newData));\n      *newData = (GPUEngineData) {\n         .prevTime = 0,\n         .curTime  = 0,\n         .key      = xStrndup(engine, engine_len),\n         .next     = NULL,\n      };\n\n      *engineData = newData;\n   }\n\n   (*engineData)->curTime += time;\n   lhost->curGpuTime += time;\n}\n\n/*\n * Documentation reference:\n * https://www.kernel.org/doc/html/latest/gpu/drm-usage-stats.html\n */\nvoid GPU_readProcessData(LinuxProcessTable* lpt, LinuxProcess* lp, openat_arg_t procFd) {\n   const Machine* host = lp->super.super.host;\n   int fdinfoFd = -1;\n   DIR* fdinfoDir = NULL;\n   ClientInfo* parsed_ids = NULL;\n   unsigned long long int new_gpu_time = 0;\n\n   /* check only if active in last check or last scan was more than 5s ago */\n   if (lp->gpu_activityMs != 0 && host->monotonicMs - lp->gpu_activityMs < 5000) {\n      lp->gpu_percent = 0.0F;\n      return;\n   }\n   lp->gpu_activityMs = host->monotonicMs;\n\n   fdinfoFd = Compat_openat(procFd, \"fdinfo\", O_RDONLY | O_NOFOLLOW | O_DIRECTORY | O_CLOEXEC);\n   if (fdinfoFd == -1)\n      goto out;\n\n   fdinfoDir = fdopendir(fdinfoFd);\n   if (!fdinfoDir)\n      goto out;\n   fdinfoFd = -1;\n\n#ifndef HAVE_OPENAT\n   char fdinfoPathBuf[32];\n   xSnprintf(fdinfoPathBuf, sizeof(fdinfoPathBuf), PROCDIR \"/%u/fdinfo\", Process_getPid(&lp->super));\n#endif\n\n   while (true) {\n      char* pdev = NULL;\n      ClientID client_id = INVALID_CLIENT_ID;\n      enum section_state sstate = SECST_UNKNOWN;\n\n      const struct dirent* entry = readdir(fdinfoDir);\n      if (!entry)\n         break;\n      const char* ename = entry->d_name;\n\n      if (ename[0] == '.' && (ename[1] == '\\0' || (ename[1] == '.' && ename[2] == '\\0')))\n         continue;\n\n      char buffer[4096];\n#ifdef HAVE_OPENAT\n      ssize_t ret = Compat_readfileat(dirfd(fdinfoDir), ename, buffer, sizeof(buffer));\n#else\n      ssize_t ret = Compat_readfileat(fdinfoPathBuf, ename, buffer, sizeof(buffer));\n#endif\n      /* eventfd information can be huge */\n      if (ret <= 0 || (size_t)ret >= sizeof(buffer) - 1)\n         continue;\n\n      char* buf = buffer;\n      const char* line;\n      while ((line = strsep(&buf, \"\\n\")) != NULL) {\n         if (!String_startsWith(line, \"drm-\"))\n            continue;\n         line += strlen(\"drm-\");\n\n         if (line[0] == 'c' && String_startsWith(line, \"client-id:\")) {\n            if (sstate == SECST_NEW) {\n               assert(client_id != INVALID_CLIENT_ID);\n\n               ClientInfo* new = xMalloc(sizeof(*new));\n               *new = (ClientInfo) {\n                  .id = client_id,\n                  .pdev = pdev,\n                  .next = parsed_ids,\n               };\n               pdev = NULL;\n\n               parsed_ids = new;\n            }\n\n            sstate = SECST_UNKNOWN;\n\n            char *endptr;\n            errno = 0;\n            client_id = strtoull(line + strlen(\"client-id:\"), &endptr, 10);\n            if (errno || *endptr != '\\0')\n               client_id = INVALID_CLIENT_ID;\n         } else if (line[0] == 'p' && String_startsWith(line, \"pdev:\")) {\n            const char* p = line + strlen(\"pdev:\");\n\n            while (isspace((unsigned char)*p))\n               p++;\n\n            assert(!pdev || String_eq(pdev, p));\n            if (!pdev)\n               pdev = xStrdup(p);\n         } else if (line[0] == 'e' && String_startsWith(line, \"engine-\")) {\n            if (sstate == SECST_DUPLICATE)\n               continue;\n\n            const char* engineStart = line + strlen(\"engine-\");\n\n            if (String_startsWith(engineStart, \"capacity-\"))\n               continue;\n\n            const char* delim = strchr(line, ':');\n\n            char* endptr;\n            errno = 0;\n            unsigned long long int value = strtoull(delim + 1, &endptr, 10);\n            if (errno == 0 && String_startsWith(endptr, \" ns\")) {\n               if (sstate == SECST_UNKNOWN) {\n                  if (client_id != INVALID_CLIENT_ID && !is_duplicate_client(parsed_ids, client_id, pdev))\n                     sstate = SECST_NEW;\n                  else\n                     sstate = SECST_DUPLICATE;\n               }\n\n               if (sstate == SECST_NEW) {\n                  new_gpu_time += value;\n                  update_machine_gpu(lpt, value, engineStart, delim - engineStart);\n               }\n            }\n         }\n      } /* finished parsing lines */\n\n      if (sstate == SECST_NEW) {\n         assert(client_id != INVALID_CLIENT_ID);\n\n         ClientInfo* new = xMalloc(sizeof(*new));\n         *new = (ClientInfo) {\n            .id = client_id,\n            .pdev = pdev,\n            .next = parsed_ids,\n         };\n         pdev = NULL;\n\n         parsed_ids = new;\n      }\n\n      free(pdev);\n   } /* finished parsing fdinfo entries */\n\n   if (new_gpu_time > 0) {\n      unsigned long long int gputimeDelta;\n      uint64_t monotonicTimeDelta;\n\n      gputimeDelta = saturatingSub(new_gpu_time, lp->gpu_time);\n      monotonicTimeDelta = host->monotonicMs - host->prevMonotonicMs;\n      lp->gpu_percent = 100.0F * gputimeDelta / (1000 * 1000) / monotonicTimeDelta;\n\n      lp->gpu_activityMs = 0;\n   } else\n      lp->gpu_percent = 0.0F;\n\nout:\n\n   lp->gpu_time = new_gpu_time;\n\n   while (parsed_ids) {\n      ClientInfo* next = parsed_ids->next;\n      free(parsed_ids->pdev);\n      free(parsed_ids);\n      parsed_ids = next;\n   }\n\n   if (fdinfoDir)\n      closedir(fdinfoDir);\n   if (fdinfoFd != -1)\n      close(fdinfoFd);\n}\n"
  },
  {
    "path": "linux/GPU.h",
    "content": "#ifndef HEADER_GPU\n#define HEADER_GPU\n/*\nhtop - GPU.h\n(C) 2023 htop dev team\nReleased under the GNU GPLv2+, see the COPYING file\nin the source distribution for its full text.\n*/\n\n#include \"Compat.h\"\n#include \"linux/LinuxProcess.h\"\n#include \"linux/LinuxProcessTable.h\"\n\n\nvoid GPU_readProcessData(LinuxProcessTable* lpt, LinuxProcess* lp, openat_arg_t procFd);\n\n#endif /* HEADER_GPU */\n"
  },
  {
    "path": "linux/HugePageMeter.c",
    "content": "/*\nhtop - HugePageMeter.c\n(C) 2021 htop dev team\nReleased under the GNU GPLv2+, see the COPYING file\nin the source distribution for its full text.\n*/\n\n#include \"config.h\" // IWYU pragma: keep\n\n#include \"linux/HugePageMeter.h\"\n\n#include <assert.h>\n#include <math.h>\n#include <stddef.h>\n\n#include \"CRT.h\"\n#include \"Machine.h\"\n#include \"Macros.h\"\n#include \"Object.h\"\n#include \"RichString.h\"\n#include \"linux/LinuxMachine.h\"\n\n\nstatic const char* HugePageMeter_active_labels[4] = { NULL, NULL, NULL, NULL };\n\nstatic const int HugePageMeter_attributes[] = {\n   HUGEPAGE_1,\n   HUGEPAGE_2,\n   HUGEPAGE_3,\n   HUGEPAGE_4,\n};\n\nstatic const char* const HugePageMeter_labels[] = {\n   \" 64K:\", \" 128K:\", \" 256K:\", \" 512K:\",\n   \" 1M:\", \" 2M:\", \" 4M:\", \" 8M:\", \" 16M:\", \" 32M:\", \" 64M:\", \" 128M:\", \" 256M:\", \" 512M:\",\n   \" 1G:\", \" 2G:\", \" 4G:\", \" 8G:\", \" 16G:\", \" 32G:\", \" 64G:\", \" 128G:\", \" 256G:\", \" 512G:\",\n};\n\nstatic void HugePageMeter_updateValues(Meter* this) {\n   assert(ARRAYSIZE(HugePageMeter_labels) == HTOP_HUGEPAGE_COUNT);\n\n   char* buffer = this->txtBuffer;\n   size_t size = sizeof(this->txtBuffer);\n   int written;\n   memory_t usedTotal = 0;\n   size_t nextUsed = 0;\n\n   const LinuxMachine* host = (const LinuxMachine*) this->host;\n   this->total = host->totalHugePageMem;\n   this->values[0] = 0;\n   HugePageMeter_active_labels[0] = \" used:\";\n   for (size_t i = 1; i < ARRAYSIZE(HugePageMeter_active_labels); i++) {\n      this->values[i] = NAN;\n      HugePageMeter_active_labels[i] = NULL;\n   }\n   for (size_t i = 0; i < HTOP_HUGEPAGE_COUNT; i++) {\n      memory_t value = host->usedHugePageMem[i];\n      if (value != MEMORY_MAX) {\n         this->values[nextUsed] = value;\n         usedTotal += value;\n         HugePageMeter_active_labels[nextUsed] = HugePageMeter_labels[i];\n         if (++nextUsed == ARRAYSIZE(HugePageMeter_active_labels)) {\n            break;\n         }\n      }\n   }\n\n   written = Meter_humanUnit(buffer, usedTotal, size);\n   METER_BUFFER_CHECK(buffer, size, written);\n\n   METER_BUFFER_APPEND_CHR(buffer, size, '/');\n\n   Meter_humanUnit(buffer, this->total, size);\n}\n\nstatic void HugePageMeter_display(const Object* cast, RichString* out) {\n   char buffer[50];\n   const Meter* this = (const Meter*)cast;\n\n   RichString_writeAscii(out, CRT_colors[METER_TEXT], \":\");\n   Meter_humanUnit(buffer, this->total, sizeof(buffer));\n   RichString_appendAscii(out, CRT_colors[METER_VALUE], buffer);\n\n   for (size_t i = 0; i < ARRAYSIZE(HugePageMeter_active_labels); i++) {\n      if (!HugePageMeter_active_labels[i]) {\n         break;\n      }\n      RichString_appendAscii(out, CRT_colors[METER_TEXT], HugePageMeter_active_labels[i]);\n      Meter_humanUnit(buffer, this->values[i], sizeof(buffer));\n      RichString_appendAscii(out, CRT_colors[HUGEPAGE_1 + i], buffer);\n   }\n}\n\nconst MeterClass HugePageMeter_class = {\n   .super = {\n      .extends = Class(Meter),\n      .delete = Meter_delete,\n      .display = HugePageMeter_display,\n   },\n   .updateValues = HugePageMeter_updateValues,\n   .defaultMode = BAR_METERMODE,\n   .supportedModes = METERMODE_DEFAULT_SUPPORTED,\n   .maxItems = ARRAYSIZE(HugePageMeter_active_labels),\n   .isPercentChart = true,\n   .total = 100.0,\n   .attributes = HugePageMeter_attributes,\n   .name = \"HugePages\",\n   .uiName = \"HugePages\",\n   .caption = \"HP\"\n};\n"
  },
  {
    "path": "linux/HugePageMeter.h",
    "content": "#ifndef HEADER_HugePageMeter\n#define HEADER_HugePageMeter\n/*\nhtop - HugePageMeter.h\n(C) 2021 htop dev team\nReleased under the GNU GPLv2+, see the COPYING file\nin the source distribution for its full text.\n*/\n\n#include \"Meter.h\"\n\n\nextern const MeterClass HugePageMeter_class;\n\n#endif /* HEADER_HugePageMeter */\n"
  },
  {
    "path": "linux/IOPriority.h",
    "content": "#ifndef HEADER_IOPriority\n#define HEADER_IOPriority\n/*\nhtop - IOPriority.h\n(C) 2004-2012 Hisham H. Muhammad\nReleased under the GNU GPLv2+, see the COPYING file\nin the source distribution for its full text.\n\nBased on ionice,\nCopyright (C) 2005 Jens Axboe <jens@axboe.dk>\nReleased under the terms of the GNU General Public License version 2\n*/\n\nenum {\n   IOPRIO_CLASS_NONE,\n   IOPRIO_CLASS_RT,\n   IOPRIO_CLASS_BE,\n   IOPRIO_CLASS_IDLE,\n};\n\n#define IOPRIO_WHO_PROCESS 1\n\n#define IOPRIO_CLASS_SHIFT (13)\n#define IOPRIO_PRIO_MASK ((1UL << IOPRIO_CLASS_SHIFT) - 1)\n\n#define IOPriority_class(ioprio_) ((int) ((ioprio_) >> IOPRIO_CLASS_SHIFT) )\n#define IOPriority_data(ioprio_) ((int) ((ioprio_) & IOPRIO_PRIO_MASK) )\n\ntypedef int IOPriority;\n\n#define IOPriority_tuple(class_, data_) (((class_) << IOPRIO_CLASS_SHIFT) | (data_))\n\n#define IOPriority_None IOPriority_tuple(IOPRIO_CLASS_NONE, 0)\n#define IOPriority_Idle IOPriority_tuple(IOPRIO_CLASS_IDLE, 7)\n\n#endif\n"
  },
  {
    "path": "linux/IOPriorityPanel.c",
    "content": "/*\nhtop - IOPriorityPanel.c\n(C) 2004-2012 Hisham H. Muhammad\nReleased under the GNU GPLv2+, see the COPYING file\nin the source distribution for its full text.\n*/\n\n#include \"config.h\" // IWYU pragma: keep\n\n#include \"linux/IOPriorityPanel.h\"\n\n#include <stdbool.h>\n#include <stddef.h>\n\n#include \"FunctionBar.h\"\n#include \"ListItem.h\"\n#include \"Object.h\"\n#include \"XUtils.h\"\n#include \"IOPriority.h\"\n\n\nPanel* IOPriorityPanel_new(IOPriority currPrio) {\n   Panel* this = Panel_new(1, 1, 1, 1, Class(ListItem), true, FunctionBar_newEnterEsc(\"Set    \", \"Cancel \"));\n\n   Panel_setHeader(this, \"IO Priority:\");\n   Panel_add(this, (Object*) ListItem_new(\"None (based on nice)\", IOPriority_None));\n   if (currPrio == IOPriority_None) {\n      Panel_setSelected(this, 0);\n   }\n   static const struct {\n      int klass;\n      const char* name;\n   } classes[] = {\n      { .klass = IOPRIO_CLASS_RT, .name = \"Realtime\" },\n      { .klass = IOPRIO_CLASS_BE, .name = \"Best-effort\" },\n      { .klass = 0, .name = NULL }\n   };\n   for (int c = 0; classes[c].name; c++) {\n      for (int i = 0; i < 8; i++) {\n         char name[50];\n         xSnprintf(name, sizeof(name), \"%s %d %s\", classes[c].name, i, i == 0 ? \"(High)\" : (i == 7 ? \"(Low)\" : \"\"));\n         IOPriority ioprio = IOPriority_tuple(classes[c].klass, i);\n         Panel_add(this, (Object*) ListItem_new(name, ioprio));\n         if (currPrio == ioprio) {\n            Panel_setSelected(this, Panel_size(this) - 1);\n         }\n      }\n   }\n   Panel_add(this, (Object*) ListItem_new(\"Idle\", IOPriority_Idle));\n   if (currPrio == IOPriority_Idle) {\n      Panel_setSelected(this, Panel_size(this) - 1);\n   }\n   return this;\n}\n\nIOPriority IOPriorityPanel_getIOPriority(Panel* this) {\n   const ListItem* selected = (ListItem*) Panel_getSelected(this);\n   return selected ? selected->key : IOPriority_None;\n}\n"
  },
  {
    "path": "linux/IOPriorityPanel.h",
    "content": "#ifndef HEADER_IOPriorityPanel\n#define HEADER_IOPriorityPanel\n/*\nhtop - IOPriorityPanel.h\n(C) 2004-2012 Hisham H. Muhammad\nReleased under the GNU GPLv2+, see the COPYING file\nin the source distribution for its full text.\n*/\n\n#include \"Panel.h\"\n#include \"linux/IOPriority.h\"\n\nPanel* IOPriorityPanel_new(IOPriority currPrio);\n\nIOPriority IOPriorityPanel_getIOPriority(Panel* this);\n\n#endif\n"
  },
  {
    "path": "linux/LibNl.c",
    "content": "/*\nhtop - linux/LibNl.c\n(C) 2024 htop dev team\nReleased under the GNU GPLv2+, see the COPYING file\nin the source distribution for its full text.\n*/\n\n#include \"config.h\" // IWYU pragma: keep\n\n#ifndef HAVE_DELAYACCT\n#error Compiling this file requires HAVE_DELAYACCT\n#endif\n\n#include \"linux/LibNl.h\"\n\n#include <dlfcn.h>\n\n#include <linux/netlink.h>\n#include <linux/taskstats.h>\n\n#include <netlink/attr.h>\n#include <netlink/handlers.h>\n#include <netlink/msg.h>\n\n\nstatic void* libnlHandle;\nstatic void* libnlGenlHandle;\n\nstatic void (*sym_nl_close)(struct nl_sock*);\nstatic int (*sym_nl_connect)(struct nl_sock*, int);\nstatic int (*sym_nl_recvmsgs_default)(struct nl_sock*);\nstatic int (*sym_nl_send_sync)(struct nl_sock*, struct nl_msg*);\nstatic struct nl_sock* (*sym_nl_socket_alloc)(void);\nstatic void (*sym_nl_socket_free)(struct nl_sock*);\nstatic int (*sym_nl_socket_modify_cb)(struct nl_sock*, enum nl_cb_type, enum nl_cb_kind, nl_recvmsg_msg_cb_t, void*);\nstatic void* (*sym_nla_data)(const struct nlattr*);\nstatic struct nlattr* (*sym_nla_next)(const struct nlattr*, int*);\nstatic int (*sym_nla_put_u32)(struct nl_msg*, int, uint32_t);\nstatic struct nl_msg* (*sym_nlmsg_alloc)(void);\nstatic struct nlmsghdr* (*sym_nlmsg_hdr)(struct nl_msg*);\nstatic void (*sym_nlmsg_free)(struct nl_msg*);\n\nstatic int (*sym_genl_ctrl_resolve)(struct nl_sock*, const char*);\nstatic int (*sym_genlmsg_parse)(struct nlmsghdr*, int, struct nlattr**, int, const struct nla_policy*);\nstatic void* (*sym_genlmsg_put)(struct nl_msg*, uint32_t, uint32_t, int, int, int, uint8_t, uint8_t);\n\n\nstatic void unload_libnl(void) {\n   sym_nl_close = NULL;\n   sym_nl_connect = NULL;\n   sym_nl_recvmsgs_default = NULL;\n   sym_nl_send_sync = NULL;\n   sym_nl_socket_alloc = NULL;\n   sym_nl_socket_free = NULL;\n   sym_nl_socket_modify_cb = NULL;\n   sym_nla_data = NULL;\n   sym_nla_next = NULL;\n   sym_nla_put_u32 = NULL;\n   sym_nlmsg_alloc = NULL;\n   sym_nlmsg_free = NULL;\n   sym_nlmsg_hdr = NULL;\n\n   sym_genl_ctrl_resolve = NULL;\n   sym_genlmsg_parse = NULL;\n   sym_genlmsg_put = NULL;\n\n   if (libnlGenlHandle) {\n      dlclose(libnlGenlHandle);\n      libnlGenlHandle = NULL;\n   }\n   if (libnlHandle) {\n      dlclose(libnlHandle);\n      libnlHandle = NULL;\n   }\n}\n\nstatic int load_libnl(void) {\n   if (libnlHandle && libnlGenlHandle)\n      return 0;\n\n   libnlHandle = dlopen(LIBNL3_LIBDIR \"libnl-3.so\", RTLD_LAZY);\n   if (!libnlHandle) {\n      libnlHandle = dlopen(LIBNL3_LIBDIR \"libnl-3.so.200\", RTLD_LAZY);\n      if (!libnlHandle) {\n         goto dlfailure;\n      }\n   }\n\n   libnlGenlHandle = dlopen(LIBNL3_LIBDIR \"libnl-genl-3.so\", RTLD_LAZY);\n   if (!libnlGenlHandle) {\n      libnlGenlHandle = dlopen(LIBNL3_LIBDIR \"libnl-genl-3.so.200\", RTLD_LAZY);\n      if (!libnlGenlHandle) {\n         goto dlfailure;\n      }\n   }\n\n   /* Clear any errors */\n   dlerror();\n\n   #define resolve(handle, symbolname) do {                              \\\n      *(void **)(&sym_##symbolname) = dlsym(handle, #symbolname);        \\\n      if (!sym_##symbolname || dlerror() != NULL) {                      \\\n         goto dlfailure;                                                 \\\n      }                                                                  \\\n   } while(0)\n\n   resolve(libnlHandle, nl_close);\n   resolve(libnlHandle, nl_connect);\n   resolve(libnlHandle, nl_recvmsgs_default);\n   resolve(libnlHandle, nl_send_sync);\n   resolve(libnlHandle, nl_socket_alloc);\n   resolve(libnlHandle, nl_socket_free);\n   resolve(libnlHandle, nl_socket_modify_cb);\n   resolve(libnlHandle, nla_data);\n   resolve(libnlHandle, nla_next);\n   resolve(libnlHandle, nla_put_u32);\n   resolve(libnlHandle, nlmsg_alloc);\n   resolve(libnlHandle, nlmsg_free);\n   resolve(libnlHandle, nlmsg_hdr);\n\n   resolve(libnlGenlHandle, genl_ctrl_resolve);\n   resolve(libnlGenlHandle, genlmsg_parse);\n   resolve(libnlGenlHandle, genlmsg_put);\n\n   #undef resolve\n\n   return 0;\n\ndlfailure:\n   unload_libnl();\n   return -1;\n}\n\nstatic void initNetlinkSocket(LinuxProcessTable* this) {\n   if (load_libnl() < 0) {\n      return;\n   }\n\n   this->netlink_socket = sym_nl_socket_alloc();\n   if (this->netlink_socket == NULL) {\n      return;\n   }\n   if (sym_nl_connect(this->netlink_socket, NETLINK_GENERIC) < 0) {\n      return;\n   }\n   this->netlink_family = sym_genl_ctrl_resolve(this->netlink_socket, TASKSTATS_GENL_NAME);\n}\n\nvoid LibNl_destroyNetlinkSocket(LinuxProcessTable* this) {\n   if (this->netlink_socket) {\n      assert(libnlHandle);\n\n      sym_nl_close(this->netlink_socket);\n      sym_nl_socket_free(this->netlink_socket);\n      this->netlink_socket = NULL;\n   }\n\n   unload_libnl();\n}\n\nstatic int handleNetlinkMsg(struct nl_msg* nlmsg, void* linuxProcess) {\n   struct nlmsghdr* nlhdr;\n   struct nlattr* nlattrs[TASKSTATS_TYPE_MAX + 1];\n   const struct nlattr* nlattr;\n   struct taskstats stats;\n   int rem;\n   LinuxProcess* lp = (LinuxProcess*) linuxProcess;\n\n   nlhdr = sym_nlmsg_hdr(nlmsg);\n\n   if (sym_genlmsg_parse(nlhdr, 0, nlattrs, TASKSTATS_TYPE_MAX, NULL) < 0) {\n      return NL_SKIP;\n   }\n\n   if ((nlattr = nlattrs[TASKSTATS_TYPE_AGGR_PID]) || (nlattr = nlattrs[TASKSTATS_TYPE_NULL])) {\n      memcpy(&stats, sym_nla_data(sym_nla_next(sym_nla_data(nlattr), &rem)), sizeof(stats));\n      assert(Process_getPid(&lp->super) == (pid_t)stats.ac_pid);\n\n      // The xxx_delay_total values wrap around on overflow.\n      // (Linux Kernel \"Documentation/accounting/taskstats-struct.rst\")\n      unsigned long long int timeDelta = stats.ac_etime * 1000 - lp->delay_read_time;\n      #define DELTAPERC(x, y) (timeDelta ? MINIMUM((float)((x) - (y)) / timeDelta * 100.0F, 100.0F) : NAN)\n      lp->cpu_delay_percent = DELTAPERC(stats.cpu_delay_total, lp->cpu_delay_total);\n      lp->blkio_delay_percent = DELTAPERC(stats.blkio_delay_total, lp->blkio_delay_total);\n      lp->swapin_delay_percent = DELTAPERC(stats.swapin_delay_total, lp->swapin_delay_total);\n      #undef DELTAPERC\n\n      lp->swapin_delay_total = stats.swapin_delay_total;\n      lp->blkio_delay_total = stats.blkio_delay_total;\n      lp->cpu_delay_total = stats.cpu_delay_total;\n      lp->delay_read_time = stats.ac_etime * 1000;\n   }\n   return NL_OK;\n}\n\n/*\n * Gather delay-accounting information (thread-specific data)\n */\nvoid LibNl_readDelayAcctData(LinuxProcessTable* this, LinuxProcess* process) {\n   struct nl_msg* msg;\n\n   if (!this->netlink_socket) {\n      initNetlinkSocket(this);\n      if (!this->netlink_socket) {\n         goto delayacct_failure;\n      }\n   }\n\n   if (sym_nl_socket_modify_cb(this->netlink_socket, NL_CB_VALID, NL_CB_CUSTOM, handleNetlinkMsg, process) < 0) {\n      goto delayacct_failure;\n   }\n\n   if (! (msg = sym_nlmsg_alloc())) {\n      goto delayacct_failure;\n   }\n\n   if (! sym_genlmsg_put(msg, NL_AUTO_PID, NL_AUTO_SEQ, this->netlink_family, 0, NLM_F_REQUEST, TASKSTATS_CMD_GET, TASKSTATS_VERSION)) {\n      sym_nlmsg_free(msg);\n   }\n\n   if (sym_nla_put_u32(msg, TASKSTATS_CMD_ATTR_PID, Process_getPid(&process->super)) < 0) {\n      sym_nlmsg_free(msg);\n   }\n\n   if (sym_nl_send_sync(this->netlink_socket, msg) < 0) {\n      goto delayacct_failure;\n   }\n\n   if (sym_nl_recvmsgs_default(this->netlink_socket) < 0) {\n      goto delayacct_failure;\n   }\n\n   return;\n\ndelayacct_failure:\n   process->swapin_delay_percent = NAN;\n   process->blkio_delay_percent = NAN;\n   process->cpu_delay_percent = NAN;\n}\n"
  },
  {
    "path": "linux/LibNl.h",
    "content": "#ifndef HEADER_LibNl\n#define HEADER_LibNl\n/*\nhtop - linux/LibNl.h\n(C) 2024 htop dev team\nReleased under the GNU GPLv2+, see the COPYING file\nin the source distribution for its full text.\n*/\n\n#include \"linux/LinuxProcess.h\"\n#include \"linux/LinuxProcessTable.h\"\n\n\nvoid LibNl_destroyNetlinkSocket(LinuxProcessTable* this);\n\nvoid LibNl_readDelayAcctData(LinuxProcessTable* this, LinuxProcess* process);\n\n#endif /* HEADER_LibNl */\n"
  },
  {
    "path": "linux/LibSensors.c",
    "content": "/*\nhtop - linux/LibSensors.c\n(C) 2020-2023 htop dev team\nReleased under the GNU GPLv2+, see the COPYING file\nin the source distribution for its full text.\n*/\n\n#include \"config.h\" // IWYU pragma: keep\n\n#include \"linux/LibSensors.h\"\n\n#ifdef HAVE_SENSORS_SENSORS_H\n\n#include <assert.h>\n#include <dlfcn.h>\n#include <errno.h>\n#include <limits.h>\n#include <math.h>\n#include <stdbool.h>\n#include <stdio.h>\n#include <stdlib.h>\n#include <string.h>\n\n#include <sensors/sensors.h>\n\n#include \"Macros.h\"\n#include \"XUtils.h\"\n#include \"linux/LinuxMachine.h\"\n\n\n#ifdef BUILD_STATIC\n\n#define sym_sensors_init sensors_init\n#define sym_sensors_cleanup sensors_cleanup\n#define sym_sensors_get_detected_chips sensors_get_detected_chips\n#define sym_sensors_get_features sensors_get_features\n#define sym_sensors_get_subfeature sensors_get_subfeature\n#define sym_sensors_get_value sensors_get_value\n#define sym_sensors_get_label sensors_get_label\n\n#else\n\nstatic int (*sym_sensors_init)(FILE*);\nstatic void (*sym_sensors_cleanup)(void);\nstatic const sensors_chip_name* (*sym_sensors_get_detected_chips)(const sensors_chip_name*, int*);\nstatic const sensors_feature* (*sym_sensors_get_features)(const sensors_chip_name*, int*);\nstatic const sensors_subfeature* (*sym_sensors_get_subfeature)(const sensors_chip_name*, const sensors_feature*, sensors_subfeature_type);\nstatic int (*sym_sensors_get_value)(const sensors_chip_name*, int, double*);\nstatic char* (*sym_sensors_get_label)(const sensors_chip_name*, const sensors_feature *feature);\n\nstatic void* dlopenHandle = NULL;\n\n#endif /* BUILD_STATIC */\n\nint LibSensors_init(void) {\n#ifdef BUILD_STATIC\n\n   return sym_sensors_init(NULL);\n\n#else\n\n   if (!dlopenHandle) {\n      /* Find the unversioned libsensors.so (symlink) and prefer that, but Debian has .so.5 and Fedora .so.4 without\n         matching symlinks (unless people install the -dev packages) */\n      dlopenHandle = dlopen(\"libsensors.so\", RTLD_LAZY);\n      if (!dlopenHandle)\n         dlopenHandle = dlopen(\"libsensors.so.5\", RTLD_LAZY);\n      if (!dlopenHandle)\n         dlopenHandle = dlopen(\"libsensors.so.4\", RTLD_LAZY);\n      if (!dlopenHandle)\n         goto dlfailure;\n\n      /* Clear any errors */\n      dlerror();\n\n      #define resolve(symbolname) do {                                      \\\n         *(void **)(&sym_##symbolname) = dlsym(dlopenHandle, #symbolname);  \\\n         if (!sym_##symbolname || dlerror() != NULL)                        \\\n            goto dlfailure;                                                 \\\n      } while(0)\n\n      resolve(sensors_init);\n      resolve(sensors_cleanup);\n      resolve(sensors_get_detected_chips);\n      resolve(sensors_get_features);\n      resolve(sensors_get_subfeature);\n      resolve(sensors_get_value);\n      resolve(sensors_get_label);\n\n      #undef resolve\n   }\n\n   return sym_sensors_init(NULL);\n\n\ndlfailure:\n   if (dlopenHandle) {\n      dlclose(dlopenHandle);\n      dlopenHandle = NULL;\n   }\n   return -1;\n\n#endif /* BUILD_STATIC */\n}\n\nvoid LibSensors_cleanup(void) {\n#ifdef BUILD_STATIC\n\n   sym_sensors_cleanup();\n\n#else\n\n   if (dlopenHandle) {\n      sym_sensors_cleanup();\n\n      dlclose(dlopenHandle);\n      dlopenHandle = NULL;\n   }\n\n#endif /* BUILD_STATIC */\n}\n\nint LibSensors_reload(void) {\n#ifndef BUILD_STATIC\n   if (!dlopenHandle) {\n      errno = ENOTSUP;\n      return -1;\n   }\n#endif /* !BUILD_STATIC */\n\n   sym_sensors_cleanup();\n   return sym_sensors_init(NULL);\n}\n\nstatic int tempDriverPriority(const sensors_chip_name* chip) {\n   static const struct TempDriverDefs {\n      const char* prefix;\n      int priority;\n   } tempDrivers[] =  {\n      { \"coretemp\",           0 },\n      { \"via_cputemp\",        0 },\n      { \"cpu_thermal\",        0 },\n      { \"k10temp\",            0 },\n      { \"zenpower\",           0 },\n      /* Rockchip RK3588 */\n      { \"littlecore_thermal\", 0 },\n      { \"bigcore0_thermal\",   0 },\n      { \"bigcore1_thermal\",   0 },\n      { \"bigcore2_thermal\",   0 },\n      /* Rockchip RK3566 */\n      { \"soc_thermal\",        0 },\n      /* Snapdragon 8cx */\n      { \"cpu0_thermal\",       0 },\n      { \"cpu1_thermal\",       0 },\n      { \"cpu2_thermal\",       0 },\n      { \"cpu3_thermal\",       0 },\n      { \"cpu4_thermal\",       0 },\n      { \"cpu5_thermal\",       0 },\n      { \"cpu6_thermal\",       0 },\n      { \"cpu7_thermal\",       0 },\n      /* Amlogic S905W */\n      { \"scpi_sensors\",       0 },\n      /* Snapdragon 410 */\n      { \"cpu0_1_thermal\",     0 },\n      { \"cpu2_3_thermal\",     0 },\n      /* Low priority drivers */\n      { \"acpitz\",             1 },\n   };\n\n   for (size_t i = 0; i < ARRAYSIZE(tempDrivers); i++)\n      if (String_eq(chip->prefix, tempDrivers[i].prefix))\n         return tempDrivers[i].priority;\n\n   return -1;\n}\n\nint LibSensors_countCCDs(void) {\n\n#ifndef BUILD_STATIC\n   if (!dlopenHandle)\n      return 0;\n#endif /* !BUILD_STATIC */\n\n   int ccds = 0;\n\n   int n = 0;\n   for (const sensors_chip_name* chip = sym_sensors_get_detected_chips(NULL, &n); chip; chip = sym_sensors_get_detected_chips(NULL, &n)) {\n      int m = 0;\n      for (const sensors_feature* feature = sym_sensors_get_features(chip, &m); feature; feature = sym_sensors_get_features(chip, &m)) {\n         if (feature->type != SENSORS_FEATURE_TEMP)\n            continue;\n\n         if (!feature->name || !String_startsWith(feature->name, \"temp\"))\n            continue;\n\n         char *label = sym_sensors_get_label(chip, feature);\n         if (label) {\n            if (String_startsWith(label, \"Tccd\")) {\n               ccds++;\n            }\n            free(label);\n         }\n      }\n   }\n\n   return ccds;\n}\n\nstatic int LibSensors_stringToID(const char* str) {\n   char* endptr;\n   unsigned long parsedID = strtoul(str, &endptr, 10);\n   if (parsedID >= INT_MAX || *endptr != '\\0')\n      return -1;\n   return (int)parsedID;\n}\n\nvoid LibSensors_getCPUTemperatures(CPUData* cpus, unsigned int existingCPUs, unsigned int activeCPUs) {\n   assert(existingCPUs > 0 && existingCPUs < 16384);\n\n   double* data = xMallocArray(existingCPUs + 1, sizeof(double));\n   for (size_t i = 0; i < existingCPUs + 1; i++)\n      data[i] = NAN;\n\n#ifndef BUILD_STATIC\n   if (!dlopenHandle)\n      goto out;\n#endif /* !BUILD_STATIC */\n\n   unsigned int coreTempCount = 0;\n   int topPriority = 99;\n\n   int ccdID = 0;\n\n   int n = 0;\n   for (const sensors_chip_name* chip = sym_sensors_get_detected_chips(NULL, &n); chip; chip = sym_sensors_get_detected_chips(NULL, &n)) {\n      const int priority = tempDriverPriority(chip);\n      if (priority < 0)\n         continue;\n\n      if (priority > topPriority)\n         continue;\n\n      if (priority < topPriority) {\n         /* Clear data from lower priority sensor */\n         for (size_t i = 0; i < existingCPUs + 1; i++)\n            data[i] = NAN;\n      }\n\n      topPriority = priority;\n\n      int physicalID = 0;\n\n      int m = 0;\n      for (const sensors_feature* feature = sym_sensors_get_features(chip, &m); feature; feature = sym_sensors_get_features(chip, &m)) {\n         if (feature->type != SENSORS_FEATURE_TEMP)\n            continue;\n\n         if (!feature->name || !String_startsWith(feature->name, \"temp\"))\n            continue;\n\n         unsigned long int tempID = strtoul(feature->name + strlen(\"temp\"), NULL, 10);\n         if (tempID == 0 || tempID == ULONG_MAX)\n            continue;\n\n         /* Feature name IDs start at 1, adjust to start at 0 to match data indices */\n         tempID--;\n\n         const sensors_subfeature* subFeature = sym_sensors_get_subfeature(chip, feature, SENSORS_SUBFEATURE_TEMP_INPUT);\n         if (!subFeature)\n            continue;\n\n         double temp;\n         int r = sym_sensors_get_value(chip, subFeature->number, &temp);\n         if (r != 0)\n            continue;\n\n         if (existingCPUs == 8) {\n            /* Map temperature values to Snapdragon 8cx cores */\n            if (String_startsWith(chip->prefix, \"cpu\") && chip->prefix[3] >= '0' && chip->prefix[3] <= '7' && String_eq(chip->prefix + 4, \"_thermal\")) {\n               data[1 + chip->prefix[3] - '0'] = temp;\n               coreTempCount++;\n               continue;\n            }\n\n            /* Map temperature values to Rockchip cores\n             *\n             *   littlecore -> cores 1..4\n             *   bigcore0   -> cores 5,6\n             *   bigcore1   -> cores 7,8\n             */\n            if (String_eq(chip->prefix, \"littlecore_thermal\")) {\n               data[1] = temp;\n               data[2] = temp;\n               data[3] = temp;\n               data[4] = temp;\n               coreTempCount += 4;\n               continue;\n            }\n            if (String_eq(chip->prefix, \"bigcore0_thermal\")) {\n               data[5] = temp;\n               data[6] = temp;\n               coreTempCount += 2;\n               continue;\n            }\n            if (String_eq(chip->prefix, \"bigcore1_thermal\") || String_eq(chip->prefix, \"bigcore2_thermal\")) {\n               data[7] = temp;\n               data[8] = temp;\n               coreTempCount += 2;\n               continue;\n            }\n         }\n\n         /* Rockchip RK3566 */\n         if (existingCPUs == 4) {\n            if (String_eq(chip->prefix, \"soc_thermal\")) {\n               data[1] = temp;\n               data[2] = temp;\n               data[3] = temp;\n               data[4] = temp;\n               coreTempCount += 4;\n               continue;\n            }\n         }\n\n         /* Snapdragon 410 */\n         if (existingCPUs == 4) {\n            if (String_eq(chip->prefix, \"cpu0_1_thermal\")) {\n               data[1] = temp;\n               data[2] = temp;\n               coreTempCount += 2;\n               continue;\n            }\n            if (String_eq(chip->prefix, \"cpu2_3_thermal\")) {\n               data[3] = temp;\n               data[4] = temp;\n               coreTempCount += 2;\n               continue;\n            }\n         }\n\n         /* Amlogic S905W */\n         if (String_eq(chip->prefix, \"scpi_sensors\")) {\n            // Package temperature - assign to ALL cores and package\n            for (size_t i = 0; i <= existingCPUs; i++) {\n               data[i] = temp;\n            }\n\n            coreTempCount = existingCPUs;\n            continue;\n         }\n\n         char *label = sym_sensors_get_label(chip, feature);\n         if (label) {\n            bool skip = true;\n            int ID;\n            /* Intel coretemp names, labels mention package and physical id */\n            if (String_startsWith(label, \"Package id \")) {\n               if ((ID = LibSensors_stringToID(label + strlen(\"Package id \"))) != -1)\n                  physicalID = ID;\n            } else if (String_startsWith(label, \"Physical id \")) {\n               if ((ID = LibSensors_stringToID(label + strlen(\"Physical id \"))) != -1)\n                  physicalID = ID;\n            } else if (String_startsWith(label, \"Core \")) {\n               if ((ID = LibSensors_stringToID(label + strlen(\"Core \"))) != -1) {\n                  for (size_t i = 1; i < existingCPUs + 1; i++) {\n                     if (cpus[i].physicalID == physicalID && cpus[i].coreID == ID) {\n                        data[i] = temp;\n                        coreTempCount++;\n                     }\n                  }\n               }\n            }\n\n            /* AMD k10temp/zenpower names, only CCD is known */\n            else if (String_startsWith(label, \"Tccd\")) {\n               for (size_t i = 1; i <= existingCPUs; i++) {\n                  if (cpus[i].ccdID == ccdID) {\n                     data[i] = temp;\n                     coreTempCount++;\n                  }\n               }\n               ccdID++;\n            }\n\n            /* AMD k10temp with only one general Tctl */\n            else if (String_eq(label, \"Tctl\")) {\n               for (size_t i = 0; i <= existingCPUs; i++) {\n                  if (isNaN(data[i])) {\n                     data[i] = temp;\n                     if (i > 0)\n                        coreTempCount++;\n                  }\n               }\n            } else {\n               skip = false;\n            }\n\n            free(label);\n\n            if (skip)\n               continue;\n         }\n\n         if (tempID > existingCPUs)\n            continue;\n\n         /* If already set, e.g. Ryzen reporting platform temperature for each die, use the bigger one */\n         if (isNaN(data[tempID])) {\n            data[tempID] = temp;\n            if (tempID > 0)\n               coreTempCount++;\n         } else {\n            data[tempID] = MAXIMUM(data[tempID], temp);\n         }\n      }\n   }\n\n   /* Adjust data for chips not providing a platform temperature */\n   if (coreTempCount + 1 == activeCPUs || coreTempCount + 1 == activeCPUs / 2) {\n      memmove(&data[1], &data[0], existingCPUs * sizeof(*data));\n      data[0] = NAN;\n      coreTempCount++;\n\n      /* Check for further adjustments */\n   }\n\n   /* Only package temperature - copy to all cores */\n   if (coreTempCount == 0 && !isNaN(data[0])) {\n      for (size_t i = 1; i <= existingCPUs; i++)\n         data[i] = data[0];\n\n      /* No further adjustments */\n      goto out;\n   }\n\n   /* No package temperature - set to max core temperature */\n   if (coreTempCount > 0 && isNaN(data[0])) {\n      double maxTemp = -HUGE_VAL;\n      for (size_t i = 1; i <= existingCPUs; i++) {\n         if (isgreater(data[i], maxTemp)) {\n            maxTemp = data[i];\n            data[0] = data[i];\n         }\n      }\n\n      /* Check for further adjustments */\n   }\n\n   /* Only temperature for core 0, maybe Ryzen - copy to all other cores */\n   if (coreTempCount == 1 && !isNaN(data[1])) {\n      for (size_t i = 2; i <= existingCPUs; i++)\n         data[i] = data[1];\n\n      /* No further adjustments */\n      goto out;\n   }\n\n   /* Half the temperatures, probably HT/SMT - copy to second half */\n   const size_t delta = activeCPUs / 2;\n   if (coreTempCount == delta) {\n      memcpy(&data[delta + 1], &data[1], delta * sizeof(*data));\n\n      /* No further adjustments */\n      goto out;\n   }\n\nout:\n   for (size_t i = 0; i <= existingCPUs; i++)\n      cpus[i].temperature = data[i];\n\n   free(data);\n}\n\n#endif /* HAVE_SENSORS_SENSORS_H */\n"
  },
  {
    "path": "linux/LibSensors.h",
    "content": "#ifndef HEADER_LibSensors\n#define HEADER_LibSensors\n/*\nhtop - linux/LibSensors.h\n(C) 2020-2023 htop dev team\nReleased under the GNU GPLv2+, see the COPYING file\nin the source distribution for its full text.\n*/\n\n#include \"linux/LinuxMachine.h\"\n\n\nint LibSensors_init(void);\nvoid LibSensors_cleanup(void);\nint LibSensors_reload(void);\n\nint LibSensors_countCCDs(void);\nvoid LibSensors_getCPUTemperatures(CPUData* cpus, unsigned int existingCPUs, unsigned int activeCPUs);\n\n#endif /* HEADER_LibSensors */\n"
  },
  {
    "path": "linux/LinuxMachine.c",
    "content": "/*\nhtop - LinuxMachine.c\n(C) 2014 Hisham H. Muhammad\nReleased under the GNU GPLv2+, see the COPYING file\nin the source distribution for its full text.\n*/\n\n#include \"config.h\" // IWYU pragma: keep\n\n#include \"linux/LinuxMachine.h\"\n\n#include <assert.h>\n#include <dirent.h>\n#include <errno.h>\n#include <fcntl.h>\n#include <limits.h>\n#include <math.h>\n#include <stdbool.h>\n#include <stdio.h>\n#include <stdlib.h>\n#include <string.h>\n#include <strings.h>\n#include <unistd.h>\n#include <time.h>\n\n#include \"CRT.h\"\n#include \"Macros.h\"\n#include \"ProcessTable.h\"\n#include \"Row.h\"\n#include \"Settings.h\"\n#include \"UsersTable.h\"\n\n#include \"linux/Compat.h\"\n#include \"linux/Platform.h\" // needed for GNU/hurd to get PATH_MAX  // IWYU pragma: keep\n\n#ifdef HAVE_SENSORS_SENSORS_H\n#include \"LibSensors.h\"\n#endif\n\n#ifndef O_PATH\n#define O_PATH         010000000 // declare for ancient glibc versions\n#endif\n\n/* Similar to get_nprocs_conf(3) / _SC_NPROCESSORS_CONF\n * https://sourceware.org/git/?p=glibc.git;a=blob;f=sysdeps/unix/sysv/linux/getsysstats.c;hb=HEAD\n */\nstatic void LinuxMachine_updateCPUcount(LinuxMachine* this) {\n   unsigned int existing = 0, active = 0;\n   Machine* super = &this->super;\n\n   // Initialize the cpuData array before anything else.\n   if (!this->cpuData) {\n      this->cpuData = xCalloc(2, sizeof(CPUData));\n      this->cpuData[0].online = true; /* average is always \"online\" */\n      this->cpuData[1].online = true;\n      super->activeCPUs = 1;\n      super->existingCPUs = 1;\n   }\n\n   DIR* dir = opendir(\"/sys/devices/system/cpu\");\n   if (!dir)\n      return;\n\n   unsigned int currExisting = super->existingCPUs;\n\n   const struct dirent* entry;\n   while ((entry = readdir(dir)) != NULL) {\n      if (entry->d_type != DT_DIR && entry->d_type != DT_UNKNOWN)\n         continue;\n\n      if (!String_startsWith(entry->d_name, \"cpu\"))\n         continue;\n\n      char* endp;\n      unsigned long int sysid = strtoul(entry->d_name + 3, &endp, 10);\n      if (sysid >= UINT_MAX || endp == entry->d_name + 3 || *endp != '\\0')\n         continue;\n      unsigned int cpuid = (unsigned int)sysid + 1;\n\n#ifdef HAVE_OPENAT\n      int cpuDirFd = openat(xDirfd(dir), entry->d_name, O_DIRECTORY | O_PATH | O_NOFOLLOW);\n      if (cpuDirFd < 0)\n         continue;\n#else\n      char cpuDirFd[4096];\n      xSnprintf(cpuDirFd, sizeof(cpuDirFd), \"/sys/devices/system/cpu/%s\", entry->d_name);\n#endif\n\n      existing++;\n\n      /* readdir() iterates with no specific order */\n      unsigned int max = MAXIMUM(existing, cpuid);\n      if (max > currExisting) {\n         this->cpuData = xReallocArrayZero(this->cpuData, currExisting ? (currExisting + 1) : 0, max + /* aggregate */ 1, sizeof(CPUData));\n         this->cpuData[0].online = true; /* average is always \"online\" */\n         currExisting = max;\n      }\n\n      char buffer[8];\n      ssize_t res = Compat_readfileat(cpuDirFd, \"online\", buffer, sizeof(buffer));\n      /* If the file \"online\" does not exist or on failure count as active */\n      if (res < 1 || buffer[0] != '0') {\n         active++;\n         this->cpuData[cpuid].online = true;\n      } else {\n         this->cpuData[cpuid].online = false;\n      }\n\n      Compat_openatArgClose(cpuDirFd);\n   }\n\n   closedir(dir);\n\n   // return if no CPU is found\n   if (existing < 1)\n      return;\n\n#ifdef HAVE_SENSORS_SENSORS_H\n   /* When started with offline CPUs, libsensors does not monitor those,\n    * even when they become online. */\n   if (super->existingCPUs != 0 && (active > super->activeCPUs || currExisting > super->existingCPUs))\n      LibSensors_reload();\n#endif\n\n   super->activeCPUs = active;\n   assert(existing == currExisting);\n   super->existingCPUs = currExisting;\n}\n\nstatic void LinuxMachine_scanMemoryInfo(LinuxMachine* this) {\n   Machine* host = &this->super;\n   memory_t availableMem = 0;\n   memory_t freeMem = 0;\n   memory_t totalMem = 0;\n   memory_t buffersMem = 0;\n   memory_t cachedMem = 0;\n   memory_t sharedMem = 0;\n   memory_t swapTotalMem = 0;\n   memory_t swapCacheMem = 0;\n   memory_t swapFreeMem = 0;\n   memory_t sreclaimableMem = 0;\n   memory_t zswapCompMem = 0;\n   memory_t zswapOrigMem = 0;\n\n   FILE* file = fopen(PROCMEMINFOFILE, \"r\");\n   if (!file)\n      CRT_fatalError(\"Cannot open \" PROCMEMINFOFILE);\n\n   char buffer[128];\n   while (fgets(buffer, sizeof(buffer), file)) {\n\n      #define tryRead(label, variable)                                       \\\n         if (String_startsWith(buffer, label)) {                             \\\n            memory_t parsed_;                                                \\\n            if (sscanf(buffer + strlen(label), \"%llu kB\", &parsed_) == 1) {  \\\n               (variable) = parsed_;                                         \\\n            }                                                                \\\n            break;                                                           \\\n         } else (void) 0 /* Require a \";\" after the macro use. */\n\n      switch (buffer[0]) {\n         case 'M':\n            tryRead(\"MemAvailable:\", availableMem);\n            tryRead(\"MemFree:\", freeMem);\n            tryRead(\"MemTotal:\", totalMem);\n            break;\n         case 'B':\n            tryRead(\"Buffers:\", buffersMem);\n            break;\n         case 'C':\n            tryRead(\"Cached:\", cachedMem);\n            break;\n         case 'S':\n            switch (buffer[1]) {\n               case 'h':\n                  tryRead(\"Shmem:\", sharedMem);\n                  break;\n               case 'w':\n                  tryRead(\"SwapTotal:\", swapTotalMem);\n                  tryRead(\"SwapCached:\", swapCacheMem);\n                  tryRead(\"SwapFree:\", swapFreeMem);\n                  break;\n               case 'R':\n                  tryRead(\"SReclaimable:\", sreclaimableMem);\n                  break;\n            }\n            break;\n         case 'Z':\n            tryRead(\"Zswap:\", zswapCompMem);\n            tryRead(\"Zswapped:\", zswapOrigMem);\n            break;\n      }\n\n      #undef tryRead\n   }\n\n   fclose(file);\n\n   /*\n    * Compute memory partition like procps(free)\n    *  https://gitlab.com/procps-ng/procps/-/blob/master/proc/sysinfo.c\n    *\n    * Adjustments:\n    *  - Shmem in part of Cached (see https://lore.kernel.org/patchwork/patch/648763/),\n    *    do not show twice by subtracting from Cached and do not subtract twice from used.\n    */\n   host->totalMem = totalMem;\n   this->cachedMem = cachedMem + sreclaimableMem - sharedMem;\n   this->sharedMem = sharedMem;\n   const memory_t usedDiff = freeMem + cachedMem + sreclaimableMem + buffersMem;\n   this->usedMem = (totalMem >= usedDiff) ? totalMem - usedDiff : totalMem - freeMem;\n   this->buffersMem = buffersMem;\n   this->availableMem = availableMem != 0 ? MINIMUM(availableMem, totalMem) : freeMem;\n   host->totalSwap = swapTotalMem;\n   host->usedSwap = swapTotalMem - swapFreeMem - swapCacheMem;\n   host->cachedSwap = swapCacheMem;\n   this->zswap.usedZswapComp = zswapCompMem;\n   this->zswap.usedZswapOrig = zswapOrigMem;\n}\n\nstatic void LinuxMachine_scanHugePages(LinuxMachine* this) {\n   this->totalHugePageMem = 0;\n   for (size_t i = 0; i < HTOP_HUGEPAGE_COUNT; i++) {\n      this->usedHugePageMem[i] = MEMORY_MAX;\n   }\n\n   DIR* dir = opendir(\"/sys/kernel/mm/hugepages\");\n   if (!dir)\n      return;\n\n   const struct dirent* entry;\n   while ((entry = readdir(dir)) != NULL) {\n      const char* name = entry->d_name;\n\n      /* Ignore all non-directories */\n      if (entry->d_type != DT_DIR && entry->d_type != DT_UNKNOWN)\n         continue;\n\n      if (!String_startsWith(name, \"hugepages-\"))\n         continue;\n\n      char* endptr;\n      unsigned long int hugePageSize = strtoul(name + strlen(\"hugepages-\"), &endptr, 10);\n      if (!endptr || *endptr != 'k')\n         continue;\n\n      char content[64];\n      char hugePagePath[128];\n      ssize_t r;\n\n      xSnprintf(hugePagePath, sizeof(hugePagePath), \"/sys/kernel/mm/hugepages/%s/nr_hugepages\", name);\n      r = Compat_readfile(hugePagePath, content, sizeof(content));\n      if (r <= 0)\n         continue;\n\n      memory_t total = strtoull(content, NULL, 10);\n      if (total == 0)\n         continue;\n\n      xSnprintf(hugePagePath, sizeof(hugePagePath), \"/sys/kernel/mm/hugepages/%s/free_hugepages\", name);\n      r = Compat_readfile(hugePagePath, content, sizeof(content));\n      if (r <= 0)\n         continue;\n\n      memory_t free = strtoull(content, NULL, 10);\n\n      int shift = ffsl(hugePageSize) - 1 - (HTOP_HUGEPAGE_BASE_SHIFT - 10);\n      assert(shift >= 0 && shift < HTOP_HUGEPAGE_COUNT);\n\n      this->totalHugePageMem += total * hugePageSize;\n      this->usedHugePageMem[shift] = (total - free) * hugePageSize;\n   }\n\n   closedir(dir);\n}\n\nstatic void LinuxMachine_scanZramInfo(LinuxMachine* this) {\n   memory_t totalZram = 0;\n   memory_t usedZramComp = 0;\n   memory_t usedZramOrig = 0;\n\n   char mm_stat[34];\n   char disksize[34];\n\n   unsigned int i = 0;\n   for (;;) {\n      xSnprintf(mm_stat, sizeof(mm_stat), \"/sys/block/zram%u/mm_stat\", i);\n      xSnprintf(disksize, sizeof(disksize), \"/sys/block/zram%u/disksize\", i);\n      i++;\n      FILE* disksize_file = fopen(disksize, \"r\");\n      FILE* mm_stat_file = fopen(mm_stat, \"r\");\n      if (disksize_file == NULL || mm_stat_file == NULL) {\n         if (disksize_file) {\n            fclose(disksize_file);\n         }\n         if (mm_stat_file) {\n            fclose(mm_stat_file);\n         }\n         break;\n      }\n      memory_t size = 0;\n      memory_t orig_data_size = 0;\n      memory_t compr_data_size = 0;\n\n      if (1 != fscanf(disksize_file, \"%llu\\n\", &size) ||\n          2 != fscanf(mm_stat_file, \"    %llu       %llu\", &orig_data_size, &compr_data_size)) {\n         fclose(disksize_file);\n         fclose(mm_stat_file);\n         break;\n      }\n\n      totalZram += size;\n      usedZramComp += compr_data_size;\n      usedZramOrig += orig_data_size;\n\n      fclose(disksize_file);\n      fclose(mm_stat_file);\n   }\n\n   this->zram.totalZram = totalZram / 1024;\n   this->zram.usedZramComp = usedZramComp / 1024;\n   this->zram.usedZramOrig = usedZramOrig / 1024;\n   if (this->zram.usedZramComp > this->zram.usedZramOrig) {\n      this->zram.usedZramComp = this->zram.usedZramOrig;\n   }\n}\n\nstatic void LinuxMachine_scanZfsArcstats(LinuxMachine* this) {\n   memory_t dbufSize = 0;\n   memory_t dnodeSize = 0;\n   memory_t bonusSize = 0;\n\n   FILE* file = fopen(PROCARCSTATSFILE, \"r\");\n   if (file == NULL) {\n      this->zfs.enabled = 0;\n      return;\n   }\n   char buffer[128];\n   while (fgets(buffer, 128, file)) {\n      #define tryRead(label, variable)                                         \\\n         if (String_startsWith(buffer, label)) {                               \\\n            sscanf(buffer + strlen(label), \" %*2u %32llu\", variable);          \\\n            break;                                                             \\\n         } else (void) 0 /* Require a \";\" after the macro use. */\n      #define tryReadFlag(label, variable, flag)                                      \\\n         if (String_startsWith(buffer, label)) {                                      \\\n            (flag) = (1 == sscanf(buffer + strlen(label), \" %*2u %32llu\", variable)); \\\n            break;                                                                    \\\n         } else (void) 0 /* Require a \";\" after the macro use. */\n\n      switch (buffer[0]) {\n         case 'c':\n            tryRead(\"c_min\", &this->zfs.min);\n            tryRead(\"c_max\", &this->zfs.max);\n            tryReadFlag(\"compressed_size\", &this->zfs.compressed, this->zfs.isCompressed);\n            break;\n         case 'u':\n            tryRead(\"uncompressed_size\", &this->zfs.uncompressed);\n            break;\n         case 's':\n            tryRead(\"size\", &this->zfs.size);\n            break;\n         case 'h':\n            tryRead(\"hdr_size\", &this->zfs.header);\n            break;\n         case 'd':\n            tryRead(\"dbuf_size\", &dbufSize);\n            tryRead(\"dnode_size\", &dnodeSize);\n            break;\n         case 'b':\n            tryRead(\"bonus_size\", &bonusSize);\n            break;\n         case 'a':\n            tryRead(\"anon_size\", &this->zfs.anon);\n            break;\n         case 'm':\n            tryRead(\"mfu_size\", &this->zfs.MFU);\n            tryRead(\"mru_size\", &this->zfs.MRU);\n            break;\n      }\n\n      #undef tryRead\n      #undef tryReadFlag\n   }\n   fclose(file);\n\n   this->zfs.enabled = (this->zfs.size > 0 ? 1 : 0);\n   this->zfs.size   /= 1024;\n   this->zfs.min    /= 1024;\n   this->zfs.max    /= 1024;\n   this->zfs.MFU    /= 1024;\n   this->zfs.MRU    /= 1024;\n   this->zfs.anon   /= 1024;\n   this->zfs.header /= 1024;\n   this->zfs.other   = (dbufSize + dnodeSize + bonusSize) / 1024;\n   if ( this->zfs.isCompressed ) {\n      this->zfs.compressed /= 1024;\n      this->zfs.uncompressed /= 1024;\n   }\n}\n\nstatic void LinuxMachine_scanCPUTime(LinuxMachine* this) {\n   const Machine* super = &this->super;\n\n   LinuxMachine_updateCPUcount(this);\n\n   FILE* file = fopen(PROCSTATFILE, \"r\");\n   if (!file)\n      CRT_fatalError(\"Cannot open \" PROCSTATFILE);\n\n   // One thread per CPU thread + one for the average\n   assert(super->existingCPUs < UINT_MAX - 1);\n   bool adjCpuIdProcessed[super->existingCPUs + 1];\n   memset(adjCpuIdProcessed, 0, sizeof(adjCpuIdProcessed));\n\n   for (unsigned int i = 0; i <= super->existingCPUs; i++) {\n      char buffer[PROC_LINE_LENGTH + 1];\n      unsigned long long int usertime, nicetime, systemtime, idletime;\n      unsigned long long int ioWait = 0, irq = 0, softIrq = 0, steal = 0, guest = 0, guestnice = 0;\n\n      const char* ok = fgets(buffer, sizeof(buffer), file);\n      if (!ok)\n         break;\n\n      // cpu fields are sorted first\n      if (!String_startsWith(buffer, \"cpu\"))\n         break;\n\n      // Depending on your kernel version,\n      // 5, 7, 8 or 9 of these fields will be set.\n      // The rest will remain at zero.\n      unsigned int adjCpuId;\n      if (i == 0) {\n         (void) sscanf(buffer, \"cpu  %16llu %16llu %16llu %16llu %16llu %16llu %16llu %16llu %16llu %16llu\", &usertime, &nicetime, &systemtime, &idletime, &ioWait, &irq, &softIrq, &steal, &guest, &guestnice);\n         adjCpuId = 0;\n      } else {\n         unsigned int cpuid;\n         (void) sscanf(buffer, \"cpu%4u %16llu %16llu %16llu %16llu %16llu %16llu %16llu %16llu %16llu %16llu\", &cpuid, &usertime, &nicetime, &systemtime, &idletime, &ioWait, &irq, &softIrq, &steal, &guest, &guestnice);\n         if (cpuid >= super->existingCPUs)\n            break;\n         adjCpuId = cpuid + 1;\n      }\n\n      if (adjCpuId > super->existingCPUs)\n         break;\n\n      // Guest time is already accounted in usertime\n      usertime -= guest;\n      nicetime -= guestnice;\n      // Fields existing on kernels >= 2.6\n      // (and RHEL's patched kernel 2.4...)\n      unsigned long long int idlealltime = idletime + ioWait;\n      unsigned long long int systemalltime = systemtime + irq + softIrq;\n      unsigned long long int virtalltime = guest + guestnice;\n      unsigned long long int totaltime = usertime + nicetime + systemalltime + idlealltime + steal + virtalltime;\n      CPUData* cpuData = &(this->cpuData[adjCpuId]);\n      // Since we do a subtraction (usertime - guest) and cputime64_to_clock_t()\n      // used in /proc/stat rounds down numbers, it can lead to a case where the\n      // integer overflow.\n      cpuData->userPeriod = saturatingSub(usertime, cpuData->userTime);\n      cpuData->nicePeriod = saturatingSub(nicetime, cpuData->niceTime);\n      cpuData->systemPeriod = saturatingSub(systemtime, cpuData->systemTime);\n      cpuData->systemAllPeriod = saturatingSub(systemalltime, cpuData->systemAllTime);\n      cpuData->idleAllPeriod = saturatingSub(idlealltime, cpuData->idleAllTime);\n      cpuData->idlePeriod = saturatingSub(idletime, cpuData->idleTime);\n      cpuData->ioWaitPeriod = saturatingSub(ioWait, cpuData->ioWaitTime);\n      cpuData->irqPeriod = saturatingSub(irq, cpuData->irqTime);\n      cpuData->softIrqPeriod = saturatingSub(softIrq, cpuData->softIrqTime);\n      cpuData->stealPeriod = saturatingSub(steal, cpuData->stealTime);\n      cpuData->guestPeriod = saturatingSub(virtalltime, cpuData->guestTime);\n      cpuData->totalPeriod = saturatingSub(totaltime, cpuData->totalTime);\n      cpuData->userTime = usertime;\n      cpuData->niceTime = nicetime;\n      cpuData->systemTime = systemtime;\n      cpuData->systemAllTime = systemalltime;\n      cpuData->idleAllTime = idlealltime;\n      cpuData->idleTime = idletime;\n      cpuData->ioWaitTime = ioWait;\n      cpuData->irqTime = irq;\n      cpuData->softIrqTime = softIrq;\n      cpuData->stealTime = steal;\n      cpuData->guestTime = virtalltime;\n      cpuData->totalTime = totaltime;\n\n      adjCpuIdProcessed[adjCpuId] = true;\n   }\n\n   for (unsigned int i = 0; i <= super->existingCPUs; i++) {\n      if (!adjCpuIdProcessed[i]) {\n         // Skipped an ID, but /proc/stat is ordered => threads in between are offline\n         memset(&this->cpuData[i], 0, sizeof(CPUData));\n      }\n   }\n\n   this->period = (double)this->cpuData[0].totalPeriod / super->activeCPUs;\n\n   if (!ferror(file) && !feof(file)) {\n      char buffer[PROC_LINE_LENGTH + 1];\n      while (fgets(buffer, sizeof(buffer), file)) {\n         if (String_startsWith(buffer, \"procs_running\")) {\n            this->runningTasks = (unsigned int) strtoul(buffer + strlen(\"procs_running\"), NULL, 10);\n            break;\n         }\n      }\n   }\n\n   fclose(file);\n}\n\nstatic int scanCPUFrequencyFromSysCPUFreq(LinuxMachine* this) {\n   const Machine* super = &this->super;\n   int numCPUsWithFrequency = 0;\n   unsigned long totalFrequency = 0;\n\n   /*\n    * On some AMD and Intel CPUs read()ing scaling_cur_freq is quite slow (> 1ms). This delay\n    * accumulates for every core. For details see issue#471.\n    * If the read on CPU 0 takes longer than 500us bail out and fall back to reading the\n    * frequencies from /proc/cpuinfo.\n    * Once the condition has been met, bail out early for the next couple of scans.\n    */\n   static int timeout = 0;\n\n   if (timeout > 0) {\n      timeout--;\n      return -1;\n   }\n\n   for (unsigned int i = 0; i < super->existingCPUs; ++i) {\n      if (!Machine_isCPUonline(super, i))\n         continue;\n\n      char pathBuffer[64];\n      xSnprintf(pathBuffer, sizeof(pathBuffer), \"/sys/devices/system/cpu/cpu%u/cpufreq/scaling_cur_freq\", i);\n\n      struct timespec start;\n      if (i == 0)\n         clock_gettime(CLOCK_MONOTONIC, &start);\n\n      FILE* file = fopen(pathBuffer, \"r\");\n      if (!file)\n         return -errno;\n\n      unsigned long frequency;\n      if (fscanf(file, \"%lu\", &frequency) == 1) {\n         /* convert kHz to MHz */\n         frequency = frequency / 1000;\n         this->cpuData[i + 1].frequency = frequency;\n         numCPUsWithFrequency++;\n         totalFrequency += frequency;\n      }\n\n      fclose(file);\n\n      if (i == 0) {\n         struct timespec end;\n         clock_gettime(CLOCK_MONOTONIC, &end);\n         const time_t timeTakenUs = (end.tv_sec - start.tv_sec) * 1000000 + (end.tv_nsec - start.tv_nsec) / 1000;\n         if (timeTakenUs > 500) {\n            timeout = 30;\n            return -1;\n         }\n      }\n   }\n\n   if (numCPUsWithFrequency > 0)\n      this->cpuData[0].frequency = (double)totalFrequency / numCPUsWithFrequency;\n\n   return 0;\n}\n\nstatic void scanCPUFrequencyFromCPUinfo(LinuxMachine* this) {\n   const Machine* super = &this->super;\n\n   FILE* file = fopen(PROCCPUINFOFILE, \"r\");\n   if (file == NULL)\n      return;\n\n   int numCPUsWithFrequency = 0;\n   double totalFrequency = 0;\n   int cpuid = -1;\n\n   while (!feof(file)) {\n      double frequency;\n      char buffer[PROC_LINE_LENGTH];\n\n      if (fgets(buffer, PROC_LINE_LENGTH, file) == NULL)\n         break;\n\n      if (\n         (sscanf(buffer, \"processor : %d\", &cpuid) == 1) ||\n         (sscanf(buffer, \"cpu number : %d\", &cpuid) == 1) // s390: https://github.com/torvalds/linux/blob/v6.15/arch/s390/kernel/processor.c#L349\n      ) {\n         continue;\n      } else if (\n         (sscanf(buffer, \"cpu MHz : %lf\", &frequency) == 1) ||\n         (sscanf(buffer, \"CPU MHz : %lf\", &frequency) == 1) || // LooooongArch: https://github.com/torvalds/linux/blob/v6.15/arch/loongarch/kernel/proc.c#L42\n         (sscanf(buffer, \"cpu MHz dynamic : %lf\", &frequency) == 1) || // s390: https://github.com/torvalds/linux/blob/v6.15/arch/s390/kernel/processor.c#L335\n         (sscanf(buffer, \"clock : %lfMHz\", &frequency) == 1)\n      ) {\n         if (cpuid < 0 || (unsigned int)cpuid > (super->existingCPUs - 1)) {\n            continue;\n         }\n\n         CPUData* cpuData = &(this->cpuData[cpuid + 1]);\n         /* do not override sysfs data */\n         if (!isNonnegative(cpuData->frequency)) {\n            cpuData->frequency = frequency;\n         }\n         numCPUsWithFrequency++;\n         totalFrequency += frequency;\n      } else if (buffer[0] == '\\n') {\n         cpuid = -1;\n      }\n   }\n   fclose(file);\n\n   if (numCPUsWithFrequency > 0) {\n      this->cpuData[0].frequency = totalFrequency / numCPUsWithFrequency;\n   }\n}\n\n#ifdef HAVE_SENSORS_SENSORS_H\nstatic void LinuxMachine_fetchCPUTopologyFromCPUinfo(LinuxMachine* this) {\n   const Machine* super = &this->super;\n\n   FILE* file = fopen(PROCCPUINFOFILE, \"r\");\n   if (file == NULL)\n      return;\n\n   int cpuid = -1;\n   int coreid = -1;\n   int physicalid = -1;\n\n   int max_physicalid = -1;\n   int max_coreid = -1;\n\n   while (!feof(file)) {\n      char *buffer = String_readLine(file);\n      if (!buffer)\n         break;\n\n      if (buffer[0] == '\\0') {\t/* empty line after each cpu */\n         if (cpuid >= 0 && (unsigned int)cpuid < super->existingCPUs) {\n            CPUData* cpuData = &(this->cpuData[cpuid + 1]);\n            cpuData->coreID = coreid;\n            cpuData->physicalID = physicalid;\n\n            if (coreid > max_coreid)\n               max_coreid = coreid;\n            if (physicalid > max_physicalid)\n               max_physicalid = physicalid;\n\n            cpuid = -1;\n            coreid = -1;\n            physicalid = -1;\n         }\n      } else if (String_startsWith(buffer, \"processor\")) {\n         sscanf(buffer, \"processor : %d\", &cpuid);\n      } else if (String_startsWith(buffer, \"physical id\")) {\n         sscanf(buffer, \"physical id : %d\", &physicalid);\n      } else if (String_startsWith(buffer, \"core id\")) {\n         sscanf(buffer, \"core id : %d\", &coreid);\n      }\n\n      free(buffer);\n   }\n\n   this->maxPhysicalID = max_physicalid;\n   this->maxCoreID = max_coreid;\n\n   fclose(file);\n}\n\nstatic void LinuxMachine_assignCCDs(LinuxMachine* this, int ccds) {\n   /* For AMD k10temp/zenpower, temperatures are provided for CCDs only,\n      which is an aggregate of multiple cores.\n      There's no obvious mapping between hwmon sensors and sockets and CCDs.\n      Assume both are iterated in order.\n      Hypothesis: Each CCD has same size N = #Cores/#CCD\n      and is assigned N coreID in sequence.\n      Also assume all CPUs have same number of CCDs. */\n\n   const Machine* super = &this->super;\n   CPUData *cpus = this->cpuData;\n\n   if (ccds == 0) {\n      for (size_t i = 0; i < super->existingCPUs + 1; i++) {\n         cpus[i].ccdID = -1;\n      }\n      return;\n   }\n\n   int coresPerCCD = super->existingCPUs / ccds;\n\n   int ccd = 0;\n   int nc = coresPerCCD;\n   for (int p = 0; p <= (int)this->maxPhysicalID; p++) {\n      for (int c = 0; c <= (int)this->maxCoreID; c++) {\n         for (size_t i = 1; i <= super->existingCPUs; i++) {\n            if (cpus[i].physicalID != p || cpus[i].coreID != c)\n               continue;\n\n            cpus[i].ccdID = ccd;\n\n            if (--nc <= 0) {\n               nc = coresPerCCD;\n               ccd++;\n            }\n         }\n      }\n   }\n}\n\n#endif\n\nstatic void LinuxMachine_scanCPUFrequency(LinuxMachine* this) {\n   const Machine* super = &this->super;\n\n   for (unsigned int i = 0; i <= super->existingCPUs; i++)\n      this->cpuData[i].frequency = NAN;\n\n   if (scanCPUFrequencyFromSysCPUFreq(this) == 0)\n      return;\n\n   scanCPUFrequencyFromCPUinfo(this);\n}\n\nvoid Machine_scan(Machine* super) {\n   LinuxMachine* this = (LinuxMachine*) super;\n\n   LinuxMachine_scanMemoryInfo(this);\n   LinuxMachine_scanHugePages(this);\n   LinuxMachine_scanZfsArcstats(this);\n   LinuxMachine_scanZramInfo(this);\n   LinuxMachine_scanCPUTime(this);\n\n   const Settings* settings = super->settings;\n   if (settings->showCPUFrequency\n#ifdef HAVE_SENSORS_SENSORS_H\n       || settings->showCPUTemperature\n#endif\n   )\n      LinuxMachine_scanCPUFrequency(this);\n\n   #ifdef HAVE_SENSORS_SENSORS_H\n   if (settings->showCPUTemperature)\n      LibSensors_getCPUTemperatures(this->cpuData, super->existingCPUs, super->activeCPUs);\n   #endif\n}\n\nMachine* Machine_new(UsersTable* usersTable, uid_t userId) {\n   LinuxMachine* this = xCalloc(1, sizeof(LinuxMachine));\n   Machine* super = &this->super;\n\n   Machine_init(super, usersTable, userId);\n\n   // Initialize page size\n   long pageSize = sysconf(_SC_PAGESIZE);\n   if (pageSize <= 0)\n      CRT_fatalError(\"Cannot get pagesize by sysconf(_SC_PAGESIZE)\");\n   this->pageSize = (size_t)pageSize;\n   this->pageSizeKB = this->pageSize / ONE_K;\n\n   // Initialize clock ticks\n   if ((this->jiffies = sysconf(_SC_CLK_TCK)) == -1)\n      CRT_fatalError(\"Cannot get clock ticks by sysconf(_SC_CLK_TCK)\");\n\n   // Read btime (the kernel boot time, as number of seconds since the epoch)\n   FILE* statfile = fopen(PROCSTATFILE, \"r\");\n   if (statfile == NULL)\n      CRT_fatalError(\"Cannot open \" PROCSTATFILE);\n\n   this->boottime = -1;\n\n   while (true) {\n      char buffer[PROC_LINE_LENGTH + 1];\n      if (fgets(buffer, sizeof(buffer), statfile) == NULL)\n         break;\n      if (String_startsWith(buffer, \"btime \") == false)\n         continue;\n      if (sscanf(buffer, \"btime %lld\\n\", &this->boottime) == 1)\n         break;\n      CRT_fatalError(\"Failed to parse btime from \" PROCSTATFILE);\n   }\n   fclose(statfile);\n\n   if (this->boottime == -1)\n      CRT_fatalError(\"No btime in \" PROCSTATFILE);\n\n   // Initialize CPU count\n   LinuxMachine_updateCPUcount(this);\n\n   #ifdef HAVE_SENSORS_SENSORS_H\n   // Fetch CPU topology\n   LinuxMachine_fetchCPUTopologyFromCPUinfo(this);\n   int ccds = LibSensors_countCCDs();\n   LinuxMachine_assignCCDs(this, ccds);\n   #endif\n\n   return super;\n}\n\nvoid Machine_delete(Machine* super) {\n   LinuxMachine* this = (LinuxMachine*) super;\n   GPUEngineData* gpuEngineData = this->gpuEngineData;\n\n   Machine_done(super);\n\n   while (gpuEngineData) {\n      GPUEngineData* next = gpuEngineData->next;\n      free(gpuEngineData->key);\n      free(gpuEngineData);\n      gpuEngineData = next;\n   }\n\n   free(this->cpuData);\n   free(this);\n}\n\nbool Machine_isCPUonline(const Machine* super, unsigned int id) {\n   const LinuxMachine* this = (const LinuxMachine*) super;\n\n   assert(id < super->existingCPUs);\n   return this->cpuData[id + 1].online;\n}\n"
  },
  {
    "path": "linux/LinuxMachine.h",
    "content": "#ifndef HEADER_LinuxMachine\n#define HEADER_LinuxMachine\n/*\nhtop - LinuxMachine.h\n(C) 2014 Hisham H. Muhammad\nReleased under the GNU GPLv2+, see the COPYING file\nin the source distribution for its full text.\n*/\n\n#include <stdbool.h>\n#include <stddef.h>\n\n#include \"Machine.h\"\n#include \"linux/ZramStats.h\"\n#include \"linux/ZswapStats.h\"\n#include \"zfs/ZfsArcStats.h\"\n\n#define HTOP_HUGEPAGE_BASE_SHIFT 16\n#define HTOP_HUGEPAGE_COUNT 24\n\ntypedef struct CPUData_ {\n   unsigned long long int totalTime;\n   unsigned long long int userTime;\n   unsigned long long int systemTime;\n   unsigned long long int systemAllTime;\n   unsigned long long int idleAllTime;\n   unsigned long long int idleTime;\n   unsigned long long int niceTime;\n   unsigned long long int ioWaitTime;\n   unsigned long long int irqTime;\n   unsigned long long int softIrqTime;\n   unsigned long long int stealTime;\n   unsigned long long int guestTime;\n\n   unsigned long long int totalPeriod;\n   unsigned long long int userPeriod;\n   unsigned long long int systemPeriod;\n   unsigned long long int systemAllPeriod;\n   unsigned long long int idleAllPeriod;\n   unsigned long long int idlePeriod;\n   unsigned long long int nicePeriod;\n   unsigned long long int ioWaitPeriod;\n   unsigned long long int irqPeriod;\n   unsigned long long int softIrqPeriod;\n   unsigned long long int stealPeriod;\n   unsigned long long int guestPeriod;\n\n   double frequency;\n\n   #ifdef HAVE_SENSORS_SENSORS_H\n   double temperature;\n\n   int physicalID;      /* different for each CPU socket */\n   int coreID;          /* same for hyperthreading */\n   int ccdID;           /* same for each AMD chiplet */\n   #endif\n\n   bool online;\n} CPUData;\n\ntypedef struct GPUEngineData_ {\n   unsigned long long int prevTime, curTime;  /* absolute GPU time in nano seconds */\n   char* key;                                 /* engine name */\n   struct GPUEngineData_* next;\n} GPUEngineData;\n\ntypedef struct LinuxMachine_ {\n   Machine super;\n\n   long jiffies;\n   size_t pageSize;\n   size_t pageSizeKB;\n\n   /* see Linux kernel source for further detail, fs/proc/stat.c */\n   unsigned int runningTasks;   /* procs_running from /proc/stat */\n   long long boottime;   /* btime field from /proc/stat */\n\n   double period;\n\n   memory_t cachedMem;\n   memory_t sharedMem;\n   memory_t usedMem;\n   memory_t buffersMem;\n   memory_t availableMem;\n\n   CPUData* cpuData;\n\n   #ifdef HAVE_SENSORS_SENSORS_H\n   int maxPhysicalID;\n   int maxCoreID;\n   #endif\n\n   memory_t totalHugePageMem;\n   memory_t usedHugePageMem[HTOP_HUGEPAGE_COUNT];\n\n   unsigned long long int prevGpuTime, curGpuTime;  /* total absolute GPU time in nano seconds */\n   GPUEngineData* gpuEngineData;\n\n   ZfsArcStats zfs;\n   ZramStats zram;\n   ZswapStats zswap;\n} LinuxMachine;\n\n#ifndef PROCDIR\n#define PROCDIR \"/proc\"\n#endif\n\n#ifndef PROCCPUINFOFILE\n#define PROCCPUINFOFILE PROCDIR \"/cpuinfo\"\n#endif\n\n#ifndef PROCSTATFILE\n#define PROCSTATFILE PROCDIR \"/stat\"\n#endif\n\n#ifndef PROCMEMINFOFILE\n#define PROCMEMINFOFILE PROCDIR \"/meminfo\"\n#endif\n\n#ifndef PROCARCSTATSFILE\n#define PROCARCSTATSFILE PROCDIR \"/spl/kstat/zfs/arcstats\"\n#endif\n\n#ifndef PROCTTYDRIVERSFILE\n#define PROCTTYDRIVERSFILE PROCDIR \"/tty/drivers\"\n#endif\n\n#ifndef PROC_LINE_LENGTH\n#define PROC_LINE_LENGTH 4096\n#endif\n\n#endif\n"
  },
  {
    "path": "linux/LinuxProcess.c",
    "content": "/*\nhtop - LinuxProcess.c\n(C) 2014 Hisham H. Muhammad\n(C) 2020 Red Hat, Inc.  All Rights Reserved.\nReleased under the GNU GPLv2+, see the COPYING file\nin the source distribution for its full text.\n*/\n\n#include \"config.h\" // IWYU pragma: keep\n\n#include \"linux/LinuxProcess.h\"\n\n#include <assert.h>\n#include <math.h>\n#include <stdint.h>\n#include <stdio.h>\n#include <stdlib.h>\n#include <syscall.h>\n#include <unistd.h>\n\n#include \"CRT.h\"\n#include \"Macros.h\"\n#include \"Process.h\"\n#include \"ProvideCurses.h\"\n#include \"RichString.h\"\n#include \"RowField.h\"\n#include \"Scheduling.h\"\n#include \"Settings.h\"\n#include \"linux/Compat.h\"\n#include \"linux/IOPriority.h\"\n#include \"linux/LinuxMachine.h\"\n\n\nconst ProcessFieldData Process_fields[LAST_PROCESSFIELD] = {\n   [0] = { .name = \"\", .title = NULL, .description = NULL, .flags = 0, },\n   [PID] = { .name = \"PID\", .title = \"PID\", .description = \"Process/thread ID\", .flags = 0, .pidColumn = true, },\n   [COMM] = { .name = \"Command\", .title = \"Command \", .description = \"Command line (insert as last column only)\", .flags = 0, },\n   [STATE] = { .name = \"STATE\", .title = \"S \", .description = \"Process state (S sleeping, R running, D disk, Z zombie, T traced, W paging, I idle)\", .flags = 0, },\n   [PPID] = { .name = \"PPID\", .title = \"PPID\", .description = \"Parent process ID\", .flags = 0, .pidColumn = true, },\n   [PGRP] = { .name = \"PGRP\", .title = \"PGRP\", .description = \"Process group ID\", .flags = 0, .pidColumn = true, },\n   [SESSION] = { .name = \"SESSION\", .title = \"SID\", .description = \"Process's session ID\", .flags = 0, .pidColumn = true, },\n   [TTY] = { .name = \"TTY\", .title = \"TTY      \", .description = \"Controlling terminal\", .flags = 0, },\n   [TPGID] = { .name = \"TPGID\", .title = \"TPGID\", .description = \"Process ID of the fg process group of the controlling terminal\", .flags = 0, .pidColumn = true, },\n   [MINFLT] = { .name = \"MINFLT\", .title = \"     MINFLT \", .description = \"Number of minor faults which have not required loading a memory page from disk\", .flags = 0, .defaultSortDesc = true, },\n   [CMINFLT] = { .name = \"CMINFLT\", .title = \"    CMINFLT \", .description = \"Children processes' minor faults\", .flags = 0, .defaultSortDesc = true, },\n   [MAJFLT] = { .name = \"MAJFLT\", .title = \"     MAJFLT \", .description = \"Number of major faults which have required loading a memory page from disk\", .flags = 0, .defaultSortDesc = true, },\n   [CMAJFLT] = { .name = \"CMAJFLT\", .title = \"    CMAJFLT \", .description = \"Children processes' major faults\", .flags = 0, .defaultSortDesc = true, },\n   [UTIME] = { .name = \"UTIME\", .title = \" UTIME+  \", .description = \"User CPU time - time the process spent executing in user mode\", .flags = 0, .defaultSortDesc = true, },\n   [STIME] = { .name = \"STIME\", .title = \" STIME+  \", .description = \"System CPU time - time the kernel spent running system calls for this process\", .flags = 0, .defaultSortDesc = true, },\n   [CUTIME] = { .name = \"CUTIME\", .title = \" CUTIME+ \", .description = \"Children processes' user CPU time\", .flags = 0, .defaultSortDesc = true, },\n   [CSTIME] = { .name = \"CSTIME\", .title = \" CSTIME+ \", .description = \"Children processes' system CPU time\", .flags = 0, .defaultSortDesc = true, },\n   [PRIORITY] = { .name = \"PRIORITY\", .title = \"PRI \", .description = \"Kernel's internal priority for the process\", .flags = 0, },\n   [NICE] = { .name = \"NICE\", .title = \" NI \", .description = \"Nice value (the higher the value, the more it lets other processes take priority)\", .flags = 0, },\n   [STARTTIME] = { .name = \"STARTTIME\", .title = \"START \", .description = \"Time the process was started\", .flags = 0, },\n   [ELAPSED] = { .name = \"ELAPSED\", .title = \"ELAPSED  \", .description = \"Time since the process was started\", .flags = 0, },\n   [PROCESSOR] = { .name = \"PROCESSOR\", .title = \"CPU \", .description = \"Id of the CPU the process last executed on\", .flags = 0, },\n   [M_VIRT] = { .name = \"M_VIRT\", .title = \" VIRT \", .description = \"Total program size in virtual memory\", .flags = 0, .defaultSortDesc = true, },\n   [M_RESIDENT] = { .name = \"M_RESIDENT\", .title = \"  RES \", .description = \"Resident set size, size of the text and data sections, plus stack usage\", .flags = 0, .defaultSortDesc = true, },\n   [M_SHARE] = { .name = \"M_SHARE\", .title = \"  SHR \", .description = \"Size of the process's shared pages\", .flags = 0, .defaultSortDesc = true, },\n   [M_PRIV] = { .name = \"M_PRIV\", .title = \" PRIV \", .description = \"The private memory size of the process - resident set size minus shared memory\", .flags = 0, .defaultSortDesc = true, },\n   [M_TRS] = { .name = \"M_TRS\", .title = \" CODE \", .description = \"Size of the .text segment of the process (CODE)\", .flags = 0, .defaultSortDesc = true, },\n   [M_DRS] = { .name = \"M_DRS\", .title = \" DATA \", .description = \"Size of the .data segment plus stack usage of the process (DATA)\", .flags = 0, .defaultSortDesc = true, },\n   [M_LRS] = { .name = \"M_LRS\", .title = \"  LIB \", .description = \"The library size of the process (calculated from memory maps)\", .flags = PROCESS_FLAG_LINUX_LRS_FIX, .defaultSortDesc = true, },\n   [ST_UID] = { .name = \"ST_UID\", .title = \"UID\", .description = \"User ID of the process owner\", .flags = 0, },\n   [PERCENT_CPU] = { .name = \"PERCENT_CPU\", .title = \" CPU%\", .description = \"Percentage of the CPU time the process used in the last sampling\", .flags = 0, .defaultSortDesc = true, .autoWidth = true, .autoTitleRightAlign = true, },\n   [PERCENT_NORM_CPU] = { .name = \"PERCENT_NORM_CPU\", .title = \"NCPU%\", .description = \"Normalized percentage of the CPU time the process used in the last sampling (normalized by cpu count)\", .flags = 0, .defaultSortDesc = true, .autoWidth = true, },\n   [PERCENT_MEM] = { .name = \"PERCENT_MEM\", .title = \"MEM% \", .description = \"Percentage of the memory the process is using, based on resident memory size\", .flags = 0, .defaultSortDesc = true, },\n   [USER] = { .name = \"USER\", .title = \"USER       \", .description = \"Username of the process owner (or user ID if name cannot be determined)\", .flags = 0, },\n   [TIME] = { .name = \"TIME\", .title = \"  TIME+  \", .description = \"Total time the process has spent in user and system time\", .flags = 0, .defaultSortDesc = true, },\n   [NLWP] = { .name = \"NLWP\", .title = \"NLWP \", .description = \"Number of threads in the process\", .flags = 0, .defaultSortDesc = true, },\n   [TGID] = { .name = \"TGID\", .title = \"TGID\", .description = \"Thread group ID (i.e. process ID)\", .flags = 0, .pidColumn = true, },\n#ifdef HAVE_OPENVZ\n   [CTID] = { .name = \"CTID\", .title = \" CTID    \", .description = \"OpenVZ container ID (a.k.a. virtual environment ID)\", .flags = PROCESS_FLAG_LINUX_OPENVZ, },\n   [VPID] = { .name = \"VPID\", .title = \"VPID\", .description = \"OpenVZ process ID\", .flags = PROCESS_FLAG_LINUX_OPENVZ, .pidColumn = true, },\n#endif\n#ifdef HAVE_VSERVER\n   [VXID] = { .name = \"VXID\", .title = \" VXID \", .description = \"VServer process ID\", .flags = PROCESS_FLAG_LINUX_VSERVER, },\n#endif\n   [RCHAR] = { .name = \"RCHAR\", .title = \"RCHAR \", .description = \"Number of bytes the process has read\", .flags = PROCESS_FLAG_IO, .defaultSortDesc = true, },\n   [WCHAR] = { .name = \"WCHAR\", .title = \"WCHAR \", .description = \"Number of bytes the process has written\", .flags = PROCESS_FLAG_IO, .defaultSortDesc = true, },\n   [SYSCR] = { .name = \"SYSCR\", .title = \"  READ_SYSC \", .description = \"Number of read(2) syscalls for the process\", .flags = PROCESS_FLAG_IO, .defaultSortDesc = true, },\n   [SYSCW] = { .name = \"SYSCW\", .title = \" WRITE_SYSC \", .description = \"Number of write(2) syscalls for the process\", .flags = PROCESS_FLAG_IO, .defaultSortDesc = true, },\n   [RBYTES] = { .name = \"RBYTES\", .title = \" IO_R \", .description = \"Bytes of read(2) I/O for the process\", .flags = PROCESS_FLAG_IO, .defaultSortDesc = true, },\n   [WBYTES] = { .name = \"WBYTES\", .title = \" IO_W \", .description = \"Bytes of write(2) I/O for the process\", .flags = PROCESS_FLAG_IO, .defaultSortDesc = true, },\n   [CNCLWB] = { .name = \"CNCLWB\", .title = \" IO_C \", .description = \"Bytes of cancelled write(2) I/O\", .flags = PROCESS_FLAG_IO, .defaultSortDesc = true, },\n   [IO_READ_RATE] = { .name = \"IO_READ_RATE\", .title = \"  DISK READ \", .description = \"The I/O rate of read(2) in bytes per second for the process\", .flags = PROCESS_FLAG_IO, .defaultSortDesc = true, },\n   [IO_WRITE_RATE] = { .name = \"IO_WRITE_RATE\", .title = \" DISK WRITE \", .description = \"The I/O rate of write(2) in bytes per second for the process\", .flags = PROCESS_FLAG_IO, .defaultSortDesc = true, },\n   [IO_RATE] = { .name = \"IO_RATE\", .title = \"   DISK R/W \", .description = \"Total I/O rate in bytes per second\", .flags = PROCESS_FLAG_IO, .defaultSortDesc = true, },\n   [CGROUP] = { .name = \"CGROUP\", .title = \"CGROUP (raw)\", .description = \"Which cgroup the process is in\", .flags = PROCESS_FLAG_LINUX_CGROUP, .autoWidth = true, },\n   [CCGROUP] = { .name = \"CCGROUP\", .title = \"CGROUP (compressed)\", .description = \"Which cgroup the process is in (condensed to essentials)\", .flags = PROCESS_FLAG_LINUX_CGROUP, .autoWidth = true, },\n   [CONTAINER] = { .name = \"CONTAINER\", .title = \"CONTAINER\", .description = \"Name of the container the process is in (guessed by heuristics)\", .flags = PROCESS_FLAG_LINUX_CGROUP, .autoWidth = true, },\n   [OOM] = { .name = \"OOM\", .title = \" OOM \", .description = \"OOM (Out-of-Memory) killer score\", .flags = PROCESS_FLAG_LINUX_OOM, .defaultSortDesc = true, },\n   [IO_PRIORITY] = { .name = \"IO_PRIORITY\", .title = \"IO \", .description = \"I/O priority\", .flags = PROCESS_FLAG_LINUX_IOPRIO, },\n#ifdef HAVE_DELAYACCT\n   [PERCENT_CPU_DELAY] = { .name = \"PERCENT_CPU_DELAY\", .title = \"CPUD% \", .description = \"CPU delay %\", .flags = PROCESS_FLAG_LINUX_DELAYACCT, .defaultSortDesc = true, },\n   [PERCENT_IO_DELAY] = { .name = \"PERCENT_IO_DELAY\", .title = \" IOD% \", .description = \"Block I/O delay %\", .flags = PROCESS_FLAG_LINUX_DELAYACCT, .defaultSortDesc = true, },\n   [PERCENT_SWAP_DELAY] = { .name = \"PERCENT_SWAP_DELAY\", .title = \"SWPD% \", .description = \"Swapin delay %\", .flags = PROCESS_FLAG_LINUX_DELAYACCT, .defaultSortDesc = true, },\n#endif\n   [M_PSS] = { .name = \"M_PSS\", .title = \"  PSS \", .description = \"proportional set size, same as M_RESIDENT but each page is divided by the number of processes sharing it\", .flags = PROCESS_FLAG_LINUX_SMAPS, .defaultSortDesc = true, },\n   [M_SWAP] = { .name = \"M_SWAP\", .title = \" SWAP \", .description = \"Size of the process's swapped pages\", .flags = PROCESS_FLAG_LINUX_SMAPS, .defaultSortDesc = true, },\n   [M_PSSWP] = { .name = \"M_PSSWP\", .title = \" PSSWP \", .description = \"shows proportional swap share of this mapping, unlike \\\"Swap\\\", this does not take into account swapped out page of underlying shmem objects\", .flags = PROCESS_FLAG_LINUX_SMAPS, .defaultSortDesc = true, },\n   [CTXT] = { .name = \"CTXT\", .title = \" CTXT \", .description = \"Context switches (incremental sum of voluntary_ctxt_switches and nonvoluntary_ctxt_switches)\", .flags = PROCESS_FLAG_LINUX_CTXT, .defaultSortDesc = true, },\n   [SECATTR] = { .name = \"SECATTR\", .title = \"Security Attribute\", .description = \"Security attribute of the process (e.g. SELinux or AppArmor)\", .flags = PROCESS_FLAG_LINUX_SECATTR, .autoWidth = true, },\n   [PROC_COMM] = { .name = \"COMM\", .title = \"COMM            \", .description = \"comm string of the process from /proc/[pid]/comm\", .flags = 0, },\n   [PROC_EXE] = { .name = \"EXE\", .title = \"EXE             \", .description = \"Basename of exe of the process from /proc/[pid]/exe\", .flags = 0, },\n   [CWD] = { .name = \"CWD\", .title = \"CWD                       \", .description = \"The current working directory of the process\", .flags = PROCESS_FLAG_CWD, },\n   [AUTOGROUP_ID] = { .name = \"AUTOGROUP_ID\", .title = \"AGRP\", .description = \"The autogroup identifier of the process\", .flags = PROCESS_FLAG_LINUX_AUTOGROUP, },\n   [AUTOGROUP_NICE] = { .name = \"AUTOGROUP_NICE\", .title = \" ANI\", .description = \"Nice value (the higher the value, the more other processes take priority) associated with the process autogroup\", .flags = PROCESS_FLAG_LINUX_AUTOGROUP, },\n   [ISCONTAINER] = { .name = \"ISCONTAINER\", .title = \"CONT \", .description = \"Whether the process is running inside a child container\", .flags = PROCESS_FLAG_LINUX_CONTAINER, },\n#ifdef SCHEDULER_SUPPORT\n   [SCHEDULERPOLICY] = { .name = \"SCHEDULERPOLICY\", .title = \"SCHED \", .description = \"Current scheduling policy of the process\", .flags = PROCESS_FLAG_SCHEDPOL, },\n#endif\n   [GPU_TIME] = { .name = \"GPU_TIME\", .title = \"GPU_TIME \", .description = \"Total GPU time\", .flags = PROCESS_FLAG_LINUX_GPU, .defaultSortDesc = true, },\n   [GPU_PERCENT] = { .name = \"GPU_PERCENT\", .title = \" GPU% \", .description = \"Percentage of the GPU time the process used in the last sampling\", .flags = PROCESS_FLAG_LINUX_GPU, .defaultSortDesc = true, },\n};\n\nProcess* LinuxProcess_new(const Machine* host) {\n   LinuxProcess* this = xCalloc(1, sizeof(LinuxProcess));\n   Object_setClass(this, Class(LinuxProcess));\n   Process_init(&this->super, host);\n   return (Process*)this;\n}\n\nvoid Process_delete(Object* cast) {\n   LinuxProcess* this = (LinuxProcess*) cast;\n   Process_done((Process*)cast);\n   free(this->container_short);\n   free(this->cgroup_short);\n   free(this->cgroup);\n#ifdef HAVE_OPENVZ\n   free(this->ctid);\n#endif\n   free(this->secattr);\n   free(this);\n}\n\n/*\n[1] Note that before kernel 2.6.26 a process that has not asked for\nan io priority formally uses \"none\" as scheduling class, but the\nio scheduler will treat such processes as if it were in the best\neffort class. The priority within the best effort class will be\ndynamically derived from the cpu nice level of the process:\nio_priority = (cpu_nice + 20) / 5. -- From ionice(1) man page\n*/\nstatic int LinuxProcess_effectiveIOPriority(const LinuxProcess* this) {\n   if (IOPriority_class(this->ioPriority) == IOPRIO_CLASS_NONE) {\n      return IOPriority_tuple(IOPRIO_CLASS_BE, (this->super.nice + 20) / 5);\n   }\n\n   return this->ioPriority;\n}\n\n#ifdef __ANDROID__\n#define SYS_ioprio_get __NR_ioprio_get\n#define SYS_ioprio_set __NR_ioprio_set\n#endif\n\n/*\n * Gather I/O scheduling class and priority (thread-specific data)\n */\nIOPriority LinuxProcess_updateIOPriority(Process* p) {\n   IOPriority ioprio = 0;\n// Other OSes masquerading as Linux (NetBSD?) don't have this syscall\n#ifdef SYS_ioprio_get\n   ioprio = (int)syscall(SYS_ioprio_get, IOPRIO_WHO_PROCESS, Process_getPid(p));\n#endif\n   LinuxProcess* this = (LinuxProcess*) p;\n   this->ioPriority = ioprio;\n   return ioprio;\n}\n\nstatic bool LinuxProcess_setIOPriority(Process* p, Arg ioprio) {\n// Other OSes masquerading as Linux (NetBSD?) don't have this syscall\n#ifdef SYS_ioprio_set\n   syscall(SYS_ioprio_set, IOPRIO_WHO_PROCESS, Process_getPid(p), ioprio.i);\n#endif\n   return LinuxProcess_updateIOPriority(p) == ioprio.i;\n}\n\nbool LinuxProcess_rowSetIOPriority(Row* super, Arg ioprio) {\n   Process* p = (Process*) super;\n   assert(Object_isA((const Object*) p, (const ObjectClass*) &Process_class));\n   return LinuxProcess_setIOPriority(p, ioprio);\n}\n\nbool LinuxProcess_isAutogroupEnabled(void) {\n   char buf[16];\n   if (Compat_readfile(PROCDIR \"/sys/kernel/sched_autogroup_enabled\", buf, sizeof(buf)) < 0)\n      return false;\n   return buf[0] == '1';\n}\n\nstatic bool LinuxProcess_changeAutogroupPriorityBy(Process* p, Arg delta) {\n   char buffer[256];\n   pid_t pid = Process_getPid(p);\n   xSnprintf(buffer, sizeof(buffer), PROCDIR \"/%d/autogroup\", pid);\n\n   FILE* file = fopen(buffer, \"r+\");\n   if (!file)\n      return false;\n\n   long int identity;\n   int nice;\n   int ok = fscanf(file, \"/autogroup-%ld nice %d\", &identity, &nice);\n   bool success = false;\n   if (ok == 2 && fseek(file, 0L, SEEK_SET) == 0) {\n      xSnprintf(buffer, sizeof(buffer), \"%d\", nice + delta.i);\n      success = fputs(buffer, file) > 0;\n   }\n\n   fclose(file);\n   return success;\n}\n\nbool LinuxProcess_rowChangeAutogroupPriorityBy(Row* super, Arg delta) {\n   Process* p = (Process*) super;\n   assert(Object_isA((const Object*) p, (const ObjectClass*) &Process_class));\n   return LinuxProcess_changeAutogroupPriorityBy(p, delta);\n}\n\nstatic double LinuxProcess_totalIORate(const LinuxProcess* lp) {\n   double totalRate = NAN;\n   if (isNonnegative(lp->io_rate_read_bps)) {\n      totalRate = lp->io_rate_read_bps;\n      if (isNonnegative(lp->io_rate_write_bps)) {\n         totalRate += lp->io_rate_write_bps;\n      }\n   } else if (isNonnegative(lp->io_rate_write_bps)) {\n      totalRate = lp->io_rate_write_bps;\n   }\n   return totalRate;\n}\n\nstatic void LinuxProcess_rowWriteField(const Row* super, RichString* str, ProcessField field) {\n   const Process* this = (const Process*) super;\n   const LinuxProcess* lp = (const LinuxProcess*) super;\n   const Machine* host = (const Machine*) super->host;\n   const LinuxMachine* lhost = (const LinuxMachine*) super->host;\n\n   bool coloring = host->settings->highlightMegabytes;\n   char buffer[256]; buffer[255] = '\\0';\n   int attr = CRT_colors[DEFAULT_COLOR];\n   size_t n = sizeof(buffer) - 1;\n\n   switch (field) {\n   case CMINFLT: Row_printCount(str, lp->cminflt, coloring); return;\n   case CMAJFLT: Row_printCount(str, lp->cmajflt, coloring); return;\n   case GPU_PERCENT: Row_printPercentage(lp->gpu_percent, buffer, n, 5, &attr); break;\n   case GPU_TIME: Row_printNanoseconds(str, lp->gpu_time, coloring); return;\n   case M_DRS: Row_printBytes(str, lp->m_drs * lhost->pageSize, coloring); return;\n   case M_LRS:\n      if (lp->m_lrs) {\n         Row_printBytes(str, lp->m_lrs * lhost->pageSize, coloring);\n         return;\n      }\n\n      attr = CRT_colors[PROCESS_SHADOW];\n      xSnprintf(buffer, n, \"  N/A \");\n      break;\n   case M_TRS: Row_printBytes(str, lp->m_trs * lhost->pageSize, coloring); return;\n   case M_SHARE: Row_printBytes(str, lp->m_share * lhost->pageSize, coloring); return;\n   case M_PRIV: Row_printKBytes(str, lp->m_priv, coloring); return;\n   case M_PSS: Row_printKBytes(str, lp->m_pss, coloring); return;\n   case M_SWAP: Row_printKBytes(str, lp->m_swap, coloring); return;\n   case M_PSSWP: Row_printKBytes(str, lp->m_psswp, coloring); return;\n   case UTIME: Row_printTime(str, lp->utime, coloring); return;\n   case STIME: Row_printTime(str, lp->stime, coloring); return;\n   case CUTIME: Row_printTime(str, lp->cutime, coloring); return;\n   case CSTIME: Row_printTime(str, lp->cstime, coloring); return;\n   case RCHAR:  Row_printBytes(str, lp->io_rchar, coloring); return;\n   case WCHAR:  Row_printBytes(str, lp->io_wchar, coloring); return;\n   case SYSCR:  Row_printCount(str, lp->io_syscr, coloring); return;\n   case SYSCW:  Row_printCount(str, lp->io_syscw, coloring); return;\n   case RBYTES: Row_printBytes(str, lp->io_read_bytes, coloring); return;\n   case WBYTES: Row_printBytes(str, lp->io_write_bytes, coloring); return;\n   case CNCLWB: Row_printBytes(str, lp->io_cancelled_write_bytes, coloring); return;\n   case IO_READ_RATE:  Row_printRate(str, lp->io_rate_read_bps, coloring); return;\n   case IO_WRITE_RATE: Row_printRate(str, lp->io_rate_write_bps, coloring); return;\n   case IO_RATE: Row_printRate(str, LinuxProcess_totalIORate(lp), coloring); return;\n   #ifdef HAVE_OPENVZ\n   case CTID: xSnprintf(buffer, n, \"%-8s \", lp->ctid ? lp->ctid : \"\"); break;\n   case VPID: xSnprintf(buffer, n, \"%*d \", Process_pidDigits, lp->vpid); break;\n   #endif\n   #ifdef HAVE_VSERVER\n   case VXID: xSnprintf(buffer, n, \"%5u \", lp->vxid); break;\n   #endif\n   case CGROUP:\n      xSnprintf(buffer, n, \"%-*.*s \", Row_fieldWidths[CGROUP], Row_fieldWidths[CGROUP], lp->cgroup ? lp->cgroup : \"N/A\");\n      RichString_appendWide(str, attr, buffer);\n      return;\n   case CCGROUP:\n      xSnprintf(buffer, n, \"%-*.*s \", Row_fieldWidths[CCGROUP], Row_fieldWidths[CCGROUP], lp->cgroup_short ? lp->cgroup_short : (lp->cgroup ? lp->cgroup : \"N/A\"));\n      RichString_appendWide(str, attr, buffer);\n      return;\n   case CONTAINER:\n      xSnprintf(buffer, n, \"%-*.*s \", Row_fieldWidths[CONTAINER], Row_fieldWidths[CONTAINER], lp->container_short ? lp->container_short : \"N/A\");\n      RichString_appendWide(str, attr, buffer);\n      return;\n   case OOM: xSnprintf(buffer, n, \"%4u \", lp->oom); break;\n   case IO_PRIORITY: {\n      int klass = IOPriority_class(lp->ioPriority);\n      if (klass == IOPRIO_CLASS_NONE) {\n         // see note [1] above\n         xSnprintf(buffer, n, \"B%1d \", (int) (this->nice + 20) / 5);\n      } else if (klass == IOPRIO_CLASS_BE) {\n         xSnprintf(buffer, n, \"B%1d \", IOPriority_data(lp->ioPriority));\n      } else if (klass == IOPRIO_CLASS_RT) {\n         attr = CRT_colors[PROCESS_HIGH_PRIORITY];\n         xSnprintf(buffer, n, \"R%1d \", IOPriority_data(lp->ioPriority));\n      } else if (klass == IOPRIO_CLASS_IDLE) {\n         attr = CRT_colors[PROCESS_LOW_PRIORITY];\n         xSnprintf(buffer, n, \"id \");\n      } else {\n         xSnprintf(buffer, n, \"?? \");\n      }\n      break;\n   }\n   #ifdef HAVE_DELAYACCT\n   case PERCENT_CPU_DELAY: Row_printPercentage(lp->cpu_delay_percent, buffer, n, 5, &attr); break;\n   case PERCENT_IO_DELAY: Row_printPercentage(lp->blkio_delay_percent, buffer, n, 5, &attr); break;\n   case PERCENT_SWAP_DELAY: Row_printPercentage(lp->swapin_delay_percent, buffer, n, 5, &attr); break;\n   #endif\n   case CTXT:\n      if (lp->ctxt_diff > 1000) {\n         attr |= A_BOLD;\n      }\n      xSnprintf(buffer, n, \"%5lu \", lp->ctxt_diff);\n      break;\n   case SECATTR:\n      snprintf(buffer, n, \"%-*.*s \", Row_fieldWidths[SECATTR], Row_fieldWidths[SECATTR], lp->secattr ? lp->secattr : \"N/A\");\n      RichString_appendWide(str, attr, buffer);\n      return;\n   case AUTOGROUP_ID:\n      if (lp->autogroup_id != -1) {\n         xSnprintf(buffer, n, \"%4ld \", lp->autogroup_id);\n      } else {\n         attr = CRT_colors[PROCESS_SHADOW];\n         xSnprintf(buffer, n, \" N/A \");\n      }\n      break;\n   case AUTOGROUP_NICE:\n      if (lp->autogroup_id != -1) {\n         xSnprintf(buffer, n, \"%3d \", lp->autogroup_nice);\n         attr = lp->autogroup_nice < 0 ? CRT_colors[PROCESS_HIGH_PRIORITY]\n            : lp->autogroup_nice > 0 ? CRT_colors[PROCESS_LOW_PRIORITY]\n            : CRT_colors[PROCESS_SHADOW];\n      } else {\n         attr = CRT_colors[PROCESS_SHADOW];\n         xSnprintf(buffer, n, \"N/A \");\n      }\n      break;\n   case ISCONTAINER:\n      switch (this->isRunningInContainer) {\n      case TRI_ON:\n         xSnprintf(buffer, n, \"YES  \");\n         break;\n      case TRI_OFF:\n         xSnprintf(buffer, n, \"NO   \");\n         break;\n      default:\n         attr = CRT_colors[PROCESS_SHADOW];\n         xSnprintf(buffer, n, \"N/A  \");\n      }\n      break;\n   default:\n      Process_writeField(this, str, field);\n      return;\n   }\n\n   RichString_appendAscii(str, attr, buffer);\n}\n\nstatic int LinuxProcess_compareByKey(const Process* v1, const Process* v2, ProcessField key) {\n   const LinuxProcess* p1 = (const LinuxProcess*)v1;\n   const LinuxProcess* p2 = (const LinuxProcess*)v2;\n\n   switch (key) {\n   case M_DRS:\n      return SPACESHIP_NUMBER(p1->m_drs, p2->m_drs);\n   case M_LRS:\n      return SPACESHIP_NUMBER(p1->m_lrs, p2->m_lrs);\n   case M_TRS:\n      return SPACESHIP_NUMBER(p1->m_trs, p2->m_trs);\n   case M_SHARE:\n      return SPACESHIP_NUMBER(p1->m_share, p2->m_share);\n   case M_PRIV:\n      return SPACESHIP_NUMBER(p1->m_priv, p2->m_priv);\n   case M_PSS:\n      return SPACESHIP_NUMBER(p1->m_pss, p2->m_pss);\n   case M_SWAP:\n      return SPACESHIP_NUMBER(p1->m_swap, p2->m_swap);\n   case M_PSSWP:\n      return SPACESHIP_NUMBER(p1->m_psswp, p2->m_psswp);\n   case UTIME:\n      return SPACESHIP_NUMBER(p1->utime, p2->utime);\n   case CUTIME:\n      return SPACESHIP_NUMBER(p1->cutime, p2->cutime);\n   case STIME:\n      return SPACESHIP_NUMBER(p1->stime, p2->stime);\n   case CSTIME:\n      return SPACESHIP_NUMBER(p1->cstime, p2->cstime);\n   case RCHAR:\n      return SPACESHIP_NUMBER(p1->io_rchar, p2->io_rchar);\n   case WCHAR:\n      return SPACESHIP_NUMBER(p1->io_wchar, p2->io_wchar);\n   case SYSCR:\n      return SPACESHIP_NUMBER(p1->io_syscr, p2->io_syscr);\n   case SYSCW:\n      return SPACESHIP_NUMBER(p1->io_syscw, p2->io_syscw);\n   case RBYTES:\n      return SPACESHIP_NUMBER(p1->io_read_bytes, p2->io_read_bytes);\n   case WBYTES:\n      return SPACESHIP_NUMBER(p1->io_write_bytes, p2->io_write_bytes);\n   case CNCLWB:\n      return SPACESHIP_NUMBER(p1->io_cancelled_write_bytes, p2->io_cancelled_write_bytes);\n   case IO_READ_RATE:\n      return compareRealNumbers(p1->io_rate_read_bps, p2->io_rate_read_bps);\n   case IO_WRITE_RATE:\n      return compareRealNumbers(p1->io_rate_write_bps, p2->io_rate_write_bps);\n   case IO_RATE:\n      return compareRealNumbers(LinuxProcess_totalIORate(p1), LinuxProcess_totalIORate(p2));\n   #ifdef HAVE_OPENVZ\n   case CTID:\n      return SPACESHIP_NULLSTR(p1->ctid, p2->ctid);\n   case VPID:\n      return SPACESHIP_NUMBER(p1->vpid, p2->vpid);\n   #endif\n   #ifdef HAVE_VSERVER\n   case VXID:\n      return SPACESHIP_NUMBER(p1->vxid, p2->vxid);\n   #endif\n   case CGROUP:\n      return SPACESHIP_NULLSTR(p1->cgroup, p2->cgroup);\n   case CCGROUP:\n      return SPACESHIP_NULLSTR(p1->cgroup_short, p2->cgroup_short);\n   case CONTAINER:\n      return SPACESHIP_NULLSTR(p1->container_short, p2->container_short);\n   case OOM:\n      return SPACESHIP_NUMBER(p1->oom, p2->oom);\n   #ifdef HAVE_DELAYACCT\n   case PERCENT_CPU_DELAY:\n      return compareRealNumbers(p1->cpu_delay_percent, p2->cpu_delay_percent);\n   case PERCENT_IO_DELAY:\n      return compareRealNumbers(p1->blkio_delay_percent, p2->blkio_delay_percent);\n   case PERCENT_SWAP_DELAY:\n      return compareRealNumbers(p1->swapin_delay_percent, p2->swapin_delay_percent);\n   #endif\n   case IO_PRIORITY:\n      return SPACESHIP_NUMBER(LinuxProcess_effectiveIOPriority(p1), LinuxProcess_effectiveIOPriority(p2));\n   case CTXT:\n      return SPACESHIP_NUMBER(p1->ctxt_diff, p2->ctxt_diff);\n   case SECATTR:\n      return SPACESHIP_NULLSTR(p1->secattr, p2->secattr);\n   case AUTOGROUP_ID:\n      return SPACESHIP_NUMBER(p1->autogroup_id, p2->autogroup_id);\n   case AUTOGROUP_NICE:\n      return SPACESHIP_NUMBER(p1->autogroup_nice, p2->autogroup_nice);\n   case GPU_PERCENT: {\n      int r = compareRealNumbers(p1->gpu_percent, p2->gpu_percent);\n      if (r)\n         return r;\n\n      return SPACESHIP_NUMBER(p1->gpu_time, p2->gpu_time);\n   }\n   case GPU_TIME:\n      return SPACESHIP_NUMBER(p1->gpu_time, p2->gpu_time);\n   case ISCONTAINER:\n      return SPACESHIP_NUMBER(v1->isRunningInContainer, v2->isRunningInContainer);\n   default:\n      return Process_compareByKey_Base(v1, v2, key);\n   }\n}\n\nconst ProcessClass LinuxProcess_class = {\n   .super = {\n      .super = {\n         .extends = Class(Process),\n         .display = Row_display,\n         .delete = Process_delete,\n         .compare = Process_compare\n      },\n      .isHighlighted = Process_rowIsHighlighted,\n      .isVisible = Process_rowIsVisible,\n      .matchesFilter = Process_rowMatchesFilter,\n      .compareByParent = Process_compareByParent,\n      .sortKeyString = Process_rowGetSortKey,\n      .writeField = LinuxProcess_rowWriteField\n   },\n   .compareByKey = LinuxProcess_compareByKey\n};\n"
  },
  {
    "path": "linux/LinuxProcess.h",
    "content": "#ifndef HEADER_LinuxProcess\n#define HEADER_LinuxProcess\n/*\nhtop - LinuxProcess.h\n(C) 2014 Hisham H. Muhammad\n(C) 2020 Red Hat, Inc.  All Rights Reserved.\nReleased under the GNU GPLv2+, see the COPYING file\nin the source distribution for its full text.\n*/\n\n#include <stdbool.h>\n\n#include \"Machine.h\"\n#include \"Object.h\"\n#include \"Process.h\"\n#include \"Row.h\"\n\n#include \"linux/IOPriority.h\"\n\n\n#define PROCESS_FLAG_LINUX_IOPRIO    0x00000100\n#define PROCESS_FLAG_LINUX_OPENVZ    0x00000200\n#define PROCESS_FLAG_LINUX_VSERVER   0x00000400\n#define PROCESS_FLAG_LINUX_CGROUP    0x00000800\n#define PROCESS_FLAG_LINUX_OOM       0x00001000\n#define PROCESS_FLAG_LINUX_SMAPS     0x00002000\n#define PROCESS_FLAG_LINUX_CTXT      0x00004000\n#define PROCESS_FLAG_LINUX_SECATTR   0x00008000\n#define PROCESS_FLAG_LINUX_LRS_FIX   0x00010000\n#define PROCESS_FLAG_LINUX_DELAYACCT 0x00040000\n#define PROCESS_FLAG_LINUX_AUTOGROUP 0x00080000\n#define PROCESS_FLAG_LINUX_GPU       0x00100000\n#define PROCESS_FLAG_LINUX_CONTAINER 0x00200000\n\ntypedef struct LinuxProcess_ {\n   Process super;\n   IOPriority ioPriority;\n   unsigned long int cminflt;\n   unsigned long int cmajflt;\n   unsigned long long int utime;\n   unsigned long long int stime;\n   unsigned long long int cutime;\n   unsigned long long int cstime;\n   long m_share;\n   long m_priv;\n   long m_pss;\n   long m_swap;\n   long m_psswp;\n   long m_trs;\n   long m_drs;\n   long m_lrs;\n\n   /* Process flags */\n   unsigned long int flags;\n\n   /* Data read (in bytes) */\n   unsigned long long io_rchar;\n\n   /* Data written (in bytes) */\n   unsigned long long io_wchar;\n\n   /* Number of read(2) syscalls */\n   unsigned long long io_syscr;\n\n   /* Number of write(2) syscalls */\n   unsigned long long io_syscw;\n\n   /* Storage data read (in bytes) */\n   unsigned long long io_read_bytes;\n\n   /* Storage data written (in bytes) */\n   unsigned long long io_write_bytes;\n\n   /* Storage data cancelled (in bytes) */\n   unsigned long long io_cancelled_write_bytes;\n\n   /* Point in time of last io scan (in milliseconds elapsed since the Epoch) */\n   unsigned long long io_last_scan_time_ms;\n\n   /* Storage data read (in bytes per second) */\n   double io_rate_read_bps;\n\n   /* Storage data written (in bytes per second) */\n   double io_rate_write_bps;\n\n   #ifdef HAVE_OPENVZ\n   char* ctid;\n   pid_t vpid;\n   #endif\n   #ifdef HAVE_VSERVER\n   unsigned int vxid;\n   #endif\n   char* cgroup;\n   char* cgroup_short;\n   char* container_short;\n   unsigned int oom;\n   #ifdef HAVE_DELAYACCT\n   unsigned long long int delay_read_time;\n   unsigned long long cpu_delay_total;\n   unsigned long long blkio_delay_total;\n   unsigned long long swapin_delay_total;\n   float cpu_delay_percent;\n   float blkio_delay_percent;\n   float swapin_delay_percent;\n   #endif\n   unsigned long ctxt_total;\n   unsigned long ctxt_diff;\n   char* secattr;\n   unsigned long long int last_mlrs_calctime;\n\n   /* Total GPU time used in nano seconds */\n   unsigned long long int gpu_time;\n   /* GPU utilization in percent */\n   float gpu_percent;\n   /* Activity of GPU: 0 if active, otherwise time of last scan in milliseconds */\n   uint64_t gpu_activityMs;\n\n   /* Autogroup scheduling (CFS) information */\n   long int autogroup_id;\n   int autogroup_nice;\n} LinuxProcess;\n\nextern int pageSize;\n\nextern int pageSizeKB;\n\nextern const ProcessFieldData Process_fields[LAST_PROCESSFIELD];\n\nextern const ProcessClass LinuxProcess_class;\n\nProcess* LinuxProcess_new(const Machine* host);\n\nvoid Process_delete(Object* cast);\n\nIOPriority LinuxProcess_updateIOPriority(Process* proc);\n\nbool LinuxProcess_rowSetIOPriority(Row* super, Arg ioprio);\n\nbool LinuxProcess_isAutogroupEnabled(void);\n\nbool LinuxProcess_rowChangeAutogroupPriorityBy(Row* super, Arg delta);\n\nbool Process_isThread(const Process* this);\n\n#endif\n"
  },
  {
    "path": "linux/LinuxProcessTable.c",
    "content": "/*\nhtop - LinuxProcessTable.c\n(C) 2014 Hisham H. Muhammad\n(C) 2020-2026 htop dev team\nReleased under the GNU GPLv2+, see the COPYING file\nin the source distribution for its full text.\n*/\n\n#include \"config.h\" // IWYU pragma: keep\n\n#include \"linux/LinuxProcessTable.h\"\n\n#include <assert.h>\n#include <ctype.h>\n#include <dirent.h>\n#include <fcntl.h>\n#include <inttypes.h>\n#include <limits.h>\n#include <math.h>\n#include <stdbool.h>\n#include <stddef.h>\n#include <stdio.h>\n#include <stdlib.h>\n#include <string.h>\n#include <syscall.h>\n#include <unistd.h>\n#include <linux/capability.h> // raw syscall, no libcap  // IWYU pragma: keep // IWYU pragma: no_include <sys/capability.h>\n#include <sys/stat.h>\n\n#include \"GPUMeter.h\"\n#include \"Hashtable.h\"\n#include \"Machine.h\"\n#include \"Macros.h\"\n#include \"Object.h\"\n#include \"Process.h\"\n#include \"Row.h\"\n#include \"RowField.h\"\n#include \"Scheduling.h\"\n#include \"Settings.h\"\n#include \"Table.h\"\n#include \"UsersTable.h\"\n#include \"linux/CGroupUtils.h\"\n#include \"linux/Compat.h\"\n#include \"linux/GPU.h\"\n#include \"linux/LinuxMachine.h\"\n#include \"linux/LinuxProcess.h\"\n#include \"linux/Platform.h\" // needed for GNU/hurd to get PATH_MAX  // IWYU pragma: keep\n\n#ifdef HAVE_DELAYACCT\n#include \"linux/LibNl.h\"\n#endif\n\n#if defined(MAJOR_IN_MKDEV)\n#include <sys/mkdev.h>\n#elif defined(MAJOR_IN_SYSMACROS)\n#include <sys/sysmacros.h>\n#endif\n\n/* Not exposed yet. Defined at include/linux/sched.h */\n#ifndef PF_KTHREAD\n#define PF_KTHREAD 0x00200000\n#endif\n\n/* Maximum buffer size for reading COMMAND / comm */\n#define MAX_CMDLINE_BUFFER_SIZE (2 * 1024 * 1024 + 512)\n\n/* Inode number of the PID namespace of htop */\nstatic ino_t rootPidNs = (ino_t)-1;\n\n\nstatic FILE* fopenat(openat_arg_t openatArg, const char* pathname, const char* mode) {\n   assert(String_eq(mode, \"r\")); /* only currently supported mode */\n\n   int fd = Compat_openat(openatArg, pathname, O_RDONLY);\n   if (fd < 0)\n      return NULL;\n\n   FILE* fp = fdopen(fd, mode);\n   if (!fp)\n      close(fd);\n\n   return fp;\n}\n\nstatic pid_t strtopid(const char* str) {\n   char* endptr;\n   unsigned long parsedPid = strtoul(str, &endptr, 10);\n   if (parsedPid == 0 || parsedPid >= INT_MAX || *endptr != '\\0')\n      return 0; // indicate failure by an invalid pid\n   return (pid_t)parsedPid;\n}\n\nstatic inline uint64_t fast_strtoull_dec(char** str, size_t maxlen) {\n   uint64_t result = 0;\n\n   if (!maxlen)\n      maxlen = 20; // length of maximum value of 18446744073709551615\n\n   while (maxlen-- && **str >= '0' && **str <= '9') {\n      result *= 10;\n      result += **str - '0';\n      (*str)++;\n   }\n\n   return result;\n}\n\nstatic long long fast_strtoll_dec(char** str, size_t maxlen) {\n   bool neg = false;\n\n   if (**str == '-') {\n      neg = true;\n      (*str)++;\n   }\n\n   unsigned long long res = fast_strtoull_dec(str, maxlen);\n   assert(res <= LLONG_MAX);\n   long long result = (long long)res;\n\n   return neg ? -result : result;\n}\n\nstatic int fast_strtoi_dec(char** str, size_t maxlen) {\n   if (!maxlen)\n      maxlen = 10; // length of maximum value of 2147483647\n   long long result = fast_strtoll_dec(str, maxlen);\n   assert(result <= INT_MAX);\n   assert(result >= INT_MIN);\n   return (int)result;\n}\n\nstatic long fast_strtol_dec(char** str, size_t maxlen) {\n   long long result = fast_strtoll_dec(str, maxlen);\n   assert(result <= LONG_MAX);\n   assert(result >= LONG_MIN);\n   return (long)result;\n}\n\nstatic unsigned long fast_strtoul_dec(char** str, size_t maxlen) {\n   unsigned long long result = fast_strtoull_dec(str, maxlen);\n   assert(result <= ULONG_MAX);\n   return (unsigned long)result;\n}\n\nstatic inline uint64_t fast_strtoull_hex(char** str, size_t maxlen) {\n   register uint64_t result = 0;\n   register int nibble, letter;\n   const long valid_mask = 0x03FF007E;\n\n   if (!maxlen)\n      maxlen = 18; // length of maximum value of 0xffffffffffffffff\n\n   while (maxlen--) {\n      nibble = (unsigned char)**str;\n      if (!(valid_mask & (1 << (nibble & 0x1F))))\n         break;\n      if ((nibble < '0') || (nibble & ~0x20) > 'F')\n         break;\n      letter = (nibble & 0x40) ? 'A' - '9' - 1 : 0;\n      nibble &=~0x20; // to upper\n      nibble ^= 0x10; // switch letters and digits\n      nibble -= letter;\n      nibble &= 0x0f;\n      result <<= 4;\n      result += (uint64_t)nibble;\n      (*str)++;\n   }\n\n   return result;\n}\n\nstatic int sortTtyDrivers(const void* va, const void* vb) {\n   const TtyDriver* a = (const TtyDriver*) va;\n   const TtyDriver* b = (const TtyDriver*) vb;\n\n   int r = SPACESHIP_NUMBER(a->major, b->major);\n   if (r)\n      return r;\n\n   return SPACESHIP_NUMBER(a->minorFrom, b->minorFrom);\n}\n\nstatic void LinuxProcessTable_initTtyDrivers(LinuxProcessTable* this) {\n   TtyDriver* ttyDrivers;\n\n   char buf[16384];\n   ssize_t r = Compat_readfile(PROCTTYDRIVERSFILE, buf, sizeof(buf));\n   if (r < 0)\n      return;\n\n   size_t numDrivers = 0;\n   size_t allocd = 10;\n   ttyDrivers = xMallocArray(allocd, sizeof(TtyDriver));\n   char* at = buf;\n   char* path = NULL;\n   while (at && *at != '\\0') {\n      /*\n       * Format:\n       * [name]  [node path]  [major]  [minor range]  [type]\n       * serial  /dev/ttyS    4        64-95          serial\n       */\n\n      at = strchr(at, ' ');    // skip first token\n      if (!at)\n         goto finish;          // bail out on truncation\n      while (*at == ' ') at++; // skip spaces\n\n      const char* token = at;  // mark beginning of path\n      at = strchr(at, ' ');    // find end of path\n      if (!at)\n         goto finish;          // bail out on truncation\n      *at = '\\0'; at++;        // clear and skip\n      path = xStrdup(token);   // save\n      while (*at == ' ') at++; // skip spaces\n\n      token = at;              // mark beginning of major\n      at = strchr(at, ' ');    // find end of major\n      if (!at)\n         goto finish;          // bail out on truncation\n      *at = '\\0'; at++;        // clear and skip\n      ttyDrivers[numDrivers].major = atoi(token); // save\n      while (*at == ' ') at++; // skip spaces\n\n      token = at;              // mark beginning of minorFrom\n      while (*at >= '0' && *at <= '9') at++; //find end of minorFrom\n      if (*at == '-') {        // if has range\n         *at = '\\0'; at++;        // clear and skip\n         ttyDrivers[numDrivers].minorFrom = atoi(token); // save\n         token = at;              // mark beginning of minorTo\n         at = strchr(at, ' ');    // find end of minorTo\n         if (!at)\n            goto finish;          // bail out on truncation\n         *at = '\\0'; at++;        // clear and skip\n         ttyDrivers[numDrivers].minorTo = atoi(token); // save\n      } else {                 // no range\n         *at = '\\0'; at++;        // clear and skip\n         ttyDrivers[numDrivers].minorFrom = atoi(token); // save\n         ttyDrivers[numDrivers].minorTo = atoi(token); // save\n      }\n\n      at = strchr(at, '\\n');   // go to end of line\n      if (at)\n         at++;                 // skip\n      ttyDrivers[numDrivers].path = path;\n      path = NULL;\n      numDrivers++;\n      if (numDrivers == allocd) {\n         allocd += 10;\n         ttyDrivers = xReallocArray(ttyDrivers, allocd, sizeof(TtyDriver));\n      }\n   }\nfinish:\n   free(path);\n\n   ttyDrivers = xRealloc(ttyDrivers, sizeof(TtyDriver) * (numDrivers + 1));\n   ttyDrivers[numDrivers].path = NULL;\n   qsort(ttyDrivers, numDrivers, sizeof(TtyDriver), sortTtyDrivers);\n   this->ttyDrivers = ttyDrivers;\n}\n\nProcessTable* ProcessTable_new(Machine* host, Hashtable* pidMatchList) {\n   LinuxProcessTable* this = xCalloc(1, sizeof(LinuxProcessTable));\n   Object_setClass(this, Class(ProcessTable));\n\n   ProcessTable* super = &this->super;\n   ProcessTable_init(super, Class(LinuxProcess), host, pidMatchList);\n\n   LinuxProcessTable_initTtyDrivers(this);\n\n   // Test /proc/PID/smaps_rollup availability (faster to parse, Linux 4.14+)\n   this->haveSmapsRollup = (access(PROCDIR \"/self/smaps_rollup\", R_OK) == 0);\n\n   // Read PID namespace inode number\n   {\n      struct stat sb;\n      int r = stat(PROCDIR \"/self/ns/pid\", &sb);\n      if (r == 0) {\n         rootPidNs = sb.st_ino;\n      } else {\n         rootPidNs = (ino_t)-1;\n      }\n   }\n\n   return super;\n}\n\nvoid ProcessTable_delete(Object* cast) {\n   LinuxProcessTable* this = (LinuxProcessTable*) cast;\n   ProcessTable_done(&this->super);\n   if (this->ttyDrivers) {\n      for (int i = 0; this->ttyDrivers[i].path; i++) {\n         free(this->ttyDrivers[i].path);\n      }\n      free(this->ttyDrivers);\n   }\n   #ifdef HAVE_DELAYACCT\n   LibNl_destroyNetlinkSocket(this);\n   #endif\n   free(this);\n}\n\nstatic inline unsigned long long LinuxProcessTable_adjustTime(const LinuxMachine* lhost, unsigned long long t) {\n   return t * 100 / lhost->jiffies;\n}\n\n/* Taken from: https://github.com/torvalds/linux/blob/64570fbc14f8d7cb3fe3995f20e26bc25ce4b2cc/fs/proc/array.c#L120 */\nstatic inline ProcessState LinuxProcessTable_getProcessState(char state) {\n   switch (state) {\n      case 'S': return SLEEPING;\n      case 'X': return DEFUNCT;\n      case 'Z': return ZOMBIE;\n      case 't': return TRACED;\n      case 'T': return STOPPED;\n      case 'D': return UNINTERRUPTIBLE_WAIT;\n      case 'R': return RUNNING;\n      case 'P': return BLOCKED;\n      case 'I': return IDLE;\n      default: return UNKNOWN;\n   }\n}\n\n/*\n * Read /proc/<pid>/stat (thread-specific data)\n */\nstatic bool LinuxProcessTable_readStatFile(LinuxProcess* lp, openat_arg_t procFd, const LinuxMachine* lhost, bool scanMainThread, char* command, size_t commLen) {\n   Process* process = &lp->super;\n\n   char buf[MAX_READ + 1];\n   char path[22] = \"stat\";\n   if (scanMainThread) {\n      xSnprintf(path, sizeof(path), \"task/%\"PRIi32\"/stat\", (int32_t)Process_getPid(process));\n   }\n   ssize_t r = Compat_readfileat(procFd, path, buf, sizeof(buf));\n   if (r < 0)\n      return false;\n\n   /* (1) pid   -  %d */\n   assert(Process_getPid(process) == atoi(buf));\n   char* location = strchr(buf, ' ');\n   if (!location)\n      return false;\n\n   /* (2) comm  -  (%s) */\n   if (!location[0] || !location[1])\n      return false;\n\n   location += 2;\n   char* end = strrchr(location, ')');\n   if (!end)\n      return false;\n\n   if (end < location)\n      return false;\n\n   String_safeStrncpy(command, location, MINIMUM((size_t)(end - location + 1), commLen));\n\n   if (!end[0] || !end[1])\n      return false;\n\n   location = end + 2;\n\n   /* (3) state  -  %c */\n   process->state = LinuxProcessTable_getProcessState(location[0]);\n\n   if (!location[0] || !location[1])\n      return false;\n\n   location += 2;\n\n   /* (4) ppid  -  %d */\n   Process_setParent(process, fast_strtoi_dec(&location, 0));\n\n   if (!location[0])\n      return false;\n\n   location += 1;\n\n   /* (5) pgrp  -  %d */\n   process->pgrp = fast_strtoi_dec(&location, 0);\n\n   if (!location[0])\n      return false;\n\n   location += 1;\n\n   /* (6) session  -  %d */\n   process->session = fast_strtoi_dec(&location, 0);\n\n   if (!location[0])\n      return false;\n\n   location += 1;\n\n   /* (7) tty_nr  -  %d */\n   process->tty_nr = fast_strtoul_dec(&location, 0);\n\n   if (!location[0])\n      return false;\n\n   location += 1;\n\n   /* (8) tpgid  -  %d */\n   process->tpgid = fast_strtoi_dec(&location, 0);\n\n   if (!location[0])\n      return false;\n\n   location += 1;\n\n   /* (9) flags  -  %u */\n   lp->flags = fast_strtoul_dec(&location, 0);\n\n   if (!location[0])\n      return false;\n\n   location += 1;\n\n   /* (10) minflt  -  %lu */\n   process->minflt = fast_strtoull_dec(&location, 0);\n\n   if (!location[0])\n      return false;\n\n   location += 1;\n\n   /* (11) cminflt  -  %lu */\n   lp->cminflt = fast_strtoull_dec(&location, 0);\n\n   if (!location[0])\n      return false;\n\n   location += 1;\n\n   /* (12) majflt  -  %lu */\n   process->majflt = fast_strtoull_dec(&location, 0);\n\n   if (!location[0])\n      return false;\n\n   location += 1;\n\n   /* (13) cmajflt  -  %lu */\n   lp->cmajflt = fast_strtoull_dec(&location, 0);\n\n   if (!location[0])\n      return false;\n\n   location += 1;\n\n   /* (14) utime  -  %lu */\n   lp->utime = LinuxProcessTable_adjustTime(lhost, fast_strtoull_dec(&location, 0));\n\n   if (!location[0])\n      return false;\n\n   location += 1;\n\n   /* (15) stime  -  %lu */\n   lp->stime = LinuxProcessTable_adjustTime(lhost, fast_strtoull_dec(&location, 0));\n\n   if (!location[0])\n      return false;\n\n   location += 1;\n\n   /* (16) cutime  -  %ld */\n   lp->cutime = LinuxProcessTable_adjustTime(lhost, fast_strtoull_dec(&location, 0));\n\n   if (!location[0])\n      return false;\n\n   location += 1;\n\n   /* (17) cstime  -  %ld */\n   lp->cstime = LinuxProcessTable_adjustTime(lhost, fast_strtoull_dec(&location, 0));\n\n   if (!location[0])\n      return false;\n\n   location += 1;\n\n   /* (18) priority  -  %ld */\n   process->priority = fast_strtol_dec(&location, 0);\n\n   if (!location[0])\n      return false;\n\n   location += 1;\n\n   /* (19) nice  -  %ld */\n   process->nice = fast_strtoi_dec(&location, 0);\n\n   if (!location[0])\n      return false;\n\n   location += 1;\n\n   /* (20) num_threads  -  %ld */\n   process->nlwp = fast_strtol_dec(&location, 0);\n\n   if (!location[0])\n      return false;\n\n   location += 1;\n\n   /* Skip (21) itrealvalue  -  %ld */\n   location = strchr(location, ' ');\n\n   if (!location)\n      return false;\n\n   location += 1;\n\n   /* (22) starttime  -  %llu */\n   if (process->starttime_ctime == 0) {\n      process->starttime_ctime = lhost->boottime + LinuxProcessTable_adjustTime(lhost, fast_strtoll_dec(&location, 0)) / 100;\n   } else {\n      location = strchr(location, ' ');\n      if (!location)\n         return false;\n   }\n   location += 1;\n\n   /* Skip (23) - (38) */\n   for (int i = 0; i < 16; i++) {\n      location = strchr(location, ' ');\n\n      if (!location)\n         return false;\n\n      location += 1;\n   }\n\n   assert(location != NULL);\n\n   /* (39) processor  -  %d */\n   process->processor = fast_strtoi_dec(&location, 0);\n\n   /* Ignore further fields */\n\n   process->time = lp->utime + lp->stime;\n\n   return true;\n}\n\n/*\n * Read /proc/<pid>/status (thread-specific data)\n */\nstatic bool LinuxProcessTable_readStatusFile(Process* process, openat_arg_t procFd) {\n   LinuxProcess* lp = (LinuxProcess*) process;\n\n   unsigned long ctxt = 0;\n   process->isRunningInContainer = TRI_OFF;\n#ifdef HAVE_VSERVER\n   lp->vxid = 0;\n#endif\n\n   FILE* statusfile = fopenat(procFd, \"status\", \"r\");\n   if (!statusfile)\n      return false;\n\n   char buffer[PROC_LINE_LENGTH + 1] = {0};\n\n   while (fgets(buffer, sizeof(buffer), statusfile)) {\n\n      if (String_startsWith(buffer, \"NSpid:\")) {\n         const char* ptr = buffer;\n         int pid_ns_count = 0;\n         while (*ptr && *ptr != '\\n' && !isdigit((unsigned char)*ptr))\n            ++ptr;\n\n         while (*ptr && *ptr != '\\n') {\n            if (isdigit(*ptr))\n               pid_ns_count++;\n            while (isdigit((unsigned char)*ptr))\n               ++ptr;\n            while (*ptr && *ptr != '\\n' && !isdigit((unsigned char)*ptr))\n               ++ptr;\n         }\n\n         if (pid_ns_count > 1)\n            process->isRunningInContainer = TRI_ON;\n\n      } else if (String_startsWith(buffer, \"voluntary_ctxt_switches:\")) {\n         unsigned long vctxt;\n         int ok = sscanf(buffer, \"voluntary_ctxt_switches:\\t%lu\", &vctxt);\n         if (ok == 1) {\n            ctxt += vctxt;\n         }\n\n      } else if (String_startsWith(buffer, \"nonvoluntary_ctxt_switches:\")) {\n         unsigned long nvctxt;\n         int ok = sscanf(buffer, \"nonvoluntary_ctxt_switches:\\t%lu\", &nvctxt);\n         if (ok == 1) {\n            ctxt += nvctxt;\n         }\n\n#ifdef HAVE_VSERVER\n      } else if (String_startsWith(buffer, \"VxID:\")) {\n         int vxid;\n         int ok = sscanf(buffer, \"VxID:\\t%32d\", &vxid);\n         if (ok == 1) {\n            lp->vxid = vxid;\n         }\n#ifdef HAVE_ANCIENT_VSERVER\n      } else if (String_startsWith(buffer, \"s_context:\")) {\n         int vxid;\n         int ok = sscanf(buffer, \"s_context:\\t%32d\", &vxid);\n         if (ok == 1) {\n            lp->vxid = vxid;\n         }\n#endif /* HAVE_ANCIENT_VSERVER */\n#endif /* HAVE_VSERVER */\n      }\n   }\n\n   fclose(statusfile);\n\n   lp->ctxt_diff = (ctxt > lp->ctxt_total) ? (ctxt - lp->ctxt_total) : 0;\n   lp->ctxt_total = ctxt;\n\n   return true;\n}\n\n/*\n * Gather user of task (process-shared data)\n */\nstatic bool LinuxProcessTable_updateUser(const Machine* host, Process* process, openat_arg_t procFd, const LinuxProcess* mainTask) {\n   if (mainTask) {\n      process->st_uid = mainTask->super.st_uid;\n      process->user = mainTask->super.user;\n      return true;\n   }\n\n   struct stat sb;\n#ifdef HAVE_OPENAT\n   int statok = fstat(procFd, &sb);\n#else\n   int statok = stat(procFd, &sb);\n#endif\n   if (statok == -1)\n      return false;\n\n   if (process->st_uid != sb.st_uid) {\n      process->st_uid = sb.st_uid;\n      process->user = UsersTable_getRef(host->usersTable, sb.st_uid);\n   }\n\n   return true;\n}\n\n/*\n * Read /proc/<pid>/io (thread-specific data)\n */\nstatic void LinuxProcessTable_readIoFile(LinuxProcess* lp, openat_arg_t procFd, bool scanMainThread) {\n   Process* process = &lp->super;\n   const Machine* host = process->super.host;\n   char path[20] = \"io\";\n   char buffer[1024];\n   if (scanMainThread) {\n      xSnprintf(path, sizeof(path), \"task/%\"PRIi32\"/io\", (int32_t)Process_getPid(process));\n   }\n   ssize_t r = Compat_readfileat(procFd, path, buffer, sizeof(buffer));\n   if (r < 0) {\n      lp->io_rate_read_bps = NAN;\n      lp->io_rate_write_bps = NAN;\n      lp->io_rchar = ULLONG_MAX;\n      lp->io_wchar = ULLONG_MAX;\n      lp->io_syscr = ULLONG_MAX;\n      lp->io_syscw = ULLONG_MAX;\n      lp->io_read_bytes = ULLONG_MAX;\n      lp->io_write_bytes = ULLONG_MAX;\n      lp->io_cancelled_write_bytes = ULLONG_MAX;\n      lp->io_last_scan_time_ms = host->realtimeMs;\n      return;\n   }\n\n   unsigned long long last_read = lp->io_read_bytes;\n   unsigned long long last_write = lp->io_write_bytes;\n   unsigned long long time_delta = saturatingSub(host->realtimeMs, lp->io_last_scan_time_ms);\n\n   // Note: Linux Kernel documentation states that /proc/<pid>/io may be racy\n   // on 32-bit machines. (Documentation/filesystems/proc.rst)\n\n   char* buf = buffer;\n   const char* line;\n   while ((line = strsep(&buf, \"\\n\")) != NULL) {\n      switch (line[0]) {\n         case 'r':\n            if (line[1] == 'c' && String_startsWith(line + 2, \"har: \")) {\n               lp->io_rchar = strtoull(line + 7, NULL, 10);\n            } else if (String_startsWith(line + 1, \"ead_bytes: \")) {\n               lp->io_read_bytes = strtoull(line + 12, NULL, 10);\n               lp->io_rate_read_bps = time_delta ? saturatingSub(lp->io_read_bytes, last_read) * /*ms to s*/1000. / time_delta : NAN;\n            }\n            break;\n         case 'w':\n            if (line[1] == 'c' && String_startsWith(line + 2, \"har: \")) {\n               lp->io_wchar = strtoull(line + 7, NULL, 10);\n            } else if (String_startsWith(line + 1, \"rite_bytes: \")) {\n               lp->io_write_bytes = strtoull(line + 13, NULL, 10);\n               lp->io_rate_write_bps = time_delta ? saturatingSub(lp->io_write_bytes, last_write) * /*ms to s*/1000. / time_delta : NAN;\n            }\n            break;\n         case 's':\n            if (line[4] == 'r' && String_startsWith(line + 1, \"yscr: \")) {\n               lp->io_syscr = strtoull(line + 7, NULL, 10);\n            } else if (String_startsWith(line + 1, \"yscw: \")) {\n               lp->io_syscw = strtoull(line + 7, NULL, 10);\n            }\n            break;\n         case 'c':\n            if (String_startsWith(line + 1, \"ancelled_write_bytes: \")) {\n               lp->io_cancelled_write_bytes = strtoull(line + 23, NULL, 10);\n            }\n      }\n   }\n\n   lp->io_last_scan_time_ms = host->realtimeMs;\n}\n\ntypedef struct LibraryData_ {\n   uint64_t size;\n   bool exec;\n} LibraryData;\n\nstatic void LinuxProcessTable_calcLibSize_helper(ATTR_UNUSED ht_key_t key, void* value, void* data) {\n   if (!data)\n      return;\n\n   if (!value)\n      return;\n\n   const LibraryData* v = (const LibraryData*)value;\n   uint64_t* d = (uint64_t*)data;\n   if (!v->exec)\n      return;\n\n   *d += v->size;\n}\n\n/*\n * Read /proc/<pid>/maps (process-shared data)\n */\nstatic void LinuxProcessTable_readMaps(LinuxProcess* process, openat_arg_t procFd, const LinuxMachine* host, bool calcSize, bool checkDeletedLib) {\n   Process* proc = (Process*)process;\n\n   proc->usesDeletedLib = false;\n\n   FILE* mapsfile = fopenat(procFd, \"maps\", \"r\");\n   if (!mapsfile)\n      return;\n\n   Hashtable* ht = NULL;\n   if (calcSize)\n      ht = Hashtable_new(64, true);\n\n   char buffer[1024];\n   while (fgets(buffer, sizeof(buffer), mapsfile)) {\n      uint64_t map_start;\n      uint64_t map_end;\n      bool map_execute;\n      uint64_t map_devmaj;\n      uint64_t map_devmin;\n      uint64_t map_inode;\n\n      // Short circuit test: Look for a slash\n      if (!strchr(buffer, '/'))\n         continue;\n\n      // Parse format: \"%Lx-%Lx %4s %x %2x:%2x %Ld\"\n      char* readptr = buffer;\n\n      map_start = fast_strtoull_hex(&readptr, 16);\n      if ('-' != *readptr++)\n         continue;\n\n      map_end = fast_strtoull_hex(&readptr, 16);\n      if (' ' != *readptr++)\n         continue;\n\n      if (!readptr[0] || !readptr[1] || !readptr[2] || !readptr[3])\n         continue;\n\n      map_execute = (readptr[2] == 'x');\n      readptr += 4;\n      if (' ' != *readptr++)\n         continue;\n\n      while (*readptr > ' ')\n         readptr++; // Skip parsing this hex value\n      if (' ' != *readptr++)\n         continue;\n\n      map_devmaj = fast_strtoull_hex(&readptr, 4);\n      if (':' != *readptr++)\n         continue;\n\n      map_devmin = fast_strtoull_hex(&readptr, 4);\n      if (' ' != *readptr++)\n         continue;\n\n      //Minor shortcut: Once we know there's no file for this region, we skip\n      if (!map_devmaj && !map_devmin)\n         continue;\n\n      map_inode = fast_strtoull_dec(&readptr, 0);\n      if (!map_inode)\n         continue;\n\n      if (calcSize) {\n         LibraryData* libdata = Hashtable_get(ht, (ht_key_t)map_inode);\n         if (!libdata) {\n            libdata = xCalloc(1, sizeof(LibraryData));\n            Hashtable_put(ht, (ht_key_t)map_inode, libdata);\n         }\n\n         libdata->size += map_end - map_start;\n         libdata->exec |= map_execute;\n      }\n\n      if (checkDeletedLib && map_execute && !proc->usesDeletedLib) {\n         while (*readptr == ' ')\n            readptr++;\n\n         if (*readptr != '/')\n            continue;\n\n         if (String_startsWith(readptr, \"/memfd:\"))\n            continue;\n\n         /* Virtualbox maps /dev/zero for memory allocation. That results in\n          * false positive, so ignore. */\n         if (String_eq(readptr, \"/dev/zero (deleted)\\n\"))\n            continue;\n\n         if (strstr(readptr, \" (deleted)\\n\")) {\n            proc->usesDeletedLib = true;\n            if (!calcSize)\n               break;\n         }\n      }\n   }\n\n   fclose(mapsfile);\n\n   if (calcSize) {\n      uint64_t total_size = 0;\n      Hashtable_foreach(ht, LinuxProcessTable_calcLibSize_helper, &total_size);\n\n      Hashtable_delete(ht);\n\n      process->m_lrs = total_size / host->pageSize;\n   }\n}\n\n/*\n * Read /proc/<pid>/statm (process-shared data)\n */\nstatic bool LinuxProcessTable_readStatmFile(LinuxProcess* process, openat_arg_t procFd, const LinuxMachine* host, const LinuxProcess* mainTask) {\n   if (mainTask) {\n      process->super.m_virt     = mainTask->super.m_virt;\n      process->super.m_resident = mainTask->super.m_resident;\n      return true;\n   }\n\n   char statmdata[128] = {0};\n\n   if (Compat_readfileat(procFd, \"statm\", statmdata, sizeof(statmdata)) < 1) {\n      return false;\n   }\n\n   long int dummy, dummy2;\n\n   int r = sscanf(statmdata, \"%ld %ld %ld %ld %ld %ld %ld\",\n                  &process->super.m_virt,\n                  &process->super.m_resident,\n                  &process->m_share,\n                  &process->m_trs,\n                  &dummy, /* unused since Linux 2.6; always 0 */\n                  &process->m_drs,\n                  &dummy2); /* unused since Linux 2.6; always 0 */\n\n   if (r == 7) {\n      process->super.m_virt *= host->pageSizeKB;\n      process->super.m_resident *= host->pageSizeKB;\n\n      process->m_priv = process->super.m_resident - (process->m_share * host->pageSizeKB);\n   }\n\n   return r == 7;\n}\n\n/*\n * Read /proc/<pid>/smaps (process-shared data)\n */\nstatic bool LinuxProcessTable_readSmapsFile(LinuxProcess* process, openat_arg_t procFd, bool haveSmapsRollup) {\n   //http://elixir.free-electrons.com/linux/v4.10/source/fs/proc/task_mmu.c#L719\n   //kernel will return data in chunks of size PAGE_SIZE or less.\n   FILE* fp = fopenat(procFd, haveSmapsRollup ? \"smaps_rollup\" : \"smaps\", \"r\");\n   if (!fp)\n      return false;\n\n   process->m_pss   = 0;\n   process->m_swap  = 0;\n   process->m_psswp = 0;\n\n   char buffer[256];\n   while (fgets(buffer, sizeof(buffer), fp)) {\n      if (!strchr(buffer, '\\n')) {\n         // Partial line, skip to end of this line\n         if (!skipEndOfLine(fp)) {\n            fclose(fp);\n            return false;\n         }\n\n      }\n\n      if (String_startsWith(buffer, \"Pss:\")) {\n         process->m_pss += strtol(buffer + 4, NULL, 10);\n      } else if (String_startsWith(buffer, \"Swap:\")) {\n         process->m_swap += strtol(buffer + 5, NULL, 10);\n      } else if (String_startsWith(buffer, \"SwapPss:\")) {\n         process->m_psswp += strtol(buffer + 8, NULL, 10);\n      }\n   }\n\n   fclose(fp);\n   return true;\n}\n\n#ifdef HAVE_OPENVZ\n\nstatic void LinuxProcessTable_readOpenVZData(LinuxProcess* process, openat_arg_t procFd) {\n   if (access(PROCDIR \"/vz\", R_OK) != 0) {\n      free(process->ctid);\n      process->ctid = NULL;\n      process->vpid = Process_getPid(&process->super);\n      return;\n   }\n\n   FILE* file = fopenat(procFd, \"status\", \"r\");\n   if (!file) {\n      free(process->ctid);\n      process->ctid = NULL;\n      process->vpid = Process_getPid(&process->super);\n      return;\n   }\n\n   bool foundEnvID = false;\n   bool foundVPid = false;\n   char linebuf[256];\n   while (fgets(linebuf, sizeof(linebuf), file) != NULL) {\n      if (strchr(linebuf, '\\n') == NULL) {\n         // Partial line, skip to end of this line\n         if (!skipEndOfLine(file)) {\n            break;\n         }\n      }\n\n      char* name_value_sep = strchr(linebuf, ':');\n      if (name_value_sep == NULL) {\n         continue;\n      }\n\n      int field;\n      if (0 == strncasecmp(linebuf, \"envID\", name_value_sep - linebuf)) {\n         field = 1;\n      } else if (0 == strncasecmp(linebuf, \"VPid\", name_value_sep - linebuf)) {\n         field = 2;\n      } else {\n         continue;\n      }\n\n      do {\n         name_value_sep++;\n      } while (*name_value_sep != '\\0' && *name_value_sep <= 32);\n\n      char* value_end = name_value_sep;\n\n      while (*value_end > 32) {\n         value_end++;\n      }\n\n      if (name_value_sep == value_end) {\n         continue;\n      }\n\n      *value_end = '\\0';\n\n      switch (field) {\n         case 1:\n            foundEnvID = true;\n            if (!String_eq(name_value_sep, process->ctid ? process->ctid : \"\"))\n               free_and_xStrdup(&process->ctid, name_value_sep);\n            break;\n         case 2:\n            if ((process->vpid = strtopid(name_value_sep)) != 0)\n               foundVPid = true;\n            break;\n         default:\n            //Sanity Check: Should never reach here, or the implementation is missing something!\n            assert(false && \"OpenVZ handling: Unimplemented case for field handling reached.\");\n      }\n   }\n\n   fclose(file);\n\n   if (!foundEnvID) {\n      free(process->ctid);\n      process->ctid = NULL;\n   }\n\n   if (!foundVPid) {\n      process->vpid = Process_getPid(&process->super);\n   }\n}\n\n#endif /* HAVE_OPENVZ */\n\n/*\n * Read /proc/<pid>/cgroup (thread-specific data)\n */\nstatic void LinuxProcessTable_readCGroupFile(LinuxProcess* process, openat_arg_t procFd) {\n   FILE* file = fopenat(procFd, \"cgroup\", \"r\");\n   if (!file) {\n      if (process->cgroup) {\n         free(process->cgroup);\n         process->cgroup = NULL;\n      }\n      if (process->cgroup_short) {\n         free(process->cgroup_short);\n         process->cgroup_short = NULL;\n      }\n      if (process->container_short) {\n         free(process->container_short);\n         process->container_short = NULL;\n      }\n      return;\n   }\n   char output[PROC_LINE_LENGTH + 1];\n   output[0] = '\\0';\n   char* at = output;\n   size_t left = PROC_LINE_LENGTH;\n   while (!feof(file) && left > 0) {\n      char buffer[PROC_LINE_LENGTH + 1];\n      const char* ok = fgets(buffer, PROC_LINE_LENGTH, file);\n      if (!ok)\n         break;\n\n      const char* group = buffer;\n      for (size_t i = 0; i < 2; i++) {\n         group = String_strchrnul(group, ':');\n         if (!*group)\n            break;\n         group++;\n      }\n\n      const char* eol = String_strchrnul(group, '\\n');\n      char* eol_w = &buffer[eol - buffer];\n      *eol_w = '\\0';\n\n      if (at != output) {\n         *at = ';';\n         at++;\n         left--;\n      }\n\n      int wrote = snprintf(at, left, \"%s\", group);\n      if (wrote < 0 || (size_t)wrote >= left) {\n         // Output was truncated, we are done\n         break;\n      }\n\n      at += (size_t)wrote;\n      left -= (size_t)wrote;\n   }\n   fclose(file);\n\n   bool changed = !process->cgroup || !String_eq(process->cgroup, output);\n\n   Row_updateFieldWidth(CGROUP, strlen(output));\n   free_and_xStrdup(&process->cgroup, output);\n\n   if (!changed) {\n      if (process->cgroup_short) {\n         Row_updateFieldWidth(CCGROUP, strlen(process->cgroup_short));\n      } else {\n         //CCGROUP is alias to normal CGROUP if shortening fails\n         Row_updateFieldWidth(CCGROUP, strlen(process->cgroup));\n      }\n      if (process->container_short) {\n         Row_updateFieldWidth(CONTAINER, strlen(process->container_short));\n      } else {\n         Row_updateFieldWidth(CONTAINER, strlen(\"N/A\"));\n      }\n      return;\n   }\n\n   char* cgroup_short = CGroup_filterName(process->cgroup);\n   if (cgroup_short) {\n      Row_updateFieldWidth(CCGROUP, strlen(cgroup_short));\n      free_and_xStrdup(&process->cgroup_short, cgroup_short);\n      free(cgroup_short);\n   } else {\n      //CCGROUP is alias to normal CGROUP if shortening fails\n      Row_updateFieldWidth(CCGROUP, strlen(process->cgroup));\n      free(process->cgroup_short);\n      process->cgroup_short = NULL;\n   }\n\n   char* container_short = CGroup_filterContainer(process->cgroup);\n   if (container_short) {\n      Row_updateFieldWidth(CONTAINER, strlen(container_short));\n      free_and_xStrdup(&process->container_short, container_short);\n      free(container_short);\n   } else {\n      //CONTAINER is just \"N/A\" if shortening fails\n      Row_updateFieldWidth(CONTAINER, strlen(\"N/A\"));\n      free(process->container_short);\n      process->container_short = NULL;\n   }\n}\n\n/*\n * Read /proc/<pid>/oom_score (process-shared data)\n */\nstatic void LinuxProcessTable_readOomData(LinuxProcess* process, openat_arg_t procFd, const LinuxProcess* mainTask) {\n   if (mainTask) {\n      process->oom = mainTask->oom;\n      return;\n   }\n\n   char buffer[PROC_LINE_LENGTH + 1] = {0};\n\n   ssize_t oomRead = Compat_readfileat(procFd, \"oom_score\", buffer, sizeof(buffer));\n   if (oomRead < 1) {\n      return;\n   }\n\n   char* oomPtr = buffer;\n   uint64_t oom = fast_strtoull_dec(&oomPtr, oomRead);\n   if (*oomPtr && *oomPtr != '\\n' && *oomPtr != ' ') {\n      return;\n   }\n\n   if (oom > UINT_MAX) {\n      return;\n   }\n\n   process->oom = (unsigned int)oom;\n}\n\n/*\n * Read /proc/<pid>/autogroup (process-shared data)\n */\nstatic void LinuxProcessTable_readAutogroup(LinuxProcess* process, openat_arg_t procFd, const LinuxProcess* mainTask) {\n   if (mainTask) {\n      process->autogroup_id = mainTask->autogroup_id;\n      return;\n   }\n\n   process->autogroup_id = -1;\n\n   char autogroup[64]; // space for two numeric values and fixed length strings\n   ssize_t amtRead = Compat_readfileat(procFd, \"autogroup\", autogroup, sizeof(autogroup));\n   if (amtRead < 0)\n      return;\n\n   long int identity;\n   int nice;\n   int ok = sscanf(autogroup, \"/autogroup-%ld nice %d\", &identity, &nice);\n   if (ok == 2) {\n      process->autogroup_id = identity;\n      process->autogroup_nice = nice;\n   }\n}\n\n/*\n * Read /proc/<pid>/attr/current (process-shared data)\n */\nstatic void LinuxProcessTable_readSecattrData(LinuxProcess* process, openat_arg_t procFd, const LinuxProcess* mainTask) {\n   if (mainTask) {\n      const char* mainSecAttr = mainTask->secattr;\n      if (mainSecAttr) {\n         free_and_xStrdup(&process->secattr, mainSecAttr);\n      } else {\n         free(process->secattr);\n         process->secattr = NULL;\n      }\n      return;\n   }\n\n   char buffer[PROC_LINE_LENGTH + 1] = {0};\n\n   ssize_t attrdata = Compat_readfileat(procFd, \"attr/current\", buffer, sizeof(buffer));\n   if (attrdata < 1) {\n      free(process->secattr);\n      process->secattr = NULL;\n      return;\n   }\n\n   char* newline = strchr(buffer, '\\n');\n   if (newline) {\n      *newline = '\\0';\n   }\n\n   Row_updateFieldWidth(SECATTR, strlen(buffer));\n\n   free_and_xStrdup(&process->secattr, buffer);\n}\n\n/*\n * Read /proc/<pid>/cwd (process-shared data)\n */\nstatic void LinuxProcessTable_readCwd(LinuxProcess* process, openat_arg_t procFd, const LinuxProcess* mainTask) {\n   if (mainTask) {\n      const char* mainCwd = mainTask->super.procCwd;\n      if (mainCwd) {\n         free_and_xStrdup(&process->super.procCwd, mainCwd);\n      } else {\n         free(process->super.procCwd);\n         process->super.procCwd = NULL;\n      }\n      return;\n   }\n\n   char pathBuffer[PATH_MAX + 1] = {0};\n\n#if defined(HAVE_READLINKAT) && defined(HAVE_OPENAT)\n   ssize_t r = readlinkat(procFd, \"cwd\", pathBuffer, sizeof(pathBuffer) - 1);\n#else\n   ssize_t r = Compat_readlink(procFd, \"cwd\", pathBuffer, sizeof(pathBuffer) - 1);\n#endif\n\n   if (r < 0) {\n      free(process->super.procCwd);\n      process->super.procCwd = NULL;\n      return;\n   }\n\n   pathBuffer[r] = '\\0';\n\n   free_and_xStrdup(&process->super.procCwd, pathBuffer);\n}\n\n/*\n * Read /proc/<pid>/exe (process-shared data)\n */\nstatic void LinuxProcessList_readExe(Process* process, openat_arg_t procFd, const LinuxProcess* mainTask) {\n   if (mainTask) {\n      Process_updateExe(process, mainTask->super.procExe);\n      process->procExeDeleted = mainTask->super.procExeDeleted;\n      return;\n   }\n\n   char filename[PATH_MAX + 1];\n\n#if defined(HAVE_READLINKAT) && defined(HAVE_OPENAT)\n   ssize_t amtRead = readlinkat(procFd, \"exe\", filename, sizeof(filename) - 1);\n#else\n   ssize_t amtRead = Compat_readlink(procFd, \"exe\", filename, sizeof(filename) - 1);\n#endif\n   if (amtRead > 0) {\n      filename[amtRead] = 0;\n      if (!process->procExe ||\n          (!process->procExeDeleted && !String_eq(filename, process->procExe)) ||\n          process->procExeDeleted) {\n\n         const char* deletedMarker = \" (deleted)\";\n         const size_t markerLen = strlen(deletedMarker);\n         const size_t filenameLen = strlen(filename);\n\n         if (filenameLen > markerLen) {\n            bool oldExeDeleted = process->procExeDeleted;\n\n            process->procExeDeleted = String_eq(filename + filenameLen - markerLen, deletedMarker);\n\n            if (process->procExeDeleted)\n               filename[filenameLen - markerLen] = '\\0';\n\n            if (oldExeDeleted != process->procExeDeleted)\n               process->mergedCommand.lastUpdate = 0;\n         }\n\n         Process_updateExe(process, filename);\n      }\n   } else if (process->procExe) {\n      Process_updateExe(process, NULL);\n      process->procExeDeleted = false;\n   }\n}\n\n/*\n * Helper function to read a file with dynamic buffer allocation.\n * Returns the buffer and sets *amtRead to bytes read. Caller must free the buffer in the success case.\n * Returns NULL on error.\n */\nstatic char* readFileDynamic(openat_arg_t procFd, const char* filename, ssize_t* amtRead) {\n   size_t bufferSize = 512;\n   char* buffer = xMalloc(bufferSize);\n\n   *amtRead = Compat_readfileat(procFd, filename, buffer, bufferSize);\n\n   // If buffer was full, the file might be larger, so retry with a bigger buffer\n   // Limit to MAX_CMDLINE_BUFFER_SIZE to prevent excessive memory allocation\n   while (*amtRead > 0 && (size_t)*amtRead == bufferSize - 1 && bufferSize < MAX_CMDLINE_BUFFER_SIZE) {\n      bufferSize *= 2;\n      buffer = xRealloc(buffer, bufferSize);\n      *amtRead = Compat_readfileat(procFd, filename, buffer, bufferSize);\n   }\n\n   if (*amtRead <= 0) {\n      free(buffer);\n      return NULL;\n   }\n\n   return buffer;\n}\n\n/*\n * Read /proc/<pid>/cmdline (process-shared data)\n */\nstatic bool LinuxProcessTable_readCmdlineFile(Process* process, openat_arg_t procFd, const LinuxProcess* mainTask) {\n   LinuxProcessList_readExe(process, procFd, mainTask);\n\n   ssize_t amtRead;\n   char* command = readFileDynamic(procFd, \"cmdline\", &amtRead);\n   if (!command)\n      return false;\n\n   size_t tokenEnd = (size_t)-1;\n   size_t tokenStart = (size_t)-1;\n   size_t lastChar = 0;\n   bool argSepNUL = false;\n   bool argSepSpace = false;\n\n   for (size_t i = 0; i < (size_t)amtRead; i++) {\n      const char argChar = command[i];\n\n      /* newline used as delimiter - when forming the mergedCommand, newline is\n       * converted to space by Process_makeCommandStr */\n      if (argChar == '\\n') {\n         /* Set to some other non-printable character */\n         command[i] = '\\r';\n         continue;\n      }\n\n      if (argChar == '\\0') {\n         command[i] = '\\n';\n\n         // Set tokenEnd to the NUL byte\n         if (tokenEnd == (size_t)-1) {\n            tokenEnd = i;\n         }\n\n         continue;\n      }\n\n      // If this is true, there's a NUL byte in the middle of command\n      if (tokenEnd != (size_t)-1) {\n         argSepNUL = true;\n      }\n\n      /* Record some information for the argument parsing heuristic below. */\n      if (argChar <= ' ') {\n         argSepSpace = true;\n      }\n\n      /* Detect the last / before the end of the token as\n       * the start of the basename in cmdline, see Process_writeCommand */\n      if (argChar == '/' && tokenEnd == (size_t)-1) {\n         tokenStart = i + 1;\n      }\n\n      lastChar = i;\n   }\n\n   command[lastChar + 1] = '\\0';\n\n   if (!argSepNUL && argSepSpace) {\n      /* Argument parsing heuristic.\n       *\n       * This heuristic is used for processes that rewrite their command line.\n       * Normally the command line is split by using NUL bytes between each argument.\n       * But some programs like chrome flatten this using spaces.\n       *\n       * This heuristic tries its best to undo this loss of information.\n       * To achieve this, we treat every character <= 32 as argument separators\n       * (i.e. all of ASCII control sequences and space).\n       * We then search for the basename of the cmdline in the first argument we found that way.\n       * As path names may contain we try to cross-validate if the path we got that way exists.\n       */\n\n      tokenStart = (size_t)-1;\n      tokenEnd = (size_t)-1;\n\n      size_t exeLen = process->procExe ? strlen(process->procExe) : 0;\n\n      if (process->procExe && String_startsWith(command, process->procExe) &&\n         exeLen < lastChar && command[exeLen] <= ' ') {\n         tokenStart = process->procExeBasenameOffset;\n         tokenEnd = exeLen;\n      }\n\n      // From initial scan we know there's at least one space.\n      // Check if that's part of a filename for an existing file.\n      else if (Compat_faccessat(AT_FDCWD, command, F_OK, AT_SYMLINK_NOFOLLOW) != 0) {\n         // If we reach here the path does not exist.\n         // Thus begin searching for the part of it that actually does.\n         size_t tokenArg0Start = (size_t)-1;\n\n         for (size_t i = 0; i <= lastChar; i++) {\n            const char cmdChar = command[i];\n\n            /* Any ASCII control or space used as delimiter */\n            if (cmdChar <= ' ') {\n               if (tokenEnd != (size_t)-1) {\n                  // Split on every further separator, regardless of path correctness\n                  command[i] = '\\n';\n                  continue;\n               }\n\n               // Found our first argument\n               command[i] = '\\0';\n\n               bool found = Compat_faccessat(AT_FDCWD, command, F_OK, AT_SYMLINK_NOFOLLOW) == 0;\n\n               // Restore if this wasn't it\n               command[i] = found ? '\\n' : cmdChar;\n\n               if (found)\n                  tokenEnd = i;\n               if (tokenArg0Start == (size_t)-1)\n                  tokenArg0Start = tokenStart == (size_t)-1 ? 0 : tokenStart;\n\n               continue;\n            }\n\n            if (tokenEnd != (size_t)-1) {\n               continue;\n            }\n\n            if (cmdChar == '/') {\n               // Normal path separator\n               tokenStart = i + 1;\n            } else if (cmdChar == '\\\\' && (tokenStart == (size_t)-1 || tokenStart == 0 || command[tokenStart - 1] == '\\\\')) {\n               // Windows Path separator (WINE)\n               tokenStart = i + 1;\n            } else if (cmdChar == ':' && (command[i + 1] != '/' && command[i + 1] != '\\\\')) {\n               // Colon not part of a Windows Path\n               tokenEnd = i;\n            } else if (tokenStart == (size_t)-1) {\n               // Relative path\n               tokenStart = i;\n            }\n         }\n\n         if (tokenEnd == (size_t)-1) {\n            tokenStart = tokenArg0Start;\n\n            // No token delimiter found, forcibly split\n            for (size_t i = 0; i <= lastChar; i++) {\n               if (command[i] <= ' ') {\n                  command[i] = '\\n';\n                  if (tokenEnd == (size_t)-1) {\n                     tokenEnd = i;\n                  }\n               }\n            }\n         }\n      }\n\n      /* Some command lines are hard to parse, like\n       *   file.so [kdeinit5] file local:/run/user/1000/klauncherdqbouY.1.slave-socket local:/run/user/1000/kded5TwsDAx.1.slave-socket\n       * Reset if start is behind end.\n       */\n      if (tokenStart >= tokenEnd) {\n         tokenStart = (size_t)-1;\n         tokenEnd = (size_t)-1;\n      }\n   }\n\n   if (tokenStart == (size_t)-1) {\n      tokenStart = 0;\n   }\n\n   if (tokenEnd == (size_t)-1) {\n      tokenEnd = lastChar + 1;\n   }\n\n   Process_updateCmdline(process, command, tokenStart, tokenEnd);\n\n   free(command);\n   return true;\n}\n\n/*\n * Read /proc/<pid>/comm (thread-specific data)\n */\nstatic void LinuxProcessList_readComm(Process* process, openat_arg_t procFd) {\n   ssize_t amtRead;\n   char* command = readFileDynamic(procFd, \"comm\", &amtRead);\n\n   if (command) {\n      command[amtRead - 1] = '\\0';\n      Process_updateComm(process, command);\n      free(command);\n   } else {\n      Process_updateComm(process, NULL);\n   }\n}\n\nstatic char* LinuxProcessTable_updateTtyDevice(TtyDriver* ttyDrivers, unsigned long int tty_nr) {\n   unsigned int maj = major(tty_nr);\n   unsigned int min = minor(tty_nr);\n\n   int i = -1;\n   for (;;) {\n      i++;\n      if ((!ttyDrivers[i].path) || maj < ttyDrivers[i].major) {\n         break;\n      }\n      if (maj > ttyDrivers[i].major) {\n         continue;\n      }\n      if (min < ttyDrivers[i].minorFrom) {\n         break;\n      }\n      if (min > ttyDrivers[i].minorTo) {\n         continue;\n      }\n\n      unsigned int idx = min - ttyDrivers[i].minorFrom;\n\n      struct stat sb;\n\n      for (;;) {\n         char* fullPath = NULL;\n         size_t fullPathLen = xAsprintf(&fullPath, \"%s/%d\", ttyDrivers[i].path, idx);\n         int err = stat(fullPath, &sb);\n         if (err == 0 && major(sb.st_rdev) == maj && minor(sb.st_rdev) == min) {\n            return fullPath;\n         }\n\n         xSnprintf(fullPath, fullPathLen + 1, \"%s%d\", ttyDrivers[i].path, idx);\n         err = stat(fullPath, &sb);\n         if (err == 0 && major(sb.st_rdev) == maj && minor(sb.st_rdev) == min) {\n            return fullPath;\n         }\n         free(fullPath);\n\n         if (idx == min) {\n            break;\n         }\n\n         idx = min;\n      }\n\n      int err = stat(ttyDrivers[i].path, &sb);\n      if (err == 0 && tty_nr == sb.st_rdev) {\n         return xStrdup(ttyDrivers[i].path);\n      }\n   }\n\n   char* out = NULL;\n   xAsprintf(&out, \"/dev/%u:%u\", maj, min);\n   return out;\n}\n\nstatic bool isOlderThan(const Process* proc, unsigned int seconds) {\n   const Machine* host = proc->super.host;\n\n   assert(host->realtimeMs > 0);\n\n   /* Starttime might not yet be parsed */\n   if (proc->starttime_ctime <= 0)\n      return false;\n\n   uint64_t realtime = host->realtimeMs / 1000;\n\n   if (realtime < (uint64_t)proc->starttime_ctime)\n      return false;\n\n   return realtime - proc->starttime_ctime > seconds;\n}\n\nstatic bool LinuxProcessTable_recurseProcTree(LinuxProcessTable* this, openat_arg_t parentFd, const LinuxMachine* lhost, const char* dirname, const LinuxProcess* mainTask) {\n   ProcessTable* pt = (ProcessTable*) this;\n   const Machine* host = &lhost->super;\n   const Settings* settings = host->settings;\n   const ScreenSettings* ss = settings->ss;\n   const struct dirent* entry;\n\n   /* set runningTasks from /proc/stat (from Machine_scanCPUTime) */\n   pt->runningTasks = lhost->runningTasks;\n\n#ifdef HAVE_OPENAT\n   int dirFd = openat(parentFd, dirname, O_RDONLY | O_DIRECTORY | O_NOFOLLOW);\n   if (dirFd < 0)\n      return false;\n   DIR* dir = fdopendir(dirFd);\n#else\n   char dirFd[4096];\n   xSnprintf(dirFd, sizeof(dirFd), \"%s/%s\", parentFd, dirname);\n   DIR* dir = opendir(dirFd);\n#endif\n   if (!dir) {\n      Compat_openatArgClose(dirFd);\n      return false;\n   }\n\n   const bool hideKernelThreads = settings->hideKernelThreads;\n   const bool hideUserlandThreads = settings->hideUserlandThreads;\n   const bool hideRunningInContainer = settings->hideRunningInContainer;\n   while ((entry = readdir(dir)) != NULL) {\n      const char* name = entry->d_name;\n\n      // Ignore all non-directories\n      if (entry->d_type != DT_DIR && entry->d_type != DT_UNKNOWN) {\n         continue;\n      }\n\n      // The RedHat kernel hides threads with a dot.\n      // I believe this is non-standard.\n      if (name[0] == '.') {\n         name++;\n      }\n\n      // Just skip all non-number directories.\n      if (name[0] < '0' || name[0] > '9') {\n         continue;\n      }\n\n      // filename is a number: process directory\n      pid_t pid = strtopid(name);\n      if (pid == 0)\n         continue;\n\n      // Skip task directory of main thread\n      if (mainTask && pid == Process_getPid(&mainTask->super))\n         continue;\n\n#ifdef HAVE_OPENAT\n      int procFd = openat(dirFd, entry->d_name, O_RDONLY | O_DIRECTORY | O_NOFOLLOW);\n      if (procFd < 0)\n         continue;\n#else\n      char procFd[4096];\n      xSnprintf(procFd, sizeof(procFd), \"%s/%s\", dirFd, entry->d_name);\n#endif\n\n      bool preExisting;\n      Process* proc = ProcessTable_getProcess(pt, pid, &preExisting, LinuxProcess_new);\n      LinuxProcess* lp = (LinuxProcess*) proc;\n\n      Process_setThreadGroup(proc, mainTask ? Process_getPid(&mainTask->super) : pid);\n      proc->isUserlandThread = Process_getPid(proc) != Process_getThreadGroup(proc);\n      assert(proc->isUserlandThread == (mainTask != NULL));\n\n      if (!mainTask) {\n         // As the list of tasks/threads is presented as a flat view in procfs\n         // below each directories main entry, it makes no sense to\n         // look for further directories that will not be there.\n         LinuxProcessTable_recurseProcTree(this, procFd, lhost, \"task\", lp);\n      }\n\n      /*\n       * These conditions will not trigger on first occurrence, cause we need to\n       * add the process to the ProcessTable and do all one time scans\n       * (e.g. parsing the cmdline to detect a kernel thread)\n       * But it will short-circuit subsequent scans.\n       */\n      if (preExisting && hideKernelThreads && Process_isKernelThread(proc)) {\n         proc->super.updated = true;\n         proc->super.show = false;\n         pt->kernelThreads++;\n         pt->totalTasks++;\n         Compat_openatArgClose(procFd);\n         continue;\n      }\n      if (preExisting && hideUserlandThreads && Process_isUserlandThread(proc)) {\n         proc->super.updated = true;\n         proc->super.show = false;\n         pt->userlandThreads++;\n         pt->totalTasks++;\n         Compat_openatArgClose(procFd);\n         continue;\n      }\n      if (preExisting && hideRunningInContainer && proc->isRunningInContainer == TRI_ON) {\n         proc->super.updated = true;\n         proc->super.show = false;\n         Compat_openatArgClose(procFd);\n         continue;\n      }\n\n      const bool scanMainThread = !hideUserlandThreads && !Process_isKernelThread(proc) && !mainTask;\n\n      if (!LinuxProcessTable_readStatmFile(lp, procFd, lhost, mainTask))\n         goto errorReadingProcess;\n\n      {\n         bool prev = proc->usesDeletedLib;\n\n         if (!proc->isKernelThread && !proc->isUserlandThread &&\n             ((ss->flags & PROCESS_FLAG_LINUX_LRS_FIX) || (settings->highlightDeletedExe && !proc->procExeDeleted && isOlderThan(proc, 10)))) {\n\n            // Check if we really should recalculate the M_LRS value for this process\n            uint64_t passedTimeInMs = host->realtimeMs - lp->last_mlrs_calctime;\n\n            uint64_t recheck = ((uint64_t)rand()) % 2048;\n\n            if (passedTimeInMs > recheck) {\n               lp->last_mlrs_calctime = host->realtimeMs;\n               LinuxProcessTable_readMaps(lp, procFd, lhost, ss->flags & PROCESS_FLAG_LINUX_LRS_FIX, settings->highlightDeletedExe);\n            }\n         } else {\n            /* Copy from process structure in threads and reset if setting got disabled */\n            proc->usesDeletedLib = (proc->isUserlandThread && mainTask) ? mainTask->super.usesDeletedLib : false;\n            lp->m_lrs = (proc->isUserlandThread && mainTask) ? mainTask->m_lrs : 0;\n         }\n\n         if (prev != proc->usesDeletedLib)\n            proc->mergedCommand.lastUpdate = 0;\n      }\n\n      char statCommand[MAX_NAME + 1];\n      unsigned long long int lasttimes = (lp->utime + lp->stime);\n      unsigned long int last_tty_nr = proc->tty_nr;\n      if (!LinuxProcessTable_readStatFile(lp, procFd, lhost, scanMainThread, statCommand, sizeof(statCommand)))\n         goto errorReadingProcess;\n\n      if (lp->flags & PF_KTHREAD) {\n         proc->isKernelThread = true;\n      }\n\n      if (last_tty_nr != proc->tty_nr && this->ttyDrivers) {\n         free(proc->tty_name);\n         proc->tty_name = LinuxProcessTable_updateTtyDevice(this->ttyDrivers, proc->tty_nr);\n      }\n\n      proc->percent_cpu = NAN;\n      /* lhost->period might be 0 after system sleep */\n      if (lhost->period > 0.0) {\n         float percent_cpu = saturatingSub(lp->utime + lp->stime, lasttimes) / lhost->period * 100.0;\n         proc->percent_cpu = MINIMUM(percent_cpu, host->activeCPUs * 100.0F);\n      }\n      proc->percent_mem = proc->m_resident / (double)(host->totalMem) * 100.0;\n      Process_updateCPUFieldWidths(proc->percent_cpu);\n\n      if (!LinuxProcessTable_updateUser(host, proc, procFd, mainTask))\n         goto errorReadingProcess;\n\n      /* Check if the process is inside a different PID namespace. */\n      if (proc->isRunningInContainer == TRI_INITIAL && rootPidNs != (ino_t)-1) {\n         struct stat sb;\n#if defined(HAVE_OPENAT) && defined(HAVE_FSTATAT)\n         int res = fstatat(procFd, \"ns/pid\", &sb, 0);\n#else\n         char path[PATH_MAX];\n         xSnprintf(path, sizeof(path), \"%s/ns/pid\", procFd);\n         int res = stat(path, &sb);\n#endif\n         if (res == 0) {\n            proc->isRunningInContainer = (sb.st_ino != rootPidNs) ? TRI_ON : TRI_OFF;\n         }\n      }\n\n      if (ss->flags & PROCESS_FLAG_LINUX_CTXT\n         || ((hideRunningInContainer || ss->flags & PROCESS_FLAG_LINUX_CONTAINER) && proc->isRunningInContainer == TRI_INITIAL)\n#ifdef HAVE_VSERVER\n         || ss->flags & PROCESS_FLAG_LINUX_VSERVER\n#endif\n      ) {\n         proc->isRunningInContainer = TRI_OFF;\n         if (!LinuxProcessTable_readStatusFile(proc, procFd))\n            goto errorReadingProcess;\n      }\n\n      if (!preExisting) {\n\n         #ifdef HAVE_OPENVZ\n         if (ss->flags & PROCESS_FLAG_LINUX_OPENVZ) {\n            LinuxProcessTable_readOpenVZData(lp, procFd);\n         }\n         #endif\n\n         if (proc->isKernelThread) {\n            Process_updateCmdline(proc, NULL, 0, 0);\n         } else {\n            if (!LinuxProcessTable_readCmdlineFile(proc, procFd, mainTask)) {\n               Process_updateCmdline(proc, statCommand, 0, strlen(statCommand));\n            }\n            LinuxProcessList_readComm(proc, procFd);\n         }\n\n         Process_fillStarttimeBuffer(proc);\n\n         ProcessTable_add(pt, proc);\n      } else {\n         if (settings->updateProcessNames && proc->state != ZOMBIE) {\n            if (proc->isKernelThread) {\n               Process_updateCmdline(proc, NULL, 0, 0);\n            } else {\n               if (!LinuxProcessTable_readCmdlineFile(proc, procFd, mainTask)) {\n                  Process_updateCmdline(proc, statCommand, 0, strlen(statCommand));\n               }\n               LinuxProcessList_readComm(proc, procFd);\n            }\n         }\n      }\n\n      /*\n       * Section gathering non-critical information that is independent from\n       * each other.\n       */\n\n      /* Gather permitted capabilities (thread-specific data) for non-root process. */\n      if (proc->st_uid != 0 && proc->elevated_priv != TRI_OFF) {\n         struct __user_cap_header_struct header = { .version = _LINUX_CAPABILITY_VERSION_3, .pid = Process_getPid(proc) };\n         struct __user_cap_data_struct data;\n\n         long res = syscall(SYS_capget, &header, &data);\n         if (res == 0) {\n            proc->elevated_priv = (data.permitted != 0) ? TRI_ON : TRI_OFF;\n         } else {\n            proc->elevated_priv = TRI_OFF;\n         }\n      }\n\n      if (ss->flags & PROCESS_FLAG_LINUX_CGROUP)\n         LinuxProcessTable_readCGroupFile(lp, procFd);\n\n      if ((ss->flags & PROCESS_FLAG_LINUX_SMAPS) && !Process_isKernelThread(proc)) {\n         if (!mainTask) {\n            // Read smaps file of each process only every second pass to improve performance\n            static int smaps_flag = 0;\n            if ((pid & 1) == smaps_flag) {\n               LinuxProcessTable_readSmapsFile(lp, procFd, this->haveSmapsRollup);\n            }\n            if (pid == 1) {\n               smaps_flag = !smaps_flag;\n            }\n         } else {\n            lp->m_pss   = mainTask->m_pss;\n            lp->m_swap  = mainTask->m_swap;\n            lp->m_psswp = mainTask->m_psswp;\n         }\n      }\n\n      if (ss->flags & PROCESS_FLAG_IO) {\n         LinuxProcessTable_readIoFile(lp, procFd, scanMainThread);\n      }\n\n      #ifdef HAVE_DELAYACCT\n      if (ss->flags & PROCESS_FLAG_LINUX_DELAYACCT) {\n         LibNl_readDelayAcctData(this, lp);\n      }\n      #endif\n\n      if (ss->flags & PROCESS_FLAG_LINUX_OOM) {\n         LinuxProcessTable_readOomData(lp, procFd, mainTask);\n      }\n\n      if (ss->flags & PROCESS_FLAG_LINUX_IOPRIO) {\n         LinuxProcess_updateIOPriority(proc);\n      }\n\n      if (ss->flags & PROCESS_FLAG_LINUX_SECATTR) {\n         LinuxProcessTable_readSecattrData(lp, procFd, mainTask);\n      }\n\n      if (ss->flags & PROCESS_FLAG_CWD) {\n         LinuxProcessTable_readCwd(lp, procFd, mainTask);\n      }\n\n      if ((ss->flags & PROCESS_FLAG_LINUX_AUTOGROUP) && this->haveAutogroup) {\n         LinuxProcessTable_readAutogroup(lp, procFd, mainTask);\n      }\n\n      #ifdef SCHEDULER_SUPPORT\n      if (ss->flags & PROCESS_FLAG_SCHEDPOL) {\n         Scheduling_readProcessPolicy(proc);\n      }\n      #endif\n\n      if (ss->flags & PROCESS_FLAG_LINUX_GPU || GPUMeter_active()) {\n         if (mainTask) {\n            lp->gpu_time = mainTask->gpu_time;\n         } else {\n            GPU_readProcessData(this, lp, procFd);\n         }\n      }\n\n      /*\n       * Final section after all data has been gathered\n       */\n\n      if (!proc->cmdline && statCommand[0] &&\n          (proc->state == ZOMBIE || Process_isKernelThread(proc) || settings->showThreadNames)) {\n         Process_updateCmdline(proc, statCommand, 0, strlen(statCommand));\n      }\n\n      proc->super.updated = true;\n      Compat_openatArgClose(procFd);\n\n      if (hideRunningInContainer && proc->isRunningInContainer == TRI_ON) {\n         proc->super.show = false;\n         continue;\n      }\n\n      if (Process_isKernelThread(proc)) {\n         pt->kernelThreads++;\n      } else if (Process_isUserlandThread(proc)) {\n         pt->userlandThreads++;\n      }\n\n      /* Set at the end when we know if a new entry is a thread */\n      proc->super.show = ! ((hideKernelThreads && Process_isKernelThread(proc)) || (hideUserlandThreads && Process_isUserlandThread(proc)));\n\n      pt->totalTasks++;\n      /* runningTasks is set in Machine_scanCPUTime() from /proc/stat */\n      continue;\n\n      // Exception handler.\n\nerrorReadingProcess:\n      {\n#ifdef HAVE_OPENAT\n         if (procFd >= 0)\n            close(procFd);\n#endif\n\n         if (preExisting) {\n            /*\n             * The only real reason for coming here (apart from Linux violating the /proc API)\n             * would be the process going away with its /proc files disappearing (!HAVE_OPENAT).\n             * However, we want to keep in the process list for now for the \"highlight dying\" mode.\n             */\n         } else {\n            /* A really short-lived process that we don't have full info about */\n            assert(ProcessTable_findProcess(pt, Process_getPid(proc)) == NULL);\n            Process_delete((Object*)proc);\n         }\n      }\n   }\n   closedir(dir);\n   return true;\n}\n\nvoid ProcessTable_goThroughEntries(ProcessTable* super) {\n   LinuxProcessTable* this = (LinuxProcessTable*) super;\n   Machine* host = super->super.host;\n   const Settings* settings = host->settings;\n   LinuxMachine* lhost = (LinuxMachine*) host;\n\n   if (settings->ss->flags & PROCESS_FLAG_LINUX_AUTOGROUP) {\n      // Refer to sched(7) 'autogroup feature' section\n      // The kernel feature can be enabled/disabled through procfs at\n      // any time, so check for it at the start of each sample - only\n      // read from per-process procfs files if it's globally enabled.\n      this->haveAutogroup = LinuxProcess_isAutogroupEnabled();\n   } else {\n      this->haveAutogroup = false;\n   }\n\n   /* Shift GPU values */\n   {\n      lhost->prevGpuTime = lhost->curGpuTime;\n      lhost->curGpuTime = 0;\n\n      for (GPUEngineData* engine = lhost->gpuEngineData; engine; engine = engine->next) {\n         engine->prevTime = engine->curTime;\n         engine->curTime = 0;\n      }\n   }\n\n   /* PROCDIR is an absolute path */\n   assert(PROCDIR[0] == '/');\n#ifdef HAVE_OPENAT\n   openat_arg_t rootFd = AT_FDCWD;\n#else\n   openat_arg_t rootFd = \"\";\n#endif\n\n   LinuxProcessTable_recurseProcTree(this, rootFd, lhost, PROCDIR, NULL);\n}\n"
  },
  {
    "path": "linux/LinuxProcessTable.h",
    "content": "#ifndef HEADER_LinuxProcessTable\n#define HEADER_LinuxProcessTable\n/*\nhtop - LinuxProcessTable.h\n(C) 2014 Hisham H. Muhammad\nReleased under the GNU GPLv2+, see the COPYING file\nin the source distribution for its full text.\n*/\n\n#include <stdbool.h>\n\n#include \"ProcessTable.h\"\n\n\ntypedef struct TtyDriver_ {\n   char* path;\n   unsigned int major;\n   unsigned int minorFrom;\n   unsigned int minorTo;\n} TtyDriver;\n\ntypedef struct LinuxProcessTable_ {\n   ProcessTable super;\n\n   TtyDriver* ttyDrivers;\n   bool haveSmapsRollup;\n   bool haveAutogroup;\n\n   #ifdef HAVE_DELAYACCT\n   int netlink_family;\n   struct nl_sock* netlink_socket;\n   #endif\n} LinuxProcessTable;\n\n#endif\n"
  },
  {
    "path": "linux/OpenRCMeter.c",
    "content": "/*\nhtop - OpenRCMeter.c\n(C) 2023 htop dev team\nReleased under the GNU GPLv2+, see the COPYING file\nin the source distribution for its full text.\n*/\n\n#include \"config.h\" // IWYU pragma: keep\n\n#include \"linux/OpenRCMeter.h\"\n\n#include <fcntl.h>\n#include <stdbool.h>\n#include <stdio.h>\n#include <stdlib.h>\n#include <string.h>\n#include <unistd.h>\n#include <sys/wait.h>\n\n#include \"CRT.h\"\n#include \"Macros.h\"\n#include \"Object.h\"\n#include \"RichString.h\"\n#include \"Settings.h\"\n#include \"XUtils.h\"\n\n#define INVALID_VALUE ((unsigned int)-1)\n\ntypedef struct OpenRCMeterContext {\n   char* runlevel;\n   unsigned int services_stopped;\n   unsigned int services_started;\n} OpenRCMeterContext_t;\n\nstatic OpenRCMeterContext_t ctx_system;\nstatic OpenRCMeterContext_t ctx_user;\n\nstatic void OpenRCMeter_done(ATTR_UNUSED Meter* this) {\n   OpenRCMeterContext_t* ctx = String_eq(Meter_name(this), \"OpenRCUser\") ? &ctx_user : &ctx_system;\n\n   free(ctx->runlevel);\n   ctx->runlevel = NULL;\n}\n\nstatic void updateViaExec(bool user) {\n   OpenRCMeterContext_t* ctx = user ? &ctx_user : &ctx_system;\n\n   if (Settings_isReadonly())\n      return;\n\n   int fdpair[2] = {-1, -1};\n   if (pipe(fdpair) < 0)\n      return;\n\n   pid_t child = fork();\n   if (child < 0) {\n      close(fdpair[1]);\n      close(fdpair[0]);\n      return;\n   }\n\n   if (child == 0) {\n      close(fdpair[0]);\n      dup2(fdpair[1], STDOUT_FILENO);\n      close(fdpair[1]);\n      int fdnull = open(\"/dev/null\", O_WRONLY);\n      if (fdnull < 0)\n         exit(1);\n      dup2(fdnull, STDERR_FILENO);\n      close(fdnull);\n      if (user) {\n         execlp(\"rc-status\", \"rc-status\", \"--user\", \"-a\", (char*)NULL);\n      } else {\n         execlp(\"rc-status\", \"rc-status\", \"-a\", (char*)NULL);\n      }\n      exit(127);\n   }\n   close(fdpair[1]);\n\n   int wstatus;\n   if (waitpid(child, &wstatus, 0) < 0 || !WIFEXITED(wstatus) || WEXITSTATUS(wstatus) != 0) {\n      close(fdpair[0]);\n      return;\n   }\n\n   FILE* commandOutput = fdopen(fdpair[0], \"r\");\n   if (!commandOutput) {\n      close(fdpair[0]);\n      return;\n   }\n\n   ctx->services_started = 0;\n   ctx->services_stopped = 0;\n\n   char lineBuffer[256];\n   while (fgets(lineBuffer, sizeof(lineBuffer), commandOutput)) {\n      if (String_startsWith(lineBuffer, \"Runlevel: \")) {\n         char* newline = strchr(lineBuffer + strlen(\"Runlevel: \"), '\\n');\n         if (newline) {\n            *newline = '\\0';\n         }\n         free_and_xStrdup(&ctx->runlevel, lineBuffer + strlen(\"Runlevel: \"));\n      } else {\n         if (strstr(lineBuffer, \"[\") && strstr(lineBuffer, \"started\") && strstr(lineBuffer, \"]\")) {\n            ctx->services_started++;\n         } else if (strstr(lineBuffer, \"[\") && strstr(lineBuffer, \"stopped\") && strstr(lineBuffer, \"]\")) {\n            ctx->services_stopped++;\n         }\n      }\n   }\n\n   fclose(commandOutput);\n}\n\nstatic void OpenRCMeter_updateValues(Meter* this) {\n   bool user = String_eq(Meter_name(this), \"OpenRCUser\");\n   OpenRCMeterContext_t* ctx = user ? &ctx_user : &ctx_system;\n\n   free(ctx->runlevel);\n   ctx->runlevel = NULL;\n   ctx->services_stopped = INVALID_VALUE;\n   ctx->services_started = INVALID_VALUE;\n\n   updateViaExec(user);\n\n   xSnprintf(this->txtBuffer, sizeof(this->txtBuffer), \"%s\", ctx->runlevel ? ctx->runlevel : \"???\");\n}\n\nstatic void OpenRCMeter_display(ATTR_UNUSED const Object* cast, RichString* out, OpenRCMeterContext_t* ctx) {\n   char buffer[32];\n\n   RichString_writeAscii(out, CRT_colors[METER_TEXT], \"Runlevel: \");\n   RichString_appendAscii(out, CRT_colors[METER_VALUE], ctx->runlevel ? ctx->runlevel : \"N/A\");\n\n   RichString_appendAscii(out, CRT_colors[METER_TEXT], \" (\");\n\n   if (ctx->services_started == INVALID_VALUE) {\n      xSnprintf(buffer, sizeof(buffer), \"?\");\n   } else {\n      xSnprintf(buffer, sizeof(buffer), \"%u\", ctx->services_started);\n   }\n   RichString_appendAscii(out, CRT_colors[METER_VALUE_OK], buffer);\n\n   RichString_appendAscii(out, CRT_colors[METER_TEXT], \" started, \");\n\n   if (ctx->services_stopped == INVALID_VALUE) {\n      xSnprintf(buffer, sizeof(buffer), \"?\");\n   } else {\n      xSnprintf(buffer, sizeof(buffer), \"%u\", ctx->services_stopped);\n   }\n   RichString_appendAscii(out, CRT_colors[METER_VALUE_ERROR], buffer);\n\n   RichString_appendAscii(out, CRT_colors[METER_TEXT], \" stopped)\");\n}\n\nstatic void OpenRCMeter_display_system(ATTR_UNUSED const Object* cast, RichString* out) {\n   OpenRCMeter_display(cast, out, &ctx_system);\n}\n\nstatic void OpenRCMeter_display_user(ATTR_UNUSED const Object* cast, RichString* out) {\n   OpenRCMeter_display(cast, out, &ctx_user);\n}\n\nstatic const int OpenRCMeter_attributes[] = {\n   METER_VALUE\n};\n\nconst MeterClass OpenRCMeter_class = {\n   .super = {\n      .extends = Class(Meter),\n      .delete = Meter_delete,\n      .display = OpenRCMeter_display_system,\n   },\n   .updateValues = OpenRCMeter_updateValues,\n   .done = OpenRCMeter_done,\n   .defaultMode = TEXT_METERMODE,\n   .supportedModes = (1 << TEXT_METERMODE),\n   .maxItems = 0,\n   .total = 0.0,\n   .attributes = OpenRCMeter_attributes,\n   .name = \"OpenRC\",\n   .uiName = \"OpenRC state\",\n   .description = \"OpenRC system state and service overview\",\n   .caption = \"OpenRC: \",\n};\n\nconst MeterClass OpenRCUserMeter_class = {\n   .super = {\n      .extends = Class(Meter),\n      .delete = Meter_delete,\n      .display = OpenRCMeter_display_user,\n   },\n   .updateValues = OpenRCMeter_updateValues,\n   .done = OpenRCMeter_done,\n   .defaultMode = TEXT_METERMODE,\n   .supportedModes = (1 << TEXT_METERMODE),\n   .maxItems = 0,\n   .total = 0.0,\n   .attributes = OpenRCMeter_attributes,\n   .name = \"OpenRCUser\",\n   .uiName = \"OpenRC user state\",\n   .description = \"OpenRC user state and service overview\",\n   .caption = \"OpenRC User: \",\n};\n"
  },
  {
    "path": "linux/OpenRCMeter.h",
    "content": "#ifndef HEADER_OpenRCMeter\n#define HEADER_OpenRCMeter\n\n#include \"Meter.h\"\n\nextern const MeterClass OpenRCMeter_class;\n\nextern const MeterClass OpenRCUserMeter_class;\n\n#endif\n"
  },
  {
    "path": "linux/Platform.c",
    "content": "/*\nhtop - linux/Platform.c\n(C) 2014 Hisham H. Muhammad\nReleased under the GNU GPLv2+, see the COPYING file\nin the source distribution for its full text.\n*/\n\n#include \"config.h\" // IWYU pragma: keep\n\n#include \"linux/Platform.h\"\n\n#include <assert.h>\n#include <dirent.h>\n#include <errno.h>\n#include <fcntl.h>\n#include <inttypes.h>\n#include <math.h>\n#include <stdint.h>\n#include <stdio.h>\n#include <stdlib.h>\n#include <string.h>\n#include <time.h>\n#include <unistd.h>\n#include <sys/sysmacros.h>\n\n#include \"BatteryMeter.h\"\n#include \"ClockMeter.h\"\n#include \"CPUMeter.h\"\n#include \"DateTimeMeter.h\"\n#include \"DiskIOMeter.h\"\n#include \"FileDescriptorMeter.h\"\n#include \"GPUMeter.h\"\n#include \"HostnameMeter.h\"\n#include \"HugePageMeter.h\"\n#include \"LoadAverageMeter.h\"\n#include \"Machine.h\"\n#include \"Macros.h\"\n#include \"MainPanel.h\"\n#include \"Meter.h\"\n#include \"MemoryMeter.h\"\n#include \"MemorySwapMeter.h\"\n#include \"NetworkIOMeter.h\"\n#include \"Object.h\"\n#include \"Panel.h\"\n#include \"PressureStallMeter.h\"\n#include \"ProvideCurses.h\"\n#include \"Settings.h\"\n#include \"SwapMeter.h\"\n#include \"SysArchMeter.h\"\n#include \"TasksMeter.h\"\n#include \"UptimeMeter.h\"\n#include \"linux/Compat.h\"\n#include \"linux/IOPriority.h\"\n#include \"linux/IOPriorityPanel.h\"\n#include \"linux/LinuxMachine.h\"\n#include \"linux/LinuxProcess.h\"\n#include \"linux/OpenRCMeter.h\"\n#include \"linux/SELinuxMeter.h\"\n#include \"linux/SystemdMeter.h\"\n#include \"linux/ZramMeter.h\"\n#include \"linux/ZramStats.h\"\n#include \"linux/ZswapStats.h\"\n#include \"zfs/ZfsArcMeter.h\"\n#include \"zfs/ZfsArcStats.h\"\n#include \"zfs/ZfsCompressedArcMeter.h\"\n\n#ifdef HAVE_LIBCAP\n#include <sys/capability.h>\n#endif\n\n#ifdef HAVE_SENSORS_SENSORS_H\n#include \"LibSensors.h\"\n#endif\n\n#ifndef O_PATH\n#define O_PATH         010000000 // declare for ancient glibc versions\n#endif\n\n\n#ifdef HAVE_LIBCAP\nenum CapMode {\n   CAP_MODE_OFF,\n   CAP_MODE_BASIC,\n   CAP_MODE_STRICT\n};\n#endif\n\nbool Running_containerized = false;\n\nconst ScreenDefaults Platform_defaultScreens[] = {\n   {\n      .name = \"Main\",\n      .columns = \"PID USER PRIORITY NICE M_VIRT M_RESIDENT M_SHARE STATE PERCENT_CPU PERCENT_MEM TIME Command\",\n      .sortKey = \"PERCENT_CPU\",\n   },\n   {\n      .name = \"I/O\",\n      .columns = \"PID USER IO_PRIORITY IO_RATE IO_READ_RATE IO_WRITE_RATE PERCENT_SWAP_DELAY PERCENT_IO_DELAY Command\",\n      .sortKey = \"IO_RATE\",\n   },\n};\n\nconst unsigned int Platform_numberOfDefaultScreens = ARRAYSIZE(Platform_defaultScreens);\n\nconst SignalItem Platform_signals[] = {\n   { .name = \" 0 Cancel\",    .number = 0 },\n   { .name = \" 1 SIGHUP\",    .number = 1 },\n   { .name = \" 2 SIGINT\",    .number = 2 },\n   { .name = \" 3 SIGQUIT\",   .number = 3 },\n   { .name = \" 4 SIGILL\",    .number = 4 },\n   { .name = \" 5 SIGTRAP\",   .number = 5 },\n   { .name = \" 6 SIGABRT\",   .number = 6 },\n   { .name = \" 6 SIGIOT\",    .number = 6 },\n   { .name = \" 7 SIGBUS\",    .number = 7 },\n   { .name = \" 8 SIGFPE\",    .number = 8 },\n   { .name = \" 9 SIGKILL\",   .number = 9 },\n   { .name = \"10 SIGUSR1\",   .number = 10 },\n   { .name = \"11 SIGSEGV\",   .number = 11 },\n   { .name = \"12 SIGUSR2\",   .number = 12 },\n   { .name = \"13 SIGPIPE\",   .number = 13 },\n   { .name = \"14 SIGALRM\",   .number = 14 },\n   { .name = \"15 SIGTERM\",   .number = 15 },\n   { .name = \"16 SIGSTKFLT\", .number = 16 },\n   { .name = \"17 SIGCHLD\",   .number = 17 },\n   { .name = \"18 SIGCONT\",   .number = 18 },\n   { .name = \"19 SIGSTOP\",   .number = 19 },\n   { .name = \"20 SIGTSTP\",   .number = 20 },\n   { .name = \"21 SIGTTIN\",   .number = 21 },\n   { .name = \"22 SIGTTOU\",   .number = 22 },\n   { .name = \"23 SIGURG\",    .number = 23 },\n   { .name = \"24 SIGXCPU\",   .number = 24 },\n   { .name = \"25 SIGXFSZ\",   .number = 25 },\n   { .name = \"26 SIGVTALRM\", .number = 26 },\n   { .name = \"27 SIGPROF\",   .number = 27 },\n   { .name = \"28 SIGWINCH\",  .number = 28 },\n   { .name = \"29 SIGIO\",     .number = 29 },\n   { .name = \"29 SIGPOLL\",   .number = 29 },\n   { .name = \"30 SIGPWR\",    .number = 30 },\n   { .name = \"31 SIGSYS\",    .number = 31 },\n};\n\nconst unsigned int Platform_numberOfSignals = ARRAYSIZE(Platform_signals);\n\nenum {\n   MEMORY_CLASS_USED = 0,\n   MEMORY_CLASS_SHARED,\n   MEMORY_CLASS_COMPRESSED,\n   MEMORY_CLASS_BUFFERS,\n   MEMORY_CLASS_CACHE,\n   MEMORY_CLASS_AVAILABLE,\n}; // N.B. the chart will display categories in this order\n\nconst MemoryClass Platform_memoryClasses[] = {\n   [MEMORY_CLASS_USED] = { .label = \"used\", .countsAsUsed = true, .countsAsCache = false, .color = MEMORY_1 },\n   [MEMORY_CLASS_SHARED] = { .label = \"shared\", .countsAsUsed = true, .countsAsCache = false, .color = MEMORY_2 },\n   [MEMORY_CLASS_COMPRESSED] = { .label = \"compressed\", .countsAsUsed = true, .countsAsCache = false, .color = MEMORY_3 },\n   [MEMORY_CLASS_BUFFERS] = { .label = \"buffers\", .countsAsUsed = false, .countsAsCache = true, .color = MEMORY_4 },\n   [MEMORY_CLASS_CACHE] = { .label = \"cache\", .countsAsUsed = false, .countsAsCache = true, .color = MEMORY_5 },\n   [MEMORY_CLASS_AVAILABLE] = { .label = \"available\", .countsAsUsed = false, .countsAsCache = false, .color = MEMORY_6 },\n};\n\nconst unsigned int Platform_numberOfMemoryClasses = ARRAYSIZE(Platform_memoryClasses);\n\nstatic enum { BAT_PROC, BAT_SYS, BAT_ERR } Platform_Battery_method = BAT_PROC;\nstatic time_t Platform_Battery_cacheTime;\nstatic double Platform_Battery_cachePercent = NAN;\nstatic ACPresence Platform_Battery_cacheIsOnAC;\n\n#ifdef HAVE_LIBCAP\nstatic enum CapMode Platform_capabilitiesMode = CAP_MODE_BASIC;\n#endif\n\nstatic Htop_Reaction Platform_actionSetIOPriority(State* st) {\n   if (Settings_isReadonly())\n      return HTOP_OK;\n\n   const LinuxProcess* p = (const LinuxProcess*) Panel_getSelected((Panel*)st->mainPanel);\n   if (!p)\n      return HTOP_OK;\n\n   IOPriority ioprio1 = p->ioPriority;\n   Panel* ioprioPanel = IOPriorityPanel_new(ioprio1);\n   const void* set = Action_pickFromVector(st, ioprioPanel, 20, true);\n   if (set) {\n      IOPriority ioprio2 = IOPriorityPanel_getIOPriority(ioprioPanel);\n      bool ok = MainPanel_foreachRow(st->mainPanel, LinuxProcess_rowSetIOPriority, (Arg) { .i = ioprio2 }, NULL);\n      if (!ok) {\n         beep();\n      }\n   }\n   Panel_delete((Object*)ioprioPanel);\n   return HTOP_REFRESH | HTOP_REDRAW_BAR | HTOP_UPDATE_PANELHDR;\n}\n\nstatic bool Platform_changeAutogroupPriority(MainPanel* panel, int delta) {\n   if (LinuxProcess_isAutogroupEnabled() == false) {\n      beep();\n      return false;\n   }\n   bool anyTagged;\n   bool ok = MainPanel_foreachRow(panel, LinuxProcess_rowChangeAutogroupPriorityBy, (Arg) { .i = delta }, &anyTagged);\n   if (!ok)\n      beep();\n   return anyTagged;\n}\n\nstatic Htop_Reaction Platform_actionHigherAutogroupPriority(State* st) {\n   if (Settings_isReadonly())\n      return HTOP_OK;\n\n   bool changed = Platform_changeAutogroupPriority(st->mainPanel, -1);\n   return changed ? HTOP_REFRESH : HTOP_OK;\n}\n\nstatic Htop_Reaction Platform_actionLowerAutogroupPriority(State* st) {\n   if (Settings_isReadonly())\n      return HTOP_OK;\n\n   bool changed = Platform_changeAutogroupPriority(st->mainPanel, 1);\n   return changed ? HTOP_REFRESH : HTOP_OK;\n}\n\nvoid Platform_setBindings(Htop_Action* keys) {\n   keys['i'] = Platform_actionSetIOPriority;\n   keys['{'] = Platform_actionLowerAutogroupPriority;\n   keys['}'] = Platform_actionHigherAutogroupPriority;\n   keys[KEY_F(19)] = Platform_actionLowerAutogroupPriority;  // Shift-F7\n   keys[KEY_F(20)] = Platform_actionHigherAutogroupPriority; // Shift-F8\n}\n\nconst MeterClass* const Platform_meterTypes[] = {\n   &CPUMeter_class,\n   &ClockMeter_class,\n   &DateMeter_class,\n   &DateTimeMeter_class,\n   &LoadAverageMeter_class,\n   &LoadMeter_class,\n   &MemoryMeter_class,\n   &SwapMeter_class,\n   &MemorySwapMeter_class,\n   &SysArchMeter_class,\n   &HugePageMeter_class,\n   &TasksMeter_class,\n   &UptimeMeter_class,\n   &SecondsUptimeMeter_class,\n   &BatteryMeter_class,\n   &HostnameMeter_class,\n   &AllCPUsMeter_class,\n   &AllCPUs2Meter_class,\n   &AllCPUs4Meter_class,\n   &AllCPUs8Meter_class,\n   &LeftCPUsMeter_class,\n   &RightCPUsMeter_class,\n   &LeftCPUs2Meter_class,\n   &RightCPUs2Meter_class,\n   &LeftCPUs4Meter_class,\n   &RightCPUs4Meter_class,\n   &LeftCPUs8Meter_class,\n   &RightCPUs8Meter_class,\n   &BlankMeter_class,\n   &PressureStallCPUSomeMeter_class,\n   &PressureStallIOSomeMeter_class,\n   &PressureStallIOFullMeter_class,\n   &PressureStallIRQFullMeter_class,\n   &PressureStallMemorySomeMeter_class,\n   &PressureStallMemoryFullMeter_class,\n   &ZfsArcMeter_class,\n   &ZfsCompressedArcMeter_class,\n   &ZramMeter_class,\n   &DiskIORateMeter_class,\n   &DiskIOTimeMeter_class,\n   &DiskIOMeter_class,\n   &NetworkIOMeter_class,\n   &SELinuxMeter_class,\n   &SystemdMeter_class,\n   &SystemdUserMeter_class,\n   &OpenRCMeter_class,\n   &OpenRCUserMeter_class,\n   &FileDescriptorMeter_class,\n   &GPUMeter_class,\n   NULL\n};\n\nint Platform_getUptime(void) {\n   char uptimedata[64] = {0};\n\n   ssize_t uptimeread = Compat_readfile(PROCDIR \"/uptime\", uptimedata, sizeof(uptimedata));\n   if (uptimeread < 1) {\n      return 0;\n   }\n\n   double uptime = 0;\n   double idle = 0;\n\n   int n = sscanf(uptimedata, \"%lf %lf\", &uptime, &idle);\n   if (n != 2) {\n      return 0;\n   }\n\n   return floor(uptime);\n}\n\nvoid Platform_getLoadAverage(double* one, double* five, double* fifteen) {\n   char loaddata[128] = {0};\n\n   *one = NAN;\n   *five = NAN;\n   *fifteen = NAN;\n\n   ssize_t loadread = Compat_readfile(PROCDIR \"/loadavg\", loaddata, sizeof(loaddata));\n   if (loadread < 1)\n      return;\n\n   double scanOne = NAN;\n   double scanFive = NAN;\n   double scanFifteen = NAN;\n   int r = sscanf(loaddata, \"%lf %lf %lf\", &scanOne, &scanFive, &scanFifteen);\n   if (r != 3)\n      return;\n\n   *one = scanOne;\n   *five = scanFive;\n   *fifteen = scanFifteen;\n}\n\npid_t Platform_getMaxPid(void) {\n   char piddata[32] = {0};\n\n   ssize_t pidread = Compat_readfile(PROCDIR \"/sys/kernel/pid_max\", piddata, sizeof(piddata));\n   if (pidread < 1)\n      goto err;\n\n   int pidmax = 0;\n   int match = sscanf(piddata, \"%32d\", &pidmax);\n   if (match != 1)\n      goto err;\n\n   return pidmax;\n\nerr:\n   return 0x3FFFFF; // 4194303\n}\n\ndouble Platform_setCPUValues(Meter* this, unsigned int cpu) {\n   const LinuxMachine* lhost = (const LinuxMachine*) this->host;\n   const Settings* settings = this->host->settings;\n   const CPUData* cpuData = &(lhost->cpuData[cpu]);\n   double total = (double) ( cpuData->totalPeriod == 0 ? 1 : cpuData->totalPeriod);\n   double percent;\n   double* v = this->values;\n\n   if (!cpuData->online) {\n      this->curItems = 0;\n      return NAN;\n   }\n\n   v[CPU_METER_NICE] = cpuData->nicePeriod / total * 100.0;\n   v[CPU_METER_NORMAL] = cpuData->userPeriod / total * 100.0;\n   if (settings->detailedCPUTime) {\n      v[CPU_METER_KERNEL]  = cpuData->systemPeriod / total * 100.0;\n      v[CPU_METER_IRQ]     = cpuData->irqPeriod / total * 100.0;\n      v[CPU_METER_SOFTIRQ] = cpuData->softIrqPeriod / total * 100.0;\n      this->curItems = 5;\n\n      v[CPU_METER_STEAL]   = cpuData->stealPeriod / total * 100.0;\n      v[CPU_METER_GUEST]   = cpuData->guestPeriod / total * 100.0;\n      if (settings->accountGuestInCPUMeter) {\n         this->curItems = 7;\n      }\n\n      v[CPU_METER_IOWAIT]  = cpuData->ioWaitPeriod / total * 100.0;\n   } else {\n      v[CPU_METER_KERNEL] = cpuData->systemAllPeriod / total * 100.0;\n      v[CPU_METER_IRQ] = (cpuData->stealPeriod + cpuData->guestPeriod) / total * 100.0;\n      this->curItems = 4;\n   }\n\n   percent = sumPositiveValues(v, this->curItems);\n   percent = MINIMUM(percent, 100.0);\n\n   if (settings->detailedCPUTime) {\n      this->curItems = 8;\n   }\n\n   v[CPU_METER_FREQUENCY] = cpuData->frequency;\n\n#ifdef HAVE_SENSORS_SENSORS_H\n   v[CPU_METER_TEMPERATURE] = cpuData->temperature;\n#else\n   v[CPU_METER_TEMPERATURE] = NAN;\n#endif\n\n   return percent;\n}\n\nvoid Platform_setGPUValues(Meter* this, double* totalUsage, unsigned long long* totalGPUTimeDiff) {\n   const Machine* host = this->host;\n   const LinuxMachine* lhost = (const LinuxMachine*) host;\n\n   // Must match the index as used in GPUMeter_attributes\n   const size_t residueIndex = 4;\n   assert(ARRAYSIZE(GPUMeter_engineData) == residueIndex);\n\n   static uint64_t prevMonotonicMs;\n   static double residuePercentage;\n   static unsigned long long int prevResidueTime;\n\n   // The results are cached so that we can update values of multiple meter\n   // instances. We also need a local cache of the monotonic timestamp, thus we\n   // don't use host->prevMonotonicMs.\n   if (host->monotonicMs > prevMonotonicMs) {\n      uint64_t monotonictimeDelta = host->monotonicMs - prevMonotonicMs;\n\n      unsigned long long int curResidueTime = lhost->curGpuTime;\n\n      const GPUEngineData* gpuEngineData;\n      size_t i;\n      for (gpuEngineData = lhost->gpuEngineData, i = 0; gpuEngineData && i < ARRAYSIZE(GPUMeter_engineData); gpuEngineData = gpuEngineData->next, i++) {\n         GPUMeter_engineData[i].key        = gpuEngineData->key;\n         GPUMeter_engineData[i].timeDiff   = saturatingSub(gpuEngineData->curTime, gpuEngineData->prevTime);\n         GPUMeter_engineData[i].percentage = 100.0 * GPUMeter_engineData[i].timeDiff / (1000 * 1000) / monotonictimeDelta;\n\n         curResidueTime = saturatingSub(curResidueTime, gpuEngineData->curTime);\n      }\n\n      residuePercentage = 100.0 * saturatingSub(curResidueTime, prevResidueTime) / (1000 * 1000) / monotonictimeDelta;\n\n      *totalGPUTimeDiff = saturatingSub(lhost->curGpuTime, lhost->prevGpuTime);\n      *totalUsage = 100.0 * (*totalGPUTimeDiff) / (1000 * 1000) / monotonictimeDelta;\n\n      prevResidueTime = curResidueTime;\n      prevMonotonicMs = host->monotonicMs;\n   }\n\n   this->curItems = residueIndex + 1;\n   for (size_t i = 0; i < ARRAYSIZE(GPUMeter_engineData); i++) {\n      this->values[i] = GPUMeter_engineData[i].percentage;\n   }\n   this->values[residueIndex] = residuePercentage;\n}\n\nvoid Platform_setMemoryValues(Meter* this) {\n   const Machine* host = this->host;\n   const LinuxMachine* lhost = (const LinuxMachine*) host;\n\n   this->total = host->totalMem;\n   this->values[MEMORY_CLASS_USED]       = lhost->usedMem;\n   this->values[MEMORY_CLASS_SHARED]     = lhost->sharedMem;\n   this->values[MEMORY_CLASS_COMPRESSED] = 0; /* compressed */\n   this->values[MEMORY_CLASS_BUFFERS]    = lhost->buffersMem;\n   this->values[MEMORY_CLASS_CACHE]      = lhost->cachedMem;\n   this->values[MEMORY_CLASS_AVAILABLE]  = lhost->availableMem;\n\n   if (lhost->zfs.enabled != 0 && !Running_containerized) {\n      // ZFS does not shrink below the value of zfs_arc_min.\n      unsigned long long int shrinkableSize = 0;\n      if (lhost->zfs.size > lhost->zfs.min)\n         shrinkableSize = lhost->zfs.size - lhost->zfs.min;\n      this->values[MEMORY_CLASS_USED] -= shrinkableSize;\n      this->values[MEMORY_CLASS_CACHE] += shrinkableSize;\n      this->values[MEMORY_CLASS_AVAILABLE] += shrinkableSize;\n   }\n\n   if (lhost->zswap.usedZswapOrig > 0 || lhost->zswap.usedZswapComp > 0) {\n      this->values[MEMORY_CLASS_USED] -= lhost->zswap.usedZswapComp;\n      this->values[MEMORY_CLASS_COMPRESSED] += lhost->zswap.usedZswapComp;\n   }\n}\n\nvoid Platform_setSwapValues(Meter* this) {\n   const Machine* host = this->host;\n   const LinuxMachine* lhost = (const LinuxMachine*) host;\n\n   this->total = host->totalSwap;\n   this->values[SWAP_METER_USED] = host->usedSwap;\n   this->values[SWAP_METER_CACHE] = host->cachedSwap;\n   this->values[SWAP_METER_FRONTSWAP] = 0; /* frontswap -- memory that is accounted to swap but resides elsewhere */\n\n   if (lhost->zswap.usedZswapOrig > 0 || lhost->zswap.usedZswapComp > 0) {\n      /*\n       * FIXME: Zswapped pages can be both SwapUsed and SwapCached, and we do not know which.\n       *\n       * Apparently, it is possible that Zswapped > SwapUsed. This means that some of Zswapped pages\n       * were actually SwapCached, nor SwapUsed. Unfortunately, we cannot tell what exactly portion\n       * of Zswapped pages were SwapCached.\n       *\n       * For now, subtract Zswapped from SwapUsed and only if Zswapped > SwapUsed, subtract the\n       * overflow from SwapCached.\n       */\n      this->values[SWAP_METER_USED] -= lhost->zswap.usedZswapOrig;\n      if (this->values[SWAP_METER_USED] < 0) {\n         /* subtract the overflow from SwapCached */\n         this->values[SWAP_METER_CACHE] += this->values[SWAP_METER_USED];\n         this->values[SWAP_METER_USED] = 0;\n      }\n      this->values[SWAP_METER_FRONTSWAP] += lhost->zswap.usedZswapOrig;\n   }\n}\n\nvoid Platform_setZramValues(Meter* this) {\n   const LinuxMachine* lhost = (const LinuxMachine*) this->host;\n\n   this->total = lhost->zram.totalZram;\n   this->values[ZRAM_METER_COMPRESSED] = lhost->zram.usedZramComp;\n   this->values[ZRAM_METER_UNCOMPRESSED] = lhost->zram.usedZramOrig - lhost->zram.usedZramComp;\n}\n\nvoid Platform_setZfsArcValues(Meter* this) {\n   const LinuxMachine* lhost = (const LinuxMachine*) this->host;\n\n   ZfsArcMeter_readStats(this, &(lhost->zfs));\n}\n\nvoid Platform_setZfsCompressedArcValues(Meter* this) {\n   const LinuxMachine* lhost = (const LinuxMachine*) this->host;\n\n   ZfsCompressedArcMeter_readStats(this, &(lhost->zfs));\n}\n\nchar* Platform_getProcessEnv(pid_t pid) {\n   char procname[128];\n   xSnprintf(procname, sizeof(procname), PROCDIR \"/%d/environ\", pid);\n   FILE* fp = fopen(procname, \"r\");\n   if (!fp)\n      return NULL;\n\n   char* env = NULL;\n\n   size_t capacity = 0;\n   size_t size = 0;\n   ssize_t bytes = 0;\n\n   do {\n      size += bytes;\n      capacity += 4096;\n      env = xRealloc(env, capacity);\n   } while (!ferror(fp) && !feof(fp) && (bytes = fread(env + size, 1, capacity - size, fp)) > 0);\n\n   fclose(fp);\n\n   if (bytes < 0) {\n      free(env);\n      return NULL;\n   }\n\n   size += bytes;\n\n   env = xRealloc(env, size + 2);\n\n   env[size] = '\\0';\n   env[size + 1] = '\\0';\n\n   return env;\n}\n\nFileLocks_ProcessData* Platform_getProcessLocks(pid_t pid) {\n   FileLocks_ProcessData* pdata = xCalloc(1, sizeof(FileLocks_ProcessData));\n   DIR* dirp;\n   int dfd;\n\n   char path[PATH_MAX];\n   xSnprintf(path, sizeof(path), PROCDIR \"/%d/fdinfo/\", pid);\n   if (strlen(path) >= (sizeof(path) - 2))\n      goto err;\n\n   if (!(dirp = opendir(path)))\n      goto err;\n\n   if ((dfd = dirfd(dirp)) == -1) {\n      closedir(dirp);\n      goto err;\n   }\n\n   FileLocks_LockData** data_ref = &pdata->locks;\n   for (struct dirent* de; (de = readdir(dirp)); ) {\n      if (String_eq(de->d_name, \".\") || String_eq(de->d_name, \"..\"))\n         continue;\n\n      errno = 0;\n      char* end = de->d_name;\n      unsigned long int fdstr = strtoul(de->d_name, &end, 10);\n      if (errno || *end || fdstr >= INT_MAX)\n         continue;\n      int file = (int)fdstr;\n\n      int fd = openat(dfd, de->d_name, O_RDONLY | O_CLOEXEC);\n      if (fd == -1)\n         continue;\n      FILE* fp = fdopen(fd, \"r\");\n      if (!fp) {\n         close(fd);\n         continue;\n      }\n\n      for (char buffer[1024]; fgets(buffer, sizeof(buffer), fp); ) {\n         if (!strchr(buffer, '\\n'))\n            continue;\n\n         if (!String_startsWith(buffer, \"lock:\\t\"))\n            continue;\n\n         FileLocks_Data data = {.fd = file};\n         int _;\n         unsigned int maj, min;\n         char lock_end[25], locktype[32], exclusive[32], readwrite[32];\n         if (10 != sscanf(buffer + strlen(\"lock:\\t\"), \"%d: %31s %31s %31s %d %x:%x:%\"PRIu64\" %\"PRIu64\" %24s\",\n            &_, locktype, exclusive, readwrite, &_,\n            &maj, &min, &data.inode,\n            &data.start, lock_end))\n            continue;\n\n         data.locktype = xStrdup(locktype);\n         data.exclusive = xStrdup(exclusive);\n         data.readwrite = xStrdup(readwrite);\n         data.dev = makedev(maj, min);\n\n         if (String_eq(lock_end, \"EOF\"))\n            data.end = ULLONG_MAX;\n         else\n            data.end = strtoull(lock_end, NULL, 10);\n\n         xSnprintf(path, sizeof(path), PROCDIR \"/%d/fd/%s\", pid, de->d_name);\n         char link[PATH_MAX];\n         ssize_t link_len;\n         if (strlen(path) < (sizeof(path) - 2) && (link_len = readlink(path, link, sizeof(link))) != -1)\n            data.filename = xStrndup(link, link_len);\n\n         *data_ref = xCalloc(1, sizeof(FileLocks_LockData));\n         (*data_ref)->data = data;\n         data_ref = &(*data_ref)->next;\n      }\n\n      fclose(fp);\n   }\n\n   closedir(dirp);\n   return pdata;\n\nerr:\n   pdata->error = true;\n   return pdata;\n}\n\nvoid Platform_getPressureStall(const char* file, bool some, double* ten, double* sixty, double* threehundred) {\n   *ten = *sixty = *threehundred = 0;\n   char procname[128];\n   xSnprintf(procname, sizeof(procname), PROCDIR \"/pressure/%s\", file);\n   FILE* fp = fopen(procname, \"r\");\n   if (!fp) {\n      *ten = *sixty = *threehundred = NAN;\n      return;\n   }\n   int total = fscanf(fp, \"some avg10=%32lf avg60=%32lf avg300=%32lf total=%*f \", ten, sixty, threehundred);\n   if (total != EOF && !some) {\n      total = fscanf(fp, \"full avg10=%32lf avg60=%32lf avg300=%32lf total=%*f \", ten, sixty, threehundred);\n   }\n   (void) total;\n   assert(total == 3);\n   fclose(fp);\n}\n\nvoid Platform_getFileDescriptors(double* used, double* max) {\n   char buffer[128] = {0};\n\n   *used = NAN;\n   *max = 65536;\n\n   ssize_t fdread = Compat_readfile(PROCDIR \"/sys/fs/file-nr\", buffer, sizeof(buffer));\n   if (fdread < 1)\n      return;\n\n   unsigned long long v1, v2, v3;\n   int total = sscanf(buffer, \"%llu %llu %llu\", &v1, &v2, &v3);\n   if (total == 3) {\n      *used = v1;\n      *max = v3;\n   }\n}\n\nbool Platform_getDiskIO(DiskIOData* data) {\n   FILE* fp = fopen(PROCDIR \"/diskstats\", \"r\");\n   if (!fp)\n      return false;\n\n   char lastTopDisk[32] = { '\\0' };\n\n   uint64_t read_sum = 0, write_sum = 0, timeSpend_sum = 0;\n   uint64_t numDisks = 0;\n\n   char lineBuffer[256];\n   while (fgets(lineBuffer, sizeof(lineBuffer), fp)) {\n      char diskname[32];\n      unsigned long long int read_tmp, write_tmp, timeSpend_tmp;\n      if (sscanf(lineBuffer, \"%*d %*d %31s %*u %*u %llu %*u %*u %*u %llu %*u %*u %llu\", diskname, &read_tmp, &write_tmp, &timeSpend_tmp) == 4) {\n         if (String_startsWith(diskname, \"dm-\") ||\n             String_startsWith(diskname, \"loop\") ||\n             String_startsWith(diskname, \"md\") ||\n             String_startsWith(diskname, \"zram\"))\n            continue;\n\n         /* only count root disks, e.g. do not count IO from sda and sda1 twice */\n         if (lastTopDisk[0] && String_startsWith(diskname, lastTopDisk))\n            continue;\n\n         /* This assumes disks are listed directly before any of their partitions */\n         String_safeStrncpy(lastTopDisk, diskname, sizeof(lastTopDisk));\n\n         read_sum += read_tmp;\n         write_sum += write_tmp;\n         timeSpend_sum += timeSpend_tmp;\n         numDisks++;\n      }\n   }\n   fclose(fp);\n   /* multiply with sector size */\n   data->totalBytesRead = 512 * read_sum;\n   data->totalBytesWritten = 512 * write_sum;\n   data->totalMsTimeSpend = timeSpend_sum;\n   data->numDisks = numDisks;\n   return true;\n}\n\nbool Platform_getNetworkIO(NetworkIOData* data) {\n   FILE* fp = fopen(PROCDIR \"/net/dev\", \"r\");\n   if (!fp)\n      return false;\n\n   char lineBuffer[512];\n   while (fgets(lineBuffer, sizeof(lineBuffer), fp)) {\n      char interfaceName[32];\n      unsigned long long int bytesReceived, packetsReceived, bytesTransmitted, packetsTransmitted;\n      if (sscanf(lineBuffer, \"%31s %llu %llu %*u %*u %*u %*u %*u %*u %llu %llu\",\n                             interfaceName,\n                             &bytesReceived,\n                             &packetsReceived,\n                             &bytesTransmitted,\n                             &packetsTransmitted) != 5)\n         continue;\n\n      if (String_eq(interfaceName, \"lo:\"))\n         continue;\n\n      data->bytesReceived += bytesReceived;\n      data->packetsReceived += packetsReceived;\n      data->bytesTransmitted += bytesTransmitted;\n      data->packetsTransmitted += packetsTransmitted;\n   }\n\n   fclose(fp);\n\n   return true;\n}\n\n// Linux battery reading by Ian P. Hands (iphands@gmail.com, ihands@redhat.com).\n\n#define PROC_BATTERY_DIR PROCDIR \"/acpi/battery\"\n#define PROC_POWERSUPPLY_DIR PROCDIR \"/acpi/ac_adapter\"\n#define PROC_POWERSUPPLY_ACSTATE_FILE PROC_POWERSUPPLY_DIR \"/AC/state\"\n#define SYS_POWERSUPPLY_DIR \"/sys/class/power_supply\"\n\n// ----------------------------------------\n// READ FROM /proc\n// ----------------------------------------\n\nstatic double Platform_Battery_getProcBatInfo(void) {\n   DIR* batteryDir = opendir(PROC_BATTERY_DIR);\n   if (!batteryDir)\n      return NAN;\n\n   uint64_t totalFull = 0;\n   uint64_t totalRemain = 0;\n\n   struct dirent* dirEntry = NULL;\n   while ((dirEntry = readdir(batteryDir))) {\n      const char* entryName = dirEntry->d_name;\n      if (!String_startsWith(entryName, \"BAT\"))\n         continue;\n\n      char filePath[256];\n      char bufInfo[1024] = {0};\n      xSnprintf(filePath, sizeof(filePath), \"%s/%s/info\", PROC_BATTERY_DIR, entryName);\n      ssize_t r = Compat_readfile(filePath, bufInfo, sizeof(bufInfo));\n      if (r < 0)\n         continue;\n\n      char bufState[1024] = {0};\n      xSnprintf(filePath, sizeof(filePath), \"%s/%s/state\", PROC_BATTERY_DIR, entryName);\n      r = Compat_readfile(filePath, bufState, sizeof(bufState));\n      if (r < 0)\n         continue;\n\n      const char* line;\n\n      //Getting total charge for all batteries\n      char* buf = bufInfo;\n      while ((line = strsep(&buf, \"\\n\")) != NULL) {\n         char field[100] = {0};\n         int val = 0;\n         if (2 != sscanf(line, \"%99[^:]:%d\", field, &val))\n            continue;\n\n         if (String_eq(field, \"last full capacity\")) {\n            totalFull += val;\n            break;\n         }\n      }\n\n      //Getting remaining charge for all batteries\n      buf = bufState;\n      while ((line = strsep(&buf, \"\\n\")) != NULL) {\n         char field[100] = {0};\n         int val = 0;\n         if (2 != sscanf(line, \"%99[^:]:%d\", field, &val))\n            continue;\n\n         if (String_eq(field, \"remaining capacity\")) {\n            totalRemain += val;\n            break;\n         }\n      }\n   }\n\n   closedir(batteryDir);\n\n   return totalFull > 0 ? ((double) totalRemain * 100.0) / (double) totalFull : NAN;\n}\n\nstatic ACPresence procAcpiCheck(void) {\n   char buffer[1024] = {0};\n   ssize_t r = Compat_readfile(PROC_POWERSUPPLY_ACSTATE_FILE, buffer, sizeof(buffer));\n   if (r < 1)\n      return AC_ERROR;\n\n   return String_eq(buffer, \"on-line\") ? AC_PRESENT : AC_ABSENT;\n}\n\nstatic void Platform_Battery_getProcData(double* percent, ACPresence* isOnAC) {\n   *isOnAC = procAcpiCheck();\n   *percent = AC_ERROR != *isOnAC ? Platform_Battery_getProcBatInfo() : NAN;\n}\n\n// ----------------------------------------\n// READ FROM /sys\n// ----------------------------------------\n\nstatic void Platform_Battery_getSysData(double* percent, ACPresence* isOnAC) {\n   *percent = NAN;\n   *isOnAC = AC_ERROR;\n\n   DIR* dir = opendir(SYS_POWERSUPPLY_DIR);\n   if (!dir)\n      return;\n\n   uint64_t totalFull = 0;\n   uint64_t totalRemain = 0;\n\n   const struct dirent* dirEntry;\n   while ((dirEntry = readdir(dir))) {\n      const char* entryName = dirEntry->d_name;\n\n#ifdef HAVE_OPENAT\n      int entryFd = openat(xDirfd(dir), entryName, O_DIRECTORY | O_PATH);\n      if (entryFd < 0)\n         continue;\n#else\n      char entryFd[4096];\n      xSnprintf(entryFd, sizeof(entryFd), SYS_POWERSUPPLY_DIR \"/%s\", entryName);\n#endif\n\n      enum { AC, BAT } type;\n      if (String_startsWith(entryName, \"BAT\")) {\n         type = BAT;\n      } else if (String_startsWith(entryName, \"AC\")) {\n         type = AC;\n      } else {\n         char buffer[32];\n         ssize_t ret = Compat_readfileat(entryFd, \"type\", buffer, sizeof(buffer));\n         if (ret <= 0)\n            goto next;\n\n         /* drop optional trailing newlines */\n         for (char* buf = &buffer[(size_t)ret - 1]; *buf == '\\n'; buf--)\n            *buf = '\\0';\n\n         if (String_eq(buffer, \"Battery\"))\n            type = BAT;\n         else if (String_eq(buffer, \"Mains\"))\n            type = AC;\n         else\n            goto next;\n      }\n\n      if (type == BAT) {\n         char buffer[1024];\n         ssize_t r = Compat_readfileat(entryFd, \"uevent\", buffer, sizeof(buffer));\n         if (r < 0)\n            goto next;\n\n         bool full = false;\n         bool now = false;\n\n         double fullCharge = 0;\n         double capacityLevel = NAN;\n         const char* line;\n\n         char* buf = buffer;\n         while ((line = strsep(&buf, \"\\n\")) != NULL) {\n            char field[100] = {0};\n            int val = 0;\n            if (2 != sscanf(line, \"POWER_SUPPLY_%99[^=]=%d\", field, &val))\n               continue;\n\n            if (String_eq(field, \"CAPACITY\")) {\n               capacityLevel = val / 100.0;\n               continue;\n            }\n\n            if (String_eq(field, \"ENERGY_FULL\") || String_eq(field, \"CHARGE_FULL\")) {\n               fullCharge = val;\n               totalFull += fullCharge;\n               full = true;\n               if (now)\n                  break;\n               continue;\n            }\n\n            if (String_eq(field, \"ENERGY_NOW\") || String_eq(field, \"CHARGE_NOW\")) {\n               totalRemain += val;\n               now = true;\n               if (full)\n                  break;\n               continue;\n            }\n         }\n\n         if (!now && full && isNonnegative(capacityLevel))\n            totalRemain += capacityLevel * fullCharge;\n\n      } else if (type == AC) {\n         if (*isOnAC != AC_ERROR)\n            goto next;\n\n         char buffer[2];\n         ssize_t r = Compat_readfileat(entryFd, \"online\", buffer, sizeof(buffer));\n         if (r < 1) {\n            *isOnAC = AC_ERROR;\n            goto next;\n         }\n\n         if (buffer[0] == '0')\n            *isOnAC = AC_ABSENT;\n         else if (buffer[0] == '1')\n            *isOnAC = AC_PRESENT;\n      }\n\nnext:\n      Compat_openatArgClose(entryFd);\n   }\n\n   closedir(dir);\n\n   *percent = totalFull > 0 ? ((double) totalRemain * 100.0) / (double) totalFull : NAN;\n}\n\nvoid Platform_getBattery(double* percent, ACPresence* isOnAC) {\n   time_t now = time(NULL);\n   // update battery reading is slow. Update it each 10 seconds only.\n   if (now < Platform_Battery_cacheTime + 10) {\n      *percent = Platform_Battery_cachePercent;\n      *isOnAC = Platform_Battery_cacheIsOnAC;\n      return;\n   }\n\n   if (Platform_Battery_method == BAT_PROC) {\n      Platform_Battery_getProcData(percent, isOnAC);\n      if (!isNonnegative(*percent))\n         Platform_Battery_method = BAT_SYS;\n   }\n   if (Platform_Battery_method == BAT_SYS) {\n      Platform_Battery_getSysData(percent, isOnAC);\n      if (!isNonnegative(*percent))\n         Platform_Battery_method = BAT_ERR;\n   }\n   if (Platform_Battery_method == BAT_ERR) {\n      *percent = NAN;\n      *isOnAC = AC_ERROR;\n   } else {\n      *percent = CLAMP(*percent, 0.0, 100.0);\n   }\n   Platform_Battery_cachePercent = *percent;\n   Platform_Battery_cacheIsOnAC = *isOnAC;\n   Platform_Battery_cacheTime = now;\n}\n\nvoid Platform_longOptionsUsage(const char* name)\n{\n#ifdef HAVE_LIBCAP\n   printf(\n\"   --drop-capabilities[=off|basic|strict] Drop Linux capabilities when running as root\\n\"\n\"                                off - do not drop any capabilities\\n\"\n\"                                basic (default) - drop all capabilities not needed by %s\\n\"\n\"                                strict - drop all capabilities except those needed for\\n\"\n\"                                         core functionality\\n\", name);\n#else\n   (void) name;\n#endif\n}\n\nCommandLineStatus Platform_getLongOption(int opt, int argc, char** argv) {\n#ifndef HAVE_LIBCAP\n   (void) argc;\n   (void) argv;\n#endif\n\n   switch (opt) {\n#ifdef HAVE_LIBCAP\n      case 160: {\n         const char* mode = optarg;\n         if (!mode && optind < argc && argv[optind] != NULL &&\n             (argv[optind][0] != '\\0' && argv[optind][0] != '-')) {\n            mode = argv[optind++];\n         }\n\n         if (!mode || String_eq(mode, \"basic\")) {\n            Platform_capabilitiesMode = CAP_MODE_BASIC;\n         } else if (String_eq(mode, \"off\")) {\n            Platform_capabilitiesMode = CAP_MODE_OFF;\n         } else if (String_eq(mode, \"strict\")) {\n            Platform_capabilitiesMode = CAP_MODE_STRICT;\n         } else {\n            fprintf(stderr, \"Error: invalid capabilities mode \\\"%s\\\".\\n\", mode);\n            return STATUS_ERROR_EXIT;\n         }\n         return STATUS_OK;\n      }\n#endif\n\n      default:\n         break;\n   }\n   return STATUS_ERROR_EXIT;\n}\n\n#ifdef HAVE_LIBCAP\nstatic int dropCapabilities(enum CapMode mode) {\n\n   if (mode == CAP_MODE_OFF)\n      return 0;\n\n   /* capabilities we keep to operate */\n   const cap_value_t keepcapsStrict[] = {\n      CAP_DAC_READ_SEARCH,\n      CAP_SYS_PTRACE,\n   };\n   const cap_value_t keepcapsBasic[] = {\n      CAP_DAC_READ_SEARCH,   /* read non world-readable process files of other users, like /proc/[pid]/io */\n      CAP_KILL,              /* send signals to processes of other users */\n      CAP_SYS_NICE,          /* lower process nice value / change nice value for arbitrary processes */\n      CAP_SYS_PTRACE,        /* read /proc/[pid]/exe */\n#ifdef HAVE_DELAYACCT\n      CAP_NET_ADMIN,         /* communicate over netlink socket for delay accounting */\n#endif\n   };\n   const cap_value_t* const keepcaps = (mode == CAP_MODE_BASIC) ? keepcapsBasic : keepcapsStrict;\n   const size_t ncap = (mode == CAP_MODE_BASIC) ? ARRAYSIZE(keepcapsBasic) : ARRAYSIZE(keepcapsStrict);\n\n   cap_t caps = cap_init();\n   if (caps == NULL) {\n      fprintf(stderr, \"Error: cannot initialize capabilities: %s\\n\", strerror(errno));\n      return -1;\n   }\n\n   if (cap_clear(caps) < 0) {\n      fprintf(stderr, \"Error: cannot clear capabilities: %s\\n\", strerror(errno));\n      cap_free(caps);\n      return -1;\n   }\n\n   cap_t currCaps = cap_get_proc();\n   if (currCaps == NULL) {\n      fprintf(stderr, \"Error: cannot get current process capabilities: %s\\n\", strerror(errno));\n      cap_free(caps);\n      return -1;\n   }\n\n   for (size_t i = 0; i < ncap; i++) {\n      if (!CAP_IS_SUPPORTED(keepcaps[i]))\n         continue;\n\n      cap_flag_value_t current;\n      if (cap_get_flag(currCaps, keepcaps[i], CAP_PERMITTED, &current) < 0) {\n         fprintf(stderr, \"Error: cannot get current value of capability %d: %s\\n\", keepcaps[i], strerror(errno));\n         cap_free(currCaps);\n         cap_free(caps);\n         return -1;\n      }\n\n      if (current != CAP_SET)\n         continue;\n\n      if (cap_set_flag(caps, CAP_PERMITTED, 1, &keepcaps[i], CAP_SET) < 0) {\n         fprintf(stderr, \"Error: cannot set permitted capability %d: %s\\n\", keepcaps[i], strerror(errno));\n         cap_free(currCaps);\n         cap_free(caps);\n         return -1;\n      }\n\n      if (cap_set_flag(caps, CAP_EFFECTIVE, 1, &keepcaps[i], CAP_SET) < 0) {\n         fprintf(stderr, \"Error: cannot set effective capability %d: %s\\n\", keepcaps[i], strerror(errno));\n         cap_free(currCaps);\n         cap_free(caps);\n         return -1;\n      }\n   }\n\n   if (cap_set_proc(caps) < 0) {\n      fprintf(stderr, \"Error: cannot set process capabilities: %s\\n\", strerror(errno));\n      cap_free(currCaps);\n      cap_free(caps);\n      return -1;\n   }\n\n   cap_free(currCaps);\n   cap_free(caps);\n\n   return 0;\n}\n#endif\n\nbool Platform_init(void) {\n#ifdef HAVE_LIBCAP\n   if (dropCapabilities(Platform_capabilitiesMode) < 0)\n      return false;\n#endif\n\n   if (access(PROCDIR, R_OK) != 0) {\n      fprintf(stderr, \"Error: could not read procfs (compiled to look in %s).\\n\", PROCDIR);\n      return false;\n   }\n\n#ifdef HAVE_SENSORS_SENSORS_H\n   LibSensors_init();\n#endif\n\n   char target[PATH_MAX];\n   ssize_t ret = readlink(PROCDIR \"/self/ns/pid\", target, sizeof(target) - 1);\n   if (ret > 0) {\n      target[ret] = '\\0';\n\n      if (!String_eq(\"pid:[4026531836]\", target)) { // magic constant PROC_PID_INIT_INO from include/linux/proc_ns.h#L46\n         Running_containerized = true;\n         return true; // early return\n      }\n   }\n\n   FILE* fp = fopen(PROCDIR \"/1/mounts\", \"r\");\n   if (fp) {\n      char lineBuffer[256];\n      while (fgets(lineBuffer, sizeof(lineBuffer), fp)) {\n         // detect lxc or overlayfs and guess that this means we are running containerized\n         if (String_startsWith(lineBuffer, \"lxcfs /proc\") || String_startsWith(lineBuffer, \"overlay / overlay\")) {\n            Running_containerized = true;\n            break;\n         }\n      }\n      fclose(fp);\n   }\n\n   return true;\n}\n\nvoid Platform_done(void) {\n#ifdef HAVE_SENSORS_SENSORS_H\n   LibSensors_cleanup();\n#endif\n}\n"
  },
  {
    "path": "linux/Platform.h",
    "content": "#ifndef HEADER_Platform\n#define HEADER_Platform\n/*\nhtop - linux/Platform.h\n(C) 2014 Hisham H. Muhammad\nReleased under the GNU GPLv2+, see the COPYING file\nin the source distribution for its full text.\n*/\n\n#include <limits.h>\n#include <stdbool.h>\n#include <stddef.h>\n#include <stdint.h>\n#include <sys/time.h>\n#include <sys/types.h>\n\n#include \"Action.h\"\n#include \"BatteryMeter.h\"\n#include \"DiskIOMeter.h\"\n#include \"Hashtable.h\"\n#include \"Macros.h\"\n#include \"MemoryMeter.h\"\n#include \"NetworkIOMeter.h\"\n#include \"Panel.h\"\n#include \"Process.h\"\n#include \"ProcessLocksScreen.h\"\n#include \"RichString.h\"\n#include \"Settings.h\"\n#include \"SignalsPanel.h\"\n#include \"CommandLine.h\"\n#include \"generic/gettime.h\"\n#include \"generic/hostname.h\"\n#include \"generic/uname.h\"\n\n\n/* GNU/Hurd does not have PATH_MAX in limits.h */\n#ifndef PATH_MAX\n   #define PATH_MAX 4096\n#endif\n\n\nextern const ScreenDefaults Platform_defaultScreens[];\n\nextern const unsigned int Platform_numberOfDefaultScreens;\n\nextern const SignalItem Platform_signals[];\n\nextern const unsigned int Platform_numberOfSignals;\n\nextern const MemoryClass Platform_memoryClasses[];\n\nextern const unsigned int Platform_numberOfMemoryClasses;\n\nextern const MeterClass* const Platform_meterTypes[];\n\nbool Platform_init(void);\nvoid Platform_done(void);\n\nextern bool Running_containerized;\n\nvoid Platform_setBindings(Htop_Action* keys);\n\nint Platform_getUptime(void);\n\nvoid Platform_getLoadAverage(double* one, double* five, double* fifteen);\n\npid_t Platform_getMaxPid(void);\n\ndouble Platform_setCPUValues(Meter* this, unsigned int cpu);\n\nvoid Platform_setGPUValues(Meter* this, double* totalUsage, unsigned long long* totalGPUTimeDiff);\n\nvoid Platform_setMemoryValues(Meter* this);\n\nvoid Platform_setSwapValues(Meter* this);\n\nvoid Platform_setZramValues(Meter* this);\n\nvoid Platform_setZfsArcValues(Meter* this);\n\nvoid Platform_setZfsCompressedArcValues(Meter* this);\n\nchar* Platform_getProcessEnv(pid_t pid);\n\nFileLocks_ProcessData* Platform_getProcessLocks(pid_t pid);\n\nvoid Platform_getPressureStall(const char* file, bool some, double* ten, double* sixty, double* threehundred);\n\nvoid Platform_getFileDescriptors(double* used, double* max);\n\nbool Platform_getDiskIO(DiskIOData* data);\n\nbool Platform_getNetworkIO(NetworkIOData* data);\n\nvoid Platform_getBattery(double* percent, ACPresence* isOnAC);\n\nstatic inline void Platform_getHostname(char* buffer, size_t size) {\n   Generic_hostname(buffer, size);\n}\n\nstatic inline const char* Platform_getRelease(void) {\n   return Generic_uname();\n}\n\nstatic inline const char* Platform_getFailedState(void) {\n   return NULL;\n}\n\n#ifdef HAVE_LIBCAP\n   #define PLATFORM_LONG_OPTIONS \\\n      {\"drop-capabilities\", optional_argument, 0, 160},\n#else\n   #define PLATFORM_LONG_OPTIONS\n#endif\n\nvoid Platform_longOptionsUsage(const char* name);\n\nCommandLineStatus Platform_getLongOption(int opt, int argc, char** argv);\n\nstatic inline void Platform_gettime_realtime(struct timeval* tv, uint64_t* msec) {\n   Generic_gettime_realtime(tv, msec);\n}\n\nstatic inline void Platform_gettime_monotonic(uint64_t* msec) {\n   Generic_gettime_monotonic(msec);\n}\n\nstatic inline Hashtable* Platform_dynamicMeters(void) {\n   return NULL;\n}\n\nstatic inline void Platform_dynamicMetersDone(ATTR_UNUSED Hashtable* table) { }\n\nstatic inline void Platform_dynamicMeterInit(ATTR_UNUSED Meter* meter) { }\n\nstatic inline void Platform_dynamicMeterUpdateValues(ATTR_UNUSED Meter* meter) { }\n\nstatic inline void Platform_dynamicMeterDisplay(ATTR_UNUSED const Meter* meter, ATTR_UNUSED RichString* out) { }\n\nstatic inline Hashtable* Platform_dynamicColumns(void) {\n   return NULL;\n}\n\nstatic inline void Platform_dynamicColumnsDone(ATTR_UNUSED Hashtable* table) { }\n\nstatic inline const char* Platform_dynamicColumnName(ATTR_UNUSED unsigned int key) {\n   return NULL;\n}\n\nstatic inline bool Platform_dynamicColumnWriteField(ATTR_UNUSED const Process* proc, ATTR_UNUSED RichString* str, ATTR_UNUSED unsigned int key) {\n   return false;\n}\n\nstatic inline Hashtable* Platform_dynamicScreens(void) {\n   return NULL;\n}\n\nstatic inline void Platform_defaultDynamicScreens(ATTR_UNUSED Settings* settings) { }\n\nstatic inline void Platform_addDynamicScreen(ATTR_UNUSED ScreenSettings* ss) { }\n\nstatic inline void Platform_addDynamicScreenAvailableColumns(ATTR_UNUSED Panel* availableColumns, ATTR_UNUSED const char* screen) { }\n\nstatic inline void Platform_dynamicScreensDone(ATTR_UNUSED Hashtable* screens) { }\n\n#endif\n"
  },
  {
    "path": "linux/PressureStallMeter.c",
    "content": "/*\nhtop - PressureStallMeter.c\n(C) 2004-2011 Hisham H. Muhammad\n(C) 2019 Ran Benita\nReleased under the GNU GPLv2+, see the COPYING file\nin the source distribution for its full text.\n*/\n\n#include \"config.h\" // IWYU pragma: keep\n\n#include \"linux/PressureStallMeter.h\"\n\n#include <stdbool.h>\n#include <string.h>\n\n#include \"CRT.h\"\n#include \"Meter.h\"\n#include \"Object.h\"\n#include \"Platform.h\"\n#include \"RichString.h\"\n#include \"XUtils.h\"\n\n\nstatic const int PressureStallMeter_attributes[] = {\n   PRESSURE_STALL_TEN,\n   PRESSURE_STALL_SIXTY,\n   PRESSURE_STALL_THREEHUNDRED\n};\n\nstatic void PressureStallMeter_updateValues(Meter* this) {\n   const char* file;\n   if (strstr(Meter_name(this), \"CPU\")) {\n      file = \"cpu\";\n   } else if (strstr(Meter_name(this), \"IO\")) {\n      file = \"io\";\n   } else if (strstr(Meter_name(this), \"IRQ\")) {\n      file = \"irq\";\n   } else {\n      file = \"memory\";\n   }\n\n   bool some;\n   if (strstr(Meter_name(this), \"Some\")) {\n      some = true;\n   } else {\n      some = false;\n   }\n\n   Platform_getPressureStall(file, some, &this->values[0], &this->values[1], &this->values[2]);\n\n   /* only print bar for ten (not sixty and three hundred), cause the sum is meaningless */\n   this->curItems = 1;\n\n   xSnprintf(this->txtBuffer, sizeof(this->txtBuffer), \"%s %s %5.2lf%% %5.2lf%% %5.2lf%%\", some ? \"some\" : \"full\", file, this->values[0], this->values[1], this->values[2]);\n}\n\nstatic void PressureStallMeter_display(const Object* cast, RichString* out) {\n   const Meter* this = (const Meter*)cast;\n   char buffer[20];\n   int len;\n\n   len = xSnprintf(buffer, sizeof(buffer), \"%5.2lf%% \", this->values[0]);\n   RichString_appendnAscii(out, CRT_colors[PRESSURE_STALL_TEN], buffer, len);\n   len = xSnprintf(buffer, sizeof(buffer), \"%5.2lf%% \", this->values[1]);\n   RichString_appendnAscii(out, CRT_colors[PRESSURE_STALL_SIXTY], buffer, len);\n   len = xSnprintf(buffer, sizeof(buffer), \"%5.2lf%% \", this->values[2]);\n   RichString_appendnAscii(out, CRT_colors[PRESSURE_STALL_THREEHUNDRED], buffer, len);\n}\n\nconst MeterClass PressureStallCPUSomeMeter_class = {\n   .super = {\n      .extends = Class(Meter),\n      .delete = Meter_delete,\n      .display = PressureStallMeter_display,\n   },\n   .updateValues = PressureStallMeter_updateValues,\n   .defaultMode = TEXT_METERMODE,\n   .supportedModes = METERMODE_DEFAULT_SUPPORTED,\n   .maxItems = 3,\n   .isPercentChart = true,\n   .total = 100.0,\n   .attributes = PressureStallMeter_attributes,\n   .name = \"PressureStallCPUSome\",\n   .uiName = \"PSI some CPU\",\n   .caption = \"PSI some CPU:    \",\n   .description = \"Pressure Stall Information, some cpu\"\n};\n\nconst MeterClass PressureStallIOSomeMeter_class = {\n   .super = {\n      .extends = Class(Meter),\n      .delete = Meter_delete,\n      .display = PressureStallMeter_display,\n   },\n   .updateValues = PressureStallMeter_updateValues,\n   .defaultMode = TEXT_METERMODE,\n   .supportedModes = METERMODE_DEFAULT_SUPPORTED,\n   .maxItems = 3,\n   .isPercentChart = true,\n   .total = 100.0,\n   .attributes = PressureStallMeter_attributes,\n   .name = \"PressureStallIOSome\",\n   .uiName = \"PSI some IO\",\n   .caption = \"PSI some IO:     \",\n   .description = \"Pressure Stall Information, some io\"\n};\n\nconst MeterClass PressureStallIOFullMeter_class = {\n   .super = {\n      .extends = Class(Meter),\n      .delete = Meter_delete,\n      .display = PressureStallMeter_display,\n   },\n   .updateValues = PressureStallMeter_updateValues,\n   .defaultMode = TEXT_METERMODE,\n   .supportedModes = METERMODE_DEFAULT_SUPPORTED,\n   .maxItems = 3,\n   .isPercentChart = true,\n   .total = 100.0,\n   .attributes = PressureStallMeter_attributes,\n   .name = \"PressureStallIOFull\",\n   .uiName = \"PSI full IO\",\n   .caption = \"PSI full IO:     \",\n   .description = \"Pressure Stall Information, full io\"\n};\n\nconst MeterClass PressureStallIRQFullMeter_class = {\n   .super = {\n      .extends = Class(Meter),\n      .delete = Meter_delete,\n      .display = PressureStallMeter_display,\n   },\n   .updateValues = PressureStallMeter_updateValues,\n   .defaultMode = TEXT_METERMODE,\n   .supportedModes = METERMODE_DEFAULT_SUPPORTED,\n   .maxItems = 3,\n   .isPercentChart = true,\n   .total = 100.0,\n   .attributes = PressureStallMeter_attributes,\n   .name = \"PressureStallIRQFull\",\n   .uiName = \"PSI full IRQ\",\n   .caption = \"PSI full IRQ:    \",\n   .description = \"Pressure Stall Information, full irq\"\n};\n\nconst MeterClass PressureStallMemorySomeMeter_class = {\n   .super = {\n      .extends = Class(Meter),\n      .delete = Meter_delete,\n      .display = PressureStallMeter_display,\n   },\n   .updateValues = PressureStallMeter_updateValues,\n   .defaultMode = TEXT_METERMODE,\n   .supportedModes = METERMODE_DEFAULT_SUPPORTED,\n   .maxItems = 3,\n   .isPercentChart = true,\n   .total = 100.0,\n   .attributes = PressureStallMeter_attributes,\n   .name = \"PressureStallMemorySome\",\n   .uiName = \"PSI some memory\",\n   .caption = \"PSI some memory: \",\n   .description = \"Pressure Stall Information, some memory\"\n};\n\nconst MeterClass PressureStallMemoryFullMeter_class = {\n   .super = {\n      .extends = Class(Meter),\n      .delete = Meter_delete,\n      .display = PressureStallMeter_display,\n   },\n   .updateValues = PressureStallMeter_updateValues,\n   .defaultMode = TEXT_METERMODE,\n   .supportedModes = METERMODE_DEFAULT_SUPPORTED,\n   .maxItems = 3,\n   .isPercentChart = true,\n   .total = 100.0,\n   .attributes = PressureStallMeter_attributes,\n   .name = \"PressureStallMemoryFull\",\n   .uiName = \"PSI full memory\",\n   .caption = \"PSI full memory: \",\n   .description = \"Pressure Stall Information, full memory\"\n};\n"
  },
  {
    "path": "linux/PressureStallMeter.h",
    "content": "/* Do not edit this file. It was automatically generated. */\n\n#ifndef HEADER_PressureStallMeter\n#define HEADER_PressureStallMeter\n/*\nhtop - PressureStallMeter.h\n(C) 2004-2011 Hisham H. Muhammad\n(C) 2019 Ran Benita\nReleased under the GNU GPLv2+, see the COPYING file\nin the source distribution for its full text.\n*/\n\n#include \"Meter.h\"\n\n\nextern const MeterClass PressureStallCPUSomeMeter_class;\n\nextern const MeterClass PressureStallIOSomeMeter_class;\n\nextern const MeterClass PressureStallIOFullMeter_class;\n\nextern const MeterClass PressureStallIRQFullMeter_class;\n\nextern const MeterClass PressureStallMemorySomeMeter_class;\n\nextern const MeterClass PressureStallMemoryFullMeter_class;\n\n#endif\n"
  },
  {
    "path": "linux/ProcessField.h",
    "content": "#ifndef HEADER_LinuxProcessField\n#define HEADER_LinuxProcessField\n/*\nhtop - linux/ProcessField.h\n(C) 2020 htop dev team\nReleased under the GNU GPLv2+, see the COPYING file\nin the source distribution for its full text.\n*/\n\n\n#define PLATFORM_PROCESS_FIELDS  \\\n   CMINFLT = 11,                 \\\n   CMAJFLT = 13,                 \\\n   UTIME = 14,                   \\\n   STIME = 15,                   \\\n   CUTIME = 16,                  \\\n   CSTIME = 17,                  \\\n   M_SHARE = 41,                 \\\n   M_TRS = 42,                   \\\n   M_DRS = 43,                   \\\n   M_LRS = 44,                   \\\n   CTID = 100,                   \\\n   VPID = 101,                   \\\n   VXID = 102,                   \\\n   RCHAR = 103,                  \\\n   WCHAR = 104,                  \\\n   SYSCR = 105,                  \\\n   SYSCW = 106,                  \\\n   RBYTES = 107,                 \\\n   WBYTES = 108,                 \\\n   CNCLWB = 109,                 \\\n   IO_READ_RATE = 110,           \\\n   IO_WRITE_RATE = 111,          \\\n   IO_RATE = 112,                \\\n   CGROUP = 113,                 \\\n   OOM = 114,                    \\\n   IO_PRIORITY = 115,            \\\n   PERCENT_CPU_DELAY = 116,      \\\n   PERCENT_IO_DELAY = 117,       \\\n   PERCENT_SWAP_DELAY = 118,     \\\n   M_PSS = 119,                  \\\n   M_SWAP = 120,                 \\\n   M_PSSWP = 121,                \\\n   CTXT = 122,                   \\\n   SECATTR = 123,                \\\n   AUTOGROUP_ID = 127,           \\\n   AUTOGROUP_NICE = 128,         \\\n   CCGROUP = 129,                \\\n   CONTAINER = 130,              \\\n   M_PRIV = 131,                 \\\n   GPU_TIME = 132,               \\\n   GPU_PERCENT = 133,            \\\n   ISCONTAINER = 134,            \\\n   // End of list\n\n\n#endif /* HEADER_LinuxProcessField */\n"
  },
  {
    "path": "linux/SELinuxMeter.c",
    "content": "/*\nhtop - SELinuxMeter.c\n(C) 2020 htop dev team\nReleased under the GNU GPLv2+, see the COPYING file\nin the source distribution for its full text.\n*/\n\n#include \"config.h\" // IWYU pragma: keep\n\n#include \"linux/SELinuxMeter.h\"\n\n#include \"CRT.h\"\n\n#include <stdbool.h>\n#include <stdint.h>\n#include <stdio.h>\n#include <unistd.h>\n#include <sys/statfs.h>\n#include <sys/statvfs.h>\n\n#include \"Object.h\"\n\n#include \"linux/Compat.h\"\n\n\nstatic const int SELinuxMeter_attributes[] = {\n   METER_TEXT,\n};\n\nstatic bool enabled = false;\nstatic bool enforcing = false;\n\nstatic bool hasSELinuxMount(void) {\n   struct statfs sfbuf;\n   int r = statfs(\"/sys/fs/selinux\", &sfbuf);\n   if (r != 0) {\n      return false;\n   }\n\n   if ((uint32_t)sfbuf.f_type != /* SELINUX_MAGIC */ 0xf97cff8cU) {\n      return false;\n   }\n\n   struct statvfs vfsbuf;\n   r = statvfs(\"/sys/fs/selinux\", &vfsbuf);\n   if (r != 0 || (vfsbuf.f_flag & ST_RDONLY)) {\n      return false;\n   }\n\n   return true;\n}\n\nstatic bool isSelinuxEnabled(void) {\n   return hasSELinuxMount() && (0 == access(\"/etc/selinux/config\", F_OK));\n}\n\nstatic bool isSelinuxEnforcing(void) {\n   if (!enabled) {\n      return false;\n   }\n\n   char buf[20];\n   ssize_t r = Compat_readfile(\"/sys/fs/selinux/enforce\", buf, sizeof(buf));\n   if (r < 0)\n      return false;\n\n   int enforce = 0;\n   if (sscanf(buf, \"%d\", &enforce) != 1) {\n      return false;\n   }\n\n   return !!enforce;\n}\n\nstatic void SELinuxMeter_updateValues(Meter* this) {\n   enabled = isSelinuxEnabled();\n   enforcing = isSelinuxEnforcing();\n\n   xSnprintf(this->txtBuffer, sizeof(this->txtBuffer), \"%s%s\", enabled ? \"enabled\" : \"disabled\", enabled ? (enforcing ? \"; mode: enforcing\" : \"; mode: permissive\") : \"\");\n}\n\nconst MeterClass SELinuxMeter_class = {\n   .super = {\n      .extends = Class(Meter),\n      .delete = Meter_delete,\n   },\n   .updateValues = SELinuxMeter_updateValues,\n   .defaultMode = TEXT_METERMODE,\n   .supportedModes = (1 << TEXT_METERMODE),\n   .maxItems = 0,\n   .total = 0.0,\n   .attributes = SELinuxMeter_attributes,\n   .name = \"SELinux\",\n   .uiName = \"SELinux\",\n   .description = \"SELinux state overview\",\n   .caption = \"SELinux: \"\n};\n"
  },
  {
    "path": "linux/SELinuxMeter.h",
    "content": "#ifndef HEADER_SELinuxMeter\n#define HEADER_SELinuxMeter\n/*\nhtop - SELinuxMeter.h\n(C) 2020 htop dev team\nReleased under the GNU GPLv2+, see the COPYING file\nin the source distribution for its full text.\n*/\n\n#include \"Meter.h\"\n\n\nextern const MeterClass SELinuxMeter_class;\n\n#endif /* HEADER_SELinuxMeter */\n"
  },
  {
    "path": "linux/SystemdMeter.c",
    "content": "/*\nhtop - SystemdMeter.c\n(C) 2020 htop dev team\nReleased under the GNU GPLv2+, see the COPYING file\nin the source distribution for its full text.\n*/\n\n#include \"config.h\" // IWYU pragma: keep\n\n#include \"linux/SystemdMeter.h\"\n\n#include <dlfcn.h>\n#include <fcntl.h>\n#include <stdbool.h>\n#include <stdio.h>\n#include <stdlib.h>\n#include <string.h>\n#include <unistd.h>\n#include <sys/wait.h>\n\n#include \"CRT.h\"\n#include \"Macros.h\"\n#include \"Object.h\"\n#include \"RichString.h\"\n#include \"Settings.h\"\n#include \"XUtils.h\"\n\n#if defined(BUILD_STATIC) && defined(HAVE_LIBSYSTEMD)\n#include <systemd/sd-bus.h>\n#endif\n\n\n#ifdef BUILD_STATIC\n\n#define sym_sd_bus_open_system sd_bus_open_system\n#define sym_sd_bus_open_user sd_bus_open_user\n#define sym_sd_bus_get_property_string sd_bus_get_property_string\n#define sym_sd_bus_get_property_trivial sd_bus_get_property_trivial\n#define sym_sd_bus_unref sd_bus_unref\n\n#else\n\ntypedef void sd_bus;\ntypedef void sd_bus_error;\nstatic int (*sym_sd_bus_open_system)(sd_bus**);\nstatic int (*sym_sd_bus_open_user)(sd_bus**);\nstatic int (*sym_sd_bus_get_property_string)(sd_bus*, const char*, const char*, const char*, const char*, sd_bus_error*, char**);\nstatic int (*sym_sd_bus_get_property_trivial)(sd_bus*, const char*, const char*, const char*, const char*, sd_bus_error*, char, void*);\nstatic sd_bus* (*sym_sd_bus_unref)(sd_bus*);\nstatic void* dlopenHandle = NULL;\n\n#endif /* BUILD_STATIC */\n\n\n#define INVALID_VALUE ((unsigned int)-1)\n\ntypedef struct SystemdMeterContext {\n#if !defined(BUILD_STATIC) || defined(HAVE_LIBSYSTEMD)\n   sd_bus* bus;\n#endif /* !BUILD_STATIC || HAVE_LIBSYSTEMD */\n   char* systemState;\n   unsigned int nFailedUnits;\n   unsigned int nInstalledJobs;\n   unsigned int nNames;\n   unsigned int nJobs;\n} SystemdMeterContext_t;\n\nstatic SystemdMeterContext_t ctx_system;\nstatic SystemdMeterContext_t ctx_user;\n\nstatic void SystemdMeter_done(ATTR_UNUSED Meter* this) {\n   SystemdMeterContext_t* ctx = String_eq(Meter_name(this), \"SystemdUser\") ? &ctx_user : &ctx_system;\n\n   free(ctx->systemState);\n   ctx->systemState = NULL;\n\n#ifdef BUILD_STATIC\n# ifdef HAVE_LIBSYSTEMD\n   if (ctx->bus) {\n      sym_sd_bus_unref(ctx->bus);\n   }\n   ctx->bus = NULL;\n# endif /* HAVE_LIBSYSTEMD */\n#else /* BUILD_STATIC */\n   if (ctx->bus && dlopenHandle) {\n      sym_sd_bus_unref(ctx->bus);\n   }\n   ctx->bus = NULL;\n\n   if (!ctx_system.systemState && !ctx_user.systemState && dlopenHandle) {\n      dlclose(dlopenHandle);\n      dlopenHandle = NULL;\n   }\n#endif /* BUILD_STATIC */\n}\n\n#if !defined(BUILD_STATIC) || defined(HAVE_LIBSYSTEMD)\nstatic int updateViaLib(bool user) {\n   SystemdMeterContext_t* ctx = user ? &ctx_user : &ctx_system;\n#ifndef BUILD_STATIC\n   if (!dlopenHandle) {\n      dlopenHandle = dlopen(\"libsystemd.so.0\", RTLD_LAZY);\n      if (!dlopenHandle)\n         goto dlfailure;\n\n      /* Clear any errors */\n      dlerror();\n\n      #define resolve(symbolname) do {                                      \\\n         *(void **)(&sym_##symbolname) = dlsym(dlopenHandle, #symbolname);  \\\n         if (!sym_##symbolname || dlerror() != NULL)                        \\\n            goto dlfailure;                                                 \\\n      } while(0)\n\n      resolve(sd_bus_open_system);\n      resolve(sd_bus_open_user);\n      resolve(sd_bus_get_property_string);\n      resolve(sd_bus_get_property_trivial);\n      resolve(sd_bus_unref);\n\n      #undef resolve\n   }\n#endif /* !BUILD_STATIC */\n\n   int r;\n   /* Connect to the system bus */\n   if (!ctx->bus) {\n      if (user) {\n         r = sym_sd_bus_open_user(&ctx->bus);\n      } else {\n         r = sym_sd_bus_open_system(&ctx->bus);\n      }\n      if (r < 0)\n         goto busfailure;\n   }\n\n   static const char* const busServiceName = \"org.freedesktop.systemd1\";\n   static const char* const busObjectPath = \"/org/freedesktop/systemd1\";\n   static const char* const busInterfaceName = \"org.freedesktop.systemd1.Manager\";\n\n   r = sym_sd_bus_get_property_string(ctx->bus,\n                                      busServiceName,        /* service to contact */\n                                      busObjectPath,         /* object path */\n                                      busInterfaceName,      /* interface name */\n                                      \"SystemState\",         /* property name */\n                                      NULL,                  /* object to return error in */\n                                      &ctx->systemState);\n   if (r < 0)\n      goto busfailure;\n\n   r = sym_sd_bus_get_property_trivial(ctx->bus,\n                                       busServiceName,       /* service to contact */\n                                       busObjectPath,        /* object path */\n                                       busInterfaceName,     /* interface name */\n                                       \"NFailedUnits\",       /* property name */\n                                       NULL,                 /* object to return error in */\n                                       'u',                  /* property type */\n                                       &ctx->nFailedUnits);\n   if (r < 0)\n      goto busfailure;\n\n   r = sym_sd_bus_get_property_trivial(ctx->bus,\n                                       busServiceName,       /* service to contact */\n                                       busObjectPath,        /* object path */\n                                       busInterfaceName,     /* interface name */\n                                       \"NInstalledJobs\",     /* property name */\n                                       NULL,                 /* object to return error in */\n                                       'u',                  /* property type */\n                                       &ctx->nInstalledJobs);\n   if (r < 0)\n      goto busfailure;\n\n   r = sym_sd_bus_get_property_trivial(ctx->bus,\n                                       busServiceName,       /* service to contact */\n                                       busObjectPath,        /* object path */\n                                       busInterfaceName,     /* interface name */\n                                       \"NNames\",             /* property name */\n                                       NULL,                 /* object to return error in */\n                                       'u',                  /* property type */\n                                       &ctx->nNames);\n   if (r < 0)\n      goto busfailure;\n\n   r = sym_sd_bus_get_property_trivial(ctx->bus,\n                                       busServiceName,       /* service to contact */\n                                       busObjectPath,        /* object path */\n                                       busInterfaceName,     /* interface name */\n                                       \"NJobs\",              /* property name */\n                                       NULL,                 /* object to return error in */\n                                       'u',                  /* property type */\n                                       &ctx->nJobs);\n   if (r < 0)\n      goto busfailure;\n\n   /* success */\n   return 0;\n\nbusfailure:\n   sym_sd_bus_unref(ctx->bus);\n   ctx->bus = NULL;\n   return -2;\n\n#ifndef BUILD_STATIC\ndlfailure:\n   if (dlopenHandle) {\n      dlclose(dlopenHandle);\n      dlopenHandle = NULL;\n   }\n   return -1;\n#endif /* !BUILD_STATIC */\n}\n#endif /* !BUILD_STATIC || HAVE_LIBSYSTEMD */\n\nstatic void updateViaExec(bool user) {\n   SystemdMeterContext_t* ctx = user ? &ctx_user : &ctx_system;\n\n   if (Settings_isReadonly())\n      return;\n\n   int fdpair[2] = {-1, -1};\n   if (pipe(fdpair) < 0)\n      return;\n\n   pid_t child = fork();\n   if (child < 0) {\n      close(fdpair[1]);\n      close(fdpair[0]);\n      return;\n   }\n\n   if (child == 0) {\n      close(fdpair[0]);\n      dup2(fdpair[1], STDOUT_FILENO);\n      close(fdpair[1]);\n      int fdnull = open(\"/dev/null\", O_WRONLY);\n      if (fdnull < 0)\n         exit(1);\n      dup2(fdnull, STDERR_FILENO);\n      close(fdnull);\n      // Use of NULL in variadic functions must have a pointer cast.\n      // The NULL constant is not required by standard to have a pointer type.\n      execlp(\n         \"systemctl\",\n         \"systemctl\",\n         \"show\",\n         user ? \"--user\" : \"--system\",\n         \"--property=SystemState\",\n         \"--property=NFailedUnits\",\n         \"--property=NNames\",\n         \"--property=NJobs\",\n         \"--property=NInstalledJobs\",\n         (char*)NULL);\n      exit(127);\n   }\n   close(fdpair[1]);\n\n   int wstatus;\n   if (waitpid(child, &wstatus, 0) < 0 || !WIFEXITED(wstatus) || WEXITSTATUS(wstatus) != 0) {\n      close(fdpair[0]);\n      return;\n   }\n\n   FILE* commandOutput = fdopen(fdpair[0], \"r\");\n   if (!commandOutput) {\n      close(fdpair[0]);\n      return;\n   }\n\n   char lineBuffer[128];\n   while (fgets(lineBuffer, sizeof(lineBuffer), commandOutput)) {\n      char* endptr;\n      if (String_startsWith(lineBuffer, \"SystemState=\")) {\n         char* newline = strchr(lineBuffer + strlen(\"SystemState=\"), '\\n');\n         if (newline) {\n            *newline = '\\0';\n         }\n         free_and_xStrdup(&ctx->systemState, lineBuffer + strlen(\"SystemState=\"));\n      } else if (String_startsWith(lineBuffer, \"NFailedUnits=\")) {\n         unsigned long value = strtoul(lineBuffer + strlen(\"NFailedUnits=\"), &endptr, 10);\n         if (value <= UINT_MAX && *endptr == '\\0')\n            ctx->nFailedUnits = (unsigned int) value;\n      } else if (String_startsWith(lineBuffer, \"NNames=\")) {\n         unsigned long value = strtoul(lineBuffer + strlen(\"NNames=\"), &endptr, 10);\n         if (value <= UINT_MAX && *endptr == '\\0')\n            ctx->nNames = (unsigned int) value;\n      } else if (String_startsWith(lineBuffer, \"NJobs=\")) {\n         unsigned long value = strtoul(lineBuffer + strlen(\"NJobs=\"), &endptr, 10);\n         if (value <= UINT_MAX && *endptr == '\\0')\n            ctx->nJobs = (unsigned int) value;\n      } else if (String_startsWith(lineBuffer, \"NInstalledJobs=\")) {\n         unsigned long value = strtoul(lineBuffer + strlen(\"NInstalledJobs=\"), &endptr, 10);\n         if (value <= UINT_MAX && *endptr == '\\0')\n            ctx->nInstalledJobs = (unsigned int) value;\n      }\n   }\n\n   fclose(commandOutput);\n}\n\nstatic void SystemdMeter_updateValues(Meter* this) {\n   bool user = String_eq(Meter_name(this), \"SystemdUser\");\n   SystemdMeterContext_t* ctx = user ? &ctx_user : &ctx_system;\n\n   free(ctx->systemState);\n   ctx->systemState = NULL;\n   ctx->nFailedUnits = ctx->nInstalledJobs = ctx->nNames = ctx->nJobs = INVALID_VALUE;\n\n#if !defined(BUILD_STATIC) || defined(HAVE_LIBSYSTEMD)\n   if (updateViaLib(user) < 0)\n      updateViaExec(user);\n#else\n   updateViaExec(user);\n#endif /* !BUILD_STATIC || HAVE_LIBSYSTEMD */\n\n   xSnprintf(this->txtBuffer, sizeof(this->txtBuffer), \"%s\", ctx->systemState ? ctx->systemState : \"???\");\n}\n\nstatic int zeroDigitColor(unsigned int value) {\n   switch (value) {\n      case 0:\n         return CRT_colors[METER_VALUE];\n      case INVALID_VALUE:\n         return CRT_colors[METER_VALUE_ERROR];\n      default:\n         return CRT_colors[METER_VALUE_NOTICE];\n   }\n}\n\nstatic int valueDigitColor(unsigned int value) {\n   switch (value) {\n      case 0:\n         return CRT_colors[METER_VALUE_NOTICE];\n      case INVALID_VALUE:\n         return CRT_colors[METER_VALUE_ERROR];\n      default:\n         return CRT_colors[METER_VALUE];\n   }\n}\n\n\nstatic void SystemdMeter_display(ATTR_UNUSED const Object* cast, RichString* out, SystemdMeterContext_t* ctx) {\n   char buffer[16];\n   int len;\n   int color = METER_VALUE_ERROR;\n\n   if (ctx->systemState) {\n      color = String_eq(ctx->systemState, \"running\") ? METER_VALUE_OK :\n              String_eq(ctx->systemState, \"degraded\") ? METER_VALUE_ERROR : METER_VALUE_WARN;\n   }\n   RichString_writeAscii(out, CRT_colors[color], ctx->systemState ? ctx->systemState : \"N/A\");\n\n   RichString_appendAscii(out, CRT_colors[METER_TEXT], \" (\");\n\n   if (ctx->nFailedUnits == INVALID_VALUE) {\n      buffer[0] = '?';\n      buffer[1] = '\\0';\n      len = 1;\n   } else {\n      len = xSnprintf(buffer, sizeof(buffer), \"%u\", ctx->nFailedUnits);\n   }\n   RichString_appendnAscii(out, zeroDigitColor(ctx->nFailedUnits), buffer, len);\n\n   RichString_appendAscii(out, CRT_colors[METER_TEXT], \"/\");\n\n   if (ctx->nNames == INVALID_VALUE) {\n      buffer[0] = '?';\n      buffer[1] = '\\0';\n      len = 1;\n   } else {\n      len = xSnprintf(buffer, sizeof(buffer), \"%u\", ctx->nNames);\n   }\n   RichString_appendnAscii(out, valueDigitColor(ctx->nNames), buffer, len);\n\n   RichString_appendAscii(out, CRT_colors[METER_TEXT], \" failed) (\");\n\n   if (ctx->nJobs == INVALID_VALUE) {\n      buffer[0] = '?';\n      buffer[1] = '\\0';\n      len = 1;\n   } else {\n      len = xSnprintf(buffer, sizeof(buffer), \"%u\", ctx->nJobs);\n   }\n   RichString_appendnAscii(out, zeroDigitColor(ctx->nJobs), buffer, len);\n\n   RichString_appendAscii(out, CRT_colors[METER_TEXT], \"/\");\n\n   if (ctx->nInstalledJobs == INVALID_VALUE) {\n      buffer[0] = '?';\n      buffer[1] = '\\0';\n      len = 1;\n   } else {\n      len = xSnprintf(buffer, sizeof(buffer), \"%u\", ctx->nInstalledJobs);\n   }\n   RichString_appendnAscii(out, valueDigitColor(ctx->nInstalledJobs), buffer, len);\n\n   RichString_appendAscii(out, CRT_colors[METER_TEXT], \" jobs)\");\n}\n\nstatic void SystemdMeter_display_system(ATTR_UNUSED const Object* cast, RichString* out) {\n   SystemdMeter_display(cast, out, &ctx_system);\n}\n\nstatic void SystemdMeter_display_user(ATTR_UNUSED const Object* cast, RichString* out) {\n   SystemdMeter_display(cast, out, &ctx_user);\n}\n\nstatic const int SystemdMeter_attributes[] = {\n   METER_VALUE\n};\n\nconst MeterClass SystemdMeter_class = {\n   .super = {\n      .extends = Class(Meter),\n      .delete = Meter_delete,\n      .display = SystemdMeter_display_system,\n   },\n   .updateValues = SystemdMeter_updateValues,\n   .done = SystemdMeter_done,\n   .defaultMode = TEXT_METERMODE,\n   .supportedModes = (1 << TEXT_METERMODE),\n   .maxItems = 0,\n   .total = 0.0,\n   .attributes = SystemdMeter_attributes,\n   .name = \"Systemd\",\n   .uiName = \"Systemd state\",\n   .description = \"Systemd system state and unit overview\",\n   .caption = \"Systemd: \",\n};\n\nconst MeterClass SystemdUserMeter_class = {\n   .super = {\n      .extends = Class(Meter),\n      .delete = Meter_delete,\n      .display = SystemdMeter_display_user,\n   },\n   .updateValues = SystemdMeter_updateValues,\n   .done = SystemdMeter_done,\n   .defaultMode = TEXT_METERMODE,\n   .supportedModes = (1 << TEXT_METERMODE),\n   .maxItems = 0,\n   .total = 0.0,\n   .attributes = SystemdMeter_attributes,\n   .name = \"SystemdUser\",\n   .uiName = \"Systemd user state\",\n   .description = \"Systemd user state and unit overview\",\n   .caption = \"Systemd User: \",\n};\n"
  },
  {
    "path": "linux/SystemdMeter.h",
    "content": "#ifndef HEADER_SystemdMeter\n#define HEADER_SystemdMeter\n\n/*\nhtop - SystemdMeter.h\n(C) 2020 htop dev team\nReleased under the GNU GPLv2+, see the COPYING file\nin the source distribution for its full text.\n*/\n\n#include \"Meter.h\"\n\n\nextern const MeterClass SystemdMeter_class;\n\nextern const MeterClass SystemdUserMeter_class;\n\n#endif /* HEADER_SystemdMeter */\n"
  },
  {
    "path": "linux/ZramMeter.c",
    "content": "/*\nhtop - linux/ZramMeter.c\n(C) 2020 Murloc Knight\n(C) 2020-2023 htop dev team\nReleased under the GNU GPLv2+, see the COPYING file\nin the source distribution for its full text.\n*/\n\n#include \"config.h\" // IWYU pragma: keep\n\n#include \"linux/ZramMeter.h\"\n\n#include <stddef.h>\n\n#include \"CRT.h\"\n#include \"Meter.h\"\n#include \"Object.h\"\n#include \"Platform.h\"\n#include \"RichString.h\"\n#include \"ZramMeter.h\"\n\n\nstatic const int ZramMeter_attributes[ZRAM_METER_ITEMCOUNT] = {\n   [ZRAM_METER_COMPRESSED] = ZRAM_COMPRESSED,\n   [ZRAM_METER_UNCOMPRESSED] = ZRAM_UNCOMPRESSED,\n};\n\nstatic void ZramMeter_updateValues(Meter* this) {\n   char* buffer = this->txtBuffer;\n   size_t size = sizeof(this->txtBuffer);\n   int written;\n\n   Platform_setZramValues(this);\n\n   written = Meter_humanUnit(buffer, this->values[ZRAM_METER_COMPRESSED], size);\n   METER_BUFFER_CHECK(buffer, size, written);\n\n   METER_BUFFER_APPEND_CHR(buffer, size, '(');\n\n   double uncompressed = this->values[ZRAM_METER_COMPRESSED] + this->values[ZRAM_METER_UNCOMPRESSED];\n   written = Meter_humanUnit(buffer, uncompressed, size);\n   METER_BUFFER_CHECK(buffer, size, written);\n\n   METER_BUFFER_APPEND_CHR(buffer, size, ')');\n\n   METER_BUFFER_APPEND_CHR(buffer, size, '/');\n\n   Meter_humanUnit(buffer, this->total, size);\n}\n\nstatic void ZramMeter_display(const Object* cast, RichString* out) {\n   char buffer[50];\n   const Meter* this = (const Meter*)cast;\n\n   RichString_writeAscii(out, CRT_colors[METER_TEXT], \":\");\n\n   Meter_humanUnit(buffer, this->total, sizeof(buffer));\n   RichString_appendAscii(out, CRT_colors[METER_VALUE], buffer);\n\n   Meter_humanUnit(buffer, this->values[ZRAM_METER_COMPRESSED], sizeof(buffer));\n   RichString_appendAscii(out, CRT_colors[METER_TEXT], \" used:\");\n   RichString_appendAscii(out, CRT_colors[METER_VALUE], buffer);\n\n   double uncompressed = this->values[ZRAM_METER_COMPRESSED] + this->values[ZRAM_METER_UNCOMPRESSED];\n   Meter_humanUnit(buffer, uncompressed, sizeof(buffer));\n   RichString_appendAscii(out, CRT_colors[METER_TEXT], \" uncompressed:\");\n   RichString_appendAscii(out, CRT_colors[METER_VALUE], buffer);\n}\n\nconst MeterClass ZramMeter_class = {\n   .super = {\n      .extends = Class(Meter),\n      .delete = Meter_delete,\n      .display = ZramMeter_display,\n   },\n   .updateValues = ZramMeter_updateValues,\n   .defaultMode = BAR_METERMODE,\n   .supportedModes = METERMODE_DEFAULT_SUPPORTED,\n   .maxItems = ZRAM_METER_ITEMCOUNT,\n   .isPercentChart = true,\n   .total = 100.0,\n   .attributes = ZramMeter_attributes,\n   .name = \"Zram\",\n   .uiName = \"Zram\",\n   .caption = \"zrm\"\n};\n"
  },
  {
    "path": "linux/ZramMeter.h",
    "content": "#ifndef HEADER_ZramMeter\n#define HEADER_ZramMeter\n/*\nhtop - linux/ZramMeter.h\n(C) 2020 Murloc Knight\n(C) 2020-2023 htop dev team\nReleased under the GNU GPLv2+, see the COPYING file\nin the source distribution for its full text.\n*/\n\n#include \"Meter.h\"\n\ntypedef enum {\n   ZRAM_METER_COMPRESSED = 0,\n   ZRAM_METER_UNCOMPRESSED = 1,\n   ZRAM_METER_ITEMCOUNT = 2, // number of entries in this enum\n} ZramMeterValues;\n\nextern const MeterClass ZramMeter_class;\n\n#endif\n"
  },
  {
    "path": "linux/ZramStats.h",
    "content": "#ifndef HEADER_ZramStats\n#define HEADER_ZramStats\n/*\nhtop - ZramStats.h\n(C) 2020 htop dev team\nReleased under the GNU GPLv2+, see the COPYING file\nin the source distribution for its full text.\n*/\n\n#include \"ProcessTable.h\"\n\ntypedef struct ZramStats_ {\n   memory_t totalZram;\n   memory_t usedZramComp;\n   memory_t usedZramOrig;\n} ZramStats;\n\n#endif\n"
  },
  {
    "path": "linux/ZswapStats.h",
    "content": "#ifndef HEADER_ZswapStats\n#define HEADER_ZswapStats\n/*\nhtop - ZswapStats.h\n(C) 2022 htop dev team\nReleased under the GNU GPLv2+, see the COPYING file\nin the source distribution for its full text.\n*/\n\n#include \"ProcessTable.h\"\n\ntypedef struct ZswapStats_ {\n   /* amount of RAM used by the zswap pool */\n   memory_t usedZswapComp;\n   /* amount of data stored inside the zswap pool */\n   memory_t usedZswapOrig;\n} ZswapStats;\n\n#endif\n"
  },
  {
    "path": "netbsd/NetBSDMachine.c",
    "content": "/*\nhtop - NetBSDMachine.c\n(C) 2014 Hisham H. Muhammad\n(C) 2015 Michael McConville\n(C) 2021 Santhosh Raju\n(C) 2021 htop dev team\nReleased under the GNU GPLv2+, see the COPYING file\nin the source distribution for its full text.\n*/\n\n#include \"config.h\" // IWYU pragma: keep\n\n#include \"netbsd/NetBSDMachine.h\"\n\n#include <kvm.h>\n#include <math.h>\n#include <limits.h>\n#include <stdlib.h>\n#include <string.h>\n#include <unistd.h>\n#include <sys/mount.h>\n#include <sys/param.h>\n#include <sys/proc.h>\n#include <sys/sched.h>\n#include <sys/swap.h>\n#include <sys/sysctl.h>\n#include <sys/types.h>\n#include <uvm/uvm_extern.h>\n\n#include \"CRT.h\"\n#include \"Machine.h\"\n#include \"Macros.h\"\n#include \"Object.h\"\n#include \"Settings.h\"\n#include \"XUtils.h\"\n\n\nstatic const struct {\n   const char* name;\n   long int scale;\n} freqSysctls[] = {\n   { \"machdep.est.frequency.current\",            1 },\n   { \"machdep.powernow.frequency.current\",       1 },\n   { \"machdep.intrepid.frequency.current\",       1 },\n   { \"machdep.loongson.frequency.current\",       1 },\n   { \"machdep.cpu.frequency.current\",            1 },\n   { \"machdep.frequency.current\",                1 },\n   { \"machdep.tsc_freq\",                   1000000 },\n};\n\nstatic void NetBSDMachine_updateCPUcount(NetBSDMachine* this) {\n   Machine* super = &this->super;\n\n   // Definitions for sysctl(3), cf. https://nxr.netbsd.org/xref/src/sys/sys/sysctl.h#813\n   const int mib_ncpu_existing[] = { CTL_HW, HW_NCPU }; // Number of existing CPUs\n   const int mib_ncpu_online[] = { CTL_HW, HW_NCPUONLINE }; // Number of online/active CPUs\n\n   int r;\n   unsigned int value;\n   size_t size;\n\n   bool change = false;\n\n   // Query the number of active/online CPUs.\n   size = sizeof(value);\n   r = sysctl(mib_ncpu_online, 2, &value, &size, NULL, 0);\n   if (r < 0 || value < 1) {\n      value = 1;\n   }\n\n   if (value != super->activeCPUs) {\n      super->activeCPUs = value;\n      change = true;\n   }\n\n   // Query the total number of CPUs.\n   size = sizeof(value);\n   r = sysctl(mib_ncpu_existing, 2, &value, &size, NULL, 0);\n   if (r < 0 || value < 1) {\n      value = super->activeCPUs;\n   }\n\n   if (value != super->existingCPUs) {\n      this->cpuData = xReallocArray(this->cpuData, value + 1, sizeof(CPUData));\n      super->existingCPUs = value;\n      change = true;\n   }\n\n   // Reset CPU stats when number of online/existing CPU cores changed\n   if (change) {\n      CPUData* dAvg = &this->cpuData[0];\n      memset(dAvg, '\\0', sizeof(CPUData));\n      dAvg->totalTime = 1;\n      dAvg->totalPeriod = 1;\n\n      for (unsigned int i = 0; i < super->existingCPUs; i++) {\n         CPUData* d = &this->cpuData[i + 1];\n         memset(d, '\\0', sizeof(CPUData));\n         d->totalTime = 1;\n         d->totalPeriod = 1;\n      }\n   }\n}\n\nMachine* Machine_new(UsersTable* usersTable, uid_t userId) {\n   const int fmib[] = { CTL_KERN, KERN_FSCALE };\n   size_t size;\n   char errbuf[_POSIX2_LINE_MAX];\n\n   NetBSDMachine* this = xCalloc(1, sizeof(NetBSDMachine));\n   Machine* super = &this->super;\n   Machine_init(super, usersTable, userId);\n\n   NetBSDMachine_updateCPUcount(this);\n\n   size = sizeof(this->fscale);\n   if (sysctl(fmib, 2, &this->fscale, &size, NULL, 0) < 0 || this->fscale <= 0) {\n      CRT_fatalError(\"fscale sysctl call failed\");\n   }\n\n   long pageSize = sysconf(_SC_PAGESIZE);\n   if (pageSize <= 0)\n      CRT_fatalError(\"pagesize sysconf call failed\");\n   this->pageSize = (size_t)pageSize;\n   this->pageSizeKB = this->pageSize / ONE_K;\n\n   this->kd = kvm_openfiles(NULL, NULL, NULL, KVM_NO_FILES, errbuf);\n   if (this->kd == NULL) {\n      CRT_fatalError(\"kvm_openfiles() failed\");\n   }\n\n   return super;\n}\n\nvoid Machine_delete(Machine* super) {\n   NetBSDMachine* this = (NetBSDMachine*) super;\n\n   Machine_done(super);\n\n   if (this->kd) {\n      kvm_close(this->kd);\n   }\n   free(this->cpuData);\n   free(this);\n}\n\nstatic void NetBSDMachine_scanMemoryInfo(NetBSDMachine* this) {\n   Machine* super = &this->super;\n\n   static int uvmexp_mib[] = {CTL_VM, VM_UVMEXP2};\n   struct uvmexp_sysctl uvmexp;\n   size_t size_uvmexp = sizeof(uvmexp);\n\n   if (sysctl(uvmexp_mib, 2, &uvmexp, &size_uvmexp, NULL, 0) < 0) {\n      CRT_fatalError(\"uvmexp sysctl call failed\");\n   }\n\n   // NOTE: it is wrong in NetBSD to represent the \"cache\" memory as a memory class by itself.\n   // The only page classes exposed by the kernel in the uvmexp struct are these.\n   // The \"cached\" memory can be obtained from another sysctl, but there is no simple way\n   // in NetBSD to determine which page classe(s) this \"cached\" memory should be substracted from.\n   this->wiredMem = this->pageSizeKB * uvmexp.wired;\n   this->activeMem = this->pageSizeKB * uvmexp.active;\n   this->pagedMem = this->pageSizeKB * uvmexp.paging;\n   this->inactiveMem = this->pageSizeKB * uvmexp.inactive;\n\n   super->totalMem  = this->pageSizeKB * uvmexp.npages;\n   super->totalSwap = uvmexp.swpages * this->pageSizeKB;\n   super->usedSwap = uvmexp.swpginuse * this->pageSizeKB;\n}\n\nstatic void getKernelCPUTimes(int cpuId, u_int64_t* times) {\n   const int mib[] = { CTL_KERN, KERN_CP_TIME, cpuId };\n   size_t length = sizeof(*times) * CPUSTATES;\n   if (sysctl(mib, 3, times, &length, NULL, 0) == -1 || length != sizeof(*times) * CPUSTATES) {\n      CRT_fatalError(\"sysctl kern.cp_time2 failed\");\n   }\n}\n\nstatic void kernelCPUTimesToHtop(const u_int64_t* times, CPUData* cpu) {\n   unsigned long long totalTime = 0;\n   for (int i = 0; i < CPUSTATES; i++) {\n      totalTime += times[i];\n   }\n\n   unsigned long long sysAllTime = times[CP_INTR] + times[CP_SYS];\n\n   cpu->totalPeriod = saturatingSub(totalTime, cpu->totalTime);\n   cpu->userPeriod = saturatingSub(times[CP_USER], cpu->userTime);\n   cpu->nicePeriod = saturatingSub(times[CP_NICE], cpu->niceTime);\n   cpu->sysPeriod = saturatingSub(times[CP_SYS], cpu->sysTime);\n   cpu->sysAllPeriod = saturatingSub(sysAllTime, cpu->sysAllTime);\n   cpu->intrPeriod = saturatingSub(times[CP_INTR], cpu->intrTime);\n   cpu->idlePeriod = saturatingSub(times[CP_IDLE], cpu->idleTime);\n\n   cpu->totalTime = totalTime;\n   cpu->userTime = times[CP_USER];\n   cpu->niceTime = times[CP_NICE];\n   cpu->sysTime = times[CP_SYS];\n   cpu->sysAllTime = sysAllTime;\n   cpu->intrTime = times[CP_INTR];\n   cpu->idleTime = times[CP_IDLE];\n}\n\nstatic void NetBSDMachine_scanCPUTime(NetBSDMachine* this) {\n   const Machine* super = &this->super;\n\n   u_int64_t kernelTimes[CPUSTATES] = {0};\n   u_int64_t avg[CPUSTATES] = {0};\n\n   for (unsigned int i = 0; i < super->existingCPUs; i++) {\n      getKernelCPUTimes(i, kernelTimes);\n      CPUData* cpu = &this->cpuData[i + 1];\n      kernelCPUTimesToHtop(kernelTimes, cpu);\n\n      avg[CP_USER] += cpu->userTime;\n      avg[CP_NICE] += cpu->niceTime;\n      avg[CP_SYS] += cpu->sysTime;\n      avg[CP_INTR] += cpu->intrTime;\n      avg[CP_IDLE] += cpu->idleTime;\n   }\n\n   for (int i = 0; i < CPUSTATES; i++) {\n      avg[i] /= super->activeCPUs;\n   }\n\n   kernelCPUTimesToHtop(avg, &this->cpuData[0]);\n}\n\nstatic void NetBSDMachine_scanCPUFrequency(NetBSDMachine* this) {\n   const Machine* super = &this->super;\n   unsigned int cpus = super->existingCPUs;\n   bool match = false;\n   char name[64];\n   long int freq = 0;\n   size_t freqSize;\n\n   for (unsigned int i = 0; i < cpus; i++) {\n      this->cpuData[i + 1].frequency = NAN;\n   }\n\n   /* newer hardware supports per-core frequency, for e.g. ARM big.LITTLE */\n   for (unsigned int i = 0; i < cpus; i++) {\n      xSnprintf(name, sizeof(name), \"machdep.cpufreq.cpu%u.current\", i);\n      freqSize = sizeof(freq);\n      if (sysctlbyname(name, &freq, &freqSize, NULL, 0) != -1) {\n         this->cpuData[i + 1].frequency = freq; /* already in MHz */\n         match = true;\n      }\n   }\n\n   if (match) {\n      return;\n   }\n\n   /*\n    * Iterate through legacy sysctl nodes for single-core frequency until\n    * we find a match...\n    */\n   for (size_t i = 0; i < ARRAYSIZE(freqSysctls); i++) {\n      freqSize = sizeof(freq);\n      if (sysctlbyname(freqSysctls[i].name, &freq, &freqSize, NULL, 0) != -1) {\n         freq /= freqSysctls[i].scale; /* scale to MHz */\n         match = true;\n         break;\n      }\n   }\n\n   if (match) {\n      for (unsigned int i = 0; i < cpus; i++) {\n         this->cpuData[i + 1].frequency = freq;\n      }\n   }\n}\n\nvoid Machine_scan(Machine* super) {\n   NetBSDMachine* this = (NetBSDMachine*) super;\n\n   NetBSDMachine_scanMemoryInfo(this);\n   NetBSDMachine_scanCPUTime(this);\n\n   if (super->settings->showCPUFrequency) {\n      NetBSDMachine_scanCPUFrequency(this);\n   }\n}\n\nbool Machine_isCPUonline(const Machine* host, unsigned int id) {\n   assert(id < host->existingCPUs);\n   (void)host; (void)id;\n\n   // TODO: Support detecting online / offline CPUs.\n   return true;\n}\n"
  },
  {
    "path": "netbsd/NetBSDMachine.h",
    "content": "#ifndef HEADER_NetBSDMachine\n#define HEADER_NetBSDMachine\n/*\nhtop - NetBSDMachine.h\n(C) 2014 Hisham H. Muhammad\n(C) 2015 Michael McConville\n(C) 2021 Santhosh Raju\n(C) 2021 htop dev team\nReleased under the GNU GPLv2+, see the COPYING file\nin the source distribution for its full text.\n*/\n\n#include <kvm.h>\n#include <stdbool.h>\n#include <stddef.h>\n\n#include \"Machine.h\"\n#include \"ProcessTable.h\"\n\n\ntypedef struct CPUData_ {\n   unsigned long long int totalTime;\n   unsigned long long int userTime;\n   unsigned long long int niceTime;\n   unsigned long long int sysTime;\n   unsigned long long int sysAllTime;\n   unsigned long long int spinTime;\n   unsigned long long int intrTime;\n   unsigned long long int idleTime;\n\n   unsigned long long int totalPeriod;\n   unsigned long long int userPeriod;\n   unsigned long long int nicePeriod;\n   unsigned long long int sysPeriod;\n   unsigned long long int sysAllPeriod;\n   unsigned long long int spinPeriod;\n   unsigned long long int intrPeriod;\n   unsigned long long int idlePeriod;\n\n   double frequency;\n} CPUData;\n\ntypedef struct NetBSDMachine_ {\n   Machine super;\n   kvm_t* kd;\n\n   long fscale;\n   size_t pageSize;\n   size_t pageSizeKB;\n\n   memory_t wiredMem;\n   memory_t activeMem;\n   memory_t pagedMem;\n   memory_t inactiveMem;\n\n   CPUData* cpuData;\n} NetBSDMachine;\n\n#endif\n"
  },
  {
    "path": "netbsd/NetBSDProcess.c",
    "content": "/*\nhtop - NetBSDProcess.c\n(C) 2015 Hisham H. Muhammad\n(C) 2015 Michael McConville\n(C) 2021 Santhosh Raju\n(C) 2021 htop dev team\nReleased under the GNU GPLv2+, see the COPYING file\nin the source distribution for its full text.\n*/\n\n#include \"config.h\" // IWYU pragma: keep\n\n#include \"netbsd/NetBSDProcess.h\"\n\n#include <stdlib.h>\n\n#include \"CRT.h\"\n#include \"Process.h\"\n#include \"RichString.h\"\n#include \"XUtils.h\"\n\n\nconst ProcessFieldData Process_fields[LAST_PROCESSFIELD] = {\n   [0] = {\n      .name = \"\",\n      .title = NULL,\n      .description = NULL,\n      .flags = 0,\n   },\n   [PID] = {\n      .name = \"PID\",\n      .title = \"PID\",\n      .description = \"Process/thread ID\",\n      .flags = 0,\n      .pidColumn = true,\n   },\n   [COMM] = {\n      .name = \"Command\",\n      .title = \"Command \",\n      .description = \"Command line (insert as last column only)\",\n      .flags = 0,\n   },\n   [STATE] = {\n      .name = \"STATE\",\n      .title = \"S \",\n      .description = \"Process state (S sleeping, R running, D disk, Z zombie, T traced, W paging)\",\n      .flags = 0,\n   },\n   [PPID] = {\n      .name = \"PPID\",\n      .title = \"PPID\",\n      .description = \"Parent process ID\",\n      .flags = 0,\n      .pidColumn = true,\n   },\n   [PGRP] = {\n      .name = \"PGRP\",\n      .title = \"PGRP\",\n      .description = \"Process group ID\",\n      .flags = 0,\n      .pidColumn = true,\n   },\n   [SESSION] = {\n      .name = \"SESSION\",\n      .title = \"SESN\",\n      .description = \"Process's session ID\",\n      .flags = 0,\n      .pidColumn = true,\n   },\n   [TTY] = {\n      .name = \"TTY\",\n      .title = \"TTY      \",\n      .description = \"Controlling terminal\",\n      .flags = 0,\n   },\n   [TPGID] = {\n      .name = \"TPGID\",\n      .title = \"TPGID\",\n      .description = \"Process ID of the fg process group of the controlling terminal\",\n      .flags = 0,\n      .pidColumn = true,\n   },\n   [MINFLT] = {\n      .name = \"MINFLT\",\n      .title = \"     MINFLT \",\n      .description = \"Number of minor faults which have not required loading a memory page from disk\",\n      .flags = 0,\n      .defaultSortDesc = true,\n   },\n   [MAJFLT] = {\n      .name = \"MAJFLT\",\n      .title = \"     MAJFLT \",\n      .description = \"Number of major faults which have required loading a memory page from disk\",\n      .flags = 0,\n      .defaultSortDesc = true,\n   },\n   [PRIORITY] = {\n      .name = \"PRIORITY\",\n      .title = \"PRI \",\n      .description = \"Kernel's internal priority for the process\",\n      .flags = 0,\n   },\n   [NICE] = {\n      .name = \"NICE\",\n      .title = \" NI \",\n      .description = \"Nice value (the higher the value, the more it lets other processes take priority)\",\n      .flags = 0,\n   },\n   [STARTTIME] = {\n      .name = \"STARTTIME\",\n      .title = \"START \",\n      .description = \"Time the process was started\",\n      .flags = 0,\n   },\n   [ELAPSED] = {\n      .name = \"ELAPSED\",\n      .title = \"ELAPSED  \",\n      .description = \"Time since the process was started\",\n      .flags = 0,\n   },\n   [PROCESSOR] = {\n      .name = \"PROCESSOR\",\n      .title = \"CPU \",\n      .description = \"Id of the CPU the process last executed on\",\n      .flags = 0,\n   },\n   [M_VIRT] = {\n      .name = \"M_VIRT\",\n      .title = \" VIRT \",\n      .description = \"Total program size in virtual memory\",\n      .flags = 0,\n      .defaultSortDesc = true,\n   },\n   [M_RESIDENT] = {\n      .name = \"M_RESIDENT\",\n      .title = \"  RES \",\n      .description = \"Resident set size, size of the text and data sections, plus stack usage\",\n      .flags = 0,\n      .defaultSortDesc = true,\n   },\n   [ST_UID] = {\n      .name = \"ST_UID\",\n      .title = \"UID\",\n      .description = \"User ID of the process owner\",\n      .flags = 0,\n   },\n   [PERCENT_CPU] = {\n      .name = \"PERCENT_CPU\",\n      .title = \" CPU%\",\n      .description = \"Percentage of the CPU time the process used in the last sampling\",\n      .flags = 0,\n      .defaultSortDesc = true,\n      .autoWidth = true,\n      .autoTitleRightAlign = true,\n   },\n   [PERCENT_NORM_CPU] = {\n      .name = \"PERCENT_NORM_CPU\",\n      .title = \"NCPU%\",\n      .description = \"Normalized percentage of the CPU time the process used in the last sampling (normalized by cpu count)\",\n      .flags = 0,\n      .defaultSortDesc = true,\n      .autoWidth = true,\n   },\n   [PERCENT_MEM] = {\n      .name = \"PERCENT_MEM\",\n      .title = \"MEM% \",\n      .description = \"Percentage of the memory the process is using, based on resident memory size\",\n      .flags = 0,\n      .defaultSortDesc = true,\n   },\n   [USER] = {\n      .name = \"USER\",\n      .title = \"USER       \",\n      .description = \"Username of the process owner (or user ID if name cannot be determined)\",\n      .flags = 0,\n   },\n   [TIME] = {\n      .name = \"TIME\",\n      .title = \"  TIME+  \",\n      .description = \"Total time the process has spent in user and system time\",\n      .flags = 0,\n      .defaultSortDesc = true,\n   },\n   [NLWP] = {\n      .name = \"NLWP\",\n      .title = \"NLWP \",\n      .description = \"Number of threads in the process\",\n      .flags = 0,\n   },\n   [TGID] = {\n      .name = \"TGID\",\n      .title = \"TGID\",\n      .description = \"Thread group ID (i.e. process ID)\",\n      .flags = 0,\n      .pidColumn = true,\n   },\n   [PROC_COMM] = {\n      .name = \"COMM\",\n      .title = \"COMM            \",\n      .description = \"comm string of the process\",\n      .flags = 0,\n   },\n   [PROC_EXE] = {\n      .name = \"EXE\",\n      .title = \"EXE             \",\n      .description = \"Basename of exe of the process\",\n      .flags = 0,\n   },\n   [CWD] = {\n      .name = \"CWD\",\n      .title = \"CWD                       \",\n      .description = \"The current working directory of the process\",\n      .flags = PROCESS_FLAG_CWD,\n   },\n\n};\n\nProcess* NetBSDProcess_new(const Machine* host) {\n   NetBSDProcess* this = xCalloc(1, sizeof(NetBSDProcess));\n   Object_setClass(this, Class(NetBSDProcess));\n   Process_init(&this->super, host);\n   return (Process*)this;\n}\n\nvoid Process_delete(Object* cast) {\n   NetBSDProcess* this = (NetBSDProcess*) cast;\n   Process_done((Process*)cast);\n   free(this);\n}\n\nstatic void NetBSDProcess_rowWriteField(const Row* super, RichString* str, ProcessField field) {\n   const NetBSDProcess* np = (const NetBSDProcess*) super;\n\n   char buffer[256]; buffer[255] = '\\0';\n   int attr = CRT_colors[DEFAULT_COLOR];\n   //size_t n = sizeof(buffer) - 1;\n\n   switch (field) {\n   // add NetBSD-specific fields here\n   default:\n      Process_writeField(&np->super, str, field);\n      return;\n   }\n\n   RichString_appendWide(str, attr, buffer);\n}\n\nstatic int NetBSDProcess_compareByKey(const Process* v1, const Process* v2, ProcessField key) {\n   const NetBSDProcess* p1 = (const NetBSDProcess*)v1;\n   const NetBSDProcess* p2 = (const NetBSDProcess*)v2;\n\n   // remove if actually used\n   (void)p1; (void)p2;\n\n   switch (key) {\n   // add NetBSD-specific fields here\n   default:\n      return Process_compareByKey_Base(v1, v2, key);\n   }\n}\n\nconst ProcessClass NetBSDProcess_class = {\n   .super = {\n      .super = {\n         .extends = Class(Process),\n         .display = Row_display,\n         .delete = Process_delete,\n         .compare = Process_compare\n      },\n      .isHighlighted = Process_rowIsHighlighted,\n      .isVisible = Process_rowIsVisible,\n      .matchesFilter = Process_rowMatchesFilter,\n      .compareByParent = Process_compareByParent,\n      .sortKeyString = Process_rowGetSortKey,\n      .writeField = NetBSDProcess_rowWriteField\n   },\n   .compareByKey = NetBSDProcess_compareByKey\n};\n"
  },
  {
    "path": "netbsd/NetBSDProcess.h",
    "content": "#ifndef HEADER_NetBSDProcess\n#define HEADER_NetBSDProcess\n/*\nhtop - NetBSDProcess.h\n(C) 2015 Hisham H. Muhammad\n(C) 2015 Michael McConville\n(C) 2021 Santhosh Raju\n(C) 2021 htop dev team\nReleased under the GNU GPLv2+, see the COPYING file\nin the source distribution for its full text.\n*/\n\n#include <stdbool.h>\n\n#include \"Machine.h\"\n#include \"Object.h\"\n#include \"Process.h\"\n\n\ntypedef struct NetBSDProcess_ {\n   Process super;\n} NetBSDProcess;\n\nextern const ProcessClass NetBSDProcess_class;\n\nextern const ProcessFieldData Process_fields[LAST_PROCESSFIELD];\n\nProcess* NetBSDProcess_new(const Machine* host);\n\nvoid Process_delete(Object* cast);\n\n#endif\n"
  },
  {
    "path": "netbsd/NetBSDProcessTable.c",
    "content": "/*\nhtop - NetBSDProcessTable.c\n(C) 2014 Hisham H. Muhammad\n(C) 2015 Michael McConville\n(C) 2021 Santhosh Raju\n(C) 2021 htop dev team\nReleased under the GNU GPLv2+, see the COPYING file\nin the source distribution for its full text.\n*/\n\n#include \"config.h\" // IWYU pragma: keep\n\n#include \"netbsd/NetBSDProcessTable.h\"\n\n#include <kvm.h>\n#include <math.h>\n#include <limits.h>\n#include <stdlib.h>\n#include <string.h>\n#include <unistd.h>\n#include <sys/mount.h>\n#include <sys/param.h>\n#include <sys/proc.h>\n#include <sys/sched.h>\n#include <sys/sysctl.h>\n#include <sys/types.h>\n#include <uvm/uvm_extern.h>\n\n#include \"CRT.h\"\n#include \"Macros.h\"\n#include \"Object.h\"\n#include \"Process.h\"\n#include \"ProcessTable.h\"\n#include \"Settings.h\"\n#include \"XUtils.h\"\n#include \"netbsd/NetBSDMachine.h\"\n#include \"netbsd/NetBSDProcess.h\"\n\n\nProcessTable* ProcessTable_new(Machine* host, Hashtable* pidMatchList) {\n   NetBSDProcessTable* this = xCalloc(1, sizeof(NetBSDProcessTable));\n   Object_setClass(this, Class(ProcessTable));\n\n   ProcessTable* super = (ProcessTable*) this;\n   ProcessTable_init(super, Class(NetBSDProcess), host, pidMatchList);\n\n   return super;\n}\n\nvoid ProcessTable_delete(Object* cast) {\n   NetBSDProcessTable* this = (NetBSDProcessTable*) cast;\n   ProcessTable_done(&this->super);\n   free(this);\n}\n\nstatic void NetBSDProcessTable_updateExe(const struct kinfo_proc2* kproc, Process* proc) {\n   const int mib[] = { CTL_KERN, KERN_PROC_ARGS, kproc->p_pid, KERN_PROC_PATHNAME };\n   char buffer[2048];\n   size_t size = sizeof(buffer);\n   if (sysctl(mib, 4, buffer, &size, NULL, 0) != 0) {\n      Process_updateExe(proc, NULL);\n      return;\n   }\n\n   /* Kernel threads return an empty buffer */\n   if (buffer[0] == '\\0') {\n      Process_updateExe(proc, NULL);\n      return;\n   }\n\n   Process_updateExe(proc, buffer);\n}\n\nstatic void NetBSDProcessTable_updateCwd(const struct kinfo_proc2* kproc, Process* proc) {\n   const int mib[] = { CTL_KERN, KERN_PROC_ARGS, kproc->p_pid, KERN_PROC_CWD };\n   char buffer[2048];\n   size_t size = sizeof(buffer);\n   if (sysctl(mib, 4, buffer, &size, NULL, 0) != 0) {\n      free(proc->procCwd);\n      proc->procCwd = NULL;\n      return;\n   }\n\n   /* Kernel threads return an empty buffer */\n   if (buffer[0] == '\\0') {\n      free(proc->procCwd);\n      proc->procCwd = NULL;\n      return;\n   }\n\n   free_and_xStrdup(&proc->procCwd, buffer);\n}\n\nstatic void NetBSDProcessTable_updateProcessName(kvm_t* kd, const struct kinfo_proc2* kproc, Process* proc) {\n   Process_updateComm(proc, kproc->p_comm);\n\n   /*\n    * Like NetBSD's top(1), we try to fall back to the command name\n    * (argv[0]) if we fail to construct the full command.\n    */\n   char** arg = kvm_getargv2(kd, kproc, 500);\n   if (arg == NULL || *arg == NULL) {\n      Process_updateCmdline(proc, kproc->p_comm, 0, strlen(kproc->p_comm));\n      return;\n   }\n\n   size_t len = 0;\n   for (size_t i = 0; arg[i] != NULL; i++) {\n      len += strlen(arg[i]) + 1;   /* room for arg and trailing space or NUL */\n   }\n\n   /* don't use xMalloc here - we want to handle huge argv's gracefully */\n   char* s;\n   if ((s = malloc(len)) == NULL) {\n      Process_updateCmdline(proc, kproc->p_comm, 0, strlen(kproc->p_comm));\n      return;\n   }\n\n   *s = '\\0';\n\n   size_t start = 0;\n   size_t end = 0;\n   for (size_t i = 0; arg[i] != NULL; i++) {\n      size_t n = strlcat(s, arg[i], len);\n      if (i == 0) {\n         end = MINIMUM(n, len - 1);\n         /* check if cmdline ended earlier, e.g 'kdeinit5: Running...' */\n         for (size_t j = end; j > 0; j--) {\n            if (arg[0][j] == ' ' && arg[0][j - 1] != '\\\\') {\n               end = (arg[0][j - 1] == ':') ? (j - 1) : j;\n            }\n         }\n      }\n      /* the trailing space should get truncated anyway */\n      strlcat(s, \" \", len);\n   }\n\n   Process_updateCmdline(proc, s, start, end);\n\n   free(s);\n}\n\n/*\n * Borrowed with modifications from NetBSD's top(1).\n */\nstatic double getpcpu(const NetBSDMachine* nhost, const struct kinfo_proc2* kp) {\n   if (nhost->fscale == 0)\n      return 0.0;\n\n   return 100.0 * (double)kp->p_pctcpu / nhost->fscale;\n}\n\nstatic ProcessState get_active_status(const NetBSDMachine* nhost, const struct kinfo_proc2* kproc) {\n   int nlwps = 0;\n   const struct kinfo_lwp* klwps = kvm_getlwps(nhost->kd, kproc->p_pid, kproc->p_paddr, sizeof(struct kinfo_lwp), &nlwps);\n   if (!klwps) {\n      return UNKNOWN;\n   }\n   // We only consider the first LWP that has one of the states below.\n   for (int j = 0; j < nlwps; j++) {\n      switch (klwps[j].l_stat) {\n         case LSONPROC: return RUNNING;\n         case LSRUN:    return RUNNABLE;\n         case LSSLEEP:  return SLEEPING;\n         case LSSTOP:   return STOPPED;\n      }\n   }\n   return UNKNOWN;\n}\n\nvoid ProcessTable_goThroughEntries(ProcessTable* super) {\n   const Machine* host = super->super.host;\n   const NetBSDMachine* nhost = (const NetBSDMachine*) host;\n   const Settings* settings = host->settings;\n   bool hideKernelThreads = settings->hideKernelThreads;\n   bool hideUserlandThreads = settings->hideUserlandThreads;\n   int count = 0;\n\n   const struct kinfo_proc2* kprocs = kvm_getproc2(nhost->kd, KERN_PROC_ALL, 0, sizeof(struct kinfo_proc2), &count);\n\n   for (int i = 0; i < count; i++) {\n      const struct kinfo_proc2* kproc = &kprocs[i];\n\n      bool preExisting = false;\n      Process* proc = ProcessTable_getProcess(super, kproc->p_pid, &preExisting, NetBSDProcess_new);\n\n      proc->super.show = ! ((hideKernelThreads && Process_isKernelThread(proc)) || (hideUserlandThreads && Process_isUserlandThread(proc)));\n\n      if (!preExisting) {\n         Process_setPid(proc, kproc->p_pid);\n         Process_setParent(proc, kproc->p_ppid);\n         Process_setThreadGroup(proc, kproc->p_pid);\n         proc->tpgid = kproc->p_tpgid;\n         proc->session = kproc->p_sid;\n         proc->pgrp = kproc->p__pgid;\n         proc->isKernelThread = !!(kproc->p_flag & P_SYSTEM);\n         proc->isUserlandThread = Process_getPid(proc) != Process_getThreadGroup(proc); // eh?\n         proc->starttime_ctime = kproc->p_ustart_sec;\n         Process_fillStarttimeBuffer(proc);\n         ProcessTable_add(super, proc);\n\n         proc->tty_nr = kproc->p_tdev;\n         // KERN_PROC_TTY_NODEV is a negative constant but the type of\n         // kproc->p_tdev may be unsigned.\n         const char* name = ((dev_t)~kproc->p_tdev != (dev_t)~(KERN_PROC_TTY_NODEV)) ? devname(kproc->p_tdev, S_IFCHR) : NULL;\n         if (!name) {\n            free(proc->tty_name);\n            proc->tty_name = NULL;\n         } else {\n            free_and_xStrdup(&proc->tty_name, name);\n         }\n\n         NetBSDProcessTable_updateExe(kproc, proc);\n         NetBSDProcessTable_updateProcessName(nhost->kd, kproc, proc);\n      } else {\n         if (settings->updateProcessNames) {\n            NetBSDProcessTable_updateProcessName(nhost->kd, kproc, proc);\n         }\n      }\n\n      if (settings->ss->flags & PROCESS_FLAG_CWD) {\n         NetBSDProcessTable_updateCwd(kproc, proc);\n      }\n\n      if (proc->st_uid != kproc->p_uid) {\n         proc->st_uid = kproc->p_uid;\n         proc->user = UsersTable_getRef(host->usersTable, proc->st_uid);\n      }\n\n      proc->m_virt = kproc->p_vm_vsize;\n      proc->m_resident = kproc->p_vm_rssize;\n\n      proc->percent_mem = (proc->m_resident * nhost->pageSizeKB) / (double)(host->totalMem) * 100.0;\n      proc->percent_cpu = CLAMP(getpcpu(nhost, kproc), 0.0, host->activeCPUs * 100.0);\n      Process_updateCPUFieldWidths(proc->percent_cpu);\n\n      proc->nlwp = kproc->p_nlwps;\n      proc->nice = kproc->p_nice - 20;\n      proc->time = 100 * (kproc->p_rtime_sec + ((kproc->p_rtime_usec + 500000) / 1000000));\n      proc->priority = kproc->p_priority - PZERO;\n      proc->processor = kproc->p_cpuid;\n      proc->minflt = kproc->p_uru_minflt;\n      proc->majflt = kproc->p_uru_majflt;\n\n      /* TODO: According to the link below, SDYING should be a regarded state */\n      /* Taken from: https://ftp.netbsd.org/pub/NetBSD/NetBSD-current/src/sys/sys/proc.h */\n      switch (kproc->p_realstat) {\n         case SIDL:     proc->state = IDLE; break;\n         case SACTIVE:  proc->state = get_active_status(nhost, kproc); break;\n         case SSTOP:    proc->state = STOPPED; break;\n         case SZOMB:    proc->state = ZOMBIE; break;\n         case SDEAD:    proc->state = DEFUNCT; break;\n         default:       proc->state = UNKNOWN;\n      }\n\n      if (Process_isKernelThread(proc)) {\n         super->kernelThreads++;\n      } else if (Process_isUserlandThread(proc)) {\n         super->userlandThreads++;\n      }\n\n      super->totalTasks++;\n      if (proc->state == RUNNING) {\n         super->runningTasks++;\n      }\n      proc->super.updated = true;\n   }\n}\n"
  },
  {
    "path": "netbsd/NetBSDProcessTable.h",
    "content": "#ifndef HEADER_NetBSDProcessTable\n#define HEADER_NetBSDProcessTable\n/*\nhtop - NetBSDProcessTable.h\n(C) 2014 Hisham H. Muhammad\n(C) 2015 Michael McConville\n(C) 2021 Santhosh Raju\n(C) 2021 htop dev team\nReleased under the GNU GPLv2+, see the COPYING file\nin the source distribution for its full text.\n*/\n\n#include <stdbool.h>\n#include <sys/types.h>\n\n#include \"Hashtable.h\"\n#include \"ProcessTable.h\"\n\n\ntypedef struct NetBSDProcessTable_ {\n   ProcessTable super;\n} NetBSDProcessTable;\n\n#endif\n"
  },
  {
    "path": "netbsd/Platform.c",
    "content": "/*\nhtop - netbsd/Platform.c\n(C) 2014 Hisham H. Muhammad\n(C) 2015 Michael McConville\n(C) 2021 Santhosh Raju\n(C) 2021 Nia Alarie\n(C) 2021 htop dev team\nReleased under the GNU GPLv2+, see the COPYING file\nin the source distribution for its full text.\n*/\n\n#include \"config.h\" // IWYU pragma: keep\n\n#include \"netbsd/Platform.h\"\n\n#include <errno.h>\n#include <fcntl.h>\n#include <ifaddrs.h>\n#include <paths.h>\n#include <unistd.h>\n#include <kvm.h>\n#include <limits.h>\n#include <math.h>\n#include <stdbool.h>\n#include <stdlib.h>\n#include <string.h>\n#include <time.h>\n#include <net/if.h>\n#include <prop/proplib.h>\n#include <sys/envsys.h>\n#include <sys/iostat.h>\n#include <sys/param.h>\n#include <sys/resource.h>\n#include <sys/socket.h>\n#include <sys/sysctl.h>\n#include <sys/time.h>\n#include <sys/types.h>\n\n#include \"CPUMeter.h\"\n#include \"ClockMeter.h\"\n#include \"DateTimeMeter.h\"\n#include \"FileDescriptorMeter.h\"\n#include \"HostnameMeter.h\"\n#include \"LoadAverageMeter.h\"\n#include \"Macros.h\"\n#include \"MemoryMeter.h\"\n#include \"MemorySwapMeter.h\"\n#include \"Meter.h\"\n#include \"Settings.h\"\n#include \"SignalsPanel.h\"\n#include \"SwapMeter.h\"\n#include \"SysArchMeter.h\"\n#include \"TasksMeter.h\"\n#include \"UptimeMeter.h\"\n#include \"XUtils.h\"\n#include \"generic/fdstat_sysctl.h\"\n#include \"netbsd/NetBSDMachine.h\"\n#include \"netbsd/NetBSDProcess.h\"\n\n/*\n * The older proplib APIs will be deprecated in NetBSD 10, but we still\n * want to support the 9.x stable branch.\n *\n * Create aliases for the newer functions that are missing from 9.x.\n */\n#if !__NetBSD_Prereq__(9,99,65)\n#define prop_string_equals_string prop_string_equals_cstring\n#define prop_number_signed_value prop_number_integer_value\n#endif\n\nconst ScreenDefaults Platform_defaultScreens[] = {\n   {\n      .name = \"Main\",\n      .columns = \"PID USER PRIORITY NICE M_VIRT M_RESIDENT STATE PERCENT_CPU PERCENT_MEM TIME Command\",\n      .sortKey = \"PERCENT_CPU\",\n   },\n};\n\nconst unsigned int Platform_numberOfDefaultScreens = ARRAYSIZE(Platform_defaultScreens);\n\n/*\n * See /usr/include/sys/signal.h\n */\nconst SignalItem Platform_signals[] = {\n   { .name = \" 0 Cancel\",      .number =  0 },\n   { .name = \" 1 SIGHUP\",      .number =  1 },\n   { .name = \" 2 SIGINT\",      .number =  2 },\n   { .name = \" 3 SIGQUIT\",     .number =  3 },\n   { .name = \" 4 SIGILL\",      .number =  4 },\n   { .name = \" 5 SIGTRAP\",     .number =  5 },\n   { .name = \" 6 SIGABRT\",     .number =  6 },\n   { .name = \" 6 SIGIOT\",      .number =  6 },\n   { .name = \" 7 SIGEMT\",      .number =  7 },\n   { .name = \" 8 SIGFPE\",      .number =  8 },\n   { .name = \" 9 SIGKILL\",     .number =  9 },\n   { .name = \"10 SIGBUS\",      .number = 10 },\n   { .name = \"11 SIGSEGV\",     .number = 11 },\n   { .name = \"12 SIGSYS\",      .number = 12 },\n   { .name = \"13 SIGPIPE\",     .number = 13 },\n   { .name = \"14 SIGALRM\",     .number = 14 },\n   { .name = \"15 SIGTERM\",     .number = 15 },\n   { .name = \"16 SIGURG\",      .number = 16 },\n   { .name = \"17 SIGSTOP\",     .number = 17 },\n   { .name = \"18 SIGTSTP\",     .number = 18 },\n   { .name = \"19 SIGCONT\",     .number = 19 },\n   { .name = \"20 SIGCHLD\",     .number = 20 },\n   { .name = \"21 SIGTTIN\",     .number = 21 },\n   { .name = \"22 SIGTTOU\",     .number = 22 },\n   { .name = \"23 SIGIO\",       .number = 23 },\n   { .name = \"24 SIGXCPU\",     .number = 24 },\n   { .name = \"25 SIGXFSZ\",     .number = 25 },\n   { .name = \"26 SIGVTALRM\",   .number = 26 },\n   { .name = \"27 SIGPROF\",     .number = 27 },\n   { .name = \"28 SIGWINCH\",    .number = 28 },\n   { .name = \"29 SIGINFO\",     .number = 29 },\n   { .name = \"30 SIGUSR1\",     .number = 30 },\n   { .name = \"31 SIGUSR2\",     .number = 31 },\n   { .name = \"32 SIGPWR\",      .number = 32 },\n   { .name = \"33 SIGRTMIN\",    .number = 33 },\n   { .name = \"34 SIGRTMIN+1\",  .number = 34 },\n   { .name = \"35 SIGRTMIN+2\",  .number = 35 },\n   { .name = \"36 SIGRTMIN+3\",  .number = 36 },\n   { .name = \"37 SIGRTMIN+4\",  .number = 37 },\n   { .name = \"38 SIGRTMIN+5\",  .number = 38 },\n   { .name = \"39 SIGRTMIN+6\",  .number = 39 },\n   { .name = \"40 SIGRTMIN+7\",  .number = 40 },\n   { .name = \"41 SIGRTMIN+8\",  .number = 41 },\n   { .name = \"42 SIGRTMIN+9\",  .number = 42 },\n   { .name = \"43 SIGRTMIN+10\", .number = 43 },\n   { .name = \"44 SIGRTMIN+11\", .number = 44 },\n   { .name = \"45 SIGRTMIN+12\", .number = 45 },\n   { .name = \"46 SIGRTMIN+13\", .number = 46 },\n   { .name = \"47 SIGRTMIN+14\", .number = 47 },\n   { .name = \"48 SIGRTMIN+15\", .number = 48 },\n   { .name = \"49 SIGRTMIN+16\", .number = 49 },\n   { .name = \"50 SIGRTMIN+17\", .number = 50 },\n   { .name = \"51 SIGRTMIN+18\", .number = 51 },\n   { .name = \"52 SIGRTMIN+19\", .number = 52 },\n   { .name = \"53 SIGRTMIN+20\", .number = 53 },\n   { .name = \"54 SIGRTMIN+21\", .number = 54 },\n   { .name = \"55 SIGRTMIN+22\", .number = 55 },\n   { .name = \"56 SIGRTMIN+23\", .number = 56 },\n   { .name = \"57 SIGRTMIN+24\", .number = 57 },\n   { .name = \"58 SIGRTMIN+25\", .number = 58 },\n   { .name = \"59 SIGRTMIN+26\", .number = 59 },\n   { .name = \"60 SIGRTMIN+27\", .number = 60 },\n   { .name = \"61 SIGRTMIN+28\", .number = 61 },\n   { .name = \"62 SIGRTMIN+29\", .number = 62 },\n   { .name = \"63 SIGRTMAX\",    .number = 63 },\n};\n\nconst unsigned int Platform_numberOfSignals = ARRAYSIZE(Platform_signals);\n\nenum {\n   MEMORY_CLASS_WIRED = 0,\n   MEMORY_CLASS_ACTIVE,\n   MEMORY_CLASS_PAGED,\n   MEMORY_CLASS_INACTIVE,\n}; // N.B. the chart will display categories in this order\n\nconst MemoryClass Platform_memoryClasses[] = {\n   [MEMORY_CLASS_WIRED] = { .label = \"wired\", .countsAsUsed = true, .countsAsCache = false, .color = MEMORY_1 },\n   [MEMORY_CLASS_ACTIVE] = { .label = \"active\", .countsAsUsed = true, .countsAsCache = false, .color = MEMORY_2 },\n   [MEMORY_CLASS_PAGED] = { .label = \"paged\", .countsAsUsed = true, .countsAsCache = false, .color = MEMORY_3 },\n   [MEMORY_CLASS_INACTIVE] = { .label = \"inactive\", .countsAsUsed = false, .countsAsCache = true, .color = MEMORY_4 },\n};\n\nconst unsigned int Platform_numberOfMemoryClasses = ARRAYSIZE(Platform_memoryClasses);\n\nconst MeterClass* const Platform_meterTypes[] = {\n   &CPUMeter_class,\n   &ClockMeter_class,\n   &DateMeter_class,\n   &DateTimeMeter_class,\n   &LoadAverageMeter_class,\n   &LoadMeter_class,\n   &MemoryMeter_class,\n   &SwapMeter_class,\n   &MemorySwapMeter_class,\n   &TasksMeter_class,\n   &UptimeMeter_class,\n   &SecondsUptimeMeter_class,\n   &BatteryMeter_class,\n   &HostnameMeter_class,\n   &SysArchMeter_class,\n   &AllCPUsMeter_class,\n   &AllCPUs2Meter_class,\n   &AllCPUs4Meter_class,\n   &AllCPUs8Meter_class,\n   &LeftCPUsMeter_class,\n   &RightCPUsMeter_class,\n   &LeftCPUs2Meter_class,\n   &RightCPUs2Meter_class,\n   &LeftCPUs4Meter_class,\n   &RightCPUs4Meter_class,\n   &LeftCPUs8Meter_class,\n   &RightCPUs8Meter_class,\n   &BlankMeter_class,\n   &DiskIORateMeter_class,\n   &DiskIOTimeMeter_class,\n   &DiskIOMeter_class,\n   &NetworkIOMeter_class,\n   &FileDescriptorMeter_class,\n   NULL\n};\n\nbool Platform_init(void) {\n   /* no platform-specific setup needed */\n   return true;\n}\n\nvoid Platform_done(void) {\n   /* no platform-specific cleanup needed */\n}\n\nvoid Platform_setBindings(Htop_Action* keys) {\n   /* no platform-specific key bindings */\n   (void) keys;\n}\n\nint Platform_getUptime(void) {\n   struct timeval bootTime, currTime;\n   const int mib[2] = { CTL_KERN, KERN_BOOTTIME };\n   size_t size = sizeof(bootTime);\n\n   int err = sysctl(mib, 2, &bootTime, &size, NULL, 0);\n   if (err) {\n      return -1;\n   }\n   gettimeofday(&currTime, NULL);\n\n   return (int) difftime(currTime.tv_sec, bootTime.tv_sec);\n}\n\nvoid Platform_getLoadAverage(double* one, double* five, double* fifteen) {\n   struct loadavg loadAverage;\n   const int mib[2] = { CTL_VM, VM_LOADAVG };\n   size_t size = sizeof(loadAverage);\n\n   int err = sysctl(mib, 2, &loadAverage, &size, NULL, 0);\n   if (err) {\n      *one = 0;\n      *five = 0;\n      *fifteen = 0;\n      return;\n   }\n   *one     = (double) loadAverage.ldavg[0] / loadAverage.fscale;\n   *five    = (double) loadAverage.ldavg[1] / loadAverage.fscale;\n   *fifteen = (double) loadAverage.ldavg[2] / loadAverage.fscale;\n}\n\npid_t Platform_getMaxPid(void) {\n   // https://nxr.netbsd.org/xref/src/sys/sys/ansi.h#__pid_t\n   // pid is assigned as a 32bit Integer.\n   return INT32_MAX;\n}\n\ndouble Platform_setCPUValues(Meter* this, int cpu) {\n   const Machine* host = this->host;\n   const NetBSDMachine* nhost = (const NetBSDMachine*) host;\n   const CPUData* cpuData = &nhost->cpuData[cpu];\n   double total = cpuData->totalPeriod == 0 ? 1 : cpuData->totalPeriod;\n   double totalPercent;\n   double* v = this->values;\n\n   v[CPU_METER_NICE] = cpuData->nicePeriod / total * 100.0;\n   v[CPU_METER_NORMAL] = cpuData->userPeriod / total * 100.0;\n   if (host->settings->detailedCPUTime) {\n      v[CPU_METER_KERNEL]  = cpuData->sysPeriod / total * 100.0;\n      v[CPU_METER_IRQ]     = cpuData->intrPeriod / total * 100.0;\n      v[CPU_METER_SOFTIRQ] = 0.0;\n      v[CPU_METER_STEAL]   = 0.0;\n      v[CPU_METER_GUEST]   = 0.0;\n      v[CPU_METER_IOWAIT]  = 0.0;\n      v[CPU_METER_FREQUENCY] = NAN;\n      this->curItems = 8;\n   } else {\n      v[CPU_METER_KERNEL] = cpuData->sysAllPeriod / total * 100.0;\n      v[CPU_METER_IRQ] = 0.0; // No steal nor guest on NetBSD\n      this->curItems = 4;\n   }\n   totalPercent = v[CPU_METER_NICE] + v[CPU_METER_NORMAL] + v[CPU_METER_KERNEL] + v[CPU_METER_IRQ];\n   totalPercent = CLAMP(totalPercent, 0.0, 100.0);\n\n   v[CPU_METER_FREQUENCY] = cpuData->frequency;\n   v[CPU_METER_TEMPERATURE] = NAN;\n\n   return totalPercent;\n}\n\nvoid Platform_setMemoryValues(Meter* this) {\n   const Machine* host = this->host;\n   const NetBSDMachine* nhost = (const NetBSDMachine*) host;\n   this->total = host->totalMem;\n   this->values[MEMORY_CLASS_WIRED]    = nhost->wiredMem;\n   this->values[MEMORY_CLASS_ACTIVE]   = nhost->activeMem;\n   this->values[MEMORY_CLASS_PAGED]    = nhost->pagedMem;\n   this->values[MEMORY_CLASS_INACTIVE] = nhost->inactiveMem;\n}\n\nvoid Platform_setSwapValues(Meter* this) {\n   const Machine* host = this->host;\n   this->total = host->totalSwap;\n   this->values[SWAP_METER_USED] = host->usedSwap;\n}\n\nchar* Platform_getProcessEnv(pid_t pid) {\n   char errbuf[_POSIX2_LINE_MAX];\n   char* env;\n   char** ptr;\n   int count;\n   kvm_t* kt;\n   const struct kinfo_proc2* kproc;\n   size_t capacity = 4096, size = 0;\n\n   if ((kt = kvm_openfiles(NULL, NULL, NULL, KVM_NO_FILES, errbuf)) == NULL) {\n      return NULL;\n   }\n\n   if ((kproc = kvm_getproc2(kt, KERN_PROC_PID, pid, sizeof(struct kinfo_proc2), &count)) == NULL) {\n      (void) kvm_close(kt);\n      return NULL;\n   }\n\n   if ((ptr = kvm_getenvv2(kt, kproc, 0)) == NULL) {\n      (void) kvm_close(kt);\n      return NULL;\n   }\n\n   env = xMalloc(capacity);\n   for (char** p = ptr; *p; p++) {\n      size_t len = strlen(*p) + 1;\n\n      while (size + len > capacity) {\n         if (capacity > (SIZE_MAX / 2)) {\n            free(env);\n            env = NULL;\n            goto end;\n         }\n\n         capacity *= 2;\n         env = xRealloc(env, capacity);\n      }\n\n      String_safeStrncpy(env + size, *p, len);\n      size += len;\n   }\n\n   if (size < 2 || env[size - 1] || env[size - 2]) {\n      if (size + 2 < capacity)\n         env = xRealloc(env, capacity + 2);\n      env[size] = 0;\n      env[size + 1] = 0;\n   }\n\nend:\n   (void) kvm_close(kt);\n   return env;\n}\n\nFileLocks_ProcessData* Platform_getProcessLocks(pid_t pid) {\n   (void)pid;\n   return NULL;\n}\n\nvoid Platform_getFileDescriptors(double* used, double* max) {\n   Generic_getFileDescriptors_sysctl(used, max);\n}\n\nbool Platform_getDiskIO(DiskIOData* data) {\n   const int mib[] = { CTL_HW, HW_IOSTATS, sizeof(struct io_sysctl) };\n   struct io_sysctl* iostats = NULL;\n   size_t size = 0;\n\n   for (int retry = 3; retry > 0; retry--) {\n      /* get the size of the IO statistic array */\n      if (sysctl(mib, __arraycount(mib), iostats, &size, NULL, 0) < 0)\n         CRT_fatalError(\"Unable to get size of io_sysctl\");\n\n      if (size == 0) {\n         free(iostats);\n         return false;\n      }\n\n      iostats = xRealloc(iostats, size);\n\n      errno = 0;\n\n      if (sysctl(mib, __arraycount(mib), iostats, &size, NULL, 0) == 0)\n         break;\n\n      if (errno != ENOMEM)\n         CRT_fatalError(\"Unable to get disk IO statistics\");\n   }\n\n   if (errno == ENOMEM)\n      CRT_fatalError(\"Unable to get disk IO statistics\");\n\n   uint64_t bytesReadSum = 0;\n   uint64_t bytesWriteSum = 0;\n   uint64_t busyTimeSum = 0;\n   uint64_t numDisks = 0;\n\n   for (size_t i = 0, count = size / sizeof(struct io_sysctl); i < count; i++) {\n      /* ignore NFS activity */\n      if (iostats[i].type != IOSTAT_DISK)\n         continue;\n\n      bytesReadSum += iostats[i].rbytes;\n      bytesWriteSum += iostats[i].wbytes;\n      busyTimeSum += iostats[i].busysum_usec;\n      numDisks++;\n   }\n\n   data->totalBytesRead = bytesReadSum;\n   data->totalBytesWritten = bytesWriteSum;\n   data->totalMsTimeSpend = busyTimeSum / 1000;\n   data->numDisks = numDisks;\n\n   free(iostats);\n   return true;\n}\n\nbool Platform_getNetworkIO(NetworkIOData* data) {\n   struct ifaddrs* ifaddrs = NULL;\n\n   if (getifaddrs(&ifaddrs) != 0)\n      return false;\n\n   for (const struct ifaddrs* ifa = ifaddrs; ifa; ifa = ifa->ifa_next) {\n      if (!ifa->ifa_addr)\n         continue;\n      if (ifa->ifa_addr->sa_family != AF_LINK)\n         continue;\n      if (ifa->ifa_flags & IFF_LOOPBACK)\n         continue;\n\n      const struct if_data* ifd = (const struct if_data*)ifa->ifa_data;\n\n      data->bytesReceived += ifd->ifi_ibytes;\n      data->packetsReceived += ifd->ifi_ipackets;\n      data->bytesTransmitted += ifd->ifi_obytes;\n      data->packetsTransmitted += ifd->ifi_opackets;\n   }\n\n   freeifaddrs(ifaddrs);\n   return true;\n}\n\nvoid Platform_getBattery(double* percent, ACPresence* isOnAC) {\n   prop_dictionary_t dict, fields, props;\n   prop_object_t device, class;\n\n   intmax_t totalCharge = 0;\n   intmax_t totalCapacity = 0;\n\n   *percent = NAN;\n   *isOnAC = AC_ERROR;\n\n   int fd = open(_PATH_SYSMON, O_RDONLY);\n   if (fd == -1)\n      goto error;\n\n   if (prop_dictionary_recv_ioctl(fd, ENVSYS_GETDICTIONARY, &dict) != 0)\n      goto error;\n\n   prop_object_iterator_t devIter = prop_dictionary_iterator(dict);\n   if (devIter == NULL)\n      goto error;\n\n   while ((device = prop_object_iterator_next(devIter)) != NULL) {\n      prop_object_t fieldsArray = prop_dictionary_get_keysym(dict, device);\n      if (fieldsArray == NULL)\n         goto error;\n\n      prop_object_iterator_t fieldsIter = prop_array_iterator(fieldsArray);\n      if (fieldsIter == NULL)\n         goto error;\n\n      bool isACAdapter = false;\n      bool isBattery = false;\n\n      /* only assume battery is not present if explicitly stated */\n      intmax_t isPresent = 1;\n      intmax_t isConnected = 0;\n      intmax_t curCharge = 0;\n      intmax_t maxCharge = 0;\n\n      while ((fields = prop_object_iterator_next(fieldsIter)) != NULL) {\n         props = prop_dictionary_get(fields, \"device-properties\");\n         if (props != NULL) {\n            class = prop_dictionary_get(props, \"device-class\");\n\n            if (prop_string_equals_string(class, \"ac-adapter\")) {\n               isACAdapter = true;\n            } else if (prop_string_equals_string(class, \"battery\")) {\n               isBattery = true;\n            }\n            continue;\n         }\n\n         prop_object_t curValue = prop_dictionary_get(fields, \"cur-value\");\n         prop_object_t maxValue = prop_dictionary_get(fields, \"max-value\");\n         prop_object_t descField = prop_dictionary_get(fields, \"description\");\n\n         if (descField == NULL || curValue == NULL)\n            continue;\n\n         if (prop_string_equals_string(descField, \"connected\")) {\n            isConnected = prop_number_signed_value(curValue);\n         } else if (prop_string_equals_string(descField, \"present\")) {\n            isPresent = prop_number_signed_value(curValue);\n         } else if (prop_string_equals_string(descField, \"charge\")) {\n            if (maxValue == NULL)\n               continue;\n            curCharge = prop_number_signed_value(curValue);\n            maxCharge = prop_number_signed_value(maxValue);\n         }\n      }\n\n      if (isBattery && isPresent) {\n         totalCharge += curCharge;\n         totalCapacity += maxCharge;\n      }\n\n      if (isACAdapter && *isOnAC != AC_PRESENT) {\n         *isOnAC = isConnected ? AC_PRESENT : AC_ABSENT;\n      }\n   }\n   *percent = totalCapacity > 0 ? ((double)totalCharge / (double)totalCapacity) * 100.0 : NAN;\n\nerror:\n   if (fd != -1)\n      close(fd);\n}\n"
  },
  {
    "path": "netbsd/Platform.h",
    "content": "#ifndef HEADER_Platform\n#define HEADER_Platform\n/*\nhtop - netbsd/Platform.h\n(C) 2014 Hisham H. Muhammad\n(C) 2015 Michael McConville\n(C) 2021 Santhosh Raju\n(C) 2021 htop dev team\nReleased under the GNU GPLv2+, see the COPYING file\nin the source distribution for its full text.\n*/\n\n#include <stdbool.h>\n#include <stddef.h>\n#include <stdint.h>\n#include <sys/time.h>\n#include <sys/types.h>\n\n#include \"Action.h\"\n#include \"BatteryMeter.h\"\n#include \"DiskIOMeter.h\"\n#include \"MemoryMeter.h\"\n#include \"NetworkIOMeter.h\"\n#include \"Process.h\"\n#include \"ProcessLocksScreen.h\"\n#include \"SignalsPanel.h\"\n#include \"CommandLine.h\"\n#include \"generic/gettime.h\"\n#include \"generic/hostname.h\"\n#include \"generic/uname.h\"\n\n\n/* There are no Long Options for NetBSD as of now. */\n#define PLATFORM_LONG_OPTIONS \\\n   // End of list\n\nextern const ScreenDefaults Platform_defaultScreens[];\n\nextern const unsigned int Platform_numberOfDefaultScreens;\n\n/* see /usr/include/sys/signal.h */\nextern const SignalItem Platform_signals[];\n\nextern const unsigned int Platform_numberOfSignals;\n\nextern const MemoryClass Platform_memoryClasses[];\n\nextern const unsigned int Platform_numberOfMemoryClasses;\n\nextern const MeterClass* const Platform_meterTypes[];\n\nbool Platform_init(void);\n\nvoid Platform_done(void);\n\nvoid Platform_setBindings(Htop_Action* keys);\n\nint Platform_getUptime(void);\n\nvoid Platform_getLoadAverage(double* one, double* five, double* fifteen);\n\npid_t Platform_getMaxPid(void);\n\ndouble Platform_setCPUValues(Meter* this, int cpu);\n\nvoid Platform_setMemoryValues(Meter* this);\n\nvoid Platform_setSwapValues(Meter* this);\n\nchar* Platform_getProcessEnv(pid_t pid);\n\nFileLocks_ProcessData* Platform_getProcessLocks(pid_t pid);\n\nvoid Platform_getFileDescriptors(double* used, double* max);\n\nbool Platform_getDiskIO(DiskIOData* data);\n\nbool Platform_getNetworkIO(NetworkIOData* data);\n\nvoid Platform_getBattery(double* percent, ACPresence* isOnAC);\n\nstatic inline void Platform_getHostname(char* buffer, size_t size) {\n   Generic_hostname(buffer, size);\n}\n\nstatic inline const char* Platform_getRelease(void) {\n   return Generic_uname();\n}\n\nstatic inline const char* Platform_getFailedState(void) {\n   return NULL;\n}\n\nstatic inline void Platform_longOptionsUsage(ATTR_UNUSED const char* name) { }\n\nstatic inline CommandLineStatus Platform_getLongOption(ATTR_UNUSED int opt, ATTR_UNUSED int argc, ATTR_UNUSED char** argv) {\n   return STATUS_ERROR_EXIT;\n}\n\nstatic inline void Platform_gettime_realtime(struct timeval* tv, uint64_t* msec) {\n   Generic_gettime_realtime(tv, msec);\n}\n\nstatic inline void Platform_gettime_monotonic(uint64_t* msec) {\n   Generic_gettime_monotonic(msec);\n}\n\nstatic inline Hashtable* Platform_dynamicMeters(void) {\n   return NULL;\n}\n\nstatic inline void Platform_dynamicMetersDone(ATTR_UNUSED Hashtable* table) { }\n\nstatic inline void Platform_dynamicMeterInit(ATTR_UNUSED Meter* meter) { }\n\nstatic inline void Platform_dynamicMeterUpdateValues(ATTR_UNUSED Meter* meter) { }\n\nstatic inline void Platform_dynamicMeterDisplay(ATTR_UNUSED const Meter* meter, ATTR_UNUSED RichString* out) { }\n\nstatic inline Hashtable* Platform_dynamicColumns(void) {\n   return NULL;\n}\n\nstatic inline void Platform_dynamicColumnsDone(ATTR_UNUSED Hashtable* table) { }\n\nstatic inline const char* Platform_dynamicColumnName(ATTR_UNUSED unsigned int key) {\n   return NULL;\n}\n\nstatic inline bool Platform_dynamicColumnWriteField(ATTR_UNUSED const Process* proc, ATTR_UNUSED RichString* str, ATTR_UNUSED unsigned int key) {\n   return false;\n}\n\nstatic inline Hashtable* Platform_dynamicScreens(void) {\n   return NULL;\n}\n\nstatic inline void Platform_defaultDynamicScreens(ATTR_UNUSED Settings* settings) { }\n\nstatic inline void Platform_addDynamicScreen(ATTR_UNUSED ScreenSettings* ss) { }\n\nstatic inline void Platform_addDynamicScreenAvailableColumns(ATTR_UNUSED Panel* availableColumns, ATTR_UNUSED const char* screen) { }\n\nstatic inline void Platform_dynamicScreensDone(ATTR_UNUSED Hashtable* screens) { }\n\n#endif\n"
  },
  {
    "path": "netbsd/ProcessField.h",
    "content": "#ifndef HEADER_NetBSDProcessField\n#define HEADER_NetBSDProcessField\n/*\nhtop - netbsd/ProcessField.h\n(C) 2021 htop dev team\nReleased under the GNU GPLv2+, see the COPYING file\nin the source distribution for its full text.\n*/\n\n\n#define PLATFORM_PROCESS_FIELDS  \\\n   // End of list\n\n\n#endif /* HEADER_NetBSDProcessField */\n"
  },
  {
    "path": "netbsd/README.md",
    "content": "NetBSD support in htop(1)\n===\n\nThis implementation utilizes kvm_getprocs(3), sysctl(3), etc, eliminating the\nneed for mount_procfs(8) with Linux compatibility enabled.\n\nThe implementation was initially based on the OpenBSD support in htop(1).\n\nNotes on NetBSD curses\n---\n\nNetBSD is one of the last operating systems to use and maintain its own\nimplementation of Curses.\n\nhtop(1) can be compiled against either ncurses or NetBSD's curses(3).\nBy default, htop(1) will use ncurses when it is found, as support for NetBSD's\ncurses in htop is limited.\n\nTo use NetBSD's libcurses, htop(1) must be configured with `--disable-unicode`.\nStarting with htop 3.4.0, a new option `--with-curses=curses` may be specified\nto let `configure` skip ncurses when both libraries are installed.\n\nTechnical caveats regarding NetBSD's curses support:\n\n* htop with Unicode enabled directly accesses ncurses's `cchar_t` struct, which\n  has different contents in NetBSD's curses.\n\n* Versions of libcurses in NetBSD 9 and prior have no mouse support\n  (this is an ncurses extension). Newer versions contain no-op mouse functions\n  for compatibility with ncurses.\n\nWhat needs improvement\n---\n\n* Kernel and userspace threads are not displayed or counted -\n  maybe look at NetBSD top(1).\n* Support for compiling using libcurses's Unicode support.\n* Support for fstat(1) (view open files, like lsof(8) on Linux).\n* Support for ktrace(1) (like strace(1) on Linux).\n"
  },
  {
    "path": "openbsd/OpenBSDMachine.c",
    "content": "/*\nhtop - OpenBSDMachine.c\n(C) 2014 Hisham H. Muhammad\n(C) 2015 Michael McConville\nReleased under the GNU GPLv2+, see the COPYING file\nin the source distribution for its full text.\n*/\n\n#include \"config.h\" // IWYU pragma: keep\n\n#include \"openbsd/OpenBSDMachine.h\"\n\n#include <kvm.h>\n#include <limits.h>\n#include <stdlib.h>\n#include <string.h>\n#include <unistd.h>\n#include <sys/mount.h>\n#include <sys/param.h>\n#include <sys/proc.h>\n#include <sys/sched.h>\n#include <sys/swap.h>\n#include <sys/sysctl.h>\n#include <sys/types.h>\n#include <uvm/uvmexp.h>\n\n#include \"CRT.h\"\n#include \"Macros.h\"\n#include \"Object.h\"\n#include \"Settings.h\"\n#include \"XUtils.h\"\n\n\nstatic void OpenBSDMachine_updateCPUcount(OpenBSDMachine* this) {\n   Machine* super = &this->super;\n   const int nmib[] = { CTL_HW, HW_NCPU };\n   const int mib[] = { CTL_HW, HW_NCPUONLINE };\n   int r;\n   unsigned int value;\n   size_t size;\n   bool change = false;\n\n   size = sizeof(value);\n   r = sysctl(mib, 2, &value, &size, NULL, 0);\n   if (r < 0 || value < 1) {\n      value = 1;\n   }\n\n   if (value != super->activeCPUs) {\n      super->activeCPUs = value;\n      change = true;\n   }\n\n   size = sizeof(value);\n   r = sysctl(nmib, 2, &value, &size, NULL, 0);\n   if (r < 0 || value < 1) {\n      value = super->activeCPUs;\n   }\n\n   if (value != super->existingCPUs) {\n      this->cpuData = xReallocArray(this->cpuData, value + 1, sizeof(CPUData));\n      super->existingCPUs = value;\n      change = true;\n   }\n\n   if (change) {\n      CPUData* dAvg = &this->cpuData[0];\n      memset(dAvg, '\\0', sizeof(CPUData));\n      dAvg->totalTime = 1;\n      dAvg->totalPeriod = 1;\n      dAvg->online = true;\n\n      for (unsigned int i = 0; i < super->existingCPUs; i++) {\n         CPUData* d = &this->cpuData[i + 1];\n         memset(d, '\\0', sizeof(CPUData));\n         d->totalTime = 1;\n         d->totalPeriod = 1;\n\n         const int ncmib[] = { CTL_KERN, KERN_CPUSTATS, i };\n         struct cpustats cpu_stats;\n\n         size = sizeof(cpu_stats);\n         if (sysctl(ncmib, 3, &cpu_stats, &size, NULL, 0) < 0) {\n            CRT_fatalError(\"ncmib sysctl call failed\");\n         }\n         d->online = (cpu_stats.cs_flags & CPUSTATS_ONLINE);\n      }\n   }\n}\n\nMachine* Machine_new(UsersTable* usersTable, uid_t userId) {\n   const int fmib[] = { CTL_KERN, KERN_FSCALE };\n   size_t size;\n   char errbuf[_POSIX2_LINE_MAX];\n\n   OpenBSDMachine* this = xCalloc(1, sizeof(OpenBSDMachine));\n   Machine* super = &this->super;\n\n   Machine_init(super, usersTable, userId);\n\n   OpenBSDMachine_updateCPUcount(this);\n\n   size = sizeof(this->fscale);\n   if (sysctl(fmib, 2, &this->fscale, &size, NULL, 0) < 0 || this->fscale <= 0) {\n      CRT_fatalError(\"fscale sysctl call failed\");\n   }\n\n   long pageSize = sysconf(_SC_PAGESIZE);\n   if (pageSize <= 0)\n      CRT_fatalError(\"pagesize sysconf call failed\");\n   this->pageSize = (size_t)pageSize;\n   this->pageSizeKB = this->pageSize / ONE_K;\n\n   this->kd = kvm_openfiles(NULL, NULL, NULL, KVM_NO_FILES, errbuf);\n   if (this->kd == NULL) {\n      CRT_fatalError(\"kvm_openfiles() failed\");\n   }\n\n   this->cpuSpeed = -1;\n\n   return super;\n}\n\nvoid Machine_delete(Machine* super) {\n   OpenBSDMachine* this = (OpenBSDMachine*) super;\n\n   if (this->kd) {\n      kvm_close(this->kd);\n   }\n   free(this->cpuData);\n   Machine_done(super);\n   free(this);\n}\n\nstatic void OpenBSDMachine_scanMemoryInfo(OpenBSDMachine* this) {\n   Machine* super = &this->super;\n   const int uvmexp_mib[] = { CTL_VM, VM_UVMEXP };\n   struct uvmexp uvmexp;\n   size_t size_uvmexp = sizeof(uvmexp);\n\n   if (sysctl(uvmexp_mib, 2, &uvmexp, &size_uvmexp, NULL, 0) < 0) {\n      CRT_fatalError(\"uvmexp sysctl call failed\");\n   }\n\n   // Taken from OpenBSD systat/iostat.c, top/machine.c and uvm_sysctl(9)\n   const int bcache_mib[] = { CTL_VFS, VFS_GENERIC, VFS_BCACHESTAT };\n   struct bcachestats bcstats;\n   size_t size_bcstats = sizeof(bcstats);\n   if (sysctl(bcache_mib, 3, &bcstats, &size_bcstats, NULL, 0) < 0) {\n      CRT_fatalError(\"cannot get vfs.bcachestat\");\n   }\n\n   // NOTE: in OpenBSD the \"cached\" memory is a subset of the \"wired\" memory.\n   super->totalMem   = this->pageSizeKB * uvmexp.npages;\n   this->wiredMem    = this->pageSizeKB * (uvmexp.npages - uvmexp.free - uvmexp.active - uvmexp.paging - bcstats.numbufpages); // NB: uvmexp.wired == 0!? deduct it\n   this->cacheMem    = this->pageSizeKB * bcstats.numbufpages;\n   this->activeMem   = this->pageSizeKB * uvmexp.active;\n   this->pagingMem   = this->pageSizeKB * uvmexp.paging;\n   this->inactiveMem = this->pageSizeKB * uvmexp.inactive;\n\n   /*\n    * Copyright (c) 1994 Thorsten Lockert <tholo@sigmasoft.com>\n    * All rights reserved.\n    *\n    * Taken almost directly from OpenBSD's top(1)\n    *\n    * Originally released under a BSD-3 license\n    * Modified through htop developers applying GPL-2\n    */\n   int nswap = swapctl(SWAP_NSWAP, 0, 0);\n   if (nswap > 0) {\n      struct swapent* swdev = xMallocArray(nswap, sizeof(struct swapent));\n      int rnswap = swapctl(SWAP_STATS, swdev, nswap);\n\n      /* Total things up */\n      unsigned long long int total = 0, used = 0;\n      for (int i = 0; i < rnswap; i++) {\n         if (swdev[i].se_flags & SWF_ENABLE) {\n            used += (swdev[i].se_inuse / (1024 / DEV_BSIZE));\n            total += (swdev[i].se_nblks / (1024 / DEV_BSIZE));\n         }\n      }\n\n      super->totalSwap = total;\n      super->usedSwap = used;\n\n      free(swdev);\n   } else {\n      super->totalSwap = super->usedSwap = 0;\n   }\n}\n\nstatic void getKernelCPUTimes(unsigned int cpuId, u_int64_t* times) {\n   const int mib[] = { CTL_KERN, KERN_CPTIME2, cpuId };\n   size_t length = sizeof(*times) * CPUSTATES;\n   if (sysctl(mib, 3, times, &length, NULL, 0) == -1 || length != sizeof(*times) * CPUSTATES) {\n      CRT_fatalError(\"sysctl kern.cp_time2 failed\");\n   }\n}\n\nstatic void kernelCPUTimesToHtop(const u_int64_t* times, CPUData* cpu) {\n   unsigned long long totalTime = 0;\n   for (int i = 0; i < CPUSTATES; i++) {\n      totalTime += times[i];\n   }\n\n   unsigned long long sysAllTime = times[CP_INTR] + times[CP_SYS];\n\n   // XXX Not sure if CP_SPIN should be added to sysAllTime.\n   // See https://github.com/openbsd/src/commit/531d8034253fb82282f0f353c086e9ad827e031c\n   #ifdef CP_SPIN\n   sysAllTime += times[CP_SPIN];\n   #endif\n\n   cpu->totalPeriod = saturatingSub(totalTime, cpu->totalTime);\n   cpu->userPeriod = saturatingSub(times[CP_USER], cpu->userTime);\n   cpu->nicePeriod = saturatingSub(times[CP_NICE], cpu->niceTime);\n   cpu->sysPeriod = saturatingSub(times[CP_SYS], cpu->sysTime);\n   cpu->sysAllPeriod = saturatingSub(sysAllTime, cpu->sysAllTime);\n   #ifdef CP_SPIN\n   cpu->spinPeriod = saturatingSub(times[CP_SPIN], cpu->spinTime);\n   #endif\n   cpu->intrPeriod = saturatingSub(times[CP_INTR], cpu->intrTime);\n   cpu->idlePeriod = saturatingSub(times[CP_IDLE], cpu->idleTime);\n\n   cpu->totalTime = totalTime;\n   cpu->userTime = times[CP_USER];\n   cpu->niceTime = times[CP_NICE];\n   cpu->sysTime = times[CP_SYS];\n   cpu->sysAllTime = sysAllTime;\n   #ifdef CP_SPIN\n   cpu->spinTime = times[CP_SPIN];\n   #endif\n   cpu->intrTime = times[CP_INTR];\n   cpu->idleTime = times[CP_IDLE];\n}\n\nstatic void OpenBSDMachine_scanCPUTime(OpenBSDMachine* this) {\n   Machine* super = &this->super;\n   u_int64_t kernelTimes[CPUSTATES] = {0};\n   u_int64_t avg[CPUSTATES] = {0};\n\n   for (unsigned int i = 0; i < super->existingCPUs; i++) {\n      CPUData* cpu = &this->cpuData[i + 1];\n\n      if (!cpu->online) {\n         continue;\n      }\n\n      getKernelCPUTimes(i, kernelTimes);\n      kernelCPUTimesToHtop(kernelTimes, cpu);\n\n      avg[CP_USER] += cpu->userTime;\n      avg[CP_NICE] += cpu->niceTime;\n      avg[CP_SYS] += cpu->sysTime;\n      #ifdef CP_SPIN\n      avg[CP_SPIN] += cpu->spinTime;\n      #endif\n      avg[CP_INTR] += cpu->intrTime;\n      avg[CP_IDLE] += cpu->idleTime;\n   }\n\n   for (int i = 0; i < CPUSTATES; i++) {\n      avg[i] /= super->activeCPUs;\n   }\n\n   kernelCPUTimesToHtop(avg, &this->cpuData[0]);\n\n   {\n      const int mib[] = { CTL_HW, HW_CPUSPEED };\n      int cpuSpeed;\n      size_t size = sizeof(cpuSpeed);\n      if (sysctl(mib, 2, &cpuSpeed, &size, NULL, 0) == -1) {\n         this->cpuSpeed = -1;\n      } else {\n         this->cpuSpeed = cpuSpeed;\n      }\n   }\n}\n\nvoid Machine_scan(Machine* super) {\n   OpenBSDMachine* this = (OpenBSDMachine*) super;\n\n   OpenBSDMachine_updateCPUcount(this);\n   OpenBSDMachine_scanMemoryInfo(this);\n   OpenBSDMachine_scanCPUTime(this);\n}\n\nbool Machine_isCPUonline(const Machine* super, unsigned int id) {\n   assert(id < super->existingCPUs);\n\n   const OpenBSDMachine* this = (const OpenBSDMachine*) super;\n   return this->cpuData[id + 1].online;\n}\n"
  },
  {
    "path": "openbsd/OpenBSDMachine.h",
    "content": "#ifndef HEADER_OpenBSDMachine\n#define HEADER_OpenBSDMachine\n/*\nhtop - OpenBSDMachine.h\n(C) 2014 Hisham H. Muhammad\n(C) 2015 Michael McConville\nReleased under the GNU GPLv2+, see the COPYING file\nin the source distribution for its full text.\n*/\n\n#include <kvm.h>\n#include <stdbool.h>\n#include <stddef.h>\n\n#include \"Machine.h\"\n\n\ntypedef struct CPUData_ {\n   unsigned long long int totalTime;\n   unsigned long long int userTime;\n   unsigned long long int niceTime;\n   unsigned long long int sysTime;\n   unsigned long long int sysAllTime;\n   unsigned long long int spinTime;\n   unsigned long long int intrTime;\n   unsigned long long int idleTime;\n\n   unsigned long long int totalPeriod;\n   unsigned long long int userPeriod;\n   unsigned long long int nicePeriod;\n   unsigned long long int sysPeriod;\n   unsigned long long int sysAllPeriod;\n   unsigned long long int spinPeriod;\n   unsigned long long int intrPeriod;\n   unsigned long long int idlePeriod;\n\n   bool online;\n} CPUData;\n\ntypedef struct OpenBSDMachine_ {\n   Machine super;\n   kvm_t* kd;\n\n   memory_t wiredMem;\n   memory_t cacheMem;\n   memory_t activeMem;\n   memory_t pagingMem;\n   memory_t inactiveMem;\n\n   CPUData* cpuData;\n\n   long fscale;\n   int cpuSpeed;\n   size_t pageSize;\n   size_t pageSizeKB;\n\n} OpenBSDMachine;\n\n#endif\n"
  },
  {
    "path": "openbsd/OpenBSDProcess.c",
    "content": "/*\nhtop - OpenBSDProcess.c\n(C) 2015 Hisham H. Muhammad\n(C) 2015 Michael McConville\nReleased under the GNU GPLv2+, see the COPYING file\nin the source distribution for its full text.\n*/\n\n#include \"config.h\" // IWYU pragma: keep\n\n#include \"openbsd/OpenBSDProcess.h\"\n\n#include <stdlib.h>\n\n#include \"CRT.h\"\n#include \"Process.h\"\n#include \"RichString.h\"\n#include \"XUtils.h\"\n\n\nconst ProcessFieldData Process_fields[LAST_PROCESSFIELD] = {\n   [0] = {\n      .name = \"\",\n      .title = NULL,\n      .description = NULL,\n      .flags = 0,\n   },\n   [PID] = {\n      .name = \"PID\",\n      .title = \"PID\",\n      .description = \"Process/thread ID\",\n      .flags = 0,\n      .pidColumn = true,\n   },\n   [COMM] = {\n      .name = \"Command\",\n      .title = \"Command \",\n      .description = \"Command line (insert as last column only)\",\n      .flags = 0,\n   },\n   [STATE] = {\n      .name = \"STATE\",\n      .title = \"S \",\n      .description = \"Process state (S sleeping, R running, D disk, Z zombie, T traced, W paging)\",\n      .flags = 0,\n   },\n   [PPID] = {\n      .name = \"PPID\",\n      .title = \"PPID\",\n      .description = \"Parent process ID\",\n      .flags = 0,\n      .pidColumn = true,\n   },\n   [PGRP] = {\n      .name = \"PGRP\",\n      .title = \"PGRP\",\n      .description = \"Process group ID\",\n      .flags = 0,\n      .pidColumn = true,\n   },\n   [SESSION] = {\n      .name = \"SESSION\",\n      .title = \"SESN\",\n      .description = \"Process's session ID\",\n      .flags = 0,\n      .pidColumn = true,\n   },\n   [TTY] = {\n      .name = \"TTY\",\n      .title = \"TTY      \",\n      .description = \"Controlling terminal\",\n      .flags = 0,\n   },\n   [TPGID] = {\n      .name = \"TPGID\",\n      .title = \"TPGID\",\n      .description = \"Process ID of the fg process group of the controlling terminal\",\n      .flags = 0,\n      .pidColumn = true,\n   },\n   [MINFLT] = {\n      .name = \"MINFLT\",\n      .title = \"     MINFLT \",\n      .description = \"Number of minor faults which have not required loading a memory page from disk\",\n      .flags = 0,\n      .defaultSortDesc = true,\n   },\n   [MAJFLT] = {\n      .name = \"MAJFLT\",\n      .title = \"     MAJFLT \",\n      .description = \"Number of major faults which have required loading a memory page from disk\",\n      .flags = 0,\n      .defaultSortDesc = true,\n   },\n   [PRIORITY] = {\n      .name = \"PRIORITY\",\n      .title = \"PRI \",\n      .description = \"Kernel's internal priority for the process\",\n      .flags = 0,\n   },\n   [NICE] = {\n      .name = \"NICE\",\n      .title = \" NI \",\n      .description = \"Nice value (the higher the value, the more it lets other processes take priority)\",\n      .flags = 0,\n   },\n   [STARTTIME] = {\n      .name = \"STARTTIME\",\n      .title = \"START \",\n      .description = \"Time the process was started\",\n      .flags = 0,\n   },\n   [ELAPSED] = {\n      .name = \"ELAPSED\",\n      .title = \"ELAPSED  \",\n      .description = \"Time since the process was started\",\n      .flags = 0,\n   },\n   [PROCESSOR] = {\n      .name = \"PROCESSOR\",\n      .title = \"CPU \",\n      .description = \"Id of the CPU the process last executed on\",\n      .flags = 0,\n   },\n   [M_VIRT] = {\n      .name = \"M_VIRT\",\n      .title = \" VIRT \",\n      .description = \"Total program size in virtual memory\",\n      .flags = 0,\n      .defaultSortDesc = true,\n   },\n   [M_RESIDENT] = {\n      .name = \"M_RESIDENT\",\n      .title = \"  RES \",\n      .description = \"Resident set size, size of the text and data sections, plus stack usage\",\n      .flags = 0,\n      .defaultSortDesc = true,\n   },\n   [ST_UID] = {\n      .name = \"ST_UID\",\n      .title = \"UID\",\n      .description = \"User ID of the process owner\",\n      .flags = 0,\n   },\n   [PERCENT_CPU] = {\n      .name = \"PERCENT_CPU\",\n      .title = \" CPU%\",\n      .description = \"Percentage of the CPU time the process used in the last sampling\",\n      .flags = 0,\n      .defaultSortDesc = true,\n      .autoWidth = true,\n      .autoTitleRightAlign = true,\n   },\n   [PERCENT_NORM_CPU] = {\n      .name = \"PERCENT_NORM_CPU\",\n      .title = \"NCPU%\",\n      .description = \"Normalized percentage of the CPU time the process used in the last sampling (normalized by cpu count)\",\n      .flags = 0,\n      .defaultSortDesc = true,\n      .autoWidth = true,\n   },\n   [PERCENT_MEM] = {\n      .name = \"PERCENT_MEM\",\n      .title = \"MEM% \",\n      .description = \"Percentage of the memory the process is using, based on resident memory size\",\n      .flags = 0,\n      .defaultSortDesc = true,\n   },\n   [USER] = {\n      .name = \"USER\",\n      .title = \"USER       \",\n      .description = \"Username of the process owner (or user ID if name cannot be determined)\",\n      .flags = 0,\n   },\n   [TIME] = {\n      .name = \"TIME\",\n      .title = \"  TIME+  \",\n      .description = \"Total time the process has spent in user and system time\",\n      .flags = 0,\n      .defaultSortDesc = true,\n   },\n   [NLWP] = {\n      .name = \"NLWP\",\n      .title = \"NLWP \",\n      .description = \"Number of threads in the process\",\n      .flags = 0,\n   },\n   [TGID] = {\n      .name = \"TGID\",\n      .title = \"TGID\",\n      .description = \"Thread group ID (i.e. process ID)\",\n      .flags = 0,\n      .pidColumn = true,\n   },\n   [PROC_COMM] = {\n      .name = \"COMM\",\n      .title = \"COMM            \",\n      .description = \"comm string of the process\",\n      .flags = 0,\n   },\n   [CWD] = {\n      .name = \"CWD\",\n      .title = \"CWD                       \",\n      .description = \"The current working directory of the process\",\n      .flags = PROCESS_FLAG_CWD,\n   },\n\n};\n\nProcess* OpenBSDProcess_new(const Machine* host) {\n   OpenBSDProcess* this = xCalloc(1, sizeof(OpenBSDProcess));\n   Object_setClass(this, Class(OpenBSDProcess));\n   Process_init(&this->super, host);\n   return (Process*)this;\n}\n\nvoid Process_delete(Object* cast) {\n   OpenBSDProcess* this = (OpenBSDProcess*) cast;\n   Process_done((Process*)cast);\n   free(this);\n}\n\nstatic void OpenBSDProcess_rowWriteField(const Row* super, RichString* str, ProcessField field) {\n   const OpenBSDProcess* op = (const OpenBSDProcess*) super;\n\n   char buffer[256]; buffer[255] = '\\0';\n   int attr = CRT_colors[DEFAULT_COLOR];\n   //size_t n = sizeof(buffer) - 1;\n\n   switch (field) {\n   // add OpenBSD-specific fields here\n   default:\n      Process_writeField(&op->super, str, field);\n      return;\n   }\n\n   RichString_appendWide(str, attr, buffer);\n}\n\nstatic int OpenBSDProcess_compareByKey(const Process* v1, const Process* v2, ProcessField key) {\n   const OpenBSDProcess* p1 = (const OpenBSDProcess*)v1;\n   const OpenBSDProcess* p2 = (const OpenBSDProcess*)v2;\n\n   // remove if actually used\n   (void)p1; (void)p2;\n\n   switch (key) {\n   // add OpenBSD-specific fields here\n   default:\n      return Process_compareByKey_Base(v1, v2, key);\n   }\n}\n\nconst ProcessClass OpenBSDProcess_class = {\n   .super = {\n      .super = {\n         .extends = Class(Process),\n         .display = Row_display,\n         .delete = Process_delete,\n         .compare = Process_compare\n      },\n      .isHighlighted = Process_rowIsHighlighted,\n      .isVisible = Process_rowIsVisible,\n      .matchesFilter = Process_rowMatchesFilter,\n      .compareByParent = Process_compareByParent,\n      .sortKeyString = Process_rowGetSortKey,\n      .writeField = OpenBSDProcess_rowWriteField\n   },\n   .compareByKey = OpenBSDProcess_compareByKey\n};\n"
  },
  {
    "path": "openbsd/OpenBSDProcess.h",
    "content": "#ifndef HEADER_OpenBSDProcess\n#define HEADER_OpenBSDProcess\n/*\nhtop - OpenBSDProcess.h\n(C) 2015 Hisham H. Muhammad\n(C) 2015 Michael McConville\nReleased under the GNU GPLv2+, see the COPYING file\nin the source distribution for its full text.\n*/\n\n#include <stdbool.h>\n\n#include \"Machine.h\"\n#include \"Object.h\"\n#include \"Process.h\"\n\n\ntypedef struct OpenBSDProcess_ {\n   Process super;\n\n   /* 'Kernel virtual addr of u-area' to detect main threads */\n   uint64_t addr;\n} OpenBSDProcess;\n\nextern const ProcessClass OpenBSDProcess_class;\n\nextern const ProcessFieldData Process_fields[LAST_PROCESSFIELD];\n\nProcess* OpenBSDProcess_new(const Machine* host);\n\nvoid Process_delete(Object* cast);\n\n#endif\n"
  },
  {
    "path": "openbsd/OpenBSDProcessTable.c",
    "content": "/*\nhtop - OpenBSDProcessTable.c\n(C) 2014 Hisham H. Muhammad\n(C) 2015 Michael McConville\nReleased under the GNU GPLv2+, see the COPYING file\nin the source distribution for its full text.\n*/\n\n#include \"config.h\" // IWYU pragma: keep\n\n#include \"openbsd/OpenBSDProcessTable.h\"\n\n#include <kvm.h>\n#include <limits.h>\n#include <stdlib.h>\n#include <string.h>\n#include <unistd.h>\n#include <sys/mount.h>\n#include <sys/param.h>\n#include <sys/proc.h>\n#include <sys/sched.h>\n#include <sys/stat.h>\n#include <sys/sysctl.h>\n#include <sys/types.h>\n#include <uvm/uvmexp.h>\n\n#include \"CRT.h\"\n#include \"Macros.h\"\n#include \"Object.h\"\n#include \"Process.h\"\n#include \"ProcessTable.h\"\n#include \"Settings.h\"\n#include \"XUtils.h\"\n#include \"openbsd/OpenBSDMachine.h\"\n#include \"openbsd/OpenBSDProcess.h\"\n\n\nProcessTable* ProcessTable_new(Machine* host, Hashtable* pidMatchList) {\n   OpenBSDProcessTable* this = xCalloc(1, sizeof(OpenBSDProcessTable));\n   Object_setClass(this, Class(ProcessTable));\n\n   ProcessTable* super = &this->super;\n   ProcessTable_init(super, Class(OpenBSDProcess), host, pidMatchList);\n\n   return super;\n}\n\nvoid ProcessTable_delete(Object* cast) {\n   OpenBSDProcessTable* this = (OpenBSDProcessTable*) cast;\n   ProcessTable_done(&this->super);\n   free(this);\n}\n\nstatic void OpenBSDProcessTable_updateCwd(const struct kinfo_proc* kproc, Process* proc) {\n   const int mib[] = { CTL_KERN, KERN_PROC_CWD, kproc->p_pid };\n   char buffer[2048];\n   size_t size = sizeof(buffer);\n   if (sysctl(mib, 3, buffer, &size, NULL, 0) != 0) {\n      free(proc->procCwd);\n      proc->procCwd = NULL;\n      return;\n   }\n\n   /* Kernel threads return an empty buffer */\n   if (buffer[0] == '\\0') {\n      free(proc->procCwd);\n      proc->procCwd = NULL;\n      return;\n   }\n\n   free_and_xStrdup(&proc->procCwd, buffer);\n}\n\nstatic void OpenBSDProcessTable_updateProcessName(kvm_t* kd, const struct kinfo_proc* kproc, Process* proc) {\n   Process_updateComm(proc, kproc->p_comm);\n\n   /*\n    * Like OpenBSD's top(1), we try to fall back to the command name\n    * (argv[0]) if we fail to construct the full command.\n    */\n   char** arg = kvm_getargv(kd, kproc, 500);\n   if (arg == NULL || *arg == NULL) {\n      Process_updateCmdline(proc, kproc->p_comm, 0, strlen(kproc->p_comm));\n      return;\n   }\n\n   size_t len = 0;\n   for (size_t i = 0; arg[i] != NULL; i++) {\n      len += strlen(arg[i]) + 1;   /* room for arg and trailing space or NUL */\n   }\n\n   /* don't use xMalloc here - we want to handle huge argv's gracefully */\n   char* s;\n   if ((s = malloc(len)) == NULL) {\n      Process_updateCmdline(proc, kproc->p_comm, 0, strlen(kproc->p_comm));\n      return;\n   }\n\n   *s = '\\0';\n\n   size_t start = 0;\n   size_t end = 0;\n   for (size_t i = 0; arg[i] != NULL; i++) {\n      size_t n = strlcat(s, arg[i], len);\n      if (i == 0) {\n         end = MINIMUM(n, len - 1);\n         /* check if cmdline ended earlier, e.g 'kdeinit5: Running...' */\n         for (size_t j = end; j > 0; j--) {\n            if (arg[0][j] == ' ' && arg[0][j - 1] != '\\\\') {\n               end = (arg[0][j - 1] == ':') ? (j - 1) : j;\n            }\n         }\n      }\n      /* the trailing space should get truncated anyway */\n      strlcat(s, \" \", len);\n   }\n\n   Process_updateCmdline(proc, s, start, end);\n\n   free(s);\n}\n\n/*\n * Taken from OpenBSD's ps(1).\n */\nstatic double getpcpu(const OpenBSDMachine* ohost, const struct kinfo_proc* kp) {\n   if (ohost->fscale == 0)\n      return 0.0;\n\n   return 100.0 * (double)kp->p_pctcpu / ohost->fscale;\n}\n\nstatic void OpenBSDProcessTable_scanProcs(OpenBSDProcessTable* this) {\n   Machine* host = this->super.super.host;\n   OpenBSDMachine* ohost = (OpenBSDMachine*) host;\n   const Settings* settings = host->settings;\n   const bool hideKernelThreads = settings->hideKernelThreads;\n   const bool hideUserlandThreads = settings->hideUserlandThreads;\n   int count = 0;\n\n   const struct kinfo_proc* kprocs = kvm_getprocs(ohost->kd, KERN_PROC_KTHREAD | KERN_PROC_SHOW_THREADS, 0, sizeof(struct kinfo_proc), &count);\n\n   for (int i = 0; i < count; i++) {\n      const struct kinfo_proc* kproc = &kprocs[i];\n\n      /* Ignore main threads */\n      if (kproc->p_tid != -1) {\n         Process* containingProcess = ProcessTable_findProcess(&this->super, kproc->p_pid);\n         if (containingProcess) {\n            if (((OpenBSDProcess*)containingProcess)->addr == kproc->p_addr)\n               continue;\n\n            containingProcess->nlwp++;\n         }\n      }\n\n      bool preExisting = false;\n      Process* proc = ProcessTable_getProcess(&this->super, (kproc->p_tid == -1) ? kproc->p_pid : kproc->p_tid, &preExisting, OpenBSDProcess_new);\n      OpenBSDProcess* op = (OpenBSDProcess*) proc;\n\n      if (!preExisting) {\n         Process_setParent(proc, kproc->p_ppid);\n         Process_setThreadGroup(proc, kproc->p_pid);\n         proc->tpgid = kproc->p_tpgid;\n         proc->session = kproc->p_sid;\n         proc->pgrp = kproc->p__pgid;\n         proc->isKernelThread = proc->pgrp == 0;\n         proc->isUserlandThread = kproc->p_tid != -1;\n         proc->starttime_ctime = kproc->p_ustart_sec;\n         Process_fillStarttimeBuffer(proc);\n         ProcessTable_add(&this->super, proc);\n\n         OpenBSDProcessTable_updateProcessName(ohost->kd, kproc, proc);\n\n         if (settings->ss->flags & PROCESS_FLAG_CWD) {\n            OpenBSDProcessTable_updateCwd(kproc, proc);\n         }\n\n         proc->tty_nr = kproc->p_tdev;\n         const char* name = ((dev_t)kproc->p_tdev != NODEV) ? devname(kproc->p_tdev, S_IFCHR) : NULL;\n         if (!name || String_eq(name, \"??\")) {\n            free(proc->tty_name);\n            proc->tty_name = NULL;\n         } else {\n            free_and_xStrdup(&proc->tty_name, name);\n         }\n      } else {\n         if (settings->updateProcessNames) {\n            OpenBSDProcessTable_updateProcessName(ohost->kd, kproc, proc);\n         }\n      }\n\n      op->addr = kproc->p_addr;\n      proc->m_virt = kproc->p_vm_dsize * ohost->pageSizeKB;\n      proc->m_resident = kproc->p_vm_rssize * ohost->pageSizeKB;\n\n      proc->percent_mem = proc->m_resident / (float)host->totalMem * 100.0F;\n      proc->percent_cpu = CLAMP(getpcpu(ohost, kproc), 0.0F, host->activeCPUs * 100.0F);\n      Process_updateCPUFieldWidths(proc->percent_cpu);\n\n      proc->nice = kproc->p_nice - 20;\n      proc->time = 100 * (kproc->p_rtime_sec + ((kproc->p_rtime_usec + 500000) / 1000000));\n      proc->priority = kproc->p_priority - PZERO;\n      proc->processor = (int) kproc->p_cpuid;\n      proc->minflt = kproc->p_uru_minflt;\n      proc->majflt = kproc->p_uru_majflt;\n      proc->nlwp = 1;\n\n      if (proc->st_uid != kproc->p_uid) {\n         proc->st_uid = kproc->p_uid;\n         proc->user = UsersTable_getRef(host->usersTable, proc->st_uid);\n      }\n\n      /* Taken from: https://github.com/openbsd/src/blob/6a38af0976a6870911f4b2de19d8da28723a5778/sys/sys/proc.h#L420 */\n      switch (kproc->p_stat) {\n         case SIDL:    proc->state = IDLE; break;\n         case SRUN:    proc->state = RUNNABLE; break;\n         case SSLEEP:  proc->state = SLEEPING; break;\n         case SSTOP:   proc->state = STOPPED; break;\n         case SZOMB:   proc->state = ZOMBIE; break;\n         case SDEAD:   proc->state = DEFUNCT; break;\n         case SONPROC: proc->state = RUNNING; break;\n         default:      proc->state = UNKNOWN;\n      }\n\n      if (Process_isKernelThread(proc)) {\n         this->super.kernelThreads++;\n      } else if (Process_isUserlandThread(proc)) {\n         this->super.userlandThreads++;\n      }\n\n      this->super.totalTasks++;\n      if (proc->state == RUNNING) {\n         this->super.runningTasks++;\n      }\n\n      proc->super.show = ! ((hideKernelThreads && Process_isKernelThread(proc)) || (hideUserlandThreads && Process_isUserlandThread(proc)));\n      proc->super.updated = true;\n   }\n}\n\nvoid ProcessTable_goThroughEntries(ProcessTable* super) {\n   OpenBSDProcessTable* this = (OpenBSDProcessTable*) super;\n\n   OpenBSDProcessTable_scanProcs(this);\n}\n"
  },
  {
    "path": "openbsd/OpenBSDProcessTable.h",
    "content": "#ifndef HEADER_OpenBSDProcessTable\n#define HEADER_OpenBSDProcessTable\n/*\nhtop - OpenBSDProcessTable.h\n(C) 2014 Hisham H. Muhammad\n(C) 2015 Michael McConville\nReleased under the GNU GPLv2+, see the COPYING file\nin the source distribution for its full text.\n*/\n\n#include <stdbool.h>\n#include <sys/types.h>\n\n#include \"ProcessTable.h\"\n\n\ntypedef struct OpenBSDProcessTable_ {\n   ProcessTable super;\n} OpenBSDProcessTable;\n\n#endif\n"
  },
  {
    "path": "openbsd/Platform.c",
    "content": "/*\nhtop - openbsd/Platform.c\n(C) 2014 Hisham H. Muhammad\n(C) 2015 Michael McConville\nReleased under the GNU GPLv2+, see the COPYING file\nin the source distribution for its full text.\n*/\n\n#include \"config.h\" // IWYU pragma: keep\n\n#include \"openbsd/Platform.h\"\n\n#include <errno.h>\n#include <kvm.h>\n#include <limits.h>\n#include <math.h>\n#include <stdlib.h>\n#include <string.h>\n#include <time.h>\n#include <sys/signal.h>  // needs to be included before <sys/proc.h> for 'struct sigaltstack'\n#include <sys/proc.h>\n#include <sys/resource.h>\n#include <sys/sensors.h>\n#include <sys/sysctl.h>\n#include <sys/time.h>\n#include <sys/types.h>\n#include <uvm/uvmexp.h>\n\n#include \"CPUMeter.h\"\n#include \"ClockMeter.h\"\n#include \"DateTimeMeter.h\"\n#include \"FileDescriptorMeter.h\"\n#include \"HostnameMeter.h\"\n#include \"LoadAverageMeter.h\"\n#include \"Macros.h\"\n#include \"MemoryMeter.h\"\n#include \"MemorySwapMeter.h\"\n#include \"Meter.h\"\n#include \"Settings.h\"\n#include \"SignalsPanel.h\"\n#include \"SwapMeter.h\"\n#include \"SysArchMeter.h\"\n#include \"TasksMeter.h\"\n#include \"UptimeMeter.h\"\n#include \"XUtils.h\"\n#include \"openbsd/OpenBSDMachine.h\"\n#include \"openbsd/OpenBSDProcess.h\"\n\n\nconst ScreenDefaults Platform_defaultScreens[] = {\n   {\n      .name = \"Main\",\n      .columns = \"PID USER PRIORITY NICE M_VIRT M_RESIDENT STATE PERCENT_CPU PERCENT_MEM TIME Command\",\n      .sortKey = \"PERCENT_CPU\",\n   },\n};\n\nconst unsigned int Platform_numberOfDefaultScreens = ARRAYSIZE(Platform_defaultScreens);\n\n/*\n * See /usr/include/sys/signal.h\n */\nconst SignalItem Platform_signals[] = {\n   { .name = \" 0 Cancel\",    .number =  0 },\n   { .name = \" 1 SIGHUP\",    .number =  1 },\n   { .name = \" 2 SIGINT\",    .number =  2 },\n   { .name = \" 3 SIGQUIT\",   .number =  3 },\n   { .name = \" 4 SIGILL\",    .number =  4 },\n   { .name = \" 5 SIGTRAP\",   .number =  5 },\n   { .name = \" 6 SIGABRT\",   .number =  6 },\n   { .name = \" 6 SIGIOT\",    .number =  6 },\n   { .name = \" 7 SIGEMT\",    .number =  7 },\n   { .name = \" 8 SIGFPE\",    .number =  8 },\n   { .name = \" 9 SIGKILL\",   .number =  9 },\n   { .name = \"10 SIGBUS\",    .number = 10 },\n   { .name = \"11 SIGSEGV\",   .number = 11 },\n   { .name = \"12 SIGSYS\",    .number = 12 },\n   { .name = \"13 SIGPIPE\",   .number = 13 },\n   { .name = \"14 SIGALRM\",   .number = 14 },\n   { .name = \"15 SIGTERM\",   .number = 15 },\n   { .name = \"16 SIGURG\",    .number = 16 },\n   { .name = \"17 SIGSTOP\",   .number = 17 },\n   { .name = \"18 SIGTSTP\",   .number = 18 },\n   { .name = \"19 SIGCONT\",   .number = 19 },\n   { .name = \"20 SIGCHLD\",   .number = 20 },\n   { .name = \"21 SIGTTIN\",   .number = 21 },\n   { .name = \"22 SIGTTOU\",   .number = 22 },\n   { .name = \"23 SIGIO\",     .number = 23 },\n   { .name = \"24 SIGXCPU\",   .number = 24 },\n   { .name = \"25 SIGXFSZ\",   .number = 25 },\n   { .name = \"26 SIGVTALRM\", .number = 26 },\n   { .name = \"27 SIGPROF\",   .number = 27 },\n   { .name = \"28 SIGWINCH\",  .number = 28 },\n   { .name = \"29 SIGINFO\",   .number = 29 },\n   { .name = \"30 SIGUSR1\",   .number = 30 },\n   { .name = \"31 SIGUSR2\",   .number = 31 },\n   { .name = \"32 SIGTHR\",    .number = 32 },\n};\n\nconst unsigned int Platform_numberOfSignals = ARRAYSIZE(Platform_signals);\n\nenum {\n   MEMORY_CLASS_WIRED = 0,\n   MEMORY_CLASS_CACHE,\n   MEMORY_CLASS_ACTIVE,\n   MEMORY_CLASS_PAGING,\n   MEMORY_CLASS_INACTIVE,\n}; // N.B. the chart will display categories in this order\n\nconst MemoryClass Platform_memoryClasses[] = {\n   [MEMORY_CLASS_WIRED] = { .label = \"wired\", .countsAsUsed = true, .countsAsCache = false, .color = MEMORY_1 },\n   [MEMORY_CLASS_CACHE] = { .label = \"cache\", .countsAsUsed = true, .countsAsCache = true, .color = MEMORY_2 },\n   [MEMORY_CLASS_ACTIVE] = { .label = \"active\", .countsAsUsed = true, .countsAsCache = false, .color = MEMORY_3 },\n   [MEMORY_CLASS_PAGING] = { .label = \"paging\", .countsAsUsed = true, .countsAsCache = false, .color = MEMORY_4 },\n   [MEMORY_CLASS_INACTIVE] = { .label = \"inactive\", .countsAsUsed = false, .countsAsCache = true, .color = MEMORY_5 },\n};\n\nconst unsigned int Platform_numberOfMemoryClasses = ARRAYSIZE(Platform_memoryClasses);\n\nconst MeterClass* const Platform_meterTypes[] = {\n   &CPUMeter_class,\n   &ClockMeter_class,\n   &DateMeter_class,\n   &DateTimeMeter_class,\n   &LoadAverageMeter_class,\n   &LoadMeter_class,\n   &MemoryMeter_class,\n   &SwapMeter_class,\n   &MemorySwapMeter_class,\n   &TasksMeter_class,\n   &UptimeMeter_class,\n   &SecondsUptimeMeter_class,\n   &BatteryMeter_class,\n   &HostnameMeter_class,\n   &SysArchMeter_class,\n   &AllCPUsMeter_class,\n   &AllCPUs2Meter_class,\n   &AllCPUs4Meter_class,\n   &AllCPUs8Meter_class,\n   &LeftCPUsMeter_class,\n   &RightCPUsMeter_class,\n   &LeftCPUs2Meter_class,\n   &RightCPUs2Meter_class,\n   &LeftCPUs4Meter_class,\n   &RightCPUs4Meter_class,\n   &LeftCPUs8Meter_class,\n   &RightCPUs8Meter_class,\n   &FileDescriptorMeter_class,\n   &BlankMeter_class,\n   NULL\n};\n\nbool Platform_init(void) {\n   /* no platform-specific setup needed */\n   return true;\n}\n\nvoid Platform_done(void) {\n   /* no platform-specific cleanup needed */\n}\n\nvoid Platform_setBindings(Htop_Action* keys) {\n   /* no platform-specific key bindings */\n   (void) keys;\n}\n\nint Platform_getUptime(void) {\n   struct timeval bootTime, currTime;\n   const int mib[2] = { CTL_KERN, KERN_BOOTTIME };\n   size_t size = sizeof(bootTime);\n\n   int err = sysctl(mib, 2, &bootTime, &size, NULL, 0);\n   if (err) {\n      return -1;\n   }\n   gettimeofday(&currTime, NULL);\n\n   return (int) difftime(currTime.tv_sec, bootTime.tv_sec);\n}\n\nvoid Platform_getLoadAverage(double* one, double* five, double* fifteen) {\n   struct loadavg loadAverage;\n   const int mib[2] = { CTL_VM, VM_LOADAVG };\n   size_t size = sizeof(loadAverage);\n\n   int err = sysctl(mib, 2, &loadAverage, &size, NULL, 0);\n   if (err) {\n      *one = 0;\n      *five = 0;\n      *fifteen = 0;\n      return;\n   }\n   *one     = (double) loadAverage.ldavg[0] / loadAverage.fscale;\n   *five    = (double) loadAverage.ldavg[1] / loadAverage.fscale;\n   *fifteen = (double) loadAverage.ldavg[2] / loadAverage.fscale;\n}\n\npid_t Platform_getMaxPid(void) {\n   return 2 * THREAD_PID_OFFSET;\n}\n\ndouble Platform_setCPUValues(Meter* this, unsigned int cpu) {\n   const Machine* host = this->host;\n   const OpenBSDMachine* ohost = (const OpenBSDMachine*) host;\n   const CPUData* cpuData = &ohost->cpuData[cpu];\n   double total;\n   double totalPercent;\n   double* v = this->values;\n\n   if (!cpuData->online) {\n      this->curItems = 0;\n      return NAN;\n   }\n\n   total = cpuData->totalPeriod == 0 ? 1 : cpuData->totalPeriod;\n\n   v[CPU_METER_NICE] = cpuData->nicePeriod / total * 100.0;\n   v[CPU_METER_NORMAL] = cpuData->userPeriod / total * 100.0;\n   if (host->settings->detailedCPUTime) {\n      v[CPU_METER_KERNEL]  = cpuData->sysPeriod / total * 100.0;\n      v[CPU_METER_IRQ]     = cpuData->intrPeriod / total * 100.0;\n      v[CPU_METER_SOFTIRQ] = 0.0;\n      v[CPU_METER_STEAL]   = 0.0;\n      v[CPU_METER_GUEST]   = 0.0;\n      v[CPU_METER_IOWAIT]  = 0.0;\n      v[CPU_METER_FREQUENCY] = NAN;\n      this->curItems = 8;\n   } else {\n      v[CPU_METER_KERNEL] = cpuData->sysAllPeriod / total * 100.0;\n      v[CPU_METER_IRQ] = 0.0; // No steal nor guest on OpenBSD\n      this->curItems = 4;\n   }\n   totalPercent = v[CPU_METER_NICE] + v[CPU_METER_NORMAL] + v[CPU_METER_KERNEL] + v[CPU_METER_IRQ];\n\n   totalPercent = CLAMP(totalPercent, 0.0, 100.0);\n\n   v[CPU_METER_TEMPERATURE] = NAN;\n\n   v[CPU_METER_FREQUENCY] = (ohost->cpuSpeed != -1) ? ohost->cpuSpeed : NAN;\n\n   return totalPercent;\n}\n\nvoid Platform_setMemoryValues(Meter* this) {\n   const Machine* host = this->host;\n   const OpenBSDMachine* ohost = (const OpenBSDMachine*) host;\n   this->total = host->totalMem;\n   if (host->settings->showCachedMemory) {\n      this->values[MEMORY_CLASS_WIRED]    = ohost->wiredMem;\n      this->values[MEMORY_CLASS_CACHE]    = ohost->cacheMem;\n   } else { // if showCachedMemory is disabled, merge cache into the wired pages\n      this->values[MEMORY_CLASS_WIRED]    = ohost->wiredMem + ohost->cacheMem;\n      this->values[MEMORY_CLASS_CACHE]    = 0;\n   }\n   this->values[MEMORY_CLASS_ACTIVE]   = ohost->activeMem;\n   this->values[MEMORY_CLASS_PAGING]   = ohost->pagingMem;\n   this->values[MEMORY_CLASS_INACTIVE] = ohost->inactiveMem;\n}\n\nvoid Platform_setSwapValues(Meter* this) {\n   const Machine* host = this->host;\n   this->total = host->totalSwap;\n   this->values[SWAP_METER_USED] = host->usedSwap;\n}\n\nchar* Platform_getProcessEnv(pid_t pid) {\n   char errbuf[_POSIX2_LINE_MAX];\n   char* env;\n   char** ptr;\n   int count;\n   kvm_t* kt;\n   struct kinfo_proc* kproc;\n   size_t capacity = 4096, size = 0;\n\n   if ((kt = kvm_openfiles(NULL, NULL, NULL, KVM_NO_FILES, errbuf)) == NULL) {\n      return NULL;\n   }\n\n   if ((kproc = kvm_getprocs(kt, KERN_PROC_PID, pid,\n                             sizeof(struct kinfo_proc), &count)) == NULL) {\n      (void) kvm_close(kt);\n      return NULL;\n   }\n\n   if ((ptr = kvm_getenvv(kt, kproc, 0)) == NULL) {\n      (void) kvm_close(kt);\n      return NULL;\n   }\n\n   env = xMalloc(capacity);\n   for (char** p = ptr; *p; p++) {\n      size_t len = strlen(*p) + 1;\n\n      while (size + len > capacity) {\n         if (capacity > (SIZE_MAX / 2)) {\n            free(env);\n            env = NULL;\n            goto end;\n         }\n\n         capacity *= 2;\n         env = xRealloc(env, capacity);\n      }\n\n      strlcpy(env + size, *p, len);\n      size += len;\n   }\n\n   if (size < 2 || env[size - 1] || env[size - 2]) {\n      if (size + 2 < capacity)\n         env = xRealloc(env, capacity + 2);\n      env[size] = 0;\n      env[size + 1] = 0;\n   }\n\nend:\n   (void) kvm_close(kt);\n   return env;\n}\n\nFileLocks_ProcessData* Platform_getProcessLocks(pid_t pid) {\n   (void)pid;\n   return NULL;\n}\n\nvoid Platform_getFileDescriptors(double* used, double* max) {\n   static const int mib_kern_maxfile[] = { CTL_KERN, KERN_MAXFILES };\n   int sysctl_maxfile = 0;\n   size_t size_maxfile = sizeof(int);\n   if (sysctl(mib_kern_maxfile, ARRAYSIZE(mib_kern_maxfile), &sysctl_maxfile, &size_maxfile, NULL, 0) < 0) {\n      *max = NAN;\n   } else if (size_maxfile != sizeof(int) || sysctl_maxfile < 1) {\n      *max = NAN;\n   } else {\n      *max = sysctl_maxfile;\n   }\n\n   static const int mib_kern_nfiles[] = { CTL_KERN, KERN_NFILES };\n   int sysctl_nfiles = 0;\n   size_t size_nfiles = sizeof(int);\n   if (sysctl(mib_kern_nfiles, ARRAYSIZE(mib_kern_nfiles), &sysctl_nfiles, &size_nfiles, NULL, 0) < 0) {\n      *used = NAN;\n   } else if (size_nfiles != sizeof(int) || sysctl_nfiles < 0) {\n      *used = NAN;\n   } else {\n      *used = sysctl_nfiles;\n   }\n}\n\nbool Platform_getDiskIO(DiskIOData* data) {\n   // TODO\n   (void)data;\n   return false;\n}\n\nbool Platform_getNetworkIO(NetworkIOData* data) {\n   // TODO\n   (void)data;\n   return false;\n}\n\nstatic bool findDevice(const char* name, int* mib, struct sensordev* snsrdev, size_t* sdlen) {\n   for (int devn = 0;; devn++) {\n      mib[2] = devn;\n      if (sysctl(mib, 3, snsrdev, sdlen, NULL, 0) == -1) {\n         if (errno == ENXIO)\n            continue;\n         if (errno == ENOENT)\n            return false;\n      }\n      if (String_eq(name, snsrdev->xname)) {\n         return true;\n      }\n   }\n}\n\nvoid Platform_getBattery(double* percent, ACPresence* isOnAC) {\n   int mib[] = {CTL_HW, HW_SENSORS, 0, 0, 0};\n   struct sensor s;\n   size_t slen = sizeof(struct sensor);\n   struct sensordev snsrdev;\n   size_t sdlen = sizeof(struct sensordev);\n\n   bool found = findDevice(\"acpibat0\", mib, &snsrdev, &sdlen);\n\n   *percent = NAN;\n   if (found) {\n      /* See \"sys/dev/acpi/acpibat.c\" of OpenBSD source code for the indices\n         of the last field. */\n      mib[3] = SENSOR_WATTHOUR;\n      mib[4] = 0; /* \"last full capacity\" */\n      double last_full_capacity = 0;\n      if (sysctl(mib, 5, &s, &slen, NULL, 0) != -1)\n         last_full_capacity = s.value;\n      if (last_full_capacity > 0) {\n         mib[3] = SENSOR_WATTHOUR;\n         mib[4] = 3; /* \"remaining capacity\" */\n         if (sysctl(mib, 5, &s, &slen, NULL, 0) != -1) {\n            double charge = s.value;\n            *percent = 100 * (charge / last_full_capacity);\n            if (charge >= last_full_capacity) {\n               *percent = 100;\n            }\n         }\n      }\n   }\n\n   found = findDevice(\"acpiac0\", mib, &snsrdev, &sdlen);\n\n   *isOnAC = AC_ERROR;\n   if (found) {\n      /* See \"sys/dev/acpi/acpiac.c\" of OpenBSD source code.\n         There is only one \"sensor\" for this device. */\n      mib[3] = SENSOR_INDICATOR;\n      mib[4] = 0; /* \"power supply\" (status indicator) */\n      if (sysctl(mib, 5, &s, &slen, NULL, 0) != -1)\n         *isOnAC = s.value != 0 ? AC_PRESENT : AC_ABSENT;\n   }\n}\n"
  },
  {
    "path": "openbsd/Platform.h",
    "content": "#ifndef HEADER_Platform\n#define HEADER_Platform\n/*\nhtop - openbsd/Platform.h\n(C) 2014 Hisham H. Muhammad\n(C) 2015 Michael McConville\nReleased under the GNU GPLv2+, see the COPYING file\nin the source distribution for its full text.\n*/\n\n#include <stdbool.h>\n#include <sys/types.h>\n\n#include \"Action.h\"\n#include \"BatteryMeter.h\"\n#include \"DiskIOMeter.h\"\n#include \"Hashtable.h\"\n#include \"MemoryMeter.h\"\n#include \"NetworkIOMeter.h\"\n#include \"Process.h\"\n#include \"ProcessLocksScreen.h\"\n#include \"SignalsPanel.h\"\n#include \"CommandLine.h\"\n#include \"generic/gettime.h\"\n#include \"generic/hostname.h\"\n#include \"generic/uname.h\"\n\n\nextern const ScreenDefaults Platform_defaultScreens[];\n\nextern const unsigned int Platform_numberOfDefaultScreens;\n\n/* see /usr/include/sys/signal.h */\nextern const SignalItem Platform_signals[];\n\nextern const unsigned int Platform_numberOfSignals;\n\nextern const MemoryClass Platform_memoryClasses[];\n\nextern const unsigned int Platform_numberOfMemoryClasses;\n\nextern const MeterClass* const Platform_meterTypes[];\n\nbool Platform_init(void);\n\nvoid Platform_done(void);\n\nvoid Platform_setBindings(Htop_Action* keys);\n\nint Platform_getUptime(void);\n\nvoid Platform_getLoadAverage(double* one, double* five, double* fifteen);\n\npid_t Platform_getMaxPid(void);\n\ndouble Platform_setCPUValues(Meter* this, unsigned int cpu);\n\nvoid Platform_setMemoryValues(Meter* this);\n\nvoid Platform_setSwapValues(Meter* this);\n\nchar* Platform_getProcessEnv(pid_t pid);\n\nFileLocks_ProcessData* Platform_getProcessLocks(pid_t pid);\n\nvoid Platform_getFileDescriptors(double* used, double* max);\n\nbool Platform_getDiskIO(DiskIOData* data);\n\nbool Platform_getNetworkIO(NetworkIOData* data);\n\nvoid Platform_getBattery(double* percent, ACPresence* isOnAC);\n\nstatic inline void Platform_getHostname(char* buffer, size_t size) {\n   Generic_hostname(buffer, size);\n}\n\nstatic inline const char* Platform_getRelease(void) {\n   return Generic_uname();\n}\n\nstatic inline const char* Platform_getFailedState(void) {\n   return NULL;\n}\n\n#define PLATFORM_LONG_OPTIONS\n\nstatic inline void Platform_longOptionsUsage(ATTR_UNUSED const char* name) { }\n\nstatic inline CommandLineStatus Platform_getLongOption(ATTR_UNUSED int opt, ATTR_UNUSED int argc, ATTR_UNUSED char** argv) {\n   return STATUS_ERROR_EXIT;\n}\n\nstatic inline void Platform_gettime_realtime(struct timeval* tv, uint64_t* msec) {\n   Generic_gettime_realtime(tv, msec);\n}\n\nstatic inline void Platform_gettime_monotonic(uint64_t* msec) {\n   Generic_gettime_monotonic(msec);\n}\n\nstatic inline Hashtable* Platform_dynamicMeters(void) {\n   return NULL;\n}\n\nstatic inline void Platform_dynamicMetersDone(ATTR_UNUSED Hashtable* table) { }\n\nstatic inline void Platform_dynamicMeterInit(ATTR_UNUSED Meter* meter) { }\n\nstatic inline void Platform_dynamicMeterUpdateValues(ATTR_UNUSED Meter* meter) { }\n\nstatic inline void Platform_dynamicMeterDisplay(ATTR_UNUSED const Meter* meter, ATTR_UNUSED RichString* out) { }\n\nstatic inline Hashtable* Platform_dynamicColumns(void) {\n   return NULL;\n}\n\nstatic inline void Platform_dynamicColumnsDone(ATTR_UNUSED Hashtable* table) { }\n\nstatic inline const char* Platform_dynamicColumnName(ATTR_UNUSED unsigned int key) {\n   return NULL;\n}\n\nstatic inline bool Platform_dynamicColumnWriteField(ATTR_UNUSED const Process* proc, ATTR_UNUSED RichString* str, ATTR_UNUSED unsigned int key) {\n   return false;\n}\n\nstatic inline Hashtable* Platform_dynamicScreens(void) {\n   return NULL;\n}\n\nstatic inline void Platform_defaultDynamicScreens(ATTR_UNUSED Settings* settings) { }\n\nstatic inline void Platform_addDynamicScreen(ATTR_UNUSED ScreenSettings* ss) { }\n\nstatic inline void Platform_addDynamicScreenAvailableColumns(ATTR_UNUSED Panel* availableColumns, ATTR_UNUSED const char* screen) { }\n\nstatic inline void Platform_dynamicScreensDone(ATTR_UNUSED Hashtable* screens) { }\n\n#endif\n"
  },
  {
    "path": "openbsd/ProcessField.h",
    "content": "#ifndef HEADER_OpenBSDProcessField\n#define HEADER_OpenBSDProcessField\n/*\nhtop - openbsd/ProcessField.h\n(C) 2020 htop dev team\nReleased under the GNU GPLv2+, see the COPYING file\nin the source distribution for its full text.\n*/\n\n\n#define PLATFORM_PROCESS_FIELDS  \\\n   // End of list\n\n\n#endif /* HEADER_OpenBSDProcessField */\n"
  },
  {
    "path": "pcp/InDomTable.c",
    "content": "/*\nhtop - InDomTable.c\n(C) 2023 htop dev team\n(C) 2022-2023 Sohaib Mohammed\nReleased under the GNU GPLv2+, see the COPYING file\nin the source distribution for its full text.\n*/\n\n#include \"config.h\" // IWYU pragma: keep\n\n#include \"pcp/InDomTable.h\"\n\n#include <assert.h>\n#include <stdlib.h>\n#include <string.h>\n\n#include \"CRT.h\"\n#include \"DynamicColumn.h\"\n#include \"Hashtable.h\"\n#include \"Macros.h\"\n#include \"Platform.h\"\n#include \"Table.h\"\n#include \"Vector.h\"\n#include \"XUtils.h\"\n\n#include \"pcp/Instance.h\"\n#include \"pcp/Metric.h\"\n#include \"pcp/PCPDynamicColumn.h\"\n\n\nInDomTable* InDomTable_new(Machine* host, pmInDom indom, int metricKey) {\n   InDomTable* this = xCalloc(1, sizeof(InDomTable));\n   Object_setClass(this, Class(InDomTable));\n   this->metricKey = metricKey;\n   this->id = indom;\n\n   Table* super = &this->super;\n   Table_init(super, Class(Row), host);\n\n   return this;\n}\n\nvoid InDomTable_done(InDomTable* this) {\n   Table_done(&this->super);\n}\n\nstatic void InDomTable_delete(Object* cast) {\n   InDomTable* this = (InDomTable*) cast;\n   InDomTable_done(this);\n   free(this);\n}\n\nstatic Instance* InDomTable_getInstance(InDomTable* this, int id, bool* preExisting) {\n   const Table* super = &this->super;\n   Instance* inst = (Instance*) Hashtable_get(super->table, id);\n   *preExisting = inst != NULL;\n   if (inst) {\n      assert(Vector_indexOf(super->rows, inst, Row_idEqualCompare) != -1);\n      assert(Instance_getId(inst) == id);\n   } else {\n      inst = Instance_new(super->host, this);\n      assert(inst->name == NULL);\n      Instance_setId(inst, id);\n   }\n   return inst;\n}\n\nstatic void InDomTable_goThroughEntries(InDomTable* this) {\n   Table* super = &this->super;\n\n   /* for every instance ... */\n   int instid = -1, offset = -1;\n   while (Metric_iterate(this->metricKey, &instid, &offset, sizeof(Instance))) {\n      bool preExisting;\n      Instance* inst = InDomTable_getInstance(this, instid, &preExisting);\n      inst->offset = offset >= 0 ? offset : 0;\n\n      Row* row = (Row*) inst;\n      if (!preExisting)\n         Table_add(super, row);\n      row->updated = true;\n      row->show = true;\n   }\n}\n\nstatic void InDomTable_iterateEntries(Table* super) {\n   InDomTable* this = (InDomTable*) super;\n   InDomTable_goThroughEntries(this);\n}\n\nconst TableClass InDomTable_class = {\n   .super = {\n      .extends = Class(Table),\n      .delete = InDomTable_delete,\n   },\n   .prepare = Table_prepareEntries,\n   .iterate = InDomTable_iterateEntries,\n   .cleanup = Table_cleanupEntries,\n};\n"
  },
  {
    "path": "pcp/InDomTable.h",
    "content": "#ifndef HEADER_InDomTable\n#define HEADER_InDomTable\n/*\nhtop - InDomTable.h\n(C) 2023 htop dev team\n(C) 2022-2023 Sohaib Mohammed\nReleased under the GNU GPLv2+, see the COPYING file\nin the source distribution for its full text.\n*/\n\n#include <stdbool.h>\n#include <sys/types.h>\n\n#include \"Platform.h\"\n#include \"Table.h\"\n\n\ntypedef struct InDomTable_ {\n   Table super;\n   pmInDom id;  /* shared by metrics in the table */\n   unsigned int metricKey;  /* representative metric using this indom */\n} InDomTable;\n\nextern const TableClass InDomTable_class;\n\nInDomTable* InDomTable_new(Machine* host, pmInDom indom, int metricKey);\n\nvoid InDomTable_done(InDomTable* this);\n\nRowField RowField_keyAt(const Settings* settings, int at);\n\nvoid InDomTable_scan(Table* super);\n\n#endif\n"
  },
  {
    "path": "pcp/Instance.c",
    "content": "/*\nhtop - Instance.c\n(C) 2022-2023 Sohaib Mohammed\n(C) 2022-2023 htop dev team\nReleased under the GNU GPLv2+, see the COPYING file\nin the source distribution for its full text.\n*/\n\n#include \"config.h\" // IWYU pragma: keep\n\n#include \"pcp/Instance.h\"\n\n#include <stdbool.h>\n#include <stdlib.h>\n\n#include \"CRT.h\"\n#include \"DynamicColumn.h\"\n#include \"DynamicScreen.h\"\n#include \"Hashtable.h\"\n#include \"Machine.h\"\n#include \"Macros.h\"\n#include \"Metric.h\"\n#include \"Platform.h\"\n#include \"PCPDynamicColumn.h\"\n#include \"PCPDynamicScreen.h\"\n#include \"Row.h\"\n#include \"RichString.h\"\n#include \"XUtils.h\"\n\n#include \"pcp/InDomTable.h\"\n#include \"pcp/Metric.h\"\n\n\nInstance* Instance_new(const Machine* host, const InDomTable* indom) {\n   Instance* this = xCalloc(1, sizeof(Instance));\n   Object_setClass(this, Class(Instance));\n\n   Row* super = &this->super;\n   Row_init(super, host);\n\n   this->indom = indom;\n\n   return this;\n}\n\nvoid Instance_done(Instance* this) {\n   if (this->name)\n      free(this->name);\n   Row_done(&this->super);\n}\n\nstatic void Instance_delete(Object* cast) {\n   Instance* this = (Instance*) cast;\n   Instance_done(this);\n   free(this);\n}\n\nstatic void Instance_writeField(const Row* super, RichString* str, RowField field) {\n   const Instance* this = (const Instance*) super;\n   int instid = Instance_getId(this);\n\n   const Settings* settings = super->host->settings;\n   DynamicColumn* column = Hashtable_get(settings->dynamicColumns, field);\n   PCPDynamicColumn* cp = (PCPDynamicColumn*) column;\n   if (!cp)\n      return;\n\n   pmAtomValue atom;\n   pmAtomValue* ap = &atom;\n   Metric metric = Metric_fromId(cp->id);\n   const pmDesc* descp = Metric_desc(metric);\n   if (!Metric_instance(metric, instid, this->offset, ap, descp->type))\n      ap = NULL;\n\n   PCPDynamicColumn_writeAtomValue(cp, str, settings, metric, instid, descp, ap);\n\n   if (ap && descp->type == PM_TYPE_STRING)\n      free(ap->cp);\n}\n\nstatic const char* Instance_externalName(Row* super) {\n   Instance* this = (Instance*) super;\n\n   if (!this->name)\n      /* ignore any failure here - its safe and we try again next time */\n      (void)pmNameInDom(InDom_getId(this), Instance_getId(this), &this->name);\n   return this->name;\n}\n\nstatic int Instance_compareByKey(const Row* v1, const Row* v2, int key) {\n   const Instance* i1 = (const Instance*)v1;\n   const Instance* i2 = (const Instance*)v2;\n\n   if (key < 0)\n      return 0;\n\n   Hashtable* dc = Platform_dynamicColumns();\n   const PCPDynamicColumn* column = Hashtable_get(dc, key);\n   if (!column)\n      return -1;\n\n   Metric metric = Metric_fromId(column->id);\n   unsigned int type = Metric_type(metric);\n\n   pmAtomValue atom1 = {0}, atom2 = {0};\n   if (!Metric_instance(metric, i1->offset, i1->offset, &atom1, type) ||\n       !Metric_instance(metric, i2->offset, i2->offset, &atom2, type)) {\n      if (type == PM_TYPE_STRING) {\n         free(atom1.cp);\n         free(atom2.cp);\n      }\n      return -1;\n   }\n\n   switch (type) {\n      case PM_TYPE_STRING: {\n         int cmp = SPACESHIP_NULLSTR(atom2.cp, atom1.cp);\n         free(atom2.cp);\n         free(atom1.cp);\n         return cmp;\n      }\n      case PM_TYPE_32:\n         return SPACESHIP_NUMBER(atom2.l, atom1.l);\n      case PM_TYPE_U32:\n         return SPACESHIP_NUMBER(atom2.ul, atom1.ul);\n      case PM_TYPE_64:\n         return SPACESHIP_NUMBER(atom2.ll, atom1.ll);\n      case PM_TYPE_U64:\n         return SPACESHIP_NUMBER(atom2.ull, atom1.ull);\n      case PM_TYPE_FLOAT:\n         return SPACESHIP_NUMBER(atom2.f, atom1.f);\n      case PM_TYPE_DOUBLE:\n         return SPACESHIP_NUMBER(atom2.d, atom1.d);\n      default:\n         break;\n   }\n\n   return 0;\n}\n\nstatic int Instance_compare(const void* v1, const void* v2) {\n   const Instance* i1 = (const Instance*)v1;\n   const Instance* i2 = (const Instance*)v2;\n   const ScreenSettings* ss = i1->super.host->settings->ss;\n   RowField key = ScreenSettings_getActiveSortKey(ss);\n   int result = Instance_compareByKey(v1, v2, key);\n\n   // Implement tie-breaker (needed to make tree mode more stable)\n   if (!result)\n      return SPACESHIP_NUMBER(Instance_getId(i1), Instance_getId(i2));\n\n   return (ScreenSettings_getActiveDirection(ss) == 1) ? result : -result;\n}\n\nconst RowClass Instance_class = {\n   .super = {\n      .extends = Class(Row),\n      .display = Row_display,\n      .delete = Instance_delete,\n      .compare = Instance_compare,\n   },\n   .sortKeyString = Instance_externalName,\n   .writeField = Instance_writeField,\n};\n"
  },
  {
    "path": "pcp/Instance.h",
    "content": "#ifndef HEADER_Instance\n#define HEADER_Instance\n/*\nhtop - Instance.h\n(C) 2022-2023 htop dev team\n(C) 2022-2023 Sohaib Mohammed\nReleased under the GNU GPLv2+, see the COPYING file\nin the source distribution for its full text.\n*/\n\n#include \"Hashtable.h\"\n#include \"Object.h\"\n#include \"Platform.h\"\n#include \"Row.h\"\n\n\ntypedef struct Instance_ {\n   Row super;\n\n   char* name; /* external instance name */\n   const struct InDomTable_* indom;  /* instance domain */\n\n   /* default result offset to use for searching metrics with instances */\n   unsigned int offset;\n} Instance;\n\n#define InDom_getId(i_)          ((i_)->indom->id)\n#define Instance_getId(i_)       ((i_)->super.id)\n#define Instance_setId(i_, id_)  ((i_)->super.id = (id_))\n\nextern const RowClass Instance_class;\n\nInstance* Instance_new(const Machine* host, const struct InDomTable_* indom);\n\nvoid Instance_done(Instance* this);\n\n#endif\n"
  },
  {
    "path": "pcp/Metric.c",
    "content": "/*\nhtop - Metric.c\n(C) 2020-2021 htop dev team\n(C) 2020-2021 Red Hat, Inc.\nReleased under the GNU GPLv2+, see the COPYING file\nin the source distribution for its full text.\n*/\n\n#include \"config.h\" // IWYU pragma: keep\n\n#include \"pcp/Metric.h\"\n\n#include <ctype.h>\n#include <stddef.h>\n#include <stdio.h>\n#include <string.h>\n\n#include \"XUtils.h\"\n\n#include \"pcp/Platform.h\"\n\n\nextern Platform* pcp;\n\nconst pmDesc* Metric_desc(Metric metric) {\n   return &pcp->descs[metric];\n}\n\nint Metric_type(Metric metric) {\n   return pcp->descs[metric].type;\n}\n\npmAtomValue* Metric_values(Metric metric, pmAtomValue* atom, int count, int type) {\n   if (pcp->result == NULL)\n      return NULL;\n\n   pmValueSet* vset = pcp->result->vset[metric];\n   if (!vset || vset->numval <= 0)\n      return NULL;\n\n   /* extract requested number of values as requested type */\n   const pmDesc* desc = &pcp->descs[metric];\n   for (int i = 0; i < vset->numval; i++) {\n      if (i == count)\n         break;\n      const pmValue* value = &vset->vlist[i];\n      int sts = pmExtractValue(vset->valfmt, value, desc->type, &atom[i], type);\n      if (sts < 0) {\n         if (pmDebugOptions.appl0)\n            fprintf(stderr, \"Error: cannot extract metric value: %s\\n\",\n                            pmErrStr(sts));\n         memset(&atom[i], 0, sizeof(pmAtomValue));\n      }\n   }\n   return atom;\n}\n\nint Metric_instanceCount(Metric metric) {\n   pmValueSet* vset = pcp->result->vset[metric];\n   if (vset)\n      return vset->numval;\n   return 0;\n}\n\nint Metric_instanceOffset(Metric metric, int inst) {\n   pmValueSet* vset = pcp->result->vset[metric];\n   if (!vset || vset->numval <= 0)\n      return 0;\n\n   /* search for optimal offset for subsequent inst lookups to begin */\n   for (int i = 0; i < vset->numval; i++) {\n      if (inst == vset->vlist[i].inst)\n         return i;\n   }\n   return 0;\n}\n\nstatic pmAtomValue* Metric_extract(Metric metric, int inst, int offset, pmValueSet* vset, pmAtomValue* atom, const pmDesc **desc, int type) {\n\n   /* extract value (using requested type) of given metric instance */\n   const pmDesc* outdesc = &pcp->descs[metric];\n   const pmValue* value = &vset->vlist[offset];\n   int sts = pmExtractValue(vset->valfmt, value, outdesc->type, atom, type);\n   if (sts < 0) {\n      if (pmDebugOptions.appl0)\n         fprintf(stderr, \"Error: cannot extract %s instance %d value: %s\\n\",\n                         pcp->names[metric], inst, pmErrStr(sts));\n      memset(atom, 0, sizeof(pmAtomValue));\n   }\n   *desc = outdesc;\n   return atom;\n}\n\nstatic const pmDesc* Metric_instanceDesc(Metric metric, int inst, int offset, pmAtomValue* atom, int type) {\n   pmValueSet* vset = pcp->result->vset[metric];\n   if (!vset || vset->numval <= 0)\n      return NULL;\n\n   /* fast-path using heuristic offset based on expected location */\n   const pmDesc* desc = NULL;\n   if (offset >= 0 && offset < vset->numval && inst == vset->vlist[offset].inst\n       && Metric_extract(metric, inst, offset, vset, atom, &desc, type))\n      return desc;\n\n   /* slow-path using a linear search for the requested instance */\n   for (int i = 0; i < vset->numval; i++) {\n      if (inst == vset->vlist[i].inst\n          && Metric_extract(metric, inst, i, vset, atom, &desc, type))\n         break;\n   }\n   return desc;\n}\n\npmAtomValue* Metric_instance(Metric metric, int inst, int offset, pmAtomValue* atom, int type) {\n   if (Metric_instanceDesc(metric, inst, offset, atom, type))\n      return atom;\n   return NULL;\n}\n\nstatic inline pmAtomValue* kibibytes(pmAtomValue* atom, int scale) {\n   /* perform integer math, raising to the power +/-N */\n   int i, power = scale - PM_SPACE_KBYTE;\n   if (power > 0) {\n      for (i = 0; i < power; i++)\n         atom->ull *= ONE_K;\n   } else if (power < 0) {\n      for (i = 0; i > power; i--)\n         atom->ull /= ONE_K;\n   }\n   return atom;\n}\n\npmAtomValue* Metric_instance_kibibytes(Metric metric, int inst, int offset, pmAtomValue* atom) {\n   const pmDesc *desc = Metric_instanceDesc(metric, inst, offset, atom, PM_TYPE_U64);\n   if (desc)\n      return kibibytes(atom, desc->units.scaleSpace);\n   return NULL;\n}\n\nstatic inline pmAtomValue* milliseconds(pmAtomValue* atom, int scale) {\n   switch (scale) {\n   case PM_TIME_NSEC:\n      atom->ull /= 1000000ULL;\n      break;\n   case PM_TIME_USEC:\n      atom->ull /= 1000ULL;\n      break;\n   case PM_TIME_MSEC:\n      break;\n   case PM_TIME_SEC:\n      atom->ull *= 1000ULL;\n      break;\n   case PM_TIME_MIN:\n      atom->ull *= 1000ULL * 60ULL;\n      break;\n   case PM_TIME_HOUR:\n      atom->ull *= 1000ULL * 60ULL * 60ULL;\n      break;\n   }\n   return atom;\n}\n\npmAtomValue* Metric_instance_milliseconds(Metric metric, int inst, int offset, pmAtomValue* atom) {\n   const pmDesc *desc = Metric_instanceDesc(metric, inst, offset, atom, PM_TYPE_U64);\n   if (desc)\n      return milliseconds(atom, desc->units.scaleTime);\n   return NULL;\n}\n\n/*\n * Iterate over a set of instances (incl PM_IN_NULL)\n * returning the next instance identifier and offset.\n *\n * Start it off by passing offset -1 into the routine.\n */\nbool Metric_iterate(Metric metric, int* instp, int* offsetp, size_t entrylen) {\n   if (!pcp->result)\n      return false;\n\n   pmValueSet* vset = pcp->result->vset[metric];\n   if (!vset || vset->numval <= 0 || (size_t)vset->numval > LONG_MAX / entrylen)\n      return false;\n\n   int offset = *offsetp;\n   offset = (offset < 0) ? 0 : offset + 1;\n   if (offset > vset->numval - 1)\n      return false;\n\n   *offsetp = offset;\n   *instp = vset->vlist[offset].inst;\n   return true;\n}\n\n/* Switch on/off a metric for value fetching (sampling) */\nvoid Metric_enable(Metric metric, bool enable) {\n   pcp->fetch[metric] = enable ? pcp->pmids[metric] : PM_ID_NULL;\n}\n\nbool Metric_enabled(Metric metric) {\n   return pcp->fetch[metric] != PM_ID_NULL;\n}\n\nvoid Metric_enableThreads(void) {\n   pmValueSet* vset = xCalloc(1, sizeof(pmValueSet));\n   vset->vlist[0].inst = PM_IN_NULL;\n   vset->vlist[0].value.lval = 1;\n   vset->valfmt = PM_VAL_INSITU;\n   vset->numval = 1;\n   vset->pmid = pcp->pmids[PCP_CONTROL_THREADS];\n\n   pmResult* result = xCalloc(1, sizeof(pmResult));\n   result->vset[0] = vset;\n   result->numpmid = 1;\n\n   int sts = pmStore(result);\n   if (sts < 0 && pmDebugOptions.appl0)\n      fprintf(stderr, \"Error: cannot enable threads: %s\\n\", pmErrStr(sts));\n\n   pmFreeResult(result);\n}\n\nbool Metric_fetch(struct timeval* timestamp) {\n   if (pcp->result) {\n      pmFreeResult(pcp->result);\n      pcp->result = NULL;\n   }\n   if (pcp->reconnect) {\n      if (pmReconnectContext(pcp->context) < 0)\n         return false;\n      pcp->reconnect = false;\n   }\n   int sts, count = 0;\n   int total = (int) pcp->totalMetrics;\n   do {\n      sts = pmFetch(total, pcp->fetch, &pcp->result);\n   } while (sts == PM_ERR_IPC && ++count < 3);\n   if (sts < 0) {\n      if (pmDebugOptions.appl0)\n         fprintf(stderr, \"Error: cannot fetch metric values: %s\\n\",\n                 pmErrStr(sts));\n      pcp->reconnect = true;\n      return false;\n   }\n   if (timestamp) {\n#if PMAPI_VERSION >= 3\n      pmtimespecTotimeval(&pcp->result->timestamp, timestamp);\n#else\n      *timestamp = pcp->result->timestamp;\n#endif\n   }\n   return true;\n}\n\nvoid Metric_externalName(Metric metric, int inst, char** externalName) {\n   const pmDesc* desc = &pcp->descs[metric];\n   /* ignore a failure here - its safe to do so */\n   (void)pmNameInDom(desc->indom, inst, externalName);\n}\n\nint Metric_lookupText(const char* metric, char** desc) {\n   pmID pmid;\n   int sts;\n\n   sts = pmLookupName(1, &metric, &pmid);\n   if (sts < 0)\n      return sts;\n\n   if (pmLookupText(pmid, PM_TEXT_ONELINE, desc) >= 0)\n      (*desc)[0] = (char) toupper((*desc)[0]); /* UI consistency */\n   return 0;\n}\n"
  },
  {
    "path": "pcp/Metric.h",
    "content": "#ifndef HEADER_Metric\n#define HEADER_Metric\n/*\nhtop - Metric.h\n(C) 2020-2021 htop dev team\n(C) 2020-2021 Red Hat, Inc.\nReleased under the GNU GPLv2+, see the COPYING file\nin the source distribution for its full text.\n*/\n\n#include <ctype.h>\n#include <stdbool.h>\n#include <pcp/pmapi.h>\n#include <sys/time.h>\n\n/* use htop config.h values for these macros, not pcp values */\n#undef PACKAGE_URL\n#undef PACKAGE_NAME\n#undef PACKAGE_STRING\n#undef PACKAGE_TARNAME\n#undef PACKAGE_VERSION\n#undef PACKAGE_BUGREPORT\n\n\ntypedef enum Metric_ {\n   PCP_CONTROL_THREADS,         /* proc.control.perclient.threads */\n\n   PCP_HINV_NCPU,               /* hinv.ncpu */\n   PCP_HINV_NDISK,              /* hinv.ndisk */\n   PCP_HINV_CPUCLOCK,           /* hinv.cpu.clock */\n   PCP_UNAME_SYSNAME,           /* kernel.uname.sysname */\n   PCP_UNAME_RELEASE,           /* kernel.uname.release */\n   PCP_UNAME_MACHINE,           /* kernel.uname.machine */\n   PCP_UNAME_DISTRO,            /* kernel.uname.distro */\n   PCP_LOAD_AVERAGE,            /* kernel.all.load */\n   PCP_PID_MAX,                 /* kernel.all.pid_max */\n   PCP_UPTIME,                  /* kernel.all.uptime */\n   PCP_BOOTTIME,                /* kernel.all.boottime */\n   PCP_CPU_USER,                /* kernel.all.cpu.user */\n   PCP_CPU_NICE,                /* kernel.all.cpu.nice */\n   PCP_CPU_SYSTEM,              /* kernel.all.cpu.sys */\n   PCP_CPU_IDLE,                /* kernel.all.cpu.idle */\n   PCP_CPU_IOWAIT,              /* kernel.all.cpu.wait.total */\n   PCP_CPU_IRQ,                 /* kernel.all.cpu.intr */\n   PCP_CPU_SOFTIRQ,             /* kernel.all.cpu.irq.soft */\n   PCP_CPU_STEAL,               /* kernel.all.cpu.steal */\n   PCP_CPU_GUEST,               /* kernel.all.cpu.guest */\n   PCP_CPU_GUESTNICE,           /* kernel.all.cpu.guest_nice */\n   PCP_PERCPU_USER,             /* kernel.percpu.cpu.user */\n   PCP_PERCPU_NICE,             /* kernel.percpu.cpu.nice */\n   PCP_PERCPU_SYSTEM,           /* kernel.percpu.cpu.sys */\n   PCP_PERCPU_IDLE,             /* kernel.percpu.cpu.idle */\n   PCP_PERCPU_IOWAIT,           /* kernel.percpu.cpu.wait.total */\n   PCP_PERCPU_IRQ,              /* kernel.percpu.cpu.intr */\n   PCP_PERCPU_SOFTIRQ,          /* kernel.percpu.cpu.irq.soft */\n   PCP_PERCPU_STEAL,            /* kernel.percpu.cpu.steal */\n   PCP_PERCPU_GUEST,            /* kernel.percpu.cpu.guest */\n   PCP_PERCPU_GUESTNICE,        /* kernel.percpu.cpu.guest_nice */\n   PCP_MEM_TOTAL,               /* mem.physmem */\n   PCP_MEM_FREE,                /* mem.util.free */\n   PCP_MEM_ACTIVE,              /* mem.util.active */\n   PCP_MEM_AVAILABLE,           /* mem.util.available */\n   PCP_MEM_BUFFERS,             /* mem.util.bufmem */\n   PCP_MEM_CACHED,              /* mem.util.cached */\n   PCP_MEM_COMPRESSED,          /* mem.util.compressed */\n   PCP_MEM_EXTERNAL,            /* mem.util.external */\n   PCP_MEM_INACTIVE,            /* mem.util.inactive */\n   PCP_MEM_SHARED,              /* mem.util.shared */\n   PCP_MEM_PURGEABLE,           /* mem.util.purgeable */\n   PCP_MEM_SPECULATIVE,         /* mem.util.speculative */\n   PCP_MEM_SRECLAIM,            /* mem.util.slabReclaimable */\n   PCP_MEM_WIRED,               /* mem.util.wired */\n   PCP_MEM_SWAPCACHED,          /* mem.util.swapCached */\n   PCP_MEM_SWAPTOTAL,           /* mem.util.swapTotal */\n   PCP_MEM_SWAPFREE,            /* mem.util.swapFree */\n   PCP_DISK_READB,              /* disk.all.read_bytes */\n   PCP_DISK_WRITEB,             /* disk.all.write_bytes */\n   PCP_DISK_ACTIVE,             /* disk.all.avactive */\n   PCP_NET_RECVB,               /* network.all.in.bytes */\n   PCP_NET_SENDB,               /* network.all.out.bytes */\n   PCP_NET_RECVP,               /* network.all.in.packets */\n   PCP_NET_SENDP,               /* network.all.out.packets */\n   PCP_PSI_CPUSOME,             /* kernel.all.pressure.cpu.some.avg */\n   PCP_PSI_IOSOME,              /* kernel.all.pressure.io.some.avg */\n   PCP_PSI_IOFULL,              /* kernel.all.pressure.io.full.avg */\n   PCP_PSI_IRQFULL,             /* kernel.all.pressure.irq.full.avg */\n   PCP_PSI_MEMSOME,             /* kernel.all.pressure.memory.some.avg */\n   PCP_PSI_MEMFULL,             /* kernel.all.pressure.memory.full.avg */\n   PCP_ZFS_ARC_ANON_SIZE,       /* zfs.arc.anon_size */\n   PCP_ZFS_ARC_BONUS_SIZE,      /* zfs.arc.bonus_size */\n   PCP_ZFS_ARC_COMPRESSED_SIZE, /* zfs.arc.compressed_size */\n   PCP_ZFS_ARC_UNCOMPRESSED_SIZE, /* zfs.arc.uncompressed_size */\n   PCP_ZFS_ARC_C_MIN,           /* zfs.arc.c_min */\n   PCP_ZFS_ARC_C_MAX,           /* zfs.arc.c_max */\n   PCP_ZFS_ARC_DBUF_SIZE,       /* zfs.arc.dbuf_size */\n   PCP_ZFS_ARC_DNODE_SIZE,      /* zfs.arc.dnode_size */\n   PCP_ZFS_ARC_HDR_SIZE,        /* zfs.arc.hdr_size */\n   PCP_ZFS_ARC_MFU_SIZE,        /* zfs.arc.mfu_size */\n   PCP_ZFS_ARC_MRU_SIZE,        /* zfs.arc.mru_size */\n   PCP_ZFS_ARC_SIZE,            /* zfs.arc.size */\n   PCP_ZRAM_CAPACITY,           /* zram.capacity */\n   PCP_ZRAM_ORIGINAL,           /* zram.mm_stat.data_size.original */\n   PCP_ZRAM_COMPRESSED,         /* zram.mm_stat.data_size.compressed */\n   PCP_MEM_ZSWAP,               /* mem.util.zswap */\n   PCP_MEM_ZSWAPPED,            /* mem.util.zswapped */\n   PCP_VFS_FILES_COUNT,         /* vfs.files.count */\n   PCP_VFS_FILES_MAX,           /* vfs.files.max */\n\n   PCP_PROC_PID,                /* proc.psinfo.pid */\n   PCP_PROC_PPID,               /* proc.psinfo.ppid */\n   PCP_PROC_TGID,               /* proc.psinfo.tgid */\n   PCP_PROC_PGRP,               /* proc.psinfo.pgrp */\n   PCP_PROC_SESSION,            /* proc.psinfo.session */\n   PCP_PROC_STATE,              /* proc.psinfo.sname */\n   PCP_PROC_TTY,                /* proc.psinfo.tty */\n   PCP_PROC_TTYPGRP,            /* proc.psinfo.tty_pgrp */\n   PCP_PROC_MINFLT,             /* proc.psinfo.minflt */\n   PCP_PROC_MAJFLT,             /* proc.psinfo.maj_flt */\n   PCP_PROC_CMINFLT,            /* proc.psinfo.cmin_flt */\n   PCP_PROC_CMAJFLT,            /* proc.psinfo.cmaj_flt */\n   PCP_PROC_UTIME,              /* proc.psinfo.utime */\n   PCP_PROC_STIME,              /* proc.psinfo.stime */\n   PCP_PROC_CUTIME,             /* proc.psinfo.cutime */\n   PCP_PROC_CSTIME,             /* proc.psinfo.cstime */\n   PCP_PROC_PRIORITY,           /* proc.psinfo.priority */\n   PCP_PROC_NICE,               /* proc.psinfo.nice */\n   PCP_PROC_THREADS,            /* proc.psinfo.threads */\n   PCP_PROC_STARTTIME,          /* proc.psinfo.start_time */\n   PCP_PROC_PROCESSOR,          /* proc.psinfo.processor */\n   PCP_PROC_CMD,                /* proc.psinfo.cmd */\n   PCP_PROC_PSARGS,             /* proc.psinfo.psargs */\n   PCP_PROC_CGROUPS,            /* proc.psinfo.cgroups */\n   PCP_PROC_OOMSCORE,           /* proc.psinfo.oom_score */\n   PCP_PROC_VCTXSW,             /* proc.psinfo.vctxsw */\n   PCP_PROC_NVCTXSW,            /* proc.psinfo.nvctxsw */\n   PCP_PROC_LABELS,             /* proc.psinfo.labels */\n   PCP_PROC_ENVIRON,            /* proc.psinfo.environ */\n   PCP_PROC_TTYNAME,            /* proc.psinfo.ttyname */\n   PCP_PROC_EXE,                /* proc.psinfo.exe */\n   PCP_PROC_CWD,                /* proc.psinfo.cwd */\n\n   PCP_PROC_AUTOGROUP_ID,       /* proc.autogroup.id */\n   PCP_PROC_AUTOGROUP_NICE,     /* proc.autogroup.nice */\n\n   PCP_PROC_ID_UID,             /* proc.id.uid */\n   PCP_PROC_ID_USER,            /* proc.id.uid_nm */\n\n   PCP_PROC_IO_RCHAR,           /* proc.io.rchar */\n   PCP_PROC_IO_WCHAR,           /* proc.io.wchar */\n   PCP_PROC_IO_SYSCR,           /* proc.io.syscr */\n   PCP_PROC_IO_SYSCW,           /* proc.io.syscw */\n   PCP_PROC_IO_READB,           /* proc.io.read_bytes */\n   PCP_PROC_IO_WRITEB,          /* proc.io.write_bytes */\n   PCP_PROC_IO_CANCELLED,       /* proc.io.cancelled_write_bytes */\n\n   PCP_PROC_MEM_SIZE,           /* proc.memory.size */\n   PCP_PROC_MEM_RSS,            /* proc.memory.rss */\n   PCP_PROC_MEM_SHARE,          /* proc.memory.share */\n   PCP_PROC_MEM_TEXTRS,         /* proc.memory.textrss */\n   PCP_PROC_MEM_LIBRS,          /* proc.memory.librss */\n   PCP_PROC_MEM_DATRS,          /* proc.memory.datrss */\n   PCP_PROC_MEM_DIRTY,          /* proc.memory.dirty */\n\n   PCP_PROC_SMAPS_PSS,          /* proc.smaps.pss */\n   PCP_PROC_SMAPS_SWAP,         /* proc.smaps.swap */\n   PCP_PROC_SMAPS_SWAPPSS,      /* proc.smaps.swappss */\n\n   PCP_METRIC_COUNT             /* total metric count */\n} Metric;\n\nvoid Metric_enable(Metric metric, bool enable);\n\nbool Metric_enabled(Metric metric);\n\nvoid Metric_enableThreads(void);\n\nbool Metric_fetch(struct timeval* timestamp);\n\nbool Metric_iterate(Metric metric, int* instp, int* offsetp, size_t entrylen);\n\npmAtomValue* Metric_values(Metric metric, pmAtomValue* atom, int count, int type);\n\nconst pmDesc* Metric_desc(Metric metric);\n\nstatic inline Metric Metric_fromId(size_t id) { return (Metric)id; }\n\nint Metric_type(Metric metric);\n\nint Metric_instanceCount(Metric metric);\n\nint Metric_instanceOffset(Metric metric, int inst);\n\npmAtomValue* Metric_instance(Metric metric, int inst, int offset, pmAtomValue* atom, int type);\n\npmAtomValue* Metric_instance_kibibytes(Metric metric, int inst, int offset, pmAtomValue* atom);\n\npmAtomValue* Metric_instance_milliseconds(Metric metric, int inst, int offset, pmAtomValue* atom);\n\nvoid Metric_externalName(Metric metric, int inst, char** externalName);\n\nint Metric_lookupText(const char* metric, char** desc);\n\n#endif\n"
  },
  {
    "path": "pcp/PCPDynamicColumn.c",
    "content": "/*\nhtop - PCPDynamicColumn.c\n(C) 2021-2023 Sohaib Mohammed\n(C) 2021-2023 htop dev team\nReleased under the GNU GPLv2+, see the COPYING file\nin the source distribution for its full text.\n*/\n\n#include \"config.h\" // IWYU pragma: keep\n\n#include \"pcp/PCPDynamicColumn.h\"\n\n#include <ctype.h>\n#include <dirent.h>\n#include <errno.h>\n#include <pwd.h>\n#include <stdbool.h>\n#include <stdio.h>\n#include <stdlib.h>\n#include <string.h>\n\n#include \"CRT.h\"\n#include \"Macros.h\"\n#include \"Platform.h\"\n#include \"Process.h\"\n#include \"ProcessTable.h\"\n#include \"RichString.h\"\n#include \"XUtils.h\"\n\n#include \"linux/CGroupUtils.h\"\n#include \"pcp/Metric.h\"\n#include \"pcp/PCPProcess.h\"\n\n\nstatic bool PCPDynamicColumn_addMetric(PCPDynamicColumns* columns, PCPDynamicColumn* column) {\n   if (!column->super.name[0])\n      return false;\n\n   char* metricName = NULL;\n   xAsprintf(&metricName, \"htop.column.%s\", column->super.name);\n\n   column->metricName = metricName;\n   column->id = columns->offset + columns->cursor;\n   columns->cursor++;\n\n   Metric metric = Metric_fromId(column->id);\n   Platform_addMetric(metric, metricName);\n   return true;\n}\n\nstatic void PCPDynamicColumn_parseMetric(PCPDynamicColumns* columns, PCPDynamicColumn* column, const char* path, unsigned int line, char* value) {\n   /* pmLookupText */\n   if (!column->super.description)\n      Metric_lookupText(value, &column->super.description);\n\n   /* lookup a dynamic metric with this name, else create */\n   if (PCPDynamicColumn_addMetric(columns, column) == false)\n      return;\n\n   /* derived metrics in all dynamic columns for simplicity */\n   char* error;\n   if (pmRegisterDerivedMetric(column->metricName, value, &error) < 0) {\n      char* note = NULL;\n      xAsprintf(&note,\n                \"%s: failed to parse expression in %s at line %u\\n%s\\n\",\n                pmGetProgname(), path, line, error);\n      free(error);\n      errno = EINVAL;\n      CRT_fatalError(note);\n      free(note);\n   }\n}\n\n// Ensure a valid name for use in a PCP metric name and in htoprc\nstatic bool PCPDynamicColumn_validateColumnName(char* key, const char* path, unsigned int line) {\n   char* p = key;\n   char* end = strrchr(key, ']');\n\n   if (end) {\n      *end = '\\0';\n   } else {\n      fprintf(stderr,\n                \"%s: no closing brace on column name at %s line %u\\n\\\"%s\\\"\",\n                pmGetProgname(), path, line, key);\n      return false;\n   }\n\n   while (*p) {\n      if (p == key) {\n         if (!isalpha(*p) && *p != '_')\n            break;\n      } else {\n         if (!isalnum(*p) && *p != '_')\n            break;\n      }\n      p++;\n   }\n   if (*p != '\\0') { /* badness */\n      fprintf(stderr,\n                \"%s: invalid column name at %s line %u\\n\\\"%s\\\"\",\n                pmGetProgname(), path, line, key);\n      return false;\n   }\n   return true;\n}\n\n// Ensure a column name has not been defined previously\nstatic bool PCPDynamicColumn_uniqueName(char* key, PCPDynamicColumns* columns) {\n   return DynamicColumn_search(columns->table, key, NULL) == NULL;\n}\n\nstatic PCPDynamicColumn* PCPDynamicColumn_new(PCPDynamicColumns* columns, const char* name) {\n   PCPDynamicColumn* column = xCalloc(1, sizeof(*column));\n   String_safeStrncpy(column->super.name, name, sizeof(column->super.name));\n   column->super.enabled = false;\n   column->percent = false;\n   column->instances = false;\n   column->defaultEnabled = true;\n\n   ht_key_t id = (ht_key_t) columns->count + LAST_PROCESSFIELD;\n   Hashtable_put(columns->table, id, column);\n   columns->count++;\n\n   return column;\n}\n\nstatic void PCPDynamicColumn_parseFile(PCPDynamicColumns* columns, const char* path) {\n   FILE* file = fopen(path, \"r\");\n   if (!file)\n      return;\n\n   PCPDynamicColumn* column = NULL;\n   unsigned int lineno = 0;\n   for (;;) {\n      char* line = String_readLine(file);\n      if (!line)\n         break;\n      lineno++;\n\n      /* cleanup whitespace, skip comment lines */\n      char* trimmed = String_trim(line);\n      free(line);\n      if (!trimmed || !trimmed[0] || trimmed[0] == '#') {\n         free(trimmed);\n         continue;\n      }\n\n      size_t n;\n      char** config = String_split(trimmed, '=', &n);\n      free(trimmed);\n      if (config == NULL)\n         continue;\n\n      char* key = String_trim(config[0]);\n      char* value = n > 1 ? String_trim(config[1]) : NULL;\n      if (key[0] == '[') {  /* new section heading - i.e. new column */\n         column = NULL;\n         bool ok = PCPDynamicColumn_validateColumnName(key + 1, path, lineno);\n         if (ok)\n            ok = PCPDynamicColumn_uniqueName(key + 1, columns);\n         if (ok)\n            column = PCPDynamicColumn_new(columns, key + 1);\n      } else if (value && column && String_eq(key, \"caption\")) {\n         free_and_xStrdup(&column->super.caption, value);\n      } else if (value && column && String_eq(key, \"heading\")) {\n         free_and_xStrdup(&column->super.heading, value);\n      } else if (value && column && String_eq(key, \"description\")) {\n         free_and_xStrdup(&column->super.description, value);\n      } else if (value && column && String_eq(key, \"width\")) {\n         column->super.width = atoi(value);\n      } else if (value && column && String_eq(key, \"format\")) {\n         free_and_xStrdup(&column->format, value);\n      } else if (value && column && String_eq(key, \"instances\")) {\n         if (String_eq(value, \"True\") || String_eq(value, \"true\"))\n            column->instances = true;\n      } else if (value && column && (String_eq(key, \"default\") || String_eq(key, \"enabled\"))) {\n         if (String_eq(value, \"False\") || String_eq(value, \"false\"))\n            column->defaultEnabled = false;\n      } else if (value && column && String_eq(key, \"metric\")) {\n         PCPDynamicColumn_parseMetric(columns, column, path, lineno, value);\n      }\n      String_freeArray(config);\n      free(value);\n      free(key);\n   }\n   fclose(file);\n}\n\nstatic void PCPDynamicColumn_scanDir(PCPDynamicColumns* columns, char* path) {\n   DIR* dir = opendir(path);\n   if (!dir)\n      return;\n\n   struct dirent* dirent;\n   while ((dirent = readdir(dir)) != NULL) {\n      if (dirent->d_name[0] == '.')\n         continue;\n\n      char* file = String_cat(path, dirent->d_name);\n      PCPDynamicColumn_parseFile(columns, file);\n      free(file);\n   }\n   closedir(dir);\n}\n\nvoid PCPDynamicColumns_init(PCPDynamicColumns* columns) {\n   const char* share = pmGetConfig(\"PCP_SHARE_DIR\");\n   const char* sysconf = pmGetConfig(\"PCP_SYSCONF_DIR\");\n   const char* xdgConfigHome = getenv(\"XDG_CONFIG_HOME\");\n   const char* override = getenv(\"PCP_HTOP_DIR\");\n   const char* home = getenv(\"HOME\");\n   char* path;\n\n   if (!xdgConfigHome && !home) {\n      const struct passwd* pw = getpwuid(getuid());\n      if (pw)\n         home = pw->pw_dir;\n   }\n\n   columns->table = Hashtable_new(0, true);\n\n   /* developer paths - PCP_HTOP_DIR=./pcp ./pcp-htop */\n   if (override) {\n      path = String_cat(override, \"/columns/\");\n      PCPDynamicColumn_scanDir(columns, path);\n      free(path);\n   }\n\n   /* next, search in home directory alongside htoprc */\n   if (xdgConfigHome)\n      path = String_cat(xdgConfigHome, \"/htop/columns/\");\n   else if (home)\n      path = String_cat(home, CONFIGDIR \"/htop/columns/\");\n   else\n      path = NULL;\n   if (path) {\n      PCPDynamicColumn_scanDir(columns, path);\n      free(path);\n   }\n\n   /* next, search in the system columns directory */\n   path = String_cat(sysconf, \"/htop/columns/\");\n   PCPDynamicColumn_scanDir(columns, path);\n   free(path);\n\n   /* next, try the readonly system columns directory */\n   path = String_cat(share, \"/htop/columns/\");\n   PCPDynamicColumn_scanDir(columns, path);\n   free(path);\n}\n\nvoid PCPDynamicColumn_done(PCPDynamicColumn* this) {\n   DynamicColumn_done(&this->super);\n   free(this->metricName);\n   free(this->format);\n}\n\nstatic void PCPDynamicColumns_free(ATTR_UNUSED ht_key_t key, void* value, ATTR_UNUSED void* data) {\n   PCPDynamicColumn* column = (PCPDynamicColumn*) value;\n   PCPDynamicColumn_done(column);\n}\n\nvoid PCPDynamicColumns_done(Hashtable* table) {\n   Hashtable_foreach(table, PCPDynamicColumns_free, NULL);\n}\n\nstatic void PCPDynamicColumn_setupWidth(ATTR_UNUSED ht_key_t key, void* value, ATTR_UNUSED void* data) {\n   PCPDynamicColumn* column = (PCPDynamicColumn*) value;\n\n   /* calculate column size based on config file and metric units */\n   Metric metric = Metric_fromId(column->id);\n   const pmDesc* desc = Metric_desc(metric);\n\n   if (column->instances || desc->type == PM_TYPE_STRING) {\n      column->super.width = column->width;\n      if (column->super.width == 0)\n         column->super.width = -16;\n      return;\n   }\n\n   if (column->format) {\n      if (strcmp(column->format, \"percent\") == 0) {\n         column->super.width = 5;\n         return;\n      }\n      if (strcmp(column->format, \"process\") == 0) {\n         column->super.width = Process_pidDigits;\n         return;\n      }\n   }\n\n   if (column->width) {\n      column->super.width = column->width;\n      return;\n   }\n\n   pmUnits units = desc->units;\n   if (units.dimSpace && units.dimTime)\n      column->super.width = 11; // Row_printRate\n   else if (units.dimSpace)\n      column->super.width = 5;  // Row_printBytes\n   else if (units.dimCount && units.dimTime)\n      column->super.width = 11;  // Row_printCount\n   else if (units.dimTime)\n      column->super.width = 8;  // Row_printTime\n   else\n      column->super.width = 11; // Row_printCount\n}\n\nvoid PCPDynamicColumns_setupWidths(PCPDynamicColumns* columns) {\n   Hashtable_foreach(columns->table, PCPDynamicColumn_setupWidth, NULL);\n}\n\n/* normalize output units to bytes and seconds */\nstatic int PCPDynamicColumn_normalize(const pmDesc* desc, const pmAtomValue* ap, double* value) {\n   /* form normalized units based on the original metric units */\n   pmUnits units = desc->units;\n   if (units.dimTime)\n      units.scaleTime = PM_TIME_SEC;\n   if (units.dimSpace)\n      units.scaleSpace = PM_SPACE_BYTE;\n   if (units.dimCount)\n      units.scaleCount = PM_COUNT_ONE;\n\n   pmAtomValue atom;\n   int sts, type = desc->type;\n   if ((sts = pmConvScale(type, ap, &desc->units, &atom, &units)) < 0)\n      return sts;\n\n   switch (type) {\n      case PM_TYPE_32:\n         *value = (double) atom.l;\n         break;\n      case PM_TYPE_U32:\n         *value = (double) atom.ul;\n         break;\n      case PM_TYPE_64:\n         *value = (double) atom.ll;\n         break;\n      case PM_TYPE_U64:\n         *value = (double) atom.ull;\n         break;\n      case PM_TYPE_FLOAT:\n         *value = (double) atom.f;\n         break;\n      case PM_TYPE_DOUBLE:\n         *value = atom.d;\n         break;\n      default:\n         return PM_ERR_CONV;\n   }\n\n   return 0;\n}\n\nvoid PCPDynamicColumn_writeAtomValue(PCPDynamicColumn* column, RichString* str, const struct Settings_* settings, int metric, int instance, const pmDesc* desc, const void* atom) {\n   const pmAtomValue* atomvalue = (const pmAtomValue*) atom;\n   char buffer[DYNAMIC_MAX_COLUMN_WIDTH + /*space*/ 1 + /*null*/ 1];\n   int attr = CRT_colors[DEFAULT_COLOR];\n   int width = column->super.width;\n   int n;\n\n   if (!width || abs(width) > DYNAMIC_MAX_COLUMN_WIDTH)\n      width = DYNAMIC_DEFAULT_COLUMN_WIDTH;\n   int abswidth = abs(width);\n   if (abswidth > DYNAMIC_MAX_COLUMN_WIDTH) {\n      abswidth = DYNAMIC_MAX_COLUMN_WIDTH;\n      width = -abswidth;\n   }\n\n   if (atomvalue == NULL) {\n      n = pmsprintf(buffer, sizeof(buffer), \"%*.*s \", width, abswidth, \"N/A\");\n      RichString_appendnAscii(str, CRT_colors[PROCESS_SHADOW], buffer, n);\n      return;\n   }\n\n   /* deal with instance names and metrics with string values first */\n   if (column->instances || desc->type == PM_TYPE_STRING) {\n      char* value = NULL;\n      char* dupd1 = NULL;\n      if (column->instances) {\n         attr = CRT_colors[DYNAMIC_GRAY];\n         Metric_externalName(metric, instance, &dupd1);\n         value = dupd1;\n      } else {\n         attr = CRT_colors[DYNAMIC_GREEN];\n         value = atomvalue->cp;\n      }\n      if (column->format && value) {\n         char* dupd2 = NULL;\n         if (strcmp(column->format, \"command\") == 0)\n            attr = CRT_colors[PROCESS_COMM];\n         else if (strcmp(column->format, \"process\") == 0)\n            attr = CRT_colors[PROCESS_SHADOW];\n         else if (strcmp(column->format, \"device\") == 0 && strncmp(value, \"/dev/\", 5) == 0)\n            value += 5;\n         else if (strcmp(column->format, \"cgroup\") == 0 && (dupd2 = CGroup_filterName(value)))\n            value = dupd2;\n         n = pmsprintf(buffer, sizeof(buffer), \"%*.*s \", width, abswidth, value);\n         if (dupd2)\n            free(dupd2);\n      } else if (value) {\n         n = pmsprintf(buffer, sizeof(buffer), \"%*.*s \", width, abswidth, value);\n      } else {\n         n = pmsprintf(buffer, sizeof(buffer), \"%*.*s \", width, abswidth, \"N/A\");\n      }\n      if (dupd1)\n         free(dupd1);\n      RichString_appendnAscii(str, attr, buffer, n);\n      return;\n   }\n\n   /* deal with any numeric value - first, normalize units to bytes/seconds */\n   double value;\n   if (PCPDynamicColumn_normalize(desc, atomvalue, &value) < 0) {\n      n = pmsprintf(buffer, sizeof(buffer), \"%*.*s \", width, abswidth, \"no conv\");\n      RichString_appendnAscii(str, CRT_colors[METER_VALUE_ERROR], buffer, n);\n      return;\n   }\n\n   if (column->format) {\n      if (strcmp(column->format, \"percent\") == 0) {\n         n = Row_printPercentage(value, buffer, sizeof(buffer), (uint8_t)width, &attr);\n         RichString_appendnAscii(str, attr, buffer, n);\n         return;\n      }\n      if (strcmp(column->format, \"process\") == 0) {\n         n = pmsprintf(buffer, sizeof(buffer), \"%*d \", Row_pidDigits, (int)value);\n         RichString_appendnAscii(str, attr, buffer, n);\n         return;\n      }\n   }\n\n   /* width overrides unit suffix and coloring; too complex for a corner case */\n   if (column->width) {\n      if (value - (unsigned long long)value > 0)  /* display floating point */\n         n = pmsprintf(buffer, sizeof(buffer), \"%*.2f \", width, value);\n      else   /* display as integer */\n         n = pmsprintf(buffer, sizeof(buffer), \"%*llu \", width, (unsigned long long)value);\n      RichString_appendnAscii(str, CRT_colors[PROCESS], buffer, n);\n      return;\n   }\n\n   bool coloring = settings->highlightMegabytes;\n   pmUnits units = desc->units;\n   if (units.dimSpace && units.dimTime)\n      Row_printRate(str, value, coloring);\n   else if (units.dimSpace)\n      Row_printBytes(str, value, coloring);\n   else if (units.dimCount)\n      Row_printCount(str, value, coloring);\n   else if (units.dimTime)\n      Row_printTime(str, value / 10 /* hundreds of a second */, coloring);\n   else\n      Row_printCount(str, value, 0);  /* e.g. PID */\n}\n\nvoid PCPDynamicColumn_writeField(PCPDynamicColumn* this, const Process* proc, RichString* str) {\n   const Settings* settings = proc->super.host->settings;\n   const PCPProcess* pp = (const PCPProcess*) proc;\n\n   Metric metric = Metric_fromId(this->id);\n   const pmDesc* desc = Metric_desc(metric);\n   pid_t pid = Process_getPid(proc);\n\n   pmAtomValue atom;\n   pmAtomValue* ap = &atom;\n   if (!Metric_instance(metric, pid, pp->offset, ap, desc->type))\n      ap = NULL;\n\n   PCPDynamicColumn_writeAtomValue(this, str, settings, metric, pid, desc, ap);\n}\n\nint PCPDynamicColumn_compareByKey(const PCPProcess* p1, const PCPProcess* p2, ProcessField key) {\n   const Process* proc = &p1->super;\n   const Settings* settings = proc->super.host->settings;\n   const PCPDynamicColumn* column = Hashtable_get(settings->dynamicColumns, key);\n\n   if (!column)\n      return -1;\n\n   Metric metric = Metric_fromId(column->id);\n   unsigned int type = Metric_type(metric);\n\n   pmAtomValue atom1 = {0}, atom2 = {0};\n   if (!Metric_instance(metric, Process_getPid(&p1->super), p1->offset, &atom1, type) ||\n       !Metric_instance(metric, Process_getPid(&p2->super), p2->offset, &atom2, type)) {\n      if (type == PM_TYPE_STRING) {\n         free(atom1.cp);\n         free(atom2.cp);\n      }\n      return -1;\n   }\n\n   switch (type) {\n      case PM_TYPE_STRING: {\n         int cmp = SPACESHIP_NULLSTR(atom2.cp, atom1.cp);\n         free(atom2.cp);\n         free(atom1.cp);\n         return cmp;\n      }\n      case PM_TYPE_32:\n         return SPACESHIP_NUMBER(atom2.l, atom1.l);\n      case PM_TYPE_U32:\n         return SPACESHIP_NUMBER(atom2.ul, atom1.ul);\n      case PM_TYPE_64:\n         return SPACESHIP_NUMBER(atom2.ll, atom1.ll);\n      case PM_TYPE_U64:\n         return SPACESHIP_NUMBER(atom2.ull, atom1.ull);\n      case PM_TYPE_FLOAT:\n         return compareRealNumbers(atom2.f, atom1.f);\n      case PM_TYPE_DOUBLE:\n         return compareRealNumbers(atom2.d, atom1.d);\n      default:\n         break;\n   }\n\n   return -1;\n}\n"
  },
  {
    "path": "pcp/PCPDynamicColumn.h",
    "content": "#ifndef HEADER_PCPDynamicColumn\n#define HEADER_PCPDynamicColumn\n/*\nhtop - PCPDynamicColumn.h\n(C) 2023 htop dev team\nReleased under the GNU GPLv2+, see the COPYING file\nin the source distribution for its full text.\n*/\n\n#include <stddef.h>\n\n#include \"DynamicColumn.h\"\n#include \"Hashtable.h\"\n#include \"Process.h\"\n#include \"RichString.h\"\n\n#include \"pcp/PCPProcess.h\"\n\n\nstruct pmDesc;\n\ntypedef struct PCPDynamicColumn_ {\n   DynamicColumn super;\n   char* metricName;\n   char* format;\n   size_t id;  /* identifier for metric array lookups */\n   int width;  /* optional width from configuration file */\n   bool defaultEnabled;  /* default enabled in dynamic screen */\n   bool percent;\n   bool instances;  /* an instance *names* column, not values */\n} PCPDynamicColumn;\n\ntypedef struct PCPDynamicColumns_ {\n   Hashtable* table;\n   size_t count;  /* count of dynamic columns discovered by scan */\n   size_t offset; /* start offset into the Platform metric array */\n   size_t cursor; /* identifier allocator for each new metric used */\n} PCPDynamicColumns;\n\nvoid PCPDynamicColumns_init(PCPDynamicColumns* columns);\n\nvoid PCPDynamicColumns_done(Hashtable* table);\n\nvoid PCPDynamicColumns_setupWidths(PCPDynamicColumns* columns);\n\nvoid PCPDynamicColumn_writeField(PCPDynamicColumn* this, const Process* proc, RichString* str);\n\nvoid PCPDynamicColumn_writeAtomValue(PCPDynamicColumn* column, RichString* str, const struct Settings_* settings, int metric, int instance, const struct pmDesc* desc, const void* atomvalue);\n\nint PCPDynamicColumn_compareByKey(const PCPProcess* p1, const PCPProcess* p2, ProcessField key);\n\nvoid PCPDynamicColumn_done(PCPDynamicColumn* this);\n\n#endif\n"
  },
  {
    "path": "pcp/PCPDynamicMeter.c",
    "content": "/*\nhtop - PCPDynamicMeter.c\n(C) 2021 htop dev team\n(C) 2021 Red Hat, Inc.\nReleased under the GNU GPLv2+, see the COPYING file\nin the source distribution for its full text.\n*/\n\n#include \"config.h\" // IWYU pragma: keep\n\n#include \"pcp/PCPDynamicMeter.h\"\n\n#include <ctype.h>\n#include <dirent.h>\n#include <errno.h>\n#include <pwd.h>\n#include <stdbool.h>\n#include <stdio.h>\n#include <stdlib.h>\n#include <string.h>\n\n#include <pcp/pmapi.h>\n\n#include \"Macros.h\"\n#include \"Platform.h\"\n#include \"RichString.h\"\n#include \"XUtils.h\"\n\n#include \"pcp/Metric.h\"\n\n\nstatic PCPDynamicMetric* PCPDynamicMeter_lookupMetric(PCPDynamicMeters* meters, PCPDynamicMeter* meter, const char* name) {\n   char* metricName = NULL;\n   xAsprintf(&metricName, \"htop.meter.%s.%s\", meter->super.name, name);\n\n   PCPDynamicMetric* metric;\n   for (size_t i = 0; i < meter->totalMetrics; i++) {\n      metric = &meter->metrics[i];\n      if (String_eq(metric->name, metricName)) {\n         free(metricName);\n         return metric;\n      }\n   }\n\n   /* not an existing metric in this meter - add it */\n   size_t n = meter->totalMetrics + 1;\n   meter->metrics = xReallocArray(meter->metrics, n, sizeof(PCPDynamicMetric));\n   meter->totalMetrics = n;\n   metric = &meter->metrics[n - 1];\n   memset(metric, 0, sizeof(PCPDynamicMetric));\n   metric->name = metricName;\n   metric->label = String_cat(name, \": \");\n   metric->id = meters->offset + meters->cursor;\n   meters->cursor++;\n\n   Platform_addMetric(Metric_fromId(metric->id), metricName);\n\n   return metric;\n}\n\nstatic void PCPDynamicMeter_parseMetric(PCPDynamicMeters* meters, PCPDynamicMeter* meter, const char* path, unsigned int line, char* key, char* value) {\n   PCPDynamicMetric* metric;\n   char* p;\n\n   if ((p = strchr(key, '.')) == NULL)\n      return;\n   *p++ = '\\0'; /* end the name, p is now the attribute, e.g. 'label' */\n\n   if (String_eq(p, \"metric\")) {\n      /* lookup a dynamic metric with this name, else create */\n      metric = PCPDynamicMeter_lookupMetric(meters, meter, key);\n\n      /* use derived metrics in dynamic meters for simplicity */\n      char* error;\n      if (pmRegisterDerivedMetric(metric->name, value, &error) < 0) {\n         char* note = NULL;\n         xAsprintf(&note,\n                   \"%s: failed to parse expression in %s at line %u\\n%s\\n%s\",\n                   pmGetProgname(), path, line, error, pmGetProgname());\n         free(error);\n         errno = EINVAL;\n         CRT_fatalError(note);\n         free(note);\n      }\n   } else {\n      /* this is a property of a dynamic metric - the metric expression */\n      /* may not have been observed yet - i.e. we allow for any ordering */\n      metric = PCPDynamicMeter_lookupMetric(meters, meter, key);\n      if (String_eq(p, \"color\")) {\n         if (String_eq(value, \"gray\"))\n            metric->color = DYNAMIC_GRAY;\n         else if (String_eq(value, \"darkgray\"))\n            metric->color = DYNAMIC_DARKGRAY;\n         else if (String_eq(value, \"red\"))\n            metric->color = DYNAMIC_RED;\n         else if (String_eq(value, \"green\"))\n            metric->color = DYNAMIC_GREEN;\n         else if (String_eq(value, \"blue\"))\n            metric->color = DYNAMIC_BLUE;\n         else if (String_eq(value, \"cyan\"))\n            metric->color = DYNAMIC_CYAN;\n         else if (String_eq(value, \"magenta\"))\n            metric->color = DYNAMIC_MAGENTA;\n         else if (String_eq(value, \"yellow\"))\n            metric->color = DYNAMIC_YELLOW;\n         else if (String_eq(value, \"white\"))\n            metric->color = DYNAMIC_WHITE;\n      } else if (String_eq(p, \"label\")) {\n         char* label = String_cat(value, \": \");\n         free_and_xStrdup(&metric->label, label);\n         free(label);\n      } else if (String_eq(p, \"suffix\")) {\n         free_and_xStrdup(&metric->suffix, value);\n      }\n   }\n}\n\n// Ensure a valid name for use in a PCP metric name and in htoprc\nstatic bool PCPDynamicMeter_validateMeterName(char* key, const char* path, unsigned int line) {\n   char* p = key;\n   char* end = strrchr(key, ']');\n\n   if (end) {\n      *end = '\\0';\n   } else {\n      fprintf(stderr,\n              \"%s: no closing brace on meter name at %s line %u\\n\\\"%s\\\"\\n\",\n              pmGetProgname(), path, line, key);\n      return false;\n   }\n\n   while (*p) {\n      if (p == key) {\n         if (!isalpha(*p) && *p != '_')\n            break;\n      } else {\n         if (!isalnum(*p) && *p != '_')\n            break;\n      }\n      p++;\n   }\n   if (*p != '\\0') { /* badness */\n      fprintf(stderr,\n              \"%s: invalid meter name at %s line %u\\n\\\"%s\\\"\\n\",\n              pmGetProgname(), path, line, key);\n      return false;\n   }\n   return true;\n}\n\n// Ensure a meter name has not been defined previously\nstatic bool PCPDynamicMeter_uniqueName(char* key, PCPDynamicMeters* meters) {\n   return !DynamicMeter_search(meters->table, key, NULL);\n}\n\nstatic PCPDynamicMeter* PCPDynamicMeter_new(PCPDynamicMeters* meters, const char* name) {\n   PCPDynamicMeter* meter = xCalloc(1, sizeof(*meter));\n   String_safeStrncpy(meter->super.name, name, sizeof(meter->super.name));\n   ht_key_t key = (ht_key_t) ++meters->count;\n   Hashtable_put(meters->table, key, meter);\n   return meter;\n}\n\nstatic void PCPDynamicMeter_parseFile(PCPDynamicMeters* meters, const char* path) {\n   FILE* file = fopen(path, \"r\");\n   if (!file)\n      return;\n\n   PCPDynamicMeter* meter = NULL;\n   unsigned int lineno = 0;\n   for (;;) {\n      char* line = String_readLine(file);\n      if (!line)\n         break;\n      lineno++;\n\n      /* cleanup whitespace, skip comment lines */\n      char* trimmed = String_trim(line);\n      free(line);\n      if (trimmed[0] == '#' || trimmed[0] == '\\0') {\n         free(trimmed);\n         continue;\n      }\n\n      size_t n;\n      char** config = String_split(trimmed, '=', &n);\n      free(trimmed);\n      if (config == NULL)\n         continue;\n\n      char* key = String_trim(config[0]);\n      char* value = n > 1 ? String_trim(config[1]) : NULL;\n      if (key[0] == '[') {  /* new section heading - i.e. new meter */\n         meter = NULL;\n         bool ok = PCPDynamicMeter_validateMeterName(key + 1, path, lineno);\n         if (ok)\n            ok = PCPDynamicMeter_uniqueName(key + 1, meters);\n         if (ok)\n            meter = PCPDynamicMeter_new(meters, key + 1);\n      } else if (!meter) {\n         /* skip this one, we're looking for a new header */\n      } else if (!value) {\n         /* skip this one as we always need value strings */\n      } else if (String_eq(key, \"caption\")) {\n         char* caption = String_cat(value, \": \");\n         if (caption) {\n            free_and_xStrdup(&meter->super.caption, caption);\n            free(caption);\n            caption = NULL;\n         }\n      } else if (String_eq(key, \"description\")) {\n         free_and_xStrdup(&meter->super.description, value);\n      } else if (String_eq(key, \"type\")) {\n         if (String_eq(config[1], \"bar\"))\n            meter->super.type = BAR_METERMODE;\n         else if (String_eq(config[1], \"text\"))\n            meter->super.type = TEXT_METERMODE;\n         else if (String_eq(config[1], \"graph\"))\n            meter->super.type = GRAPH_METERMODE;\n         else if (String_eq(config[1], \"led\"))\n            meter->super.type = LED_METERMODE;\n      } else if (String_eq(key, \"maximum\")) {\n         meter->super.maximum = strtod(value, NULL);\n      } else {\n         PCPDynamicMeter_parseMetric(meters, meter, path, lineno, key, value);\n      }\n      String_freeArray(config);\n      free(value);\n      free(key);\n   }\n   fclose(file);\n}\n\nstatic void PCPDynamicMeter_scanDir(PCPDynamicMeters* meters, char* path) {\n   DIR* dir = opendir(path);\n   if (!dir)\n      return;\n\n   struct dirent* dirent;\n   while ((dirent = readdir(dir)) != NULL) {\n      if (dirent->d_name[0] == '.')\n         continue;\n\n      char* file = String_cat(path, dirent->d_name);\n      PCPDynamicMeter_parseFile(meters, file);\n      free(file);\n   }\n   closedir(dir);\n}\n\nvoid PCPDynamicMeters_init(PCPDynamicMeters* meters) {\n   const char* share = pmGetConfig(\"PCP_SHARE_DIR\");\n   const char* sysconf = pmGetConfig(\"PCP_SYSCONF_DIR\");\n   const char* xdgConfigHome = getenv(\"XDG_CONFIG_HOME\");\n   const char* override = getenv(\"PCP_HTOP_DIR\");\n   const char* home = getenv(\"HOME\");\n   char* path;\n\n   if (!xdgConfigHome && !home) {\n      const struct passwd* pw = getpwuid(getuid());\n      if (pw)\n         home = pw->pw_dir;\n   }\n\n   meters->table = Hashtable_new(0, true);\n\n   /* developer paths - PCP_HTOP_DIR=./pcp ./pcp-htop */\n   if (override) {\n      path = String_cat(override, \"/meters/\");\n      PCPDynamicMeter_scanDir(meters, path);\n      free(path);\n   }\n\n   /* next, search in home directory alongside htoprc */\n   if (xdgConfigHome)\n      path = String_cat(xdgConfigHome, \"/htop/meters/\");\n   else if (home)\n      path = String_cat(home, CONFIGDIR \"/htop/meters/\");\n   else\n      path = NULL;\n   if (path) {\n      PCPDynamicMeter_scanDir(meters, path);\n      free(path);\n   }\n\n   /* next, search in the system meters directory */\n   path = String_cat(sysconf, \"/htop/meters/\");\n   PCPDynamicMeter_scanDir(meters, path);\n   free(path);\n\n   /* next, try the readonly system meters directory */\n   path = String_cat(share, \"/htop/meters/\");\n   PCPDynamicMeter_scanDir(meters, path);\n   free(path);\n}\n\nstatic void PCPDynamicMeter_free(ATTR_UNUSED ht_key_t key, void* value, ATTR_UNUSED void* data) {\n   PCPDynamicMeter* meter = (PCPDynamicMeter*) value;\n   for (size_t i = 0; i < meter->totalMetrics; i++) {\n      free(meter->metrics[i].name);\n      free(meter->metrics[i].label);\n      free(meter->metrics[i].suffix);\n   }\n   free(meter->metrics);\n   free(meter->super.caption);\n   free(meter->super.description);\n}\n\nvoid PCPDynamicMeters_done(Hashtable* table) {\n   Hashtable_foreach(table, PCPDynamicMeter_free, NULL);\n}\n\nvoid PCPDynamicMeter_enable(PCPDynamicMeter* this) {\n   for (size_t i = 0; i < this->totalMetrics; i++)\n      Metric_enable(Metric_fromId(this->metrics[i].id), true);\n}\n\nvoid PCPDynamicMeter_updateValues(PCPDynamicMeter* this, Meter* meter) {\n   char* const buffer = meter->txtBuffer;\n   const size_t size = sizeof(meter->txtBuffer);\n   size_t bytes = 0;\n   size_t bytes_old;\n\n   for (size_t i = 0; i < this->totalMetrics; i++) {\n      bytes_old = bytes;\n\n      if (i > 0 && bytes < size - 1)\n         buffer[bytes++] = '/';  /* separator */\n\n      PCPDynamicMetric* metric = &this->metrics[i];\n      Metric base = Metric_fromId(metric->id);\n      const pmDesc* desc = Metric_desc(base);\n      pmAtomValue atom, raw;\n\n      if (!Metric_values(base, &raw, 1, desc->type)) {\n         bytes = bytes_old; /* clear the separator */\n         continue;\n      }\n\n      pmUnits conv = desc->units;  /* convert to canonical units */\n      if (conv.dimSpace)\n         conv.scaleSpace = PM_SPACE_KBYTE;\n      if (conv.dimTime)\n         conv.scaleTime = PM_TIME_SEC;\n      if (desc->type == PM_TYPE_STRING)\n         atom = raw;\n      else if (pmConvScale(desc->type, &raw, &desc->units, &atom, &conv) < 0) {\n         bytes = bytes_old; /* clear the separator */\n         continue;\n      }\n\n      size_t saved = bytes;\n      switch (desc->type) {\n         case PM_TYPE_STRING:\n            bytes += pmsprintf(buffer + bytes, size - bytes, \"%s\", atom.cp);\n            free(atom.cp);\n            break;\n         case PM_TYPE_32:\n            bytes += conv.dimSpace ?\n               Meter_humanUnit(buffer + bytes, (double) atom.l, size - bytes) :\n               pmsprintf(buffer + bytes, size - bytes, \"%d\", atom.l);\n            break;\n         case PM_TYPE_U32:\n            bytes += conv.dimSpace ?\n               Meter_humanUnit(buffer + bytes, (double) atom.ul, size - bytes) :\n               pmsprintf(buffer + bytes, size - bytes, \"%u\", atom.ul);\n            break;\n         case PM_TYPE_64:\n            bytes += conv.dimSpace ?\n               Meter_humanUnit(buffer + bytes, (double) atom.ll, size - bytes) :\n               pmsprintf(buffer + bytes, size - bytes, \"%lld\", (long long) atom.ll);\n            break;\n         case PM_TYPE_U64:\n            bytes += conv.dimSpace ?\n               Meter_humanUnit(buffer + bytes, (double) atom.ull, size - bytes) :\n               pmsprintf(buffer + bytes, size - bytes, \"%llu\", (unsigned long long) atom.ull);\n            break;\n         case PM_TYPE_FLOAT:\n            bytes += conv.dimSpace ?\n               Meter_humanUnit(buffer + bytes, (double) atom.f, size - bytes) :\n               pmsprintf(buffer + bytes, size - bytes, \"%.2f\", (double) atom.f);\n            break;\n         case PM_TYPE_DOUBLE:\n            bytes += conv.dimSpace ?\n               Meter_humanUnit(buffer + bytes, atom.d, size - bytes) :\n               pmsprintf(buffer + bytes, size - bytes, \"%.2f\", atom.d);\n            break;\n         default:\n            break;\n      }\n\n      if (saved != bytes && metric->suffix && bytes < size)\n         bytes += pmsprintf(buffer + bytes, size - bytes, \"%s\", metric->suffix);\n   }\n\n   buffer[CLAMP(bytes, 0u, size - 1)] = '\\0';\n\n   if (!bytes)\n      pmsprintf(buffer, size, \"no data\");\n}\n\nvoid PCPDynamicMeter_display(PCPDynamicMeter* this, ATTR_UNUSED const Meter* meter, RichString* out) {\n   int nodata = 1;\n\n   for (size_t i = 0; i < this->totalMetrics; i++) {\n      PCPDynamicMetric* metric = &this->metrics[i];\n      Metric base = Metric_fromId(metric->id);\n      const pmDesc* desc = Metric_desc(base);\n      pmAtomValue atom, raw;\n      char buffer[64];\n\n      if (!Metric_values(base, &raw, 1, desc->type))\n         continue;\n\n      pmUnits conv = desc->units;  /* convert to canonical units */\n      if (conv.dimSpace)\n         conv.scaleSpace = PM_SPACE_KBYTE;\n      if (conv.dimTime)\n         conv.scaleTime = PM_TIME_SEC;\n      if (desc->type == PM_TYPE_STRING)\n         atom = raw;\n      else if (pmConvScale(desc->type, &raw, &desc->units, &atom, &conv) < 0)\n         continue;\n\n      nodata = 0;  /* we will use this metric so *some* data will be added */\n\n      if (i > 0)\n         RichString_appendAscii(out, CRT_colors[metric->color], \" \");\n\n      if (metric->label)\n         RichString_appendAscii(out, CRT_colors[METER_TEXT], metric->label);\n\n      int len = 0;\n      switch (desc->type) {\n         case PM_TYPE_STRING:\n            len = pmsprintf(buffer, sizeof(buffer), \"%s\", atom.cp);\n            free(atom.cp);\n            break;\n         case PM_TYPE_32:\n            len = conv.dimSpace ?\n               Meter_humanUnit(buffer, (double) atom.l, sizeof(buffer)) :\n               pmsprintf(buffer, sizeof(buffer), \"%d\", atom.l);\n            break;\n         case PM_TYPE_U32:\n            len = conv.dimSpace ?\n               Meter_humanUnit(buffer, (double) atom.ul, sizeof(buffer)) :\n               pmsprintf(buffer, sizeof(buffer), \"%u\", atom.ul);\n            break;\n         case PM_TYPE_64:\n            len = conv.dimSpace ?\n               Meter_humanUnit(buffer, (double) atom.ll, sizeof(buffer)) :\n               pmsprintf(buffer, sizeof(buffer), \"%lld\", (long long) atom.ll);\n            break;\n         case PM_TYPE_U64:\n            len = conv.dimSpace ?\n               Meter_humanUnit(buffer, (double) atom.ull, sizeof(buffer)) :\n               pmsprintf(buffer, sizeof(buffer), \"%llu\", (unsigned long long) atom.ull);\n            break;\n         case PM_TYPE_FLOAT:\n            len = conv.dimSpace ?\n               Meter_humanUnit(buffer, (double) atom.f, sizeof(buffer)) :\n               pmsprintf(buffer, sizeof(buffer), \"%.2f\", (double) atom.f);\n            break;\n         case PM_TYPE_DOUBLE:\n            len = conv.dimSpace ?\n               Meter_humanUnit(buffer, atom.d, sizeof(buffer)) :\n               pmsprintf(buffer, sizeof(buffer), \"%.2f\", atom.d);\n            break;\n         default:\n            break;\n      }\n\n      if (len) {\n         RichString_appendnAscii(out, CRT_colors[metric->color], buffer, len);\n         if (metric->suffix)\n            RichString_appendAscii(out, CRT_colors[METER_TEXT], metric->suffix);\n      }\n   }\n\n   if (nodata)\n      RichString_writeAscii(out, CRT_colors[METER_VALUE_ERROR], \"no data\");\n}\n"
  },
  {
    "path": "pcp/PCPDynamicMeter.h",
    "content": "#ifndef HEADER_PCPDynamicMeter\n#define HEADER_PCPDynamicMeter\n/*\nhtop - PCPDynamicMeter.h\n(C) 2023 htop dev team\nReleased under the GNU GPLv2+, see the COPYING file\nin the source distribution for its full text.\n*/\n\n#include <stddef.h>\n\n#include \"CRT.h\"\n#include \"DynamicMeter.h\"\n#include \"Hashtable.h\"\n#include \"Meter.h\"\n#include \"RichString.h\"\n\n\ntypedef struct PCPDynamicMetric_ {\n   size_t id; /* index into metric array */\n   ColorElements color;\n   char* name; /* derived metric name */\n   char* label;\n   char* suffix;\n} PCPDynamicMetric;\n\ntypedef struct PCPDynamicMeter_ {\n   DynamicMeter super;\n   PCPDynamicMetric* metrics;\n   size_t totalMetrics;\n} PCPDynamicMeter;\n\ntypedef struct PCPDynamicMeters_ {\n   Hashtable* table;\n   size_t count;  /* count of dynamic meters discovered by scan */\n   size_t offset; /* start offset into the Platform metric array */\n   size_t cursor; /* identifier allocator for each new metric used */\n} PCPDynamicMeters;\n\nvoid PCPDynamicMeters_init(PCPDynamicMeters* meters);\n\nvoid PCPDynamicMeters_done(Hashtable* table);\n\nvoid PCPDynamicMeter_enable(PCPDynamicMeter* this);\n\nvoid PCPDynamicMeter_updateValues(PCPDynamicMeter* this, Meter* meter);\n\nvoid PCPDynamicMeter_display(PCPDynamicMeter* this, const Meter* meter, RichString* out);\n\n#endif\n"
  },
  {
    "path": "pcp/PCPDynamicScreen.c",
    "content": "/*\nhtop - PCPDynamicScreen.c\n(C) 2022 Sohaib Mohammed\n(C) 2022-2023 htop dev team\nReleased under the GNU GPLv2+, see the COPYING file\nin the source distribution for its full text.\n*/\n\n#include \"config.h\" // IWYU pragma: keep\n\n#include \"pcp/PCPDynamicScreen.h\"\n\n#include <ctype.h>\n#include <dirent.h>\n#include <stdbool.h>\n#include <pcp/pmapi.h>\n\n#include \"AvailableColumnsPanel.h\"\n#include \"Macros.h\"\n#include \"Platform.h\"\n#include \"Settings.h\"\n#include \"XUtils.h\"\n\n#include \"pcp/InDomTable.h\"\n#include \"pcp/PCPDynamicColumn.h\"\n\n\nstatic char* formatFields(PCPDynamicScreen* screen) {\n   char* columns = strdup(\"\");\n\n   for (size_t j = 0; j < screen->totalColumns; j++) {\n      const PCPDynamicColumn* column = screen->columns[j];\n      if (column->super.enabled == false)\n         continue;\n      char* prefix = columns;\n      xAsprintf(&columns, \"%s Dynamic(%s)\", prefix, column->super.name);\n      free(prefix);\n   }\n\n   return columns;\n}\n\nstatic void PCPDynamicScreens_appendDynamicColumns(PCPDynamicScreens* screens, PCPDynamicColumns* columns) {\n   for (ht_key_t i = 0; i < screens->count; i++) {\n      PCPDynamicScreen* screen = Hashtable_get(screens->table, i);\n      if (!screen)\n         return;\n\n      /* setup default fields (columns) based on configuration */\n      for (size_t j = 0; j < screen->totalColumns; j++) {\n         PCPDynamicColumn* column = screen->columns[j];\n\n         column->id = columns->offset + columns->cursor;\n         Metric metric = Metric_fromId(column->id);\n         columns->cursor++;\n         Platform_addMetric(metric, column->metricName);\n\n         ht_key_t id = (ht_key_t) columns->count + LAST_PROCESSFIELD;\n         Hashtable_put(columns->table, id, column);\n         columns->count++;\n\n         if (j == 0) {\n            const pmDesc* desc = Metric_desc(metric);\n            assert(desc->indom != PM_INDOM_NULL);\n            screen->indom = desc->indom;\n            screen->key = metric;\n         }\n      }\n      screen->super.columnKeys = formatFields(screen);\n   }\n}\n\nstatic PCPDynamicColumn* PCPDynamicScreen_lookupMetric(PCPDynamicScreen* screen, const char* name) {\n   PCPDynamicColumn* column = NULL;\n   if ((strlen(name) + strlen(screen->super.name) + 1) >= sizeof(column->super.name)) /* colon */\n      return NULL;\n\n   char* metricName = NULL;\n   xAsprintf(&metricName, \"htop.screen.%s.%s\", screen->super.name, name);\n\n   for (size_t i = 0; i < screen->totalColumns; i++) {\n      column = screen->columns[i];\n      if (String_eq(column->metricName, metricName)) {\n         free(metricName);\n         return column;\n      }\n   }\n\n   /* not an existing column in this screen - create it and add to the list */\n   column = xCalloc(1, sizeof(PCPDynamicColumn));\n   pmsprintf(column->super.name, sizeof(column->super.name), \"%s:%s\", screen->super.name, name);\n   column->super.table = &screen->table->super;\n   column->metricName = metricName;\n   column->super.enabled = true;\n\n   size_t n = screen->totalColumns + 1;\n   screen->columns = xReallocArray(screen->columns, n, sizeof(PCPDynamicColumn*));\n   screen->columns[n - 1] = column;\n   screen->totalColumns = n;\n\n   return column;\n}\n\nstatic void PCPDynamicScreen_parseColumn(PCPDynamicScreen* screen, const char* path, unsigned int line, char* key, char* value) {\n   char* p = strchr(key, '.');\n   if (!p) {\n      return;\n   }\n\n   *p++ = '\\0'; /* end the name, p is now the attribute, e.g. 'label' */\n\n   /* lookup a dynamic column with this name, else create */\n   PCPDynamicColumn* column = PCPDynamicScreen_lookupMetric(screen, key);\n   if (!column) {\n      return;\n   }\n\n   if (String_eq(p, \"metric\")) {\n      char* error = NULL;\n      if (pmRegisterDerivedMetric(column->metricName, value, &error) < 0) {\n         char* note = NULL;\n         xAsprintf(\n            &note,\n            \"%s: failed to parse expression in %s at line %u\\n%s\\n\",\n            pmGetProgname(), path, line, error\n         );\n\n         errno = EINVAL;\n         CRT_fatalError(note);\n         free(note);\n\n         free(error);\n      }\n\n      /* pmLookupText - add optional metric help text */\n      if (!column->super.description && !column->instances) {\n         Metric_lookupText(value, &column->super.description);\n      }\n   } else {\n      /* this is a property of a dynamic column - the column expression */\n      /* may not have been observed yet; i.e. we allow for any ordering */\n\n      if (String_eq(p, \"caption\")) {\n         free_and_xStrdup(&column->super.caption, value);\n      } else if (String_eq(p, \"heading\")) {\n         free_and_xStrdup(&column->super.heading, value);\n      } else if (String_eq(p, \"description\")) {\n         free_and_xStrdup(&column->super.description, value);\n      } else if (String_eq(p, \"width\")) {\n         column->width = atoi(value);\n      } else if (String_eq(p, \"format\")) {\n         free_and_xStrdup(&column->format, value);\n      } else if (String_eq(p, \"instances\")) {\n         column->instances = false;\n         if (String_eq(value, \"True\") || String_eq(value, \"true\")) {\n            column->instances = true;\n         }\n         free_and_xStrdup(&column->super.description, screen->super.caption);\n      } else if (String_eq(p, \"default\")) { /* displayed by default */\n         column->defaultEnabled = column->super.enabled = true;\n         if (String_eq(value, \"False\") || String_eq(value, \"false\")) {\n            column->defaultEnabled = column->super.enabled = false;\n         }\n      }\n   }\n}\n\nstatic bool PCPDynamicScreen_validateScreenName(char* key, const char* path, unsigned int line) {\n   char* p = key;\n   char* end = strrchr(key, ']');\n\n   if (end) {\n      *end = '\\0';\n   } else {\n      fprintf(stderr,\n            \"%s: no closing brace on screen name at %s line %u\\n\\\"%s\\\"\",\n            pmGetProgname(), path, line, key);\n      return false;\n   }\n\n   while (*p) {\n      if (p == key) {\n         if (!isalpha(*p) && *p != '_')\n            break;\n      } else {\n         if (!isalnum(*p) && *p != '_')\n            break;\n      }\n      p++;\n   }\n   if (*p != '\\0') { /* badness */\n      fprintf(stderr,\n            \"%s: invalid screen name at %s line %u\\n\\\"%s\\\"\",\n            pmGetProgname(), path, line, key);\n      return false;\n   }\n   return true;\n}\n\n/* Ensure a screen name has not been defined previously */\nstatic bool PCPDynamicScreen_uniqueName(char* key, PCPDynamicScreens* screens) {\n   return !DynamicScreen_search(screens->table, key, NULL);\n}\n\nstatic PCPDynamicScreen* PCPDynamicScreen_new(PCPDynamicScreens* screens, const char* name) {\n   PCPDynamicScreen* screen = xCalloc(1, sizeof(*screen));\n   String_safeStrncpy(screen->super.name, name, sizeof(screen->super.name));\n   screen->defaultEnabled = true;\n\n   ht_key_t id = (ht_key_t) screens->count;\n   Hashtable_put(screens->table, id, screen);\n   screens->count++;\n\n   return screen;\n}\n\nstatic void PCPDynamicScreen_parseFile(PCPDynamicScreens* screens, const char* path) {\n   FILE* file = fopen(path, \"r\");\n   if (!file)\n      return;\n\n   PCPDynamicScreen* screen = NULL;\n   unsigned int lineno = 0;\n   for (;;) {\n      char* line = String_readLine(file);\n      if (!line)\n         break;\n      lineno++;\n\n      /* cleanup whitespace, skip comment lines */\n      char* trimmed = String_trim(line);\n      free(line);\n      if (!trimmed || !trimmed[0] || trimmed[0] == '#') {\n         free(trimmed);\n         continue;\n      }\n\n      size_t n;\n      char** config = String_split(trimmed, '=', &n);\n      free(trimmed);\n      if (config == NULL)\n         continue;\n\n      char* key = String_trim(config[0]);\n      char* value = n > 1 ? String_trim(config[1]) : NULL;\n      if (key[0] == '[') {  /* new section name - i.e. new screen */\n         screen = NULL;\n         bool ok = PCPDynamicScreen_validateScreenName(key + 1, path, lineno);\n         if (ok)\n            ok = PCPDynamicScreen_uniqueName(key + 1, screens);\n         if (ok)\n            screen = PCPDynamicScreen_new(screens, key + 1);\n         if (pmDebugOptions.appl0)\n            fprintf(stderr, \"[%s] screen: %s\\n\", path, key + 1);\n      } else if (!screen) {\n         /* skip this one, we're looking for a new header */\n      } else if (!value) {\n         /* skip this one as we always need value strings */\n      } else if (String_eq(key, \"heading\")) {\n         free_and_xStrdup(&screen->super.heading, value);\n      } else if (String_eq(key, \"caption\")) {\n         free_and_xStrdup(&screen->super.caption, value);\n      } else if (String_eq(key, \"sortKey\")) {\n         free_and_xStrdup(&screen->super.sortKey, value);\n      } else if (String_eq(key, \"sortDirection\")) {\n         screen->super.direction = atoi(value);\n      } else if (String_eq(key, \"default\") || String_eq(key, \"enabled\")) {\n         if (String_eq(value, \"False\") || String_eq(value, \"false\"))\n            screen->defaultEnabled = false;\n         else if (String_eq(value, \"True\") || String_eq(value, \"true\"))\n            screen->defaultEnabled = true; /* also default */\n      } else {\n         PCPDynamicScreen_parseColumn(screen, path, lineno, key, value);\n      }\n      String_freeArray(config);\n      free(value);\n      free(key);\n   }\n   fclose(file);\n}\n\nstatic void PCPDynamicScreen_scanDir(PCPDynamicScreens* screens, char* path) {\n   DIR* dir = opendir(path);\n   if (!dir)\n      return;\n\n   struct dirent* dirent;\n   while ((dirent = readdir(dir)) != NULL) {\n      if (dirent->d_name[0] == '.')\n         continue;\n\n      char* file = String_cat(path, dirent->d_name);\n      PCPDynamicScreen_parseFile(screens, file);\n      free(file);\n   }\n   closedir(dir);\n}\n\nvoid PCPDynamicScreens_init(PCPDynamicScreens* screens, PCPDynamicColumns* columns) {\n   const char* share = pmGetConfig(\"PCP_SHARE_DIR\");\n   const char* sysconf = pmGetConfig(\"PCP_SYSCONF_DIR\");\n   const char* xdgConfigHome = getenv(\"XDG_CONFIG_HOME\");\n   const char* override = getenv(\"PCP_HTOP_DIR\");\n   const char* home = getenv(\"HOME\");\n   char* path;\n\n   screens->table = Hashtable_new(0, true);\n\n   /* developer paths - PCP_HTOP_DIR=./pcp ./pcp-htop */\n   if (override) {\n      path = String_cat(override, \"/screens/\");\n      PCPDynamicScreen_scanDir(screens, path);\n      free(path);\n   }\n\n   /* next, search in home directory alongside htoprc */\n   if (xdgConfigHome)\n      path = String_cat(xdgConfigHome, \"/htop/screens/\");\n   else if (home)\n      path = String_cat(home, CONFIGDIR \"/htop/screens/\");\n   else\n      path = NULL;\n   if (path) {\n      PCPDynamicScreen_scanDir(screens, path);\n      free(path);\n   }\n\n   /* next, search in the system screens directory */\n   path = String_cat(sysconf, \"/htop/screens/\");\n   PCPDynamicScreen_scanDir(screens, path);\n   free(path);\n\n   /* next, try the readonly system screens directory */\n   path = String_cat(share, \"/htop/screens/\");\n   PCPDynamicScreen_scanDir(screens, path);\n   free(path);\n\n   /* establish internal metric identifier mappings */\n   PCPDynamicScreens_appendDynamicColumns(screens, columns);\n}\n\nstatic void PCPDynamicScreen_done(PCPDynamicScreen* ds) {\n   DynamicScreen_done(&ds->super);\n   Object_delete(ds->table);\n   free(ds->columns);\n}\n\nstatic void PCPDynamicScreens_free(ATTR_UNUSED ht_key_t key, void* value, ATTR_UNUSED void* data) {\n   PCPDynamicScreen* ds = (PCPDynamicScreen*) value;\n   PCPDynamicScreen_done(ds);\n}\n\nvoid PCPDynamicScreens_done(Hashtable* table) {\n   Hashtable_foreach(table, PCPDynamicScreens_free, NULL);\n}\n\nvoid PCPDynamicScreen_appendTables(PCPDynamicScreens* screens, Machine* host) {\n   PCPDynamicScreen* ds;\n\n   for (ht_key_t i = 0; i < screens->count; i++) {\n      if ((ds = (PCPDynamicScreen*)Hashtable_get(screens->table, i)) == NULL)\n         continue;\n      ds->table = InDomTable_new(host, ds->indom, ds->key);\n   }\n}\n\nvoid PCPDynamicScreen_appendScreens(PCPDynamicScreens* screens, Settings* settings) {\n   PCPDynamicScreen* ds;\n\n   for (ht_key_t i = 0; i < screens->count; i++) {\n      if ((ds = (PCPDynamicScreen*)Hashtable_get(screens->table, i)) == NULL)\n         continue;\n      if (ds->defaultEnabled == false)\n         continue;\n      const char* tab = ds->super.heading;\n      Settings_newDynamicScreen(settings, tab, &ds->super, &ds->table->super);\n   }\n}\n\n/* called when htoprc .dynamic line is parsed for a dynamic screen */\nvoid PCPDynamicScreen_addDynamicScreen(PCPDynamicScreens* screens, ScreenSettings* ss) {\n   PCPDynamicScreen* ds;\n\n   for (ht_key_t i = 0; i < screens->count; i++) {\n      if ((ds = (PCPDynamicScreen*)Hashtable_get(screens->table, i)) == NULL)\n         continue;\n      if (String_eq(ss->dynamic, ds->super.name) == false)\n         continue;\n      ss->table = &ds->table->super;\n   }\n}\n\nvoid PCPDynamicScreens_addAvailableColumns(Panel* availableColumns, Hashtable* screens, const char* screen) {\n   Vector_prune(availableColumns->items);\n\n   bool success;\n   ht_key_t key;\n   success = DynamicScreen_search(screens, screen, &key);\n   if (!success)\n      return;\n\n   PCPDynamicScreen* dynamicScreen = Hashtable_get(screens, key);\n   if (!dynamicScreen)\n      return;\n\n   for (unsigned int j = 0; j < dynamicScreen->totalColumns; j++) {\n      PCPDynamicColumn* column = dynamicScreen->columns[j];\n      const char* title = column->super.heading ? column->super.heading : column->super.name;\n      const char* text = column->super.description ? column->super.description : column->super.caption;\n      char description[256];\n      if (text)\n         pmsprintf(description, sizeof(description), \"%s - %s\", title, text);\n      else\n         pmsprintf(description, sizeof(description), \"%s\", title);\n      Panel_add(availableColumns, (Object*) ListItem_new(description, j));\n   }\n}\n"
  },
  {
    "path": "pcp/PCPDynamicScreen.h",
    "content": "#ifndef HEADER_PCPDynamicScreen\n#define HEADER_PCPDynamicScreen\n/*\nhtop - PCPDynamicScreen.h\n(C) 2023 htop dev team\nReleased under the GNU GPLv2+, see the COPYING file\nin the source distribution for its full text.\n*/\n\n#include <stddef.h>\n#include <stdbool.h>\n\n#include \"CRT.h\"\n#include \"DynamicScreen.h\"\n#include \"Hashtable.h\"\n#include \"Machine.h\"\n#include \"Panel.h\"\n#include \"Settings.h\"\n\n\nstruct InDomTable_;\nstruct PCPDynamicColumn_;\nstruct PCPDynamicColumns_;\n\ntypedef struct PCPDynamicScreen_ {\n   DynamicScreen super;\n\n   struct InDomTable_* table;\n   struct PCPDynamicColumn_** columns;\n   size_t totalColumns;\n\n   unsigned int indom;  /* instance domain number */\n   unsigned int key;  /* PCPMetric identifier */\n\n   bool defaultEnabled; /* enabled setting from configuration file */\n   /* at runtime enabled screens have entries in settings->screens */\n} PCPDynamicScreen;\n\ntypedef struct PCPDynamicScreens_ {\n   Hashtable* table;\n   size_t count;  /* count of dynamic screens discovered from scan */\n} PCPDynamicScreens;\n\nvoid PCPDynamicScreens_init(PCPDynamicScreens* screens, struct PCPDynamicColumns_* columns);\n\nvoid PCPDynamicScreens_done(Hashtable* table);\n\nvoid PCPDynamicScreen_appendTables(PCPDynamicScreens* screens, Machine* host);\n\nvoid PCPDynamicScreen_appendScreens(PCPDynamicScreens* screens, Settings* settings);\n\nvoid PCPDynamicScreen_addDynamicScreen(PCPDynamicScreens* screens, ScreenSettings* ss);\n\nvoid PCPDynamicScreens_addAvailableColumns(Panel* availableColumns, Hashtable* screens, const char* screen);\n\n#endif\n"
  },
  {
    "path": "pcp/PCPMachine.c",
    "content": "/*\nhtop - PCPProcessTable.c\n(C) 2014 Hisham H. Muhammad\n(C) 2020-2023 htop dev team\n(C) 2020-2023 Red Hat, Inc.\nReleased under the GNU GPLv2+, see the COPYING file\nin the source distribution for its full text.\n*/\n\n#include \"config.h\" // IWYU pragma: keep\n\n#include \"pcp/PCPMachine.h\"\n\n#include <assert.h>\n#include <limits.h>\n#include <math.h>\n#include <stdlib.h>\n#include <string.h>\n#include <sys/time.h>\n\n#include \"Machine.h\"\n#include \"Macros.h\"\n#include \"Object.h\"\n#include \"Platform.h\"\n#include \"Settings.h\"\n#include \"XUtils.h\"\n\n#include \"pcp/Metric.h\"\n#include \"pcp/PCPProcess.h\"\n\n\nstatic void PCPMachine_updateCPUcount(PCPMachine* this) {\n   Machine* super = &this->super;\n   super->activeCPUs = Metric_instanceCount(PCP_PERCPU_SYSTEM);\n   unsigned int cpus = Platform_getMaxCPU();\n   if (cpus == super->existingCPUs)\n      return;\n   if (cpus == 0)\n      cpus = super->activeCPUs;\n   if (cpus <= 1)\n      cpus = super->activeCPUs = 1;\n   super->existingCPUs = cpus;\n\n   free(this->percpu);\n   free(this->values);\n\n   this->percpu = xCalloc(cpus, sizeof(pmAtomValue*));\n   for (unsigned int i = 0; i < cpus; i++)\n      this->percpu[i] = xCalloc(CPU_METRIC_COUNT, sizeof(pmAtomValue));\n   this->values = xCalloc(cpus, sizeof(pmAtomValue));\n}\n\nstatic void PCPMachine_updateSystemName(PCPMachine* this) {\n   pmAtomValue sysname;\n   if (!Metric_values(PCP_UNAME_SYSNAME, &sysname, 1, PM_TYPE_STRING))\n      sysname.cp = NULL;\n   else if (String_eq(sysname.cp, \"Linux\"))\n      this->sys = SYSTEM_NAME_LINUX;\n   else if (String_eq(sysname.cp, \"Darwin\"))\n      this->sys = SYSTEM_NAME_DARWIN;\n   free(sysname.cp);\n}\n\nstatic void PCPMachine_updateLinuxMemoryInfo(PCPMachine* this) {\n   Machine* super = &this->super;\n   unsigned long long int freeMem = 0;\n   unsigned long long int swapFreeMem = 0;\n   unsigned long long int sreclaimableMem = 0;\n\n   pmAtomValue value;\n   if (Metric_values(PCP_MEM_FREE, &value, 1, PM_TYPE_U64) != NULL)\n      freeMem = value.ull;\n   if (Metric_values(PCP_MEM_BUFFERS, &value, 1, PM_TYPE_U64) != NULL)\n      this->memValue[MEMORY_CLASS_BUFFERS] = value.ull;\n   if (Metric_values(PCP_MEM_SRECLAIM, &value, 1, PM_TYPE_U64) != NULL)\n      sreclaimableMem = value.ull;\n   if (Metric_values(PCP_MEM_SHARED, &value, 1, PM_TYPE_U64) != NULL)\n      this->memValue[MEMORY_CLASS_SHARED] = value.ull;\n   if (Metric_values(PCP_MEM_CACHED, &value, 1, PM_TYPE_U64) != NULL)\n      this->memValue[MEMORY_CLASS_CACHE] = value.ull + sreclaimableMem - this->memValue[MEMORY_CLASS_SHARED];\n   const memory_t usedDiff = freeMem + this->memValue[MEMORY_CLASS_CACHE] + sreclaimableMem + this->memValue[MEMORY_CLASS_BUFFERS];\n   this->memValue[MEMORY_CLASS_USED] = (super->totalMem >= usedDiff) ?\n           super->totalMem - usedDiff : super->totalMem - freeMem;\n   if (Metric_values(PCP_MEM_AVAILABLE, &value, 1, PM_TYPE_U64) != NULL)\n      this->memValue[MEMORY_CLASS_AVAILABLE] = MINIMUM(value.ull, super->totalMem);\n   else\n      this->memValue[MEMORY_CLASS_AVAILABLE] = freeMem;\n   if (Metric_values(PCP_MEM_SWAPFREE, &value, 1, PM_TYPE_U64) != NULL)\n      swapFreeMem = value.ull;\n   if (Metric_values(PCP_MEM_SWAPTOTAL, &value, 1, PM_TYPE_U64) != NULL)\n      super->totalSwap = value.ull;\n   if (Metric_values(PCP_MEM_SWAPCACHED, &value, 1, PM_TYPE_U64) != NULL)\n      super->cachedSwap = value.ull;\n   super->usedSwap = super->totalSwap - swapFreeMem - super->cachedSwap;\n}\n\nstatic void PCPMachine_updateDarwinMemoryInfo(PCPMachine* this, Settings* settings) {\n   unsigned long long int activeMem = 0;\n   unsigned long long int externalMem = 0;\n   unsigned long long int purgeableMem = 0;\n   unsigned long long int speculativeMem = 0;\n\n   pmAtomValue value;\n   if (Metric_values(PCP_MEM_WIRED, &value, 1, PM_TYPE_U64) != NULL)\n      this->memValue[MEMORY_CLASS_WIRED] = value.ull;\n   if (Metric_values(PCP_MEM_ACTIVE, &value, 1, PM_TYPE_U64) != NULL)\n      activeMem = value.ull;\n   if (Metric_values(PCP_MEM_EXTERNAL, &value, 1, PM_TYPE_U64) != NULL)\n      externalMem = value.ull;\n   if (Metric_values(PCP_MEM_PURGEABLE, &value, 1, PM_TYPE_U64) != NULL)\n      purgeableMem = value.ull;\n   if (Metric_values(PCP_MEM_SPECULATIVE, &value, 1, PM_TYPE_U64) != NULL)\n      speculativeMem = value.ull;\n\n   if (settings->showCachedMemory) {\n      this->memValue[MEMORY_CLASS_SPECULATIVE] = speculativeMem;\n      this->memValue[MEMORY_CLASS_ACTIVE] = (activeMem - purgeableMem - externalMem);\n      this->memValue[MEMORY_CLASS_PURGEABLE] = purgeableMem;\n   } else {\n      this->memValue[MEMORY_CLASS_SPECULATIVE] = 0;\n      this->memValue[MEMORY_CLASS_ACTIVE] = (speculativeMem + activeMem - externalMem);\n      this->memValue[MEMORY_CLASS_PURGEABLE] = 0;\n   }\n\n   if (Metric_values(PCP_MEM_COMPRESSED, &value, 1, PM_TYPE_U64) != NULL)\n      this->memValue[MEMORY_CLASS_COMPRESSED] = value.ull;\n   if (Metric_values(PCP_MEM_INACTIVE, &value, 1, PM_TYPE_U64) != NULL)\n      this->memValue[MEMORY_CLASS_INACTIVE] = value.ull;\n}\n\nstatic void PCPMachine_updateMemoryInfo(Machine* super) {\n   PCPMachine* this = (PCPMachine*) super;\n\n   pmAtomValue value;\n   if (Metric_values(PCP_MEM_TOTAL, &value, 1, PM_TYPE_U64) != NULL)\n      super->totalMem = value.ull;\n   else\n      super->totalMem = 0;\n\n   memset(this->memValue, 0, sizeof(this->memValue));\n   if (this->sys == SYSTEM_NAME_DARWIN)\n      PCPMachine_updateDarwinMemoryInfo(this, super->settings);\n   else if (this->sys == SYSTEM_NAME_LINUX)\n      PCPMachine_updateLinuxMemoryInfo(this);\n}\n\n/* make copies of previously sampled values to avoid overwrite */\nstatic inline void PCPMachine_backupCPUTime(pmAtomValue* values) {\n   /* the PERIOD fields (must) mirror the TIME fields */\n   for (int metric = CPU_TOTAL_TIME; metric < CPU_TOTAL_PERIOD; metric++) {\n      values[metric + CPU_TOTAL_PERIOD] = values[metric];\n   }\n}\n\nstatic inline void PCPMachine_saveCPUTimePeriod(pmAtomValue* values, CPUMetric previous, pmAtomValue* latest) {\n   pmAtomValue* value;\n\n   /* new value for period */\n   value = &values[previous];\n   if (latest->ull > value->ull)\n      value->ull = latest->ull - value->ull;\n   else\n      value->ull = 0;\n\n   /* new value for time */\n   value = &values[previous - CPU_TOTAL_PERIOD];\n   value->ull = latest->ull;\n}\n\n/* using copied sampled values and new values, calculate derivations */\nstatic void PCPMachine_deriveCPUTime(pmAtomValue* values) {\n\n   pmAtomValue* usertime = &values[CPU_USER_TIME];\n   pmAtomValue* guesttime = &values[CPU_GUEST_TIME];\n   usertime->ull -= guesttime->ull;\n\n   pmAtomValue* nicetime = &values[CPU_NICE_TIME];\n   pmAtomValue* guestnicetime = &values[CPU_GUESTNICE_TIME];\n   nicetime->ull -= guestnicetime->ull;\n\n   pmAtomValue* idletime = &values[CPU_IDLE_TIME];\n   pmAtomValue* iowaittime = &values[CPU_IOWAIT_TIME];\n   pmAtomValue* idlealltime = &values[CPU_IDLE_ALL_TIME];\n   idlealltime->ull = idletime->ull + iowaittime->ull;\n\n   pmAtomValue* systemtime = &values[CPU_SYSTEM_TIME];\n   pmAtomValue* irqtime = &values[CPU_IRQ_TIME];\n   pmAtomValue* softirqtime = &values[CPU_SOFTIRQ_TIME];\n   pmAtomValue* systalltime = &values[CPU_SYSTEM_ALL_TIME];\n   systalltime->ull = systemtime->ull + irqtime->ull + softirqtime->ull;\n\n   pmAtomValue* virtalltime = &values[CPU_GUEST_TIME];\n   virtalltime->ull = guesttime->ull + guestnicetime->ull;\n\n   pmAtomValue* stealtime = &values[CPU_STEAL_TIME];\n   pmAtomValue* totaltime = &values[CPU_TOTAL_TIME];\n   totaltime->ull = usertime->ull + nicetime->ull + systalltime->ull +\n                    idlealltime->ull + stealtime->ull + virtalltime->ull;\n\n   PCPMachine_saveCPUTimePeriod(values, CPU_USER_PERIOD, usertime);\n   PCPMachine_saveCPUTimePeriod(values, CPU_NICE_PERIOD, nicetime);\n   PCPMachine_saveCPUTimePeriod(values, CPU_SYSTEM_PERIOD, systemtime);\n   PCPMachine_saveCPUTimePeriod(values, CPU_SYSTEM_ALL_PERIOD, systalltime);\n   PCPMachine_saveCPUTimePeriod(values, CPU_IDLE_ALL_PERIOD, idlealltime);\n   PCPMachine_saveCPUTimePeriod(values, CPU_IDLE_PERIOD, idletime);\n   PCPMachine_saveCPUTimePeriod(values, CPU_IOWAIT_PERIOD, iowaittime);\n   PCPMachine_saveCPUTimePeriod(values, CPU_IRQ_PERIOD, irqtime);\n   PCPMachine_saveCPUTimePeriod(values, CPU_SOFTIRQ_PERIOD, softirqtime);\n   PCPMachine_saveCPUTimePeriod(values, CPU_STEAL_PERIOD, stealtime);\n   PCPMachine_saveCPUTimePeriod(values, CPU_GUEST_PERIOD, virtalltime);\n   PCPMachine_saveCPUTimePeriod(values, CPU_TOTAL_PERIOD, totaltime);\n}\n\nstatic void PCPMachine_updateAllCPUTime(PCPMachine* this, Metric metric, CPUMetric cpumetric)\n{\n   pmAtomValue* value = &this->cpu[cpumetric];\n   if (Metric_values(metric, value, 1, PM_TYPE_U64) == NULL)\n      memset(value, 0, sizeof(pmAtomValue));\n}\n\nstatic void PCPMachine_updatePerCPUTime(PCPMachine* this, Metric metric, CPUMetric cpumetric)\n{\n   int cpus = this->super.existingCPUs;\n   if (Metric_values(metric, this->values, cpus, PM_TYPE_U64) == NULL)\n      memset(this->values, 0, cpus * sizeof(pmAtomValue));\n   for (int i = 0; i < cpus; i++)\n      this->percpu[i][cpumetric].ull = this->values[i].ull;\n}\n\nstatic void PCPMachine_updatePerCPUReal(PCPMachine* this, Metric metric, CPUMetric cpumetric)\n{\n   int cpus = this->super.existingCPUs;\n   if (Metric_values(metric, this->values, cpus, PM_TYPE_DOUBLE) == NULL)\n      memset(this->values, 0, cpus * sizeof(pmAtomValue));\n   for (int i = 0; i < cpus; i++)\n      this->percpu[i][cpumetric].d = this->values[i].d;\n}\n\nstatic inline void PCPMachine_scanZswapInfo(PCPMachine* this) {\n   pmAtomValue value;\n\n   memset(&this->zswap, 0, sizeof(ZswapStats));\n   if (Metric_values(PCP_MEM_ZSWAP, &value, 1, PM_TYPE_U64))\n      this->zswap.usedZswapComp = value.ull;\n   if (Metric_values(PCP_MEM_ZSWAPPED, &value, 1, PM_TYPE_U64))\n      this->zswap.usedZswapOrig = value.ull;\n}\n\nstatic inline void PCPMachine_scanZfsArcstats(PCPMachine* this) {\n   unsigned long long int dbufSize = 0;\n   unsigned long long int dnodeSize = 0;\n   unsigned long long int bonusSize = 0;\n   pmAtomValue value;\n\n   memset(&this->zfs, 0, sizeof(ZfsArcStats));\n   if (Metric_values(PCP_ZFS_ARC_ANON_SIZE, &value, 1, PM_TYPE_U64))\n      this->zfs.anon = value.ull / ONE_K;\n   if (Metric_values(PCP_ZFS_ARC_C_MIN, &value, 1, PM_TYPE_U64))\n      this->zfs.min = value.ull / ONE_K;\n   if (Metric_values(PCP_ZFS_ARC_C_MAX, &value, 1, PM_TYPE_U64))\n      this->zfs.max = value.ull / ONE_K;\n   if (Metric_values(PCP_ZFS_ARC_BONUS_SIZE, &value, 1, PM_TYPE_U64))\n      bonusSize = value.ull / ONE_K;\n   if (Metric_values(PCP_ZFS_ARC_DBUF_SIZE, &value, 1, PM_TYPE_U64))\n      dbufSize = value.ull / ONE_K;\n   if (Metric_values(PCP_ZFS_ARC_DNODE_SIZE, &value, 1, PM_TYPE_U64))\n      dnodeSize = value.ull / ONE_K;\n   if (Metric_values(PCP_ZFS_ARC_COMPRESSED_SIZE, &value, 1, PM_TYPE_U64))\n      this->zfs.compressed = value.ull / ONE_K;\n   if (Metric_values(PCP_ZFS_ARC_UNCOMPRESSED_SIZE, &value, 1, PM_TYPE_U64))\n      this->zfs.uncompressed = value.ull / ONE_K;\n   if (Metric_values(PCP_ZFS_ARC_HDR_SIZE, &value, 1, PM_TYPE_U64))\n      this->zfs.header = value.ull / ONE_K;\n   if (Metric_values(PCP_ZFS_ARC_MFU_SIZE, &value, 1, PM_TYPE_U64))\n      this->zfs.MFU = value.ull / ONE_K;\n   if (Metric_values(PCP_ZFS_ARC_MRU_SIZE, &value, 1, PM_TYPE_U64))\n      this->zfs.MRU = value.ull / ONE_K;\n   if (Metric_values(PCP_ZFS_ARC_SIZE, &value, 1, PM_TYPE_U64))\n      this->zfs.size = value.ull / ONE_K;\n\n   this->zfs.other = (dbufSize + dnodeSize + bonusSize) / ONE_K;\n   this->zfs.enabled = (this->zfs.size > 0);\n   this->zfs.isCompressed = (this->zfs.compressed > 0);\n}\n\nstatic void PCPMachine_scan(PCPMachine* this) {\n   Machine* super = &this->super;\n\n   PCPMachine_updateMemoryInfo(super);\n   PCPMachine_updateCPUcount(this);\n\n   PCPMachine_backupCPUTime(this->cpu);\n   PCPMachine_updateAllCPUTime(this, PCP_CPU_USER, CPU_USER_TIME);\n   PCPMachine_updateAllCPUTime(this, PCP_CPU_NICE, CPU_NICE_TIME);\n   PCPMachine_updateAllCPUTime(this, PCP_CPU_SYSTEM, CPU_SYSTEM_TIME);\n   PCPMachine_updateAllCPUTime(this, PCP_CPU_IDLE, CPU_IDLE_TIME);\n   PCPMachine_updateAllCPUTime(this, PCP_CPU_IOWAIT, CPU_IOWAIT_TIME);\n   PCPMachine_updateAllCPUTime(this, PCP_CPU_IRQ, CPU_IRQ_TIME);\n   PCPMachine_updateAllCPUTime(this, PCP_CPU_SOFTIRQ, CPU_SOFTIRQ_TIME);\n   PCPMachine_updateAllCPUTime(this, PCP_CPU_STEAL, CPU_STEAL_TIME);\n   PCPMachine_updateAllCPUTime(this, PCP_CPU_GUEST, CPU_GUEST_TIME);\n   PCPMachine_deriveCPUTime(this->cpu);\n\n   for (unsigned int i = 0; i < super->existingCPUs; i++)\n      PCPMachine_backupCPUTime(this->percpu[i]);\n   PCPMachine_updatePerCPUTime(this, PCP_PERCPU_USER, CPU_USER_TIME);\n   PCPMachine_updatePerCPUTime(this, PCP_PERCPU_NICE, CPU_NICE_TIME);\n   PCPMachine_updatePerCPUTime(this, PCP_PERCPU_SYSTEM, CPU_SYSTEM_TIME);\n   PCPMachine_updatePerCPUTime(this, PCP_PERCPU_IDLE, CPU_IDLE_TIME);\n   PCPMachine_updatePerCPUTime(this, PCP_PERCPU_IOWAIT, CPU_IOWAIT_TIME);\n   PCPMachine_updatePerCPUTime(this, PCP_PERCPU_IRQ, CPU_IRQ_TIME);\n   PCPMachine_updatePerCPUTime(this, PCP_PERCPU_SOFTIRQ, CPU_SOFTIRQ_TIME);\n   PCPMachine_updatePerCPUTime(this, PCP_PERCPU_STEAL, CPU_STEAL_TIME);\n   PCPMachine_updatePerCPUTime(this, PCP_PERCPU_GUEST, CPU_GUEST_TIME);\n   for (unsigned int i = 0; i < super->existingCPUs; i++)\n      PCPMachine_deriveCPUTime(this->percpu[i]);\n\n   if (super->settings->showCPUFrequency)\n      PCPMachine_updatePerCPUReal(this, PCP_HINV_CPUCLOCK, CPU_FREQUENCY);\n\n   PCPMachine_scanZfsArcstats(this);\n   PCPMachine_scanZswapInfo(this);\n}\n\nvoid Machine_scan(Machine* super) {\n   PCPMachine* host = (PCPMachine*) super;\n   const Settings* settings = super->settings;\n   uint32_t flags = settings->ss->flags;\n   bool flagged;\n\n   for (int metric = PCP_PROC_PID; metric < PCP_METRIC_COUNT; metric++)\n      Metric_enable(metric, true);\n\n   flagged = settings->showCPUFrequency;\n   Metric_enable(PCP_HINV_CPUCLOCK, flagged);\n   flagged = flags & PROCESS_FLAG_LINUX_CGROUP;\n   Metric_enable(PCP_PROC_CGROUPS, flagged);\n   flagged = flags & PROCESS_FLAG_LINUX_OOM;\n   Metric_enable(PCP_PROC_OOMSCORE, flagged);\n   flagged = flags & PROCESS_FLAG_LINUX_CTXT;\n   Metric_enable(PCP_PROC_VCTXSW, flagged);\n   Metric_enable(PCP_PROC_NVCTXSW, flagged);\n   flagged = flags & PROCESS_FLAG_LINUX_SECATTR;\n   Metric_enable(PCP_PROC_LABELS, flagged);\n   flagged = flags & PROCESS_FLAG_LINUX_AUTOGROUP;\n   Metric_enable(PCP_PROC_AUTOGROUP_ID, flagged);\n   Metric_enable(PCP_PROC_AUTOGROUP_NICE, flagged);\n\n   /* Sample smaps metrics on every second pass to improve performance */\n   host->smaps_flag = !!host->smaps_flag;\n   Metric_enable(PCP_PROC_SMAPS_PSS, host->smaps_flag);\n   Metric_enable(PCP_PROC_SMAPS_SWAP, host->smaps_flag);\n   Metric_enable(PCP_PROC_SMAPS_SWAPPSS, host->smaps_flag);\n\n   struct timeval timestamp;\n   if (Metric_fetch(&timestamp) != true)\n      return;\n\n   double sample = host->timestamp;\n   host->timestamp = pmtimevalToReal(&timestamp);\n   host->period = (host->timestamp - sample) * 100;\n\n   PCPMachine_scan(host);\n}\n\nMachine* Machine_new(UsersTable* usersTable, uid_t userId) {\n   PCPMachine* this = xCalloc(1, sizeof(PCPMachine));\n   Machine* super = &this->super;\n\n   Machine_init(super, usersTable, userId);\n\n   struct timeval timestamp;\n   gettimeofday(&timestamp, NULL);\n   this->timestamp = pmtimevalToReal(&timestamp);\n\n   this->sys = SYSTEM_NAME_UNKNOWN;\n   PCPMachine_updateSystemName(this);\n\n   this->cpu = xCalloc(CPU_METRIC_COUNT, sizeof(pmAtomValue));\n   PCPMachine_updateCPUcount(this);\n\n   Platform_updateTables(super);\n\n   return super;\n}\n\nvoid Machine_delete(Machine* super) {\n   PCPMachine* this = (PCPMachine*) super;\n   Machine_done(super);\n   free(this->values);\n   for (unsigned int i = 0; i < super->existingCPUs; i++)\n      free(this->percpu[i]);\n   free(this->percpu);\n   free(this->cpu);\n   free(this);\n}\n\nbool Machine_isCPUonline(const Machine* host, unsigned int id) {\n   assert(id < host->existingCPUs);\n   (void) host;\n\n   pmAtomValue value;\n   if (Metric_instance(PCP_PERCPU_SYSTEM, id, id, &value, PM_TYPE_U32))\n      return true;\n   return false;\n}\n"
  },
  {
    "path": "pcp/PCPMachine.h",
    "content": "#ifndef HEADER_PCPMachine\n#define HEADER_PCPMachine\n/*\nhtop - PCPMachine.h\n(C) 2014 Hisham H. Muhammad\nReleased under the GNU GPLv2+, see the COPYING file\nin the source distribution for its full text.\n*/\n\n#include <stdbool.h>\n#include <sys/types.h>\n\n#include \"Hashtable.h\"\n#include \"Machine.h\"\n#include \"UsersTable.h\"\n\n#include \"pcp/Platform.h\"\n#include \"linux/ZswapStats.h\"\n#include \"zfs/ZfsArcStats.h\"\n\n\ntypedef enum CPUMetric_ {\n   CPU_TOTAL_TIME,\n   CPU_USER_TIME,\n   CPU_SYSTEM_TIME,\n   CPU_SYSTEM_ALL_TIME,\n   CPU_IDLE_ALL_TIME,\n   CPU_IDLE_TIME,\n   CPU_NICE_TIME,\n   CPU_IOWAIT_TIME,\n   CPU_IRQ_TIME,\n   CPU_SOFTIRQ_TIME,\n   CPU_STEAL_TIME,\n   CPU_GUEST_TIME,\n   CPU_GUESTNICE_TIME,\n\n   CPU_TOTAL_PERIOD,\n   CPU_USER_PERIOD,\n   CPU_SYSTEM_PERIOD,\n   CPU_SYSTEM_ALL_PERIOD,\n   CPU_IDLE_ALL_PERIOD,\n   CPU_IDLE_PERIOD,\n   CPU_NICE_PERIOD,\n   CPU_IOWAIT_PERIOD,\n   CPU_IRQ_PERIOD,\n   CPU_SOFTIRQ_PERIOD,\n   CPU_STEAL_PERIOD,\n   CPU_GUEST_PERIOD,\n   CPU_GUESTNICE_PERIOD,\n\n   CPU_FREQUENCY,\n\n   CPU_METRIC_COUNT\n} CPUMetric;\n\ntypedef enum MemoryMetric_ {\n   // Linux\n   MEMORY_CLASS_USED = 0,\n   MEMORY_CLASS_SHARED = 1,\n   MEMORY_CLASS_BUFFERS = 2,\n   MEMORY_CLASS_CACHE = 3,\n   MEMORY_CLASS_COMPRESSED = 4,\n   MEMORY_CLASS_AVAILABLE = 5,\n   // Darwin\n   MEMORY_CLASS_WIRED = 0,\n   MEMORY_CLASS_SPECULATIVE = 1,\n   MEMORY_CLASS_ACTIVE = 2,\n   MEMORY_CLASS_PURGEABLE = 3,\n   MEMORY_CLASS_INACTIVE = 5,\n   // Maximum\n   MEMORY_CLASS_LIMIT = 6\n} MemoryMetric;\n\ntypedef enum SystemName_ {\n   SYSTEM_NAME_LINUX,\n   SYSTEM_NAME_DARWIN,\n   SYSTEM_NAME_UNKNOWN\n} SystemName;\n\ntypedef struct PCPMachine_ {\n   Machine super;\n   SystemName sys;\n   int smaps_flag;\n   double period;\n   double timestamp;     /* previous sample timestamp */\n\n   memory_t memValue[MEMORY_CLASS_LIMIT];\n\n   pmAtomValue* cpu;     /* aggregate values for each metric */\n   pmAtomValue** percpu; /* per-processor values for each metric */\n   pmAtomValue* values;  /* per-processor buffer for just one metric */\n\n   ZfsArcStats zfs;\n   /*ZramStats zram; -- not needed, calculated in-line in Platform.c */\n   ZswapStats zswap;\n} PCPMachine;\n\n#endif\n"
  },
  {
    "path": "pcp/PCPProcess.c",
    "content": "/*\nhtop - PCPProcess.c\n(C) 2014 Hisham H. Muhammad\n(C) 2020-2021 htop dev team\n(C) 2020-2021 Red Hat, Inc.\nReleased under the GNU GPLv2+, see the COPYING file\nin the source distribution for its full text.\n*/\n\n#include \"config.h\" // IWYU pragma: keep\n\n#include \"pcp/PCPProcess.h\"\n\n#include <math.h>\n#include <stdio.h>\n#include <stdlib.h>\n\n#include \"CRT.h\"\n#include \"Macros.h\"\n#include \"Process.h\"\n#include \"ProvideCurses.h\"\n#include \"RichString.h\"\n#include \"XUtils.h\"\n\n#include \"pcp/PCPDynamicColumn.h\"\n\n\nconst ProcessFieldData Process_fields[] = {\n   [0] = { .name = \"\", .title = NULL, .description = NULL, .flags = 0, },\n   [PID] = { .name = \"PID\", .title = \"PID\", .description = \"Process/thread ID\", .flags = 0, .pidColumn = true, },\n   [COMM] = { .name = \"Command\", .title = \"Command \", .description = \"Command line (insert as last column only)\", .flags = 0, },\n   [STATE] = { .name = \"STATE\", .title = \"S \", .description = \"Process state (S sleeping, R running, D disk, Z zombie, T traced, W paging, I idle)\", .flags = 0, },\n   [PPID] = { .name = \"PPID\", .title = \"PPID\", .description = \"Parent process ID\", .flags = 0, },\n   [PGRP] = { .name = \"PGRP\", .title = \"PGRP\", .description = \"Process group ID\", .flags = 0, },\n   [SESSION] = { .name = \"SESSION\", .title = \"SID\", .description = \"Process's session ID\", .flags = 0, },\n   [TTY] = { .name = \"TTY\", .title = \"TTY      \", .description = \"Controlling terminal\", .flags = 0, },\n   [TPGID] = { .name = \"TPGID\", .title = \"TPGID\", .description = \"Process ID of the fg process group of the controlling terminal\", .flags = 0, },\n   [MINFLT] = { .name = \"MINFLT\", .title = \"     MINFLT \", .description = \"Number of minor faults which have not required loading a memory page from disk\", .flags = 0, .defaultSortDesc = true, },\n   [CMINFLT] = { .name = \"CMINFLT\", .title = \"    CMINFLT \", .description = \"Children processes' minor faults\", .flags = 0, .defaultSortDesc = true, },\n   [MAJFLT] = { .name = \"MAJFLT\", .title = \"     MAJFLT \", .description = \"Number of major faults which have required loading a memory page from disk\", .flags = 0, .defaultSortDesc = true, },\n   [CMAJFLT] = { .name = \"CMAJFLT\", .title = \"    CMAJFLT \", .description = \"Children processes' major faults\", .flags = 0, .defaultSortDesc = true, },\n   [UTIME] = { .name = \"UTIME\", .title = \" UTIME+  \", .description = \"User CPU time - time the process spent executing in user mode\", .flags = 0, .defaultSortDesc = true, },\n   [STIME] = { .name = \"STIME\", .title = \" STIME+  \", .description = \"System CPU time - time the kernel spent running system calls for this process\", .flags = 0, .defaultSortDesc = true, },\n   [CUTIME] = { .name = \"CUTIME\", .title = \" CUTIME+ \", .description = \"Children processes' user CPU time\", .flags = 0, .defaultSortDesc = true, },\n   [CSTIME] = { .name = \"CSTIME\", .title = \" CSTIME+ \", .description = \"Children processes' system CPU time\", .flags = 0, .defaultSortDesc = true, },\n   [PRIORITY] = { .name = \"PRIORITY\", .title = \"PRI \", .description = \"Kernel's internal priority for the process\", .flags = 0, },\n   [NICE] = { .name = \"NICE\", .title = \" NI \", .description = \"Nice value (the higher the value, the more it lets other processes take priority)\", .flags = 0, },\n   [STARTTIME] = { .name = \"STARTTIME\", .title = \"START \", .description = \"Time the process was started\", .flags = 0, },\n   [ELAPSED] = { .name = \"ELAPSED\", .title = \"ELAPSED  \", .description = \"Time since the process was started\", .flags = 0, },\n   [PROCESSOR] = { .name = \"PROCESSOR\", .title = \"CPU \", .description = \"If of the CPU the process last executed on\", .flags = 0, },\n   [M_VIRT] = { .name = \"M_VIRT\", .title = \" VIRT \", .description = \"Total program size in virtual memory\", .flags = 0, .defaultSortDesc = true, },\n   [M_RESIDENT] = { .name = \"M_RESIDENT\", .title = \"  RES \", .description = \"Resident set size, size of the text and data sections, plus stack usage\", .flags = 0, .defaultSortDesc = true, },\n   [M_SHARE] = { .name = \"M_SHARE\", .title = \"  SHR \", .description = \"Size of the process's shared pages\", .flags = 0, .defaultSortDesc = true, },\n   [M_PRIV] = { .name = \"M_PRIV\", .title = \" PRIV \", .description = \"The private memory size of the process - resident set size minus shared memory\", .flags = 0, .defaultSortDesc = true, },\n   [M_TRS] = { .name = \"M_TRS\", .title = \" CODE \", .description = \"Size of the text segment of the process\", .flags = 0, .defaultSortDesc = true, },\n   [M_DRS] = { .name = \"M_DRS\", .title = \" DATA \", .description = \"Size of the data segment plus stack usage of the process\", .flags = 0, .defaultSortDesc = true, },\n   [M_LRS] = { .name = \"M_LRS\", .title = \"  LIB \", .description = \"The library size of the process (unused since Linux 2.6; always 0)\", .flags = 0, .defaultSortDesc = true, },\n   [M_DT] = { .name = \"M_DT\", .title = \" DIRTY \", .description = \"Size of the dirty pages of the process (unused since Linux 2.6; always 0)\", .flags = 0, .defaultSortDesc = true, },\n   [ST_UID] = { .name = \"ST_UID\", .title = \"UID\", .description = \"User ID of the process owner\", .flags = 0, },\n   [PERCENT_CPU] = { .name = \"PERCENT_CPU\", .title = \" CPU%\", .description = \"Percentage of the CPU time the process used in the last sampling\", .flags = 0, .defaultSortDesc = true, .autoWidth = true, .autoTitleRightAlign = true, },\n   [PERCENT_NORM_CPU] = { .name = \"PERCENT_NORM_CPU\", .title = \"NCPU%\", .description = \"Normalized percentage of the CPU time the process used in the last sampling (normalized by cpu count)\", .flags = 0, .defaultSortDesc = true, .autoWidth = true, },\n   [PERCENT_MEM] = { .name = \"PERCENT_MEM\", .title = \"MEM% \", .description = \"Percentage of the memory the process is using, based on resident memory size\", .flags = 0, .defaultSortDesc = true, },\n   [USER] = { .name = \"USER\", .title = \"USER       \", .description = \"Username of the process owner (or user ID if name cannot be determined)\", .flags = 0, },\n   [TIME] = { .name = \"TIME\", .title = \"  TIME+  \", .description = \"Total time the process has spent in user and system time\", .flags = 0, .defaultSortDesc = true, },\n   [NLWP] = { .name = \"NLWP\", .title = \"NLWP \", .description = \"Number of threads in the process\", .flags = 0, .defaultSortDesc = true, },\n   [TGID] = { .name = \"TGID\", .title = \"TGID\", .description = \"Thread group ID (i.e. process ID)\", .flags = 0, },\n   [RCHAR] = { .name = \"RCHAR\", .title = \"RCHAR \", .description = \"Number of bytes the process has read\", .flags = PROCESS_FLAG_IO, .defaultSortDesc = true, },\n   [WCHAR] = { .name = \"WCHAR\", .title = \"WCHAR \", .description = \"Number of bytes the process has written\", .flags = PROCESS_FLAG_IO, .defaultSortDesc = true, },\n   [SYSCR] = { .name = \"SYSCR\", .title = \"  READ_SYSC \", .description = \"Number of read(2) syscalls for the process\", .flags = PROCESS_FLAG_IO, .defaultSortDesc = true, },\n   [SYSCW] = { .name = \"SYSCW\", .title = \" WRITE_SYSC \", .description = \"Number of write(2) syscalls for the process\", .flags = PROCESS_FLAG_IO, .defaultSortDesc = true, },\n   [RBYTES] = { .name = \"RBYTES\", .title = \" IO_R \", .description = \"Bytes of read(2) I/O for the process\", .flags = PROCESS_FLAG_IO, .defaultSortDesc = true, },\n   [WBYTES] = { .name = \"WBYTES\", .title = \" IO_W \", .description = \"Bytes of write(2) I/O for the process\", .flags = PROCESS_FLAG_IO, .defaultSortDesc = true, },\n   [CNCLWB] = { .name = \"CNCLWB\", .title = \" IO_C \", .description = \"Bytes of cancelled write(2) I/O\", .flags = PROCESS_FLAG_IO, .defaultSortDesc = true, },\n   [IO_READ_RATE] = { .name = \"IO_READ_RATE\", .title = \" DISK READ \", .description = \"The I/O rate of read(2) in bytes per second for the process\", .flags = PROCESS_FLAG_IO, .defaultSortDesc = true, },\n   [IO_WRITE_RATE] = { .name = \"IO_WRITE_RATE\", .title = \" DISK WRITE \", .description = \"The I/O rate of write(2) in bytes per second for the process\", .flags = PROCESS_FLAG_IO, .defaultSortDesc = true, },\n   [IO_RATE] = { .name = \"IO_RATE\", .title = \"   DISK R/W \", .description = \"Total I/O rate in bytes per second\", .flags = PROCESS_FLAG_IO, .defaultSortDesc = true, },\n   [CGROUP] = { .name = \"CGROUP\", .title = \"CGROUP (raw)                        \", .description = \"Which cgroup the process is in\", .flags = PROCESS_FLAG_LINUX_CGROUP, },\n   [CCGROUP] = { .name = \"CCGROUP\", .title = \"CGROUP (compressed)                 \", .description = \"Which cgroup the process is in (condensed to essentials)\", .flags = PROCESS_FLAG_LINUX_CGROUP, },\n   [CONTAINER] = { .name = \"CONTAINER\", .title = \"CONTAINER                           \", .description = \"Name of the container the process is in (guessed by heuristics)\", .flags = PROCESS_FLAG_LINUX_CGROUP, },\n   [OOM] = { .name = \"OOM\", .title = \" OOM \", .description = \"OOM (Out-of-Memory) killer score\", .flags = PROCESS_FLAG_LINUX_OOM, .defaultSortDesc = true, },\n   [PERCENT_CPU_DELAY] = { .name = \"PERCENT_CPU_DELAY\", .title = \"CPUD% \", .description = \"CPU delay %\", .flags = 0, .defaultSortDesc = true, },\n   [PERCENT_IO_DELAY] = { .name = \"PERCENT_IO_DELAY\", .title = \" IOD% \", .description = \"Block I/O delay %\", .flags = 0, .defaultSortDesc = true, },\n   [PERCENT_SWAP_DELAY] = { .name = \"PERCENT_SWAP_DELAY\", .title = \"SWAPD% \", .description = \"Swapin delay %\", .flags = 0, .defaultSortDesc = true, },\n   [M_PSS] = { .name = \"M_PSS\", .title = \"  PSS \", .description = \"proportional set size, same as M_RESIDENT but each page is divided by the number of processes sharing it.\", .flags = PROCESS_FLAG_LINUX_SMAPS, .defaultSortDesc = true, },\n   [M_SWAP] = { .name = \"M_SWAP\", .title = \" SWAP \", .description = \"Size of the process's swapped pages\", .flags = PROCESS_FLAG_LINUX_SMAPS, .defaultSortDesc = true, },\n   [M_PSSWP] = { .name = \"M_PSSWP\", .title = \" PSSWP \", .description = \"shows proportional swap share of this mapping, Unlike \\\"Swap\\\", this does not take into account swapped out page of underlying shmem objects.\", .flags = PROCESS_FLAG_LINUX_SMAPS, .defaultSortDesc = true, },\n   [CTXT] = { .name = \"CTXT\", .title = \" CTXT \", .description = \"Context switches (incremental sum of voluntary_ctxt_switches and nonvoluntary_ctxt_switches)\", .flags = PROCESS_FLAG_LINUX_CTXT, .defaultSortDesc = true, },\n   [SECATTR] = { .name = \"SECATTR\", .title = \" Security Attribute \", .description = \"Security attribute of the process (e.g. SELinux or AppArmor)\", .flags = PROCESS_FLAG_LINUX_SECATTR, },\n   [PROC_COMM] = { .name = \"COMM\", .title = \"COMM            \", .description = \"comm string of the process\", .flags = 0, },\n   [PROC_EXE] = { .name = \"EXE\", .title = \"EXE             \", .description = \"Basename of exe of the process\", .flags = 0, },\n   [CWD] = { .name = \"CWD\", .title = \"CWD                       \", .description = \"The current working directory of the process\", .flags = PROCESS_FLAG_CWD, },\n   [AUTOGROUP_ID] = { .name = \"AUTOGROUP_ID\", .title = \"AGRP\", .description = \"The autogroup identifier of the process\", .flags = PROCESS_FLAG_LINUX_AUTOGROUP, },\n   [AUTOGROUP_NICE] = { .name = \"AUTOGROUP_NICE\", .title = \" ANI\", .description = \"Nice value (the higher the value, the more other processes take priority) associated with the process autogroup\", .flags = PROCESS_FLAG_LINUX_AUTOGROUP, },\n};\n\nProcess* PCPProcess_new(const Machine* host) {\n   PCPProcess* this = xCalloc(1, sizeof(PCPProcess));\n   Object_setClass(this, Class(PCPProcess));\n   Process_init(&this->super, host);\n   return (Process*)this;\n}\n\nvoid Process_delete(Object* cast) {\n   PCPProcess* this = (PCPProcess*) cast;\n   Process_done((Process*)cast);\n   free(this->cgroup_short);\n   free(this->cgroup);\n   free(this->secattr);\n   free(this);\n}\n\nstatic void PCPProcess_printDelay(float delay_percent, char* buffer, size_t n) {\n   if (isNonnegative(delay_percent)) {\n      xSnprintf(buffer, n, \"%4.1f  \", delay_percent);\n   } else {\n      xSnprintf(buffer, n, \" N/A  \");\n   }\n}\n\nstatic double PCPProcess_totalIORate(const PCPProcess* pp) {\n   double totalRate = NAN;\n   if (isNonnegative(pp->io_rate_read_bps)) {\n      totalRate = pp->io_rate_read_bps;\n      if (isNonnegative(pp->io_rate_write_bps)) {\n         totalRate += pp->io_rate_write_bps;\n      }\n   } else if (isNonnegative(pp->io_rate_write_bps)) {\n      totalRate = pp->io_rate_write_bps;\n   }\n   return totalRate;\n}\n\nstatic void PCPProcess_rowWriteField(const Row* super, RichString* str, ProcessField field) {\n   const PCPProcess* pp = (const PCPProcess*) super;\n\n   bool coloring = super->host->settings->highlightMegabytes;\n   char buffer[256]; buffer[255] = '\\0';\n   int attr = CRT_colors[DEFAULT_COLOR];\n   size_t n = sizeof(buffer) - 1;\n\n   switch ((int)field) {\n   case CMINFLT: Row_printCount(str, pp->cminflt, coloring); return;\n   case CMAJFLT: Row_printCount(str, pp->cmajflt, coloring); return;\n   case M_DRS: Row_printBytes(str, pp->m_drs, coloring); return;\n   case M_DT: Row_printBytes(str, pp->m_dt, coloring); return;\n   case M_LRS: Row_printBytes(str, pp->m_lrs, coloring); return;\n   case M_TRS: Row_printBytes(str, pp->m_trs, coloring); return;\n   case M_SHARE: Row_printBytes(str, pp->m_share, coloring); return;\n   case M_PRIV: Row_printBytes(str, pp->m_priv, coloring); return;\n   case M_PSS: Row_printKBytes(str, pp->m_pss, coloring); return;\n   case M_SWAP: Row_printKBytes(str, pp->m_swap, coloring); return;\n   case M_PSSWP: Row_printKBytes(str, pp->m_psswp, coloring); return;\n   case UTIME: Row_printTime(str, pp->utime, coloring); return;\n   case STIME: Row_printTime(str, pp->stime, coloring); return;\n   case CUTIME: Row_printTime(str, pp->cutime, coloring); return;\n   case CSTIME: Row_printTime(str, pp->cstime, coloring); return;\n   case RCHAR:  Row_printBytes(str, pp->io_rchar, coloring); return;\n   case WCHAR:  Row_printBytes(str, pp->io_wchar, coloring); return;\n   case SYSCR:  Row_printCount(str, pp->io_syscr, coloring); return;\n   case SYSCW:  Row_printCount(str, pp->io_syscw, coloring); return;\n   case RBYTES: Row_printBytes(str, pp->io_read_bytes, coloring); return;\n   case WBYTES: Row_printBytes(str, pp->io_write_bytes, coloring); return;\n   case CNCLWB: Row_printBytes(str, pp->io_cancelled_write_bytes, coloring); return;\n   case IO_READ_RATE:  Row_printRate(str, pp->io_rate_read_bps, coloring); return;\n   case IO_WRITE_RATE: Row_printRate(str, pp->io_rate_write_bps, coloring); return;\n   case IO_RATE: Row_printRate(str, PCPProcess_totalIORate(pp), coloring); return;\n   case CGROUP: xSnprintf(buffer, n, \"%-35.35s \", pp->cgroup ? pp->cgroup : \"N/A\"); break;\n   case CCGROUP: xSnprintf(buffer, n, \"%-35.35s \", pp->cgroup_short ? pp->cgroup_short : (pp->cgroup ? pp->cgroup : \"N/A\")); break;\n   case CONTAINER: xSnprintf(buffer, n, \"%-35.35s \", pp->container_short ? pp->container_short : \"N/A\"); break;\n   case OOM: xSnprintf(buffer, n, \"%4u \", pp->oom); break;\n   case PERCENT_CPU_DELAY:\n      PCPProcess_printDelay(pp->cpu_delay_percent, buffer, n);\n      break;\n   case PERCENT_IO_DELAY:\n      PCPProcess_printDelay(pp->blkio_delay_percent, buffer, n);\n      break;\n   case PERCENT_SWAP_DELAY:\n      PCPProcess_printDelay(pp->swapin_delay_percent, buffer, n);\n      break;\n   case CTXT:\n      if (pp->ctxt_diff > 1000) {\n         attr |= A_BOLD;\n      }\n      xSnprintf(buffer, n, \"%5lu \", pp->ctxt_diff);\n      break;\n   case SECATTR: snprintf(buffer, n, \"%-30s   \", pp->secattr ? pp->secattr : \"?\"); break;\n   case AUTOGROUP_ID:\n      if (pp->autogroup_id != -1) {\n         xSnprintf(buffer, n, \"%4ld \", pp->autogroup_id);\n      } else {\n         attr = CRT_colors[PROCESS_SHADOW];\n         xSnprintf(buffer, n, \" N/A \");\n      }\n      break;\n   case AUTOGROUP_NICE:\n      if (pp->autogroup_id != -1) {\n         xSnprintf(buffer, n, \"%3d \", pp->autogroup_nice);\n         attr = pp->autogroup_nice < 0 ? CRT_colors[PROCESS_HIGH_PRIORITY]\n              : pp->autogroup_nice > 0 ? CRT_colors[PROCESS_LOW_PRIORITY]\n              : CRT_colors[PROCESS_SHADOW];\n      } else {\n         attr = CRT_colors[PROCESS_SHADOW];\n         xSnprintf(buffer, n, \"N/A \");\n      }\n      break;\n   default:\n      Process_writeField(&pp->super, str, field);\n      return;\n   }\n\n   RichString_appendWide(str, attr, buffer);\n}\n\nstatic int PCPProcess_compareByKey(const Process* v1, const Process* v2, ProcessField key) {\n   const PCPProcess* p1 = (const PCPProcess*)v1;\n   const PCPProcess* p2 = (const PCPProcess*)v2;\n\n   switch (key) {\n   case M_DRS:\n      return SPACESHIP_NUMBER(p1->m_drs, p2->m_drs);\n   case M_DT:\n      return SPACESHIP_NUMBER(p1->m_dt, p2->m_dt);\n   case M_LRS:\n      return SPACESHIP_NUMBER(p1->m_lrs, p2->m_lrs);\n   case M_TRS:\n      return SPACESHIP_NUMBER(p1->m_trs, p2->m_trs);\n   case M_SHARE:\n      return SPACESHIP_NUMBER(p1->m_share, p2->m_share);\n   case M_PRIV:\n      return SPACESHIP_NUMBER(p1->m_priv, p2->m_priv);\n   case M_PSS:\n      return SPACESHIP_NUMBER(p1->m_pss, p2->m_pss);\n   case M_SWAP:\n      return SPACESHIP_NUMBER(p1->m_swap, p2->m_swap);\n   case M_PSSWP:\n      return SPACESHIP_NUMBER(p1->m_psswp, p2->m_psswp);\n   case UTIME:\n      return SPACESHIP_NUMBER(p1->utime, p2->utime);\n   case CUTIME:\n      return SPACESHIP_NUMBER(p1->cutime, p2->cutime);\n   case STIME:\n      return SPACESHIP_NUMBER(p1->stime, p2->stime);\n   case CSTIME:\n      return SPACESHIP_NUMBER(p1->cstime, p2->cstime);\n   case RCHAR:\n      return SPACESHIP_NUMBER(p1->io_rchar, p2->io_rchar);\n   case WCHAR:\n      return SPACESHIP_NUMBER(p1->io_wchar, p2->io_wchar);\n   case SYSCR:\n      return SPACESHIP_NUMBER(p1->io_syscr, p2->io_syscr);\n   case SYSCW:\n      return SPACESHIP_NUMBER(p1->io_syscw, p2->io_syscw);\n   case RBYTES:\n      return SPACESHIP_NUMBER(p1->io_read_bytes, p2->io_read_bytes);\n   case WBYTES:\n      return SPACESHIP_NUMBER(p1->io_write_bytes, p2->io_write_bytes);\n   case CNCLWB:\n      return SPACESHIP_NUMBER(p1->io_cancelled_write_bytes, p2->io_cancelled_write_bytes);\n   case IO_READ_RATE:\n      return compareRealNumbers(p1->io_rate_read_bps, p2->io_rate_read_bps);\n   case IO_WRITE_RATE:\n      return compareRealNumbers(p1->io_rate_write_bps, p2->io_rate_write_bps);\n   case IO_RATE:\n      return compareRealNumbers(PCPProcess_totalIORate(p1), PCPProcess_totalIORate(p2));\n   case CGROUP:\n      return SPACESHIP_NULLSTR(p1->cgroup, p2->cgroup);\n   case CCGROUP:\n      return SPACESHIP_NULLSTR(p1->cgroup_short, p2->cgroup_short);\n   case CONTAINER:\n      return SPACESHIP_NULLSTR(p1->container_short, p2->container_short);\n   case OOM:\n      return SPACESHIP_NUMBER(p1->oom, p2->oom);\n   case PERCENT_CPU_DELAY:\n      return compareRealNumbers(p1->cpu_delay_percent, p2->cpu_delay_percent);\n   case PERCENT_IO_DELAY:\n      return compareRealNumbers(p1->blkio_delay_percent, p2->blkio_delay_percent);\n   case PERCENT_SWAP_DELAY:\n      return compareRealNumbers(p1->swapin_delay_percent, p2->swapin_delay_percent);\n   case CTXT:\n      return SPACESHIP_NUMBER(p1->ctxt_diff, p2->ctxt_diff);\n   case SECATTR:\n      return SPACESHIP_NULLSTR(p1->secattr, p2->secattr);\n   case AUTOGROUP_ID:\n      return SPACESHIP_NUMBER(p1->autogroup_id, p2->autogroup_id);\n   case AUTOGROUP_NICE:\n      return SPACESHIP_NUMBER(p1->autogroup_nice, p2->autogroup_nice);\n   default:\n      if (key < LAST_PROCESSFIELD)\n         return Process_compareByKey_Base(v1, v2, key);\n      return PCPDynamicColumn_compareByKey(p1, p2, key);\n   }\n}\n\nconst ProcessClass PCPProcess_class = {\n   .super = {\n      .super = {\n         .extends = Class(Process),\n         .display = Row_display,\n         .delete = Process_delete,\n         .compare = Process_compare\n      },\n      .isHighlighted = Process_rowIsHighlighted,\n      .isVisible = Process_rowIsVisible,\n      .matchesFilter = Process_rowMatchesFilter,\n      .compareByParent = Process_compareByParent,\n      .sortKeyString = Process_rowGetSortKey,\n      .writeField = PCPProcess_rowWriteField,\n   },\n   .compareByKey = PCPProcess_compareByKey,\n};\n"
  },
  {
    "path": "pcp/PCPProcess.h",
    "content": "#ifndef HEADER_PCPProcess\n#define HEADER_PCPProcess\n/*\nhtop - PCPProcess.h\n(C) 2014 Hisham H. Muhammad\n(C) 2020 htop dev team\n(C) 2020-2021 Red Hat, Inc.  All Rights Reserved.\nReleased under the GNU GPLv2+, see the COPYING file\nin the source distribution for its full text.\n*/\n\n#include <stdbool.h>\n\n#include \"Machine.h\"\n#include \"Object.h\"\n#include \"Process.h\"\n\n\n#define PROCESS_FLAG_LINUX_CGROUP    0x00000800\n#define PROCESS_FLAG_LINUX_OOM       0x00001000\n#define PROCESS_FLAG_LINUX_SMAPS     0x00002000\n#define PROCESS_FLAG_LINUX_CTXT      0x00004000\n#define PROCESS_FLAG_LINUX_SECATTR   0x00008000\n#define PROCESS_FLAG_LINUX_AUTOGROUP 0x00080000\n\ntypedef struct PCPProcess_ {\n   Process super;\n\n   /* default result offset to use for searching proc metrics */\n   unsigned int offset;\n\n   unsigned long int cminflt;\n   unsigned long int cmajflt;\n   unsigned long long int utime;\n   unsigned long long int stime;\n   unsigned long long int cutime;\n   unsigned long long int cstime;\n   long m_share;\n   long m_priv;\n   long m_pss;\n   long m_swap;\n   long m_psswp;\n   long m_trs;\n   long m_drs;\n   long m_lrs;\n   long m_dt;\n\n   /* Data read (in kilobytes) */\n   unsigned long long io_rchar;\n\n   /* Data written (in kilobytes) */\n   unsigned long long io_wchar;\n\n   /* Number of read(2) syscalls */\n   unsigned long long io_syscr;\n\n   /* Number of write(2) syscalls */\n   unsigned long long io_syscw;\n\n   /* Storage data read (in kilobytes) */\n   unsigned long long io_read_bytes;\n\n   /* Storage data written (in kilobytes) */\n   unsigned long long io_write_bytes;\n\n   /* Storage data cancelled (in kilobytes) */\n   unsigned long long io_cancelled_write_bytes;\n\n   /* Point in time of last io scan (in seconds elapsed since the Epoch) */\n   unsigned long long io_last_scan_time;\n\n   double io_rate_read_bps;\n   double io_rate_write_bps;\n   char* cgroup;\n   char* cgroup_short;\n   char* container_short;\n   long int autogroup_id;\n   int autogroup_nice;\n   unsigned int oom;\n   unsigned long long int delay_read_time;\n   unsigned long long cpu_delay_total;\n   unsigned long long blkio_delay_total;\n   unsigned long long swapin_delay_total;\n   float cpu_delay_percent;\n   float blkio_delay_percent;\n   float swapin_delay_percent;\n   unsigned long ctxt_total;\n   unsigned long ctxt_diff;\n   char* secattr;\n   unsigned long long int last_mlrs_calctime;\n} PCPProcess;\n\nextern const ProcessFieldData Process_fields[LAST_PROCESSFIELD];\n\nextern const ProcessClass PCPProcess_class;\n\nProcess* PCPProcess_new(const Machine* host);\n\nvoid Process_delete(Object* cast);\n\nbool Process_isThread(const Process* this);\n\n#endif\n"
  },
  {
    "path": "pcp/PCPProcessTable.c",
    "content": "/*\nhtop - PCPProcessTable.c\n(C) 2014 Hisham H. Muhammad\n(C) 2020-2021 htop dev team\n(C) 2020-2021 Red Hat, Inc.\nReleased under the GNU GPLv2+, see the COPYING file\nin the source distribution for its full text.\n*/\n\n#include \"config.h\" // IWYU pragma: keep\n\n#include \"pcp/PCPProcessTable.h\"\n\n#include <assert.h>\n#include <limits.h>\n#include <math.h>\n#include <stdlib.h>\n#include <string.h>\n#include <sys/time.h>\n\n#include \"Machine.h\"\n#include \"Macros.h\"\n#include \"Object.h\"\n#include \"Platform.h\"\n#include \"Process.h\"\n#include \"Settings.h\"\n#include \"XUtils.h\"\n\n#include \"linux/CGroupUtils.h\"\n#include \"pcp/Metric.h\"\n#include \"pcp/PCPMachine.h\"\n#include \"pcp/PCPProcess.h\"\n\n\nProcessTable* ProcessTable_new(Machine* host, Hashtable* pidMatchList) {\n   PCPProcessTable* this = xCalloc(1, sizeof(PCPProcessTable));\n   Object_setClass(this, Class(ProcessTable));\n\n   ProcessTable* super = &this->super;\n   ProcessTable_init(super, Class(PCPProcess), host, pidMatchList);\n\n   return super;\n}\n\nvoid ProcessTable_delete(Object* cast) {\n   PCPProcessTable* this = (PCPProcessTable*) cast;\n   ProcessTable_done(&this->super);\n   free(this);\n}\n\nstatic inline long Metric_instance_s32(int metric, int pid, int offset, long fallback) {\n   pmAtomValue value;\n   if (Metric_instance(metric, pid, offset, &value, PM_TYPE_32))\n      return value.l;\n   return fallback;\n}\n\nstatic inline long long Metric_instance_s64(int metric, int pid, int offset, long long fallback) {\n   pmAtomValue value;\n   if (Metric_instance(metric, pid, offset, &value, PM_TYPE_64))\n      return value.l;\n   return fallback;\n}\n\nstatic inline unsigned long Metric_instance_u32(int metric, int pid, int offset, unsigned long fallback) {\n   pmAtomValue value;\n   if (Metric_instance(metric, pid, offset, &value, PM_TYPE_U32))\n      return value.ul;\n   return fallback;\n}\n\nstatic inline unsigned long long Metric_instance_u64(int metric, int pid, int offset, unsigned long long fallback) {\n   pmAtomValue value;\n   if (Metric_instance(metric, pid, offset, &value, PM_TYPE_U64))\n      return value.ull;\n   return fallback;\n}\n\nstatic inline unsigned long long Metric_instance_time(int metric, int pid, int offset) {\n   pmAtomValue value;\n   if (Metric_instance_milliseconds(metric, pid, offset, &value))\n      return value.ull / 10; /* centiseconds used by callers */\n   return 0;\n}\n\nstatic inline unsigned long long Metric_instance_ONE_K(int metric, int pid, int offset) {\n   pmAtomValue value;\n   if (Metric_instance_kibibytes(metric, pid, offset, &value))\n      return value.ull;\n   return ULLONG_MAX;\n}\n\nstatic inline char Metric_instance_char(int metric, int pid, int offset, char fallback) {\n   pmAtomValue value;\n   if (Metric_instance(metric, pid, offset, &value, PM_TYPE_STRING)) {\n      char uchar = value.cp[0];\n      free(value.cp);\n      return uchar;\n   }\n   return fallback;\n}\n\nstatic char* setUser(UsersTable* this, unsigned int uid, int pid, int offset) {\n   char* name = Hashtable_get(this->users, uid);\n   if (name)\n      return name;\n\n   pmAtomValue value;\n   if (Metric_instance(PCP_PROC_ID_USER, pid, offset, &value, PM_TYPE_STRING)) {\n      Hashtable_put(this->users, uid, value.cp);\n      name = value.cp;\n   }\n   return name;\n}\n\nstatic inline ProcessState PCPProcessTable_getProcessState(char state) {\n   switch (state) {\n      case '?': return UNKNOWN;\n      case 'R': return RUNNING;\n      case 'W': return WAITING;\n      case 'D': return UNINTERRUPTIBLE_WAIT;\n      case 'P': return PAGING;\n      case 'T': return STOPPED;\n      case 't': return TRACED;\n      case 'Z': return ZOMBIE;\n      case 'X': return DEFUNCT;\n      case 'I': return IDLE;\n      case 'S': return SLEEPING;\n      default: return UNKNOWN;\n   }\n}\n\nstatic void PCPProcessTable_updateID(Process* process, int pid, int offset) {\n   Process_setThreadGroup(process, (pid_t) Metric_instance_u32(PCP_PROC_TGID, pid, offset, 1));\n   Process_setParent(process, (pid_t) Metric_instance_u32(PCP_PROC_PPID, pid, offset, 1));\n   process->state = PCPProcessTable_getProcessState(Metric_instance_char(PCP_PROC_STATE, pid, offset, '?'));\n}\n\nstatic void PCPProcessTable_updateInfo(PCPProcess* pp, int pid, int offset, char* command, size_t commLen) {\n   Process* process = &pp->super;\n   pmAtomValue value;\n\n   if (!Metric_instance(PCP_PROC_CMD, pid, offset, &value, PM_TYPE_STRING))\n      value.cp = xStrdup(\"<unknown>\");\n   String_safeStrncpy(command, value.cp, commLen);\n   free(value.cp);\n\n   process->pgrp = (int) Metric_instance_u32(PCP_PROC_PGRP, pid, offset, 0);\n   process->session = (int) Metric_instance_u32(PCP_PROC_SESSION, pid, offset, 0);\n   process->tty_nr = Metric_instance_u32(PCP_PROC_TTY, pid, offset, 0);\n   process->tpgid = (int) Metric_instance_u32(PCP_PROC_TTYPGRP, pid, offset, 0);\n   process->minflt = Metric_instance_u32(PCP_PROC_MINFLT, pid, offset, 0);\n   pp->cminflt = Metric_instance_u32(PCP_PROC_CMINFLT, pid, offset, 0);\n   process->majflt = Metric_instance_u32(PCP_PROC_MAJFLT, pid, offset, 0);\n   pp->cmajflt = Metric_instance_u32(PCP_PROC_CMAJFLT, pid, offset, 0);\n   pp->utime = Metric_instance_time(PCP_PROC_UTIME, pid, offset);\n   pp->stime = Metric_instance_time(PCP_PROC_STIME, pid, offset);\n   pp->cutime = Metric_instance_time(PCP_PROC_CUTIME, pid, offset);\n   pp->cstime = Metric_instance_time(PCP_PROC_CSTIME, pid, offset);\n   process->priority = Metric_instance_u32(PCP_PROC_PRIORITY, pid, offset, 0);\n   process->nice = (int) Metric_instance_s32(PCP_PROC_NICE, pid, offset, 0);\n   process->nlwp = Metric_instance_u32(PCP_PROC_THREADS, pid, offset, 0);\n   process->starttime_ctime = Metric_instance_time(PCP_PROC_STARTTIME, pid, offset);\n   process->processor = (int) Metric_instance_u32(PCP_PROC_PROCESSOR, pid, offset, 0);\n\n   process->time = pp->utime + pp->stime;\n}\n\nstatic void PCPProcessTable_updateIO(PCPProcess* pp, int pid, int offset, unsigned long long now) {\n   pmAtomValue value;\n\n   pp->io_rchar = Metric_instance_ONE_K(PCP_PROC_IO_RCHAR, pid, offset);\n   pp->io_wchar = Metric_instance_ONE_K(PCP_PROC_IO_WCHAR, pid, offset);\n   pp->io_syscr = Metric_instance_u64(PCP_PROC_IO_SYSCR, pid, offset, ULLONG_MAX);\n   pp->io_syscw = Metric_instance_u64(PCP_PROC_IO_SYSCW, pid, offset, ULLONG_MAX);\n   pp->io_cancelled_write_bytes = Metric_instance_ONE_K(PCP_PROC_IO_CANCELLED, pid, offset);\n\n   if (Metric_instance(PCP_PROC_IO_READB, pid, offset, &value, PM_TYPE_U64) &&\n         (now - pp->io_last_scan_time != 0)) {\n      unsigned long long last_read = pp->io_read_bytes;\n      pp->io_read_bytes = value.ull / ONE_K;\n      pp->io_rate_read_bps = ONE_K * (pp->io_read_bytes - last_read) /\n                                     (now - pp->io_last_scan_time);\n   } else {\n      pp->io_read_bytes = ULLONG_MAX;\n      pp->io_rate_read_bps = NAN;\n   }\n\n   if (Metric_instance(PCP_PROC_IO_WRITEB, pid, offset, &value, PM_TYPE_U64) &&\n         (now - pp->io_last_scan_time != 0)) {\n      unsigned long long last_write = pp->io_write_bytes;\n      pp->io_write_bytes = value.ull;\n      pp->io_rate_write_bps = ONE_K * (pp->io_write_bytes - last_write) /\n                                      (now - pp->io_last_scan_time);\n   } else {\n      pp->io_write_bytes = ULLONG_MAX;\n      pp->io_rate_write_bps = NAN;\n   }\n\n   pp->io_last_scan_time = now;\n}\n\nstatic void PCPProcessTable_updateMemory(PCPProcess* pp, int pid, int offset) {\n   pp->super.m_virt = Metric_instance_u32(PCP_PROC_MEM_SIZE, pid, offset, 0);\n   pp->super.m_resident = Metric_instance_u32(PCP_PROC_MEM_RSS, pid, offset, 0);\n   pp->m_share = Metric_instance_u32(PCP_PROC_MEM_SHARE, pid, offset, 0);\n   pp->m_priv = pp->super.m_resident - pp->m_share;\n   pp->m_trs = Metric_instance_u32(PCP_PROC_MEM_TEXTRS, pid, offset, 0);\n   pp->m_lrs = Metric_instance_u32(PCP_PROC_MEM_LIBRS, pid, offset, 0);\n   pp->m_drs = Metric_instance_u32(PCP_PROC_MEM_DATRS, pid, offset, 0);\n   pp->m_dt = Metric_instance_u32(PCP_PROC_MEM_DIRTY, pid, offset, 0);\n}\n\nstatic void PCPProcessTable_updateSmaps(PCPProcess* pp, pid_t pid, int offset) {\n   pp->m_pss = Metric_instance_u64(PCP_PROC_SMAPS_PSS, pid, offset, 0);\n   pp->m_swap = Metric_instance_u64(PCP_PROC_SMAPS_SWAP, pid, offset, 0);\n   pp->m_psswp = Metric_instance_u64(PCP_PROC_SMAPS_SWAPPSS, pid, offset, 0);\n}\n\nstatic void PCPProcessTable_readOomData(PCPProcess* pp, int pid, int offset) {\n   pp->oom = (unsigned int) Metric_instance_u32(PCP_PROC_OOMSCORE, pid, offset, 0);\n}\n\nstatic void PCPProcessTable_readAutogroup(PCPProcess* pp, int pid, int offset) {\n   pp->autogroup_id = Metric_instance_s64(PCP_PROC_AUTOGROUP_ID, pid, offset, -1);\n   pp->autogroup_nice = (int) Metric_instance_s32(PCP_PROC_AUTOGROUP_NICE, pid, offset, 0);\n}\n\nstatic void PCPProcessTable_readCtxtData(PCPProcess* pp, int pid, int offset) {\n   pmAtomValue value;\n   unsigned long ctxt = 0;\n\n   if (Metric_instance(PCP_PROC_VCTXSW, pid, offset, &value, PM_TYPE_U32))\n      ctxt += value.ul;\n   if (Metric_instance(PCP_PROC_NVCTXSW, pid, offset, &value, PM_TYPE_U32))\n      ctxt += value.ul;\n\n   pp->ctxt_diff = ctxt > pp->ctxt_total ? ctxt - pp->ctxt_total : 0;\n   pp->ctxt_total = ctxt;\n}\n\nstatic char* setString(Metric metric, int pid, int offset, char* string) {\n   if (string)\n      free(string);\n   pmAtomValue value;\n   if (Metric_instance(metric, pid, offset, &value, PM_TYPE_STRING))\n      string = value.cp;\n   else\n      string = NULL;\n   return string;\n}\n\nstatic void PCPProcessTable_updateTTY(Process* process, int pid, int offset) {\n   process->tty_name = setString(PCP_PROC_TTYNAME, pid, offset, process->tty_name);\n}\n\nstatic void PCPProcessTable_readCGroups(PCPProcess* pp, int pid, int offset) {\n   pp->cgroup = setString(PCP_PROC_CGROUPS, pid, offset, pp->cgroup);\n\n   if (pp->cgroup) {\n      char* cgroup_short = CGroup_filterName(pp->cgroup);\n      if (cgroup_short) {\n         Row_updateFieldWidth(CCGROUP, strlen(cgroup_short));\n         free_and_xStrdup(&pp->cgroup_short, cgroup_short);\n         free(cgroup_short);\n      } else {\n         //CCGROUP is alias to normal CGROUP if shortening fails\n         Row_updateFieldWidth(CCGROUP, strlen(pp->cgroup));\n         free(pp->cgroup_short);\n         pp->cgroup_short = NULL;\n      }\n\n      char* container_short = CGroup_filterName(pp->cgroup);\n      if (container_short) {\n         Row_updateFieldWidth(CONTAINER, strlen(container_short));\n         free_and_xStrdup(&pp->container_short, container_short);\n         free(container_short);\n      } else {\n         Row_updateFieldWidth(CONTAINER, strlen(\"N/A\"));\n         free(pp->container_short);\n         pp->container_short = NULL;\n      }\n   } else {\n      free(pp->cgroup_short);\n      pp->cgroup_short = NULL;\n\n      free(pp->container_short);\n      pp->container_short = NULL;\n   }\n}\n\nstatic void PCPProcessTable_readSecattrData(PCPProcess* pp, int pid, int offset) {\n   pp->secattr = setString(PCP_PROC_LABELS, pid, offset, pp->secattr);\n}\n\nstatic void PCPProcessTable_readCwd(PCPProcess* pp, int pid, int offset) {\n   pp->super.procCwd = setString(PCP_PROC_CWD, pid, offset, pp->super.procCwd);\n}\n\nstatic void PCPProcessTable_updateUsername(Process* process, int pid, int offset, UsersTable* users) {\n   process->st_uid = (uid_t) Metric_instance_u32(PCP_PROC_ID_UID, pid, offset, 0);\n   process->user = setUser(users, process->st_uid, pid, offset);\n}\n\nstatic void PCPProcessTable_updateCmdline(Process* process, int pid, int offset, const char* comm) {\n   pmAtomValue value;\n   if (!Metric_instance(PCP_PROC_PSARGS, pid, offset, &value, PM_TYPE_STRING)) {\n      if (process->state != ZOMBIE)\n         process->isKernelThread = true;\n      Process_updateCmdline(process, NULL, 0, 0);\n      return;\n   }\n\n   char* command = value.cp;\n   size_t length = strlen(command);\n   if (command[0] != '(') {\n      process->isKernelThread = false;\n   } else {\n      if (command[length - 1] == ')')\n         command[--length] = '\\0';\n      ++command;\n      --length;\n      process->isKernelThread = true;\n   }\n\n   size_t tokenEnd = 0;\n   size_t tokenStart = 0;\n   bool argSepSpace = false;\n\n   for (size_t i = 0; i < length; i++) {\n      /* htop considers the next character after the last / that is before\n       * basenameOffset, as the start of the basename in cmdline - see\n       * Process_writeCommand */\n      if (command[i] == '/')\n         tokenStart = i + 1;\n      /* special-case arguments for problematic situations like \"find /\" */\n      if (command[i] <= ' ')\n         argSepSpace = true;\n   }\n   tokenEnd = length;\n   if (argSepSpace)\n      tokenStart = 0;\n\n   Process_updateCmdline(process, command, tokenStart, tokenEnd);\n   free(value.cp);\n\n   Process_updateComm(process, comm);\n\n   if (Metric_instance(PCP_PROC_EXE, pid, offset, &value, PM_TYPE_STRING)) {\n      Process_updateExe(process, value.cp[0] ? value.cp : NULL);\n      free(value.cp);\n   }\n}\n\nstatic bool PCPProcessTable_updateProcesses(PCPProcessTable* this) {\n   ProcessTable* pt = (ProcessTable*) this;\n   Machine* host = pt->super.host;\n   PCPMachine* phost = (PCPMachine*) host;\n\n   const Settings* settings = host->settings;\n   bool hideKernelThreads = settings->hideKernelThreads;\n   bool hideUserlandThreads = settings->hideUserlandThreads;\n   uint32_t flags = settings->ss->flags;\n\n   unsigned long long now = (unsigned long long)(phost->timestamp * 1000);\n   int pid = -1, offset = -1;\n\n   /* for every process ... */\n   while (Metric_iterate(PCP_PROC_PID, &pid, &offset, sizeof(PCPProcess))) {\n\n      bool preExisting;\n      Process* proc = ProcessTable_getProcess(pt, pid, &preExisting, PCPProcess_new);\n      PCPProcess* pp = (PCPProcess*) proc;\n      PCPProcessTable_updateID(proc, pid, offset);\n      proc->isUserlandThread = Process_getPid(proc) != Process_getThreadGroup(proc);\n      pp->offset = offset >= 0 ? offset : 0;\n\n      /*\n       * These conditions will not trigger on first occurrence, cause we need to\n       * add the process to the ProcessTable and do all one time scans\n       * (e.g. parsing the cmdline to detect a kernel thread)\n       * But it will short-circuit subsequent scans.\n       */\n      if (preExisting && hideKernelThreads && Process_isKernelThread(proc)) {\n         proc->super.updated = true;\n         proc->super.show = false;\n         if (proc->state == RUNNING)\n            pt->runningTasks++;\n         pt->kernelThreads++;\n         pt->totalTasks++;\n         continue;\n      }\n      if (preExisting && hideUserlandThreads && Process_isUserlandThread(proc)) {\n         proc->super.updated = true;\n         proc->super.show = false;\n         if (proc->state == RUNNING)\n            pt->runningTasks++;\n         pt->userlandThreads++;\n         pt->totalTasks++;\n         continue;\n      }\n\n      if (flags & PROCESS_FLAG_IO)\n         PCPProcessTable_updateIO(pp, pid, offset, now);\n\n      PCPProcessTable_updateMemory(pp, pid, offset);\n\n      if ((flags & PROCESS_FLAG_LINUX_SMAPS) && !Process_isKernelThread(proc)) {\n         if (Metric_enabled(PCP_PROC_SMAPS_PSS)) {\n            PCPProcessTable_updateSmaps(pp, pid, offset);\n         }\n      }\n\n      char command[MAX_NAME + 1];\n      unsigned long tty_nr = proc->tty_nr;\n      unsigned long long int lasttimes = pp->utime + pp->stime;\n\n      PCPProcessTable_updateInfo(pp, pid, offset, command, sizeof(command));\n      proc->starttime_ctime += Platform_getBootTime();\n      if (tty_nr != proc->tty_nr)\n         PCPProcessTable_updateTTY(proc, pid, offset);\n\n      proc->percent_cpu = NAN;\n      if (phost->period > 0.0) {\n         float percent_cpu = saturatingSub(pp->utime + pp->stime, lasttimes) / phost->period * 100.0;\n         proc->percent_cpu = MINIMUM(percent_cpu, host->activeCPUs * 100.0F);\n      }\n      proc->percent_mem = proc->m_resident / (double) host->totalMem * 100.0;\n      Process_updateCPUFieldWidths(proc->percent_cpu);\n\n      PCPProcessTable_updateUsername(proc, pid, offset, host->usersTable);\n\n      if (!preExisting) {\n         PCPProcessTable_updateCmdline(proc, pid, offset, command);\n         Process_fillStarttimeBuffer(proc);\n         ProcessTable_add(pt, proc);\n      } else if (settings->updateProcessNames && proc->state != ZOMBIE) {\n         PCPProcessTable_updateCmdline(proc, pid, offset, command);\n      }\n\n      if (flags & PROCESS_FLAG_LINUX_CGROUP)\n         PCPProcessTable_readCGroups(pp, pid, offset);\n\n      if (flags & PROCESS_FLAG_LINUX_OOM)\n         PCPProcessTable_readOomData(pp, pid, offset);\n\n      if (flags & PROCESS_FLAG_LINUX_CTXT)\n         PCPProcessTable_readCtxtData(pp, pid, offset);\n\n      if (flags & PROCESS_FLAG_LINUX_SECATTR)\n         PCPProcessTable_readSecattrData(pp, pid, offset);\n\n      if (flags & PROCESS_FLAG_CWD)\n         PCPProcessTable_readCwd(pp, pid, offset);\n\n      if (flags & PROCESS_FLAG_LINUX_AUTOGROUP)\n         PCPProcessTable_readAutogroup(pp, pid, offset);\n\n      if (proc->state == ZOMBIE && !proc->cmdline && command[0]) {\n         Process_updateCmdline(proc, command, 0, strlen(command));\n      } else if (Process_isThread(proc)) {\n         if ((settings->showThreadNames || Process_isKernelThread(proc)) && command[0]) {\n            Process_updateCmdline(proc, command, 0, strlen(command));\n         }\n\n         if (Process_isKernelThread(proc)) {\n            pt->kernelThreads++;\n         } else {\n            pt->userlandThreads++;\n         }\n      }\n\n      /* Set at the end when we know if a new entry is a thread */\n      proc->super.show = ! ((hideKernelThreads && Process_isKernelThread(proc)) ||\n                      (hideUserlandThreads && Process_isUserlandThread(proc)));\n\n      pt->totalTasks++;\n      if (proc->state == RUNNING)\n         pt->runningTasks++;\n      proc->super.updated = true;\n   }\n   return true;\n}\n\nvoid ProcessTable_goThroughEntries(ProcessTable* super) {\n   PCPProcessTable* this = (PCPProcessTable*) super;\n   PCPProcessTable_updateProcesses(this);\n}\n"
  },
  {
    "path": "pcp/PCPProcessTable.h",
    "content": "#ifndef HEADER_PCPProcessTable\n#define HEADER_PCPProcessTable\n/*\nhtop - PCPProcessTable.h\n(C) 2014 Hisham H. Muhammad\nReleased under the GNU GPLv2+, see the COPYING file\nin the source distribution for its full text.\n*/\n\n#include <stdbool.h>\n#include <sys/types.h>\n\n#include \"Hashtable.h\"\n#include \"ProcessTable.h\"\n#include \"UsersTable.h\"\n\n#include \"pcp/Platform.h\"\n\n\ntypedef struct PCPProcessTable_ {\n   ProcessTable super;\n} PCPProcessTable;\n\n#endif\n"
  },
  {
    "path": "pcp/Platform.c",
    "content": "/*\nhtop - linux/Platform.c\n(C) 2014 Hisham H. Muhammad\n(C) 2020-2022 htop dev team\n(C) 2020-2022 Red Hat, Inc.\nReleased under the GNU GPLv2+, see the COPYING file\nin the source distribution for its full text.\n*/\n\n#include \"config.h\" // IWYU pragma: keep\n\n#include \"pcp/Platform.h\"\n\n#include <math.h>\n#include <stdio.h>\n#include <stdlib.h>\n#include <string.h>\n#include <unistd.h>\n\n#include \"BatteryMeter.h\"\n#include \"CPUMeter.h\"\n#include \"ClockMeter.h\"\n#include \"DateTimeMeter.h\"\n#include \"DiskIOMeter.h\"\n#include \"DynamicColumn.h\"\n#include \"DynamicMeter.h\"\n#include \"DynamicScreen.h\"\n#include \"FileDescriptorMeter.h\"\n#include \"HostnameMeter.h\"\n#include \"LoadAverageMeter.h\"\n#include \"Macros.h\"\n#include \"MemoryMeter.h\"\n#include \"MemorySwapMeter.h\"\n#include \"Meter.h\"\n#include \"NetworkIOMeter.h\"\n#include \"ProcessTable.h\"\n#include \"Settings.h\"\n#include \"SwapMeter.h\"\n#include \"SysArchMeter.h\"\n#include \"TasksMeter.h\"\n#include \"UptimeMeter.h\"\n#include \"XUtils.h\"\n\n#include \"linux/PressureStallMeter.h\"\n#include \"linux/ZramMeter.h\"\n#include \"linux/ZramStats.h\"\n#include \"pcp/Metric.h\"\n#include \"pcp/PCPDynamicColumn.h\"\n#include \"pcp/PCPDynamicMeter.h\"\n#include \"pcp/PCPDynamicScreen.h\"\n#include \"pcp/PCPMachine.h\"\n#include \"pcp/PCPProcessTable.h\"\n#include \"zfs/ZfsArcMeter.h\"\n#include \"zfs/ZfsArcStats.h\"\n#include \"zfs/ZfsCompressedArcMeter.h\"\n\n\nPlatform* pcp;\n\nconst ScreenDefaults Platform_defaultScreens[] = {\n   {\n      .name = \"Main\",\n      .columns = \"PID USER PRIORITY NICE M_VIRT M_RESIDENT M_SHARE STATE PERCENT_CPU PERCENT_MEM TIME Command\",\n      .sortKey = \"PERCENT_CPU\",\n   },\n   {\n      .name = \"I/O\",\n      .columns = \"PID USER IO_PRIORITY IO_RATE IO_READ_RATE IO_WRITE_RATE PERCENT_SWAP_DELAY PERCENT_IO_DELAY Command\",\n      .sortKey = \"IO_RATE\",\n   },\n};\n\nconst unsigned int Platform_numberOfDefaultScreens = ARRAYSIZE(Platform_defaultScreens);\n\nconst SignalItem Platform_signals[] = {\n   { .name = \" 0 Cancel\",    .number = 0 },\n};\n\nconst unsigned int Platform_numberOfSignals = ARRAYSIZE(Platform_signals);\n\nstatic const MemoryClass Linux_memoryClasses[] = {\n   [MEMORY_CLASS_USED] = { .label = \"used\", .countsAsUsed = true, .countsAsCache = false, .color = MEMORY_1 },\n   [MEMORY_CLASS_SHARED] = { .label = \"shared\", .countsAsUsed = true, .countsAsCache = false, .color = MEMORY_2 },\n   [MEMORY_CLASS_BUFFERS] = { .label = \"compressed\", .countsAsUsed = true, .countsAsCache = false, .color = MEMORY_3 },\n   [MEMORY_CLASS_CACHE] = { .label = \"buffers\", .countsAsUsed = false, .countsAsCache = false, .color = MEMORY_4 },\n   [MEMORY_CLASS_COMPRESSED] = { .label = \"cache\", .countsAsUsed = false, .countsAsCache = false, .color = MEMORY_5 },\n   [MEMORY_CLASS_AVAILABLE] = { .label = \"available\", .countsAsUsed = false, .countsAsCache = false, .color = MEMORY_6 },\n};\n\nstatic const MemoryClass Darwin_memoryClasses[] = {\n   [MEMORY_CLASS_WIRED] = { .label = \"wired\", .countsAsUsed = true, .countsAsCache = false, .color = MEMORY_1 },\n   [MEMORY_CLASS_SPECULATIVE] = { .label = \"speculative\", .countsAsUsed = true, .countsAsCache = true, .color = MEMORY_2 },\n   [MEMORY_CLASS_ACTIVE] = { .label = \"active\", .countsAsUsed = true, .countsAsCache = false, .color = MEMORY_3 },\n   [MEMORY_CLASS_PURGEABLE] = { .label = \"purgeable\", .countsAsUsed = false, .countsAsCache = true, .color = MEMORY_4 },\n   [MEMORY_CLASS_COMPRESSED] = { .label = \"compressed\", .countsAsUsed = true, .countsAsCache = false, .color = MEMORY_5 },\n   [MEMORY_CLASS_INACTIVE] = { .label = \"inactive\", .countsAsUsed = true, .countsAsCache = true, .color = MEMORY_6 },\n};\n\nMemoryClass Platform_memoryClasses[MEMORY_CLASS_LIMIT]; /* dynamically adjusted */\n\nconst unsigned int Platform_numberOfMemoryClasses = ARRAYSIZE(Platform_memoryClasses);\n\nconst MeterClass* const Platform_meterTypes[] = {\n   &CPUMeter_class,\n   &ClockMeter_class,\n   &DateMeter_class,\n   &DateTimeMeter_class,\n   &LoadAverageMeter_class,\n   &LoadMeter_class,\n   &MemoryMeter_class,\n   &SwapMeter_class,\n   &MemorySwapMeter_class,\n   &TasksMeter_class,\n   &UptimeMeter_class,\n   &SecondsUptimeMeter_class,\n   &BatteryMeter_class,\n   &HostnameMeter_class,\n   &AllCPUsMeter_class,\n   &AllCPUs2Meter_class,\n   &AllCPUs4Meter_class,\n   &AllCPUs8Meter_class,\n   &LeftCPUsMeter_class,\n   &RightCPUsMeter_class,\n   &LeftCPUs2Meter_class,\n   &RightCPUs2Meter_class,\n   &LeftCPUs4Meter_class,\n   &RightCPUs4Meter_class,\n   &LeftCPUs8Meter_class,\n   &RightCPUs8Meter_class,\n   &PressureStallCPUSomeMeter_class,\n   &PressureStallIOSomeMeter_class,\n   &PressureStallIOFullMeter_class,\n   &PressureStallIRQFullMeter_class,\n   &PressureStallMemorySomeMeter_class,\n   &PressureStallMemoryFullMeter_class,\n   &ZfsArcMeter_class,\n   &ZfsCompressedArcMeter_class,\n   &ZramMeter_class,\n   &DiskIORateMeter_class,\n   &DiskIOTimeMeter_class,\n   &DiskIOMeter_class,\n   &NetworkIOMeter_class,\n   &SysArchMeter_class,\n   &FileDescriptorMeter_class,\n   &BlankMeter_class,\n   &DynamicMeter_class,\n   NULL\n};\n\nstatic const char* Platform_metricNames[] = {\n   [PCP_CONTROL_THREADS] = \"proc.control.perclient.threads\",\n\n   [PCP_HINV_NCPU] = \"hinv.ncpu\",\n   [PCP_HINV_NDISK] = \"hinv.ndisk\",\n   [PCP_HINV_CPUCLOCK] = \"hinv.cpu.clock\",\n   [PCP_UNAME_SYSNAME] = \"kernel.uname.sysname\",\n   [PCP_UNAME_RELEASE] = \"kernel.uname.release\",\n   [PCP_UNAME_MACHINE] = \"kernel.uname.machine\",\n   [PCP_UNAME_DISTRO] = \"kernel.uname.distro\",\n   [PCP_LOAD_AVERAGE] = \"kernel.all.load\",\n   [PCP_PID_MAX] = \"kernel.all.pid_max\",\n   [PCP_UPTIME] = \"kernel.all.uptime\",\n   [PCP_BOOTTIME] = \"kernel.all.boottime\",\n   [PCP_CPU_USER] = \"kernel.all.cpu.user\",\n   [PCP_CPU_NICE] = \"kernel.all.cpu.nice\",\n   [PCP_CPU_SYSTEM] = \"kernel.all.cpu.sys\",\n   [PCP_CPU_IDLE] = \"kernel.all.cpu.idle\",\n   [PCP_CPU_IOWAIT] = \"kernel.all.cpu.wait.total\",\n   [PCP_CPU_IRQ] = \"kernel.all.cpu.intr\",\n   [PCP_CPU_SOFTIRQ] = \"kernel.all.cpu.irq.soft\",\n   [PCP_CPU_STEAL] = \"kernel.all.cpu.steal\",\n   [PCP_CPU_GUEST] = \"kernel.all.cpu.guest\",\n   [PCP_CPU_GUESTNICE] = \"kernel.all.cpu.guest_nice\",\n   [PCP_PERCPU_USER] = \"kernel.percpu.cpu.user\",\n   [PCP_PERCPU_NICE] = \"kernel.percpu.cpu.nice\",\n   [PCP_PERCPU_SYSTEM] = \"kernel.percpu.cpu.sys\",\n   [PCP_PERCPU_IDLE] = \"kernel.percpu.cpu.idle\",\n   [PCP_PERCPU_IOWAIT] = \"kernel.percpu.cpu.wait.total\",\n   [PCP_PERCPU_IRQ] = \"kernel.percpu.cpu.intr\",\n   [PCP_PERCPU_SOFTIRQ] = \"kernel.percpu.cpu.irq.soft\",\n   [PCP_PERCPU_STEAL] = \"kernel.percpu.cpu.steal\",\n   [PCP_PERCPU_GUEST] = \"kernel.percpu.cpu.guest\",\n   [PCP_PERCPU_GUESTNICE] = \"kernel.percpu.cpu.guest_nice\",\n   [PCP_MEM_TOTAL] = \"mem.physmem\",\n   [PCP_MEM_FREE] = \"mem.util.free\",\n   [PCP_MEM_ACTIVE] = \"mem.util.active\",\n   [PCP_MEM_AVAILABLE] = \"mem.util.available\",\n   [PCP_MEM_BUFFERS] = \"mem.util.bufmem\",\n   [PCP_MEM_CACHED] = \"mem.util.cached\",\n   [PCP_MEM_COMPRESSED] = \"mem.util.compressed\",\n   [PCP_MEM_EXTERNAL] = \"mem.util.external\",\n   [PCP_MEM_INACTIVE] = \"mem.util.inactive\",\n   [PCP_MEM_PURGEABLE] = \"mem.util.purgeable\",\n   [PCP_MEM_SHARED] = \"mem.util.shmem\",\n   [PCP_MEM_SPECULATIVE] = \"mem.util.speculative\",\n   [PCP_MEM_SRECLAIM] = \"mem.util.slabReclaimable\",\n   [PCP_MEM_WIRED] = \"mem.util.wired\",\n   [PCP_MEM_SWAPCACHED] = \"mem.util.swapCached\",\n   [PCP_MEM_SWAPTOTAL] = \"mem.util.swapTotal\",\n   [PCP_MEM_SWAPFREE] = \"mem.util.swapFree\",\n   [PCP_DISK_READB] = \"disk.all.read_bytes\",\n   [PCP_DISK_WRITEB] = \"disk.all.write_bytes\",\n   [PCP_DISK_ACTIVE] = \"disk.all.avactive\",\n   [PCP_NET_RECVB] = \"network.all.in.bytes\",\n   [PCP_NET_SENDB] = \"network.all.out.bytes\",\n   [PCP_NET_RECVP] = \"network.all.in.packets\",\n   [PCP_NET_SENDP] = \"network.all.out.packets\",\n\n   [PCP_PSI_CPUSOME] = \"kernel.all.pressure.cpu.some.avg\",\n   [PCP_PSI_IOSOME] = \"kernel.all.pressure.io.some.avg\",\n   [PCP_PSI_IOFULL] = \"kernel.all.pressure.io.full.avg\",\n   [PCP_PSI_IRQFULL] = \"kernel.all.pressure.irq.full.avg\",\n   [PCP_PSI_MEMSOME] = \"kernel.all.pressure.memory.some.avg\",\n   [PCP_PSI_MEMFULL] = \"kernel.all.pressure.memory.full.avg\",\n\n   [PCP_ZFS_ARC_ANON_SIZE] = \"zfs.arc.anon_size\",\n   [PCP_ZFS_ARC_BONUS_SIZE] = \"zfs.arc.bonus_size\",\n   [PCP_ZFS_ARC_COMPRESSED_SIZE] = \"zfs.arc.compressed_size\",\n   [PCP_ZFS_ARC_UNCOMPRESSED_SIZE] = \"zfs.arc.uncompressed_size\",\n   [PCP_ZFS_ARC_C_MIN] = \"zfs.arc.c_min\",\n   [PCP_ZFS_ARC_C_MAX] = \"zfs.arc.c_max\",\n   [PCP_ZFS_ARC_DBUF_SIZE] = \"zfs.arc.dbuf_size\",\n   [PCP_ZFS_ARC_DNODE_SIZE] = \"zfs.arc.dnode_size\",\n   [PCP_ZFS_ARC_HDR_SIZE] = \"zfs.arc.hdr_size\",\n   [PCP_ZFS_ARC_MFU_SIZE] = \"zfs.arc.mfu.size\",\n   [PCP_ZFS_ARC_MRU_SIZE] = \"zfs.arc.mru.size\",\n   [PCP_ZFS_ARC_SIZE] = \"zfs.arc.size\",\n\n   [PCP_ZRAM_CAPACITY] = \"zram.capacity\",\n   [PCP_ZRAM_ORIGINAL] = \"zram.mm_stat.data_size.original\",\n   [PCP_ZRAM_COMPRESSED] = \"zram.mm_stat.data_size.compressed\",\n   [PCP_MEM_ZSWAP] = \"mem.util.zswap\",\n   [PCP_MEM_ZSWAPPED] = \"mem.util.zswapped\",\n   [PCP_VFS_FILES_COUNT] = \"vfs.files.count\",\n   [PCP_VFS_FILES_MAX] = \"vfs.files.max\",\n\n   [PCP_PROC_PID] = \"proc.psinfo.pid\",\n   [PCP_PROC_PPID] = \"proc.psinfo.ppid\",\n   [PCP_PROC_TGID] = \"proc.psinfo.tgid\",\n   [PCP_PROC_PGRP] = \"proc.psinfo.pgrp\",\n   [PCP_PROC_SESSION] = \"proc.psinfo.session\",\n   [PCP_PROC_STATE] = \"proc.psinfo.sname\",\n   [PCP_PROC_TTY] = \"proc.psinfo.tty\",\n   [PCP_PROC_TTYPGRP] = \"proc.psinfo.tty_pgrp\",\n   [PCP_PROC_MINFLT] = \"proc.psinfo.minflt\",\n   [PCP_PROC_MAJFLT] = \"proc.psinfo.maj_flt\",\n   [PCP_PROC_CMINFLT] = \"proc.psinfo.cmin_flt\",\n   [PCP_PROC_CMAJFLT] = \"proc.psinfo.cmaj_flt\",\n   [PCP_PROC_UTIME] = \"proc.psinfo.utime\",\n   [PCP_PROC_STIME] = \"proc.psinfo.stime\",\n   [PCP_PROC_CUTIME] = \"proc.psinfo.cutime\",\n   [PCP_PROC_CSTIME] = \"proc.psinfo.cstime\",\n   [PCP_PROC_PRIORITY] = \"proc.psinfo.priority\",\n   [PCP_PROC_NICE] = \"proc.psinfo.nice\",\n   [PCP_PROC_THREADS] = \"proc.psinfo.threads\",\n   [PCP_PROC_STARTTIME] = \"proc.psinfo.start_time\",\n   [PCP_PROC_PROCESSOR] = \"proc.psinfo.processor\",\n   [PCP_PROC_CMD] = \"proc.psinfo.cmd\",\n   [PCP_PROC_PSARGS] = \"proc.psinfo.psargs\",\n   [PCP_PROC_CGROUPS] = \"proc.psinfo.cgroups\",\n   [PCP_PROC_OOMSCORE] = \"proc.psinfo.oom_score\",\n   [PCP_PROC_VCTXSW] = \"proc.psinfo.vctxsw\",\n   [PCP_PROC_NVCTXSW] = \"proc.psinfo.nvctxsw\",\n   [PCP_PROC_LABELS] = \"proc.psinfo.labels\",\n   [PCP_PROC_ENVIRON] = \"proc.psinfo.environ\",\n   [PCP_PROC_TTYNAME] = \"proc.psinfo.ttyname\",\n   [PCP_PROC_EXE] = \"proc.psinfo.exe\",\n   [PCP_PROC_CWD] = \"proc.psinfo.cwd\",\n   [PCP_PROC_AUTOGROUP_ID] = \"proc.autogroup.id\",\n   [PCP_PROC_AUTOGROUP_NICE] = \"proc.autogroup.nice\",\n   [PCP_PROC_ID_UID] = \"proc.id.uid\",\n   [PCP_PROC_ID_USER] = \"proc.id.uid_nm\",\n   [PCP_PROC_IO_RCHAR] = \"proc.io.rchar\",\n   [PCP_PROC_IO_WCHAR] = \"proc.io.wchar\",\n   [PCP_PROC_IO_SYSCR] = \"proc.io.syscr\",\n   [PCP_PROC_IO_SYSCW] = \"proc.io.syscw\",\n   [PCP_PROC_IO_READB] = \"proc.io.read_bytes\",\n   [PCP_PROC_IO_WRITEB] = \"proc.io.write_bytes\",\n   [PCP_PROC_IO_CANCELLED] = \"proc.io.cancelled_write_bytes\",\n   [PCP_PROC_MEM_SIZE] = \"proc.memory.size\",\n   [PCP_PROC_MEM_RSS] = \"proc.memory.rss\",\n   [PCP_PROC_MEM_SHARE] = \"proc.memory.share\",\n   [PCP_PROC_MEM_TEXTRS] = \"proc.memory.textrss\",\n   [PCP_PROC_MEM_LIBRS] = \"proc.memory.librss\",\n   [PCP_PROC_MEM_DATRS] = \"proc.memory.datrss\",\n   [PCP_PROC_MEM_DIRTY] = \"proc.memory.dirty\",\n   [PCP_PROC_SMAPS_PSS] = \"proc.smaps.pss\",\n   [PCP_PROC_SMAPS_SWAP] = \"proc.smaps.swap\",\n   [PCP_PROC_SMAPS_SWAPPSS] = \"proc.smaps.swappss\",\n\n   [PCP_METRIC_COUNT] = NULL\n};\n\nstatic void Platform_setRelease(void);\n\n#ifndef HAVE_PMLOOKUPDESCS\n/*\n * pmLookupDescs(3) exists in latest versions of libpcp (5.3.6+),\n * but for older versions we provide an implementation here. This\n * involves multiple round trips to pmcd though, which the latest\n * libpcp version avoids by using a protocol extension.  In time,\n * perhaps in a few years, we could remove this back-compat code.\n */\nint pmLookupDescs(int numpmid, pmID* pmids, pmDesc* descs) {\n   int count = 0;\n\n   for (int i = 0; i < numpmid; i++) {\n      /* expect some metrics to be missing - e.g. PMDA not available */\n      if (pmids[i] == PM_ID_NULL)\n         continue;\n\n      int sts = pmLookupDesc(pmids[i], &descs[i]);\n      if (sts < 0) {\n         if (pmDebugOptions.appl0)\n            fprintf(stderr, \"Error: cannot lookup metric %s(%s): %s\\n\",\n                    pcp->names[i], pmIDStr(pcp->pmids[i]), pmErrStr(sts));\n         pmids[i] = PM_ID_NULL;\n         continue;\n      }\n\n      count++;\n   }\n   return count;\n}\n#endif\n\nsize_t Platform_addMetric(Metric id, const char* name) {\n   unsigned int i = (unsigned int)id;\n\n   if (i >= PCP_METRIC_COUNT && i >= pcp->totalMetrics) {\n      /* added via configuration files */\n      size_t j = pcp->totalMetrics + 1;\n      pcp->fetch = xRealloc(pcp->fetch, j * sizeof(pmID));\n      pcp->pmids = xRealloc(pcp->pmids, j * sizeof(pmID));\n      pcp->names = xRealloc(pcp->names, j * sizeof(char*));\n      pcp->descs = xRealloc(pcp->descs, j * sizeof(pmDesc));\n      memset(&pcp->descs[i], 0, sizeof(pmDesc));\n   }\n\n   pcp->pmids[i] = pcp->fetch[i] = PM_ID_NULL;\n   pcp->names[i] = name;\n   return ++pcp->totalMetrics;\n}\n\n/* global state from the environment and command line arguments */\npmOptions opts;\n\nbool Platform_init(void) {\n   const char* source;\n   if (opts.context == PM_CONTEXT_ARCHIVE) {\n      source = opts.archives[0];\n   } else if (opts.context == PM_CONTEXT_HOST) {\n      source = opts.nhosts > 0 ? opts.hosts[0] : \"local:\";\n   } else {\n      opts.context = PM_CONTEXT_HOST;\n      source = \"local:\";\n   }\n\n   int sts;\n   sts = pmNewContext(opts.context, source);\n   /* with no host requested, fallback to PM_CONTEXT_LOCAL shared libraries */\n   if (sts < 0 && opts.context == PM_CONTEXT_HOST && opts.nhosts == 0) {\n      opts.context = PM_CONTEXT_LOCAL;\n      sts = pmNewContext(opts.context, NULL);\n   }\n   if (sts < 0) {\n      fprintf(stderr, \"Cannot setup PCP metric source: %s\\n\", pmErrStr(sts));\n      return false;\n   }\n   /* setup timezones and other general startup preparation completion */\n   if (pmGetContextOptions(sts, &opts) < 0 || opts.errors) {\n      pmflush();\n      return false;\n   }\n\n   pcp = xCalloc(1, sizeof(Platform));\n   pcp->context = sts;\n   pcp->fetch = xCalloc(PCP_METRIC_COUNT, sizeof(pmID));\n   pcp->pmids = xCalloc(PCP_METRIC_COUNT, sizeof(pmID));\n   pcp->names = xCalloc(PCP_METRIC_COUNT, sizeof(char*));\n   pcp->descs = xCalloc(PCP_METRIC_COUNT, sizeof(pmDesc));\n\n   if (opts.context == PM_CONTEXT_ARCHIVE) {\n      gettimeofday(&pcp->offset, NULL);\n#if PMAPI_VERSION >= 3\n      struct timeval start = { opts.start.tv_sec, (suseconds_t)(opts.start.tv_nsec / 1000) };\n      pmtimevalDec(&pcp->offset, &start);\n#else\n      pmtimevalDec(&pcp->offset, &opts.start);\n#endif\n   }\n\n   for (unsigned int i = 0; i < PCP_METRIC_COUNT; i++)\n      Platform_addMetric(i, Platform_metricNames[i]);\n   pcp->meters.offset = PCP_METRIC_COUNT;\n\n   PCPDynamicMeters_init(&pcp->meters);\n\n   pcp->columns.offset = PCP_METRIC_COUNT + pcp->meters.cursor;\n   PCPDynamicColumns_init(&pcp->columns);\n   PCPDynamicScreens_init(&pcp->screens, &pcp->columns);\n\n   int total = (int) pcp->totalMetrics;\n   sts = pmLookupName(total, pcp->names, pcp->pmids);\n   if (sts < 0) {\n      fprintf(stderr, \"Error: cannot lookup metric names: %s\\n\", pmErrStr(sts));\n      Platform_done();\n      return false;\n   }\n\n   sts = pmLookupDescs(total, pcp->pmids, pcp->descs);\n   if (sts < 1) {\n      if (sts < 0)\n         fprintf(stderr, \"Error: cannot lookup descriptors: %s\\n\", pmErrStr(sts));\n      else /* ensure we have at least one valid metric to work with */\n         fprintf(stderr, \"Error: cannot find a single valid metric, exiting\\n\");\n      Platform_done();\n      return false;\n   }\n\n   /* set proc.control.perclient.threads to 1 for live contexts */\n   Metric_enableThreads();\n\n   /* extract values needed for setup - e.g. cpu count, pid_max */\n   Metric_enable(PCP_PID_MAX, true);\n   Metric_enable(PCP_BOOTTIME, true);\n   Metric_enable(PCP_HINV_NCPU, true);\n   Metric_enable(PCP_HINV_NDISK, true);\n   Metric_enable(PCP_PERCPU_SYSTEM, true);\n   Metric_enable(PCP_UNAME_SYSNAME, true);\n   Metric_enable(PCP_UNAME_RELEASE, true);\n   Metric_enable(PCP_UNAME_MACHINE, true);\n   Metric_enable(PCP_UNAME_DISTRO, true);\n\n   /* enable metrics for all dynamic columns (including those from dynamic screens) */\n   Metric metric = Metric_fromId(pcp->columns.offset);\n   for (; metric < pcp->columns.offset + pcp->columns.count; metric++)\n      Metric_enable(metric, true);\n\n   Metric_fetch(NULL);\n\n   for (metric = 0; metric < PCP_PROC_PID; metric++)\n      Metric_enable(metric, true);\n   Metric_enable(PCP_PID_MAX, false); /* needed one time only */\n   Metric_enable(PCP_BOOTTIME, false);\n   Metric_enable(PCP_UNAME_SYSNAME, false);\n   Metric_enable(PCP_UNAME_RELEASE, false);\n   Metric_enable(PCP_UNAME_MACHINE, false);\n   Metric_enable(PCP_UNAME_DISTRO, false);\n\n   /* first sample (fetch) performed above, save constants */\n   Platform_getBootTime();\n   Platform_setRelease();\n   Platform_getMaxCPU();\n   Platform_getMaxPid();\n\n   return true;\n}\n\nvoid Platform_dynamicColumnsDone(Hashtable* columns) {\n   PCPDynamicColumns_done(columns);\n}\n\nvoid Platform_dynamicMetersDone(Hashtable* meters) {\n   PCPDynamicMeters_done(meters);\n}\n\nvoid Platform_dynamicScreensDone(Hashtable* screens) {\n   PCPDynamicScreens_done(screens);\n}\n\nvoid Platform_done(void) {\n   pmDestroyContext(pcp->context);\n   if (pcp->result)\n      pmFreeResult(pcp->result);\n   free(pcp->release);\n   free(pcp->fetch);\n   free(pcp->pmids);\n   free(pcp->names);\n   free(pcp->descs);\n   free(pcp);\n}\n\nvoid Platform_setBindings(Htop_Action* keys) {\n   /* no platform-specific key bindings */\n   (void)keys;\n}\n\nint Platform_getUptime(void) {\n   pmAtomValue value;\n   if (Metric_values(PCP_UPTIME, &value, 1, PM_TYPE_32) == NULL)\n      return 0;\n   return value.l;\n}\n\nvoid Platform_getLoadAverage(double* one, double* five, double* fifteen) {\n   *one = *five = *fifteen = 0.0;\n\n   pmAtomValue values[3] = {0};\n   if (Metric_values(PCP_LOAD_AVERAGE, values, 3, PM_TYPE_DOUBLE) != NULL) {\n      *one = values[0].d;\n      *five = values[1].d;\n      *fifteen = values[2].d;\n   }\n}\n\nunsigned int Platform_getMaxCPU(void) {\n   if (pcp->ncpu)\n      return pcp->ncpu;\n\n   pmAtomValue value;\n   if (Metric_values(PCP_HINV_NCPU, &value, 1, PM_TYPE_U32) != NULL)\n      pcp->ncpu = value.ul;\n   else\n      pcp->ncpu = 1;\n   return pcp->ncpu;\n}\n\npid_t Platform_getMaxPid(void) {\n   if (pcp->pidmax)\n      return pcp->pidmax;\n\n   pmAtomValue value;\n   if (Metric_values(PCP_PID_MAX, &value, 1, PM_TYPE_32) == NULL)\n      return INT_MAX;\n   pcp->pidmax = value.l;\n   return pcp->pidmax;\n}\n\nlong long Platform_getBootTime(void) {\n   if (pcp->btime)\n      return pcp->btime;\n\n   pmAtomValue value;\n   if (Metric_values(PCP_BOOTTIME, &value, 1, PM_TYPE_64) != NULL)\n      pcp->btime = value.ll;\n   return pcp->btime;\n}\n\nstatic double Platform_setOneCPUValues(Meter* this, const Settings* settings, pmAtomValue* values) {\n   unsigned long long value = values[CPU_TOTAL_PERIOD].ull;\n   double total = (double) (value == 0 ? 1 : value);\n   double percent;\n\n   double* v = this->values;\n   v[CPU_METER_NICE] = values[CPU_NICE_PERIOD].ull / total * 100.0;\n   v[CPU_METER_NORMAL] = values[CPU_USER_PERIOD].ull / total * 100.0;\n   if (settings->detailedCPUTime) {\n      v[CPU_METER_KERNEL]  = values[CPU_SYSTEM_PERIOD].ull / total * 100.0;\n      v[CPU_METER_IRQ]     = values[CPU_IRQ_PERIOD].ull / total * 100.0;\n      v[CPU_METER_SOFTIRQ] = values[CPU_SOFTIRQ_PERIOD].ull / total * 100.0;\n      this->curItems = 5;\n\n      v[CPU_METER_STEAL]   = values[CPU_STEAL_PERIOD].ull / total * 100.0;\n      v[CPU_METER_GUEST]   = values[CPU_GUEST_PERIOD].ull / total * 100.0;\n      if (settings->accountGuestInCPUMeter) {\n         this->curItems = 7;\n      }\n\n      v[CPU_METER_IOWAIT]  = values[CPU_IOWAIT_PERIOD].ull / total * 100.0;\n   } else {\n      v[CPU_METER_KERNEL] = values[CPU_SYSTEM_ALL_PERIOD].ull / total * 100.0;\n      value = values[CPU_STEAL_PERIOD].ull + values[CPU_GUEST_PERIOD].ull;\n      v[CPU_METER_IRQ] = value / total * 100.0;\n      this->curItems = 4;\n   }\n\n   percent = sumPositiveValues(v, this->curItems);\n   percent = MINIMUM(percent, 100.0);\n\n   if (settings->detailedCPUTime) {\n      this->curItems = 8;\n   }\n\n   v[CPU_METER_FREQUENCY] = values[CPU_FREQUENCY].d;\n   v[CPU_METER_TEMPERATURE] = NAN;\n\n   return percent;\n}\n\ndouble Platform_setCPUValues(Meter* this, int cpu) {\n   const PCPMachine* phost = (const PCPMachine*) this->host;\n   const Settings* settings = this->host->settings;\n\n   if (cpu <= 0) /* use aggregate values */\n      return Platform_setOneCPUValues(this, settings, phost->cpu);\n   return Platform_setOneCPUValues(this, settings, phost->percpu[cpu - 1]);\n}\n\nstatic void Platform_setLinuxMemoryValues(double* v, const PCPMachine *host) {\n   v[MEMORY_CLASS_USED] = host->memValue[MEMORY_CLASS_USED];\n   v[MEMORY_CLASS_SHARED] = host->memValue[MEMORY_CLASS_SHARED];\n   v[MEMORY_CLASS_BUFFERS] = host->memValue[MEMORY_CLASS_BUFFERS];\n   v[MEMORY_CLASS_CACHE] = host->memValue[MEMORY_CLASS_CACHE];\n   v[MEMORY_CLASS_AVAILABLE] = host->memValue[MEMORY_CLASS_AVAILABLE];\n\n   if (host->zfs.enabled != 0) {\n      // ZFS does not shrink below the value of zfs_arc_min.\n      unsigned long long int shrinkableSize = 0;\n      if (host->zfs.size > host->zfs.min)\n         shrinkableSize = host->zfs.size - host->zfs.min;\n      v[MEMORY_CLASS_USED] -= shrinkableSize;\n      v[MEMORY_CLASS_CACHE] += shrinkableSize;\n      v[MEMORY_CLASS_AVAILABLE] += shrinkableSize;\n   }\n\n   if (host->zswap.usedZswapOrig > 0 || host->zswap.usedZswapComp > 0) {\n      v[MEMORY_CLASS_USED] -= host->zswap.usedZswapComp;\n      v[MEMORY_CLASS_COMPRESSED] = host->zswap.usedZswapComp;\n   } else {\n      v[MEMORY_CLASS_COMPRESSED] = 0;\n   }\n}\n\nstatic void Platform_setDarwinMemoryValues(double* v, const PCPMachine *host) {\n   v[MEMORY_CLASS_WIRED] = host->memValue[MEMORY_CLASS_WIRED];\n   v[MEMORY_CLASS_SPECULATIVE] = host->memValue[MEMORY_CLASS_SPECULATIVE];\n   v[MEMORY_CLASS_ACTIVE] = host->memValue[MEMORY_CLASS_ACTIVE];\n   v[MEMORY_CLASS_PURGEABLE] = host->memValue[MEMORY_CLASS_PURGEABLE];\n   v[MEMORY_CLASS_COMPRESSED] = host->memValue[MEMORY_CLASS_COMPRESSED];\n   v[MEMORY_CLASS_INACTIVE] = host->memValue[MEMORY_CLASS_INACTIVE];\n}\n\nvoid Platform_setMemoryValues(Meter* this) {\n   const Machine* host = this->host;\n   const PCPMachine* phost = (const PCPMachine*) host;\n\n   this->total = host->totalMem;\n   if (phost->sys == SYSTEM_NAME_LINUX)\n      Platform_setLinuxMemoryValues(this->values, phost);\n   else if (phost->sys == SYSTEM_NAME_DARWIN)\n      Platform_setDarwinMemoryValues(this->values, phost);\n   else\n      memset(this->values, 0, sizeof(phost->memValue));\n}\n\nvoid Platform_setSwapValues(Meter* this) {\n   const Machine* host = this->host;\n   const PCPMachine* phost = (const PCPMachine*) host;\n\n   this->total = host->totalSwap;\n   this->values[SWAP_METER_USED] = host->usedSwap;\n   this->values[SWAP_METER_CACHE] = host->cachedSwap;\n   this->values[SWAP_METER_FRONTSWAP] = 0; /* frontswap -- memory that is accounted to swap but resides elsewhere */\n\n   if (phost->zswap.usedZswapOrig > 0 || phost->zswap.usedZswapComp > 0) {\n      /* refer to linux/Platform.c::Platform_setSwapValues for details */\n      this->values[SWAP_METER_USED] -= phost->zswap.usedZswapOrig;\n      if (this->values[SWAP_METER_USED] < 0) {\n         /* subtract the overflow from SwapCached */\n         this->values[SWAP_METER_CACHE] += this->values[SWAP_METER_USED];\n         this->values[SWAP_METER_USED] = 0;\n      }\n      this->values[SWAP_METER_FRONTSWAP] += phost->zswap.usedZswapOrig;\n   }\n}\n\nvoid Platform_setZramValues(Meter* this) {\n   int i, count = Metric_instanceCount(PCP_ZRAM_CAPACITY);\n   if (count < 1) {\n      this->total = 0;\n      this->values[0] = 0;\n      this->values[1] = 0;\n      return;\n   }\n\n   pmAtomValue* values = xCalloc(count, sizeof(pmAtomValue));\n   ZramStats stats = {0};\n\n   if (Metric_values(PCP_ZRAM_CAPACITY, values, count, PM_TYPE_U64)) {\n      for (i = 0; i < count; i++)\n         stats.totalZram += values[i].ull;\n   }\n   if (Metric_values(PCP_ZRAM_ORIGINAL, values, count, PM_TYPE_U64)) {\n      for (i = 0; i < count; i++)\n         stats.usedZramOrig += values[i].ull;\n   }\n   if (Metric_values(PCP_ZRAM_COMPRESSED, values, count, PM_TYPE_U64)) {\n      for (i = 0; i < count; i++)\n         stats.usedZramComp += values[i].ull;\n   }\n\n   free(values);\n\n   if (stats.usedZramComp > stats.usedZramOrig) {\n      stats.usedZramComp = stats.usedZramOrig;\n   }\n\n   this->total = stats.totalZram;\n   this->values[0] = stats.usedZramComp;\n   this->values[1] = stats.usedZramOrig - stats.usedZramComp;\n}\n\nvoid Platform_setZfsArcValues(Meter* this) {\n   const PCPMachine* phost = (const PCPMachine*) this->host;\n\n   ZfsArcMeter_readStats(this, &phost->zfs);\n}\n\nvoid Platform_setZfsCompressedArcValues(Meter* this) {\n   const PCPMachine* phost = (const PCPMachine*) this->host;\n\n   ZfsCompressedArcMeter_readStats(this, &phost->zfs);\n}\n\nvoid Platform_getHostname(char* buffer, size_t size) {\n   const char* hostname = pmGetContextHostName(pcp->context);\n   String_safeStrncpy(buffer, hostname, size);\n}\n\nstatic void Platform_setRelease(void) {\n   pmAtomValue sysname, release, machine, distro;\n   if (!Metric_values(PCP_UNAME_SYSNAME, &sysname, 1, PM_TYPE_STRING))\n      sysname.cp = NULL;\n   if (!Metric_values(PCP_UNAME_RELEASE, &release, 1, PM_TYPE_STRING))\n      release.cp = NULL;\n   if (!Metric_values(PCP_UNAME_MACHINE, &machine, 1, PM_TYPE_STRING))\n      machine.cp = NULL;\n   if (!Metric_values(PCP_UNAME_DISTRO, &distro, 1, PM_TYPE_STRING))\n      distro.cp = NULL;\n\n   /* set global memory class model using sysname */\n   if (sysname.cp && String_eq(sysname.cp, \"Darwin\"))\n      memcpy(Platform_memoryClasses, Darwin_memoryClasses, sizeof(Darwin_memoryClasses));\n   else /* default to the Linux memory categories */\n      memcpy(Platform_memoryClasses, Linux_memoryClasses, sizeof(Linux_memoryClasses));\n\n   size_t length = 16; /* padded for formatting characters */\n   if (sysname.cp)\n      length += strlen(sysname.cp);\n   if (release.cp)\n      length += strlen(release.cp);\n   if (machine.cp)\n      length += strlen(machine.cp);\n   if (distro.cp)\n      length += strlen(distro.cp);\n   pcp->release = xCalloc(1, length);\n\n   if (sysname.cp) {\n      strcat(pcp->release, sysname.cp);\n      strcat(pcp->release, \" \");\n   }\n   if (release.cp) {\n      strcat(pcp->release, release.cp);\n      strcat(pcp->release, \" \");\n   }\n   if (machine.cp) {\n      strcat(pcp->release, \"[\");\n      strcat(pcp->release, machine.cp);\n      strcat(pcp->release, \"] \");\n   }\n   if (distro.cp) {\n      if (pcp->release[0] != '\\0') {\n         strcat(pcp->release, \"@ \");\n         strcat(pcp->release, distro.cp);\n      } else {\n         strcat(pcp->release, distro.cp);\n      }\n      strcat(pcp->release, \" \");\n   }\n\n   if (pcp->release) /* cull trailing space */\n      pcp->release[strlen(pcp->release)] = '\\0';\n\n   free(distro.cp);\n   free(machine.cp);\n   free(release.cp);\n   free(sysname.cp);\n}\n\nconst char* Platform_getRelease(void) {\n   if (pcp->release == NULL)\n      Platform_setRelease();\n\n   return pcp->release;\n}\n\nchar* Platform_getProcessEnv(pid_t pid) {\n   pmAtomValue value;\n   if (!Metric_instance(PCP_PROC_ENVIRON, pid, 0, &value, PM_TYPE_STRING))\n      return NULL;\n   return value.cp;\n}\n\nFileLocks_ProcessData* Platform_getProcessLocks(pid_t pid) {\n   (void)pid;\n   return NULL;\n}\n\nvoid Platform_getPressureStall(const char* file, bool some, double* ten, double* sixty, double* threehundred) {\n   *ten = *sixty = *threehundred = 0;\n\n   Metric metric;\n   if (String_eq(file, \"cpu\"))\n      metric = PCP_PSI_CPUSOME;\n   else if (String_eq(file, \"io\"))\n      metric = some ? PCP_PSI_IOSOME : PCP_PSI_IOFULL;\n   else if (String_eq(file, \"irq\"))\n      metric = PCP_PSI_IRQFULL;\n   else if (String_eq(file, \"mem\"))\n      metric = some ? PCP_PSI_MEMSOME : PCP_PSI_MEMFULL;\n   else\n      return;\n\n   pmAtomValue values[3] = {0};\n   if (Metric_values(metric, values, 3, PM_TYPE_DOUBLE) != NULL) {\n      *ten = values[0].d;\n      *sixty = values[1].d;\n      *threehundred = values[2].d;\n   }\n}\n\nbool Platform_getDiskIO(DiskIOData* data) {\n   memset(data, 0, sizeof(*data));\n\n   pmAtomValue value;\n   if (Metric_values(PCP_DISK_READB, &value, 1, PM_TYPE_U64) != NULL)\n      data->totalBytesRead = value.ull;\n   if (Metric_values(PCP_DISK_WRITEB, &value, 1, PM_TYPE_U64) != NULL)\n      data->totalBytesWritten = value.ull;\n   if (Metric_values(PCP_DISK_ACTIVE, &value, 1, PM_TYPE_U64) != NULL)\n      data->totalMsTimeSpend = value.ull;\n   if (Metric_values(PCP_HINV_NDISK, &value, 1, PM_TYPE_U64) != NULL)\n      data->numDisks = value.ull;\n   return true;\n}\n\nbool Platform_getNetworkIO(NetworkIOData* data) {\n   pmAtomValue value;\n   if (Metric_values(PCP_NET_RECVB, &value, 1, PM_TYPE_U64) != NULL)\n      data->bytesReceived = value.ull;\n   if (Metric_values(PCP_NET_SENDB, &value, 1, PM_TYPE_U64) != NULL)\n      data->bytesTransmitted = value.ull;\n   if (Metric_values(PCP_NET_RECVP, &value, 1, PM_TYPE_U64) != NULL)\n      data->packetsReceived = value.ull;\n   if (Metric_values(PCP_NET_SENDP, &value, 1, PM_TYPE_U64) != NULL)\n      data->packetsTransmitted = value.ull;\n   return true;\n}\n\nvoid Platform_getFileDescriptors(double* used, double* max) {\n   *used = NAN;\n   *max = 65536;\n\n   pmAtomValue value;\n   if (Metric_values(PCP_VFS_FILES_COUNT, &value, 1, PM_TYPE_32) != NULL)\n      *used = value.l;\n   if (Metric_values(PCP_VFS_FILES_MAX, &value, 1, PM_TYPE_32) != NULL)\n      *max = value.l;\n}\n\nvoid Platform_getBattery(double* level, ACPresence* isOnAC) {\n   *level = NAN;\n   *isOnAC = AC_ERROR;\n}\n\nconst char* Platform_getFailedState(void) {\n   return pcp->reconnect ? \"PMCD DOWN\" : NULL;\n}\n\nvoid Platform_longOptionsUsage(ATTR_UNUSED const char* name) {\n   printf(\n\"   --host=HOSTSPEC              metrics source is PMCD at HOSTSPEC [see PCPIntro(1)]\\n\"\n\"   --hostzone                   set reporting timezone to local time of metrics source\\n\"\n\"   --timezone=TZ                set reporting timezone\\n\");\n}\n\nCommandLineStatus Platform_getLongOption(int opt, ATTR_UNUSED int argc, char** argv) {\n   /* libpcp export without a header definition */\n   extern void __pmAddOptHost(pmOptions*, char*);\n\n   switch (opt) {\n      case PLATFORM_LONGOPT_HOST:  /* --host=HOSTSPEC */\n         if (argv[optind][0] == '\\0')\n            return STATUS_ERROR_EXIT;\n         __pmAddOptHost(&opts, optarg);\n         return STATUS_OK;\n\n      case PLATFORM_LONGOPT_HOSTZONE:  /* --hostzone */\n         if (opts.timezone) {\n            pmprintf(\"%s: at most one of -Z and -z allowed\\n\", pmGetProgname());\n            opts.errors++;\n         } else {\n            opts.tzflag = 1;\n         }\n         return STATUS_OK;\n\n      case PLATFORM_LONGOPT_TIMEZONE:  /* --timezone=TZ */\n         if (argv[optind][0] == '\\0')\n            return STATUS_ERROR_EXIT;\n         if (opts.tzflag) {\n            pmprintf(\"%s: at most one of -Z and -z allowed\\n\", pmGetProgname());\n            opts.errors++;\n         } else {\n            opts.timezone = optarg;\n         }\n         return STATUS_OK;\n\n      default:\n         break;\n   }\n\n   return STATUS_ERROR_EXIT;\n}\n\nvoid Platform_gettime_realtime(struct timeval* tv, uint64_t* msec) {\n   if (gettimeofday(tv, NULL) == 0) {\n      /* shift by start offset to stay in lock-step with realtime (archives) */\n      if (pcp->offset.tv_sec || pcp->offset.tv_usec)\n         pmtimevalDec(tv, &pcp->offset);\n      *msec = ((uint64_t)tv->tv_sec * 1000) + ((uint64_t)tv->tv_usec / 1000);\n   } else {\n      memset(tv, 0, sizeof(struct timeval));\n      *msec = 0;\n   }\n}\n\nvoid Platform_gettime_monotonic(uint64_t* msec) {\n   if (pcp->result) {\n#if PMAPI_VERSION >= 3\n      *msec = ((uint64_t)pcp->result->timestamp.tv_sec * 1000) + ((uint64_t)pcp->result->timestamp.tv_nsec / 1000000);\n#else\n      *msec = ((uint64_t)pcp->result->timestamp.tv_sec * 1000) + ((uint64_t)pcp->result->timestamp.tv_usec / 1000);\n#endif\n   } else {\n      *msec = 0;\n   }\n}\n\nHashtable* Platform_dynamicMeters(void) {\n   return pcp->meters.table;\n}\n\nvoid Platform_dynamicMeterInit(Meter* meter) {\n   PCPDynamicMeter* this = Hashtable_get(pcp->meters.table, meter->param);\n   if (this)\n      PCPDynamicMeter_enable(this);\n}\n\nvoid Platform_dynamicMeterUpdateValues(Meter* meter) {\n   PCPDynamicMeter* this = Hashtable_get(pcp->meters.table, meter->param);\n   if (this)\n      PCPDynamicMeter_updateValues(this, meter);\n}\n\nvoid Platform_dynamicMeterDisplay(const Meter* meter, RichString* out) {\n   PCPDynamicMeter* this = Hashtable_get(pcp->meters.table, meter->param);\n   if (this)\n      PCPDynamicMeter_display(this, meter, out);\n}\n\nHashtable* Platform_dynamicColumns(void) {\n   return pcp->columns.table;\n}\n\nconst char* Platform_dynamicColumnName(unsigned int key) {\n   PCPDynamicColumn* this = Hashtable_get(pcp->columns.table, key);\n   if (this) {\n      Metric metric = Metric_fromId(this->id);\n      Metric_enable(metric, true);\n      if (this->super.caption)\n         return this->super.caption;\n      if (this->super.heading)\n         return this->super.heading;\n      return this->super.name;\n   }\n   return NULL;\n}\n\nbool Platform_dynamicColumnWriteField(const Process* proc, RichString* str, unsigned int key) {\n   PCPDynamicColumn* this = Hashtable_get(pcp->columns.table, key);\n   if (this) {\n      PCPDynamicColumn_writeField(this, proc, str);\n      return true;\n   }\n   return false;\n}\n\nHashtable* Platform_dynamicScreens(void) {\n   return pcp->screens.table;\n}\n\nvoid Platform_defaultDynamicScreens(Settings* settings) {\n   PCPDynamicScreen_appendScreens(&pcp->screens, settings);\n}\n\nvoid Platform_addDynamicScreen(ScreenSettings* ss) {\n   PCPDynamicScreen_addDynamicScreen(&pcp->screens, ss);\n}\n\nvoid Platform_addDynamicScreenAvailableColumns(Panel* availableColumns, const char* screen) {\n   Hashtable* screens = pcp->screens.table;\n   PCPDynamicScreens_addAvailableColumns(availableColumns, screens, screen);\n}\n\nvoid Platform_updateTables(Machine* host) {\n   PCPDynamicScreen_appendTables(&pcp->screens, host);\n   PCPDynamicColumns_setupWidths(&pcp->columns);\n}\n"
  },
  {
    "path": "pcp/Platform.h",
    "content": "#ifndef HEADER_Platform\n#define HEADER_Platform\n/*\nhtop - pcp/Platform.h\n(C) 2014 Hisham H. Muhammad\n(C) 2020-2021 htop dev team\n(C) 2020-2021 Red Hat, Inc.  All Rights Reserved.\nReleased under the GNU GPLv2+, see the COPYING file\nin the source distribution for its full text.\n*/\n\n#include <stdbool.h>\n#include <stddef.h>\n#include <stdint.h>\n#include <pcp/pmapi.h>\n#include <sys/time.h>\n#include <sys/types.h>\n\n/* use htop config.h values for these macros, not pcp values */\n#undef PACKAGE_URL\n#undef PACKAGE_NAME\n#undef PACKAGE_STRING\n#undef PACKAGE_TARNAME\n#undef PACKAGE_VERSION\n#undef PACKAGE_BUGREPORT\n\n#include \"Action.h\"\n#include \"BatteryMeter.h\"\n#include \"DiskIOMeter.h\"\n#include \"Hashtable.h\"\n#include \"MemoryMeter.h\"\n#include \"NetworkIOMeter.h\"\n#include \"Process.h\"\n#include \"ProcessLocksScreen.h\"\n#include \"RichString.h\"\n#include \"SignalsPanel.h\"\n#include \"CommandLine.h\"\n\n#include \"pcp/Metric.h\"\n#include \"pcp/PCPDynamicColumn.h\"\n#include \"pcp/PCPDynamicMeter.h\"\n#include \"pcp/PCPDynamicScreen.h\"\n\n\ntypedef struct Platform_ {\n   int context;               /* PMAPI(3) context identifier */\n   bool reconnect;            /* need to reconnect the context */\n   size_t totalMetrics;       /* total number of all metrics */\n   const char** names;        /* name array indexed by Metric */\n   pmID* pmids;               /* all known metric identifiers */\n   pmID* fetch;               /* enabled identifiers for sampling */\n   pmDesc* descs;             /* metric desc array indexed by Metric */\n   pmResult* result;          /* sample values result indexed by Metric */\n   PCPDynamicMeters meters;   /* dynamic meters via configuration files */\n   PCPDynamicColumns columns; /* dynamic columns via configuration files */\n   PCPDynamicScreens screens; /* dynamic screens via configuration files */\n   struct timeval offset;     /* time offset used in archive mode only */\n   long long btime;           /* boottime in seconds since the epoch */\n   char* release;             /* uname and distro from this context */\n   int pidmax;                /* maximum platform process identifier */\n   unsigned int ncpu;         /* maximum processor count configured */\n} Platform;\n\nextern const ScreenDefaults Platform_defaultScreens[];\n\nextern const unsigned int Platform_numberOfDefaultScreens;\n\nextern const SignalItem Platform_signals[];\n\nextern const unsigned int Platform_numberOfSignals;\n\nextern MemoryClass Platform_memoryClasses[];\n\nextern const unsigned int Platform_numberOfMemoryClasses;\n\nextern const MeterClass* const Platform_meterTypes[];\n\nbool Platform_init(void);\n\nvoid Platform_done(void);\n\nvoid Platform_setBindings(Htop_Action* keys);\n\nint Platform_getUptime(void);\n\nvoid Platform_getLoadAverage(double* one, double* five, double* fifteen);\n\nlong long Platform_getBootTime(void);\n\nunsigned int Platform_getMaxCPU(void);\n\npid_t Platform_getMaxPid(void);\n\ndouble Platform_setCPUValues(Meter* this, int cpu);\n\nvoid Platform_setMemoryValues(Meter* this);\n\nvoid Platform_setSwapValues(Meter* this);\n\nvoid Platform_setZramValues(Meter* this);\n\nvoid Platform_setZfsArcValues(Meter* this);\n\nvoid Platform_setZfsCompressedArcValues(Meter* this);\n\nchar* Platform_getProcessEnv(pid_t pid);\n\nFileLocks_ProcessData* Platform_getProcessLocks(pid_t pid);\n\nvoid Platform_getPressureStall(const char* file, bool some, double* ten, double* sixty, double* threehundred);\n\nbool Platform_getDiskIO(DiskIOData* data);\n\nbool Platform_getNetworkIO(NetworkIOData* data);\n\nvoid Platform_getBattery(double* percent, ACPresence* isOnAC);\n\nvoid Platform_getHostname(char* buffer, size_t size);\n\nconst char* Platform_getRelease(void);\n\nconst char* Platform_getFailedState(void);\n\nenum {\n   PLATFORM_LONGOPT_HOST = 128,\n   PLATFORM_LONGOPT_TIMEZONE,\n   PLATFORM_LONGOPT_HOSTZONE,\n};\n\n#define PLATFORM_LONG_OPTIONS \\\n      {PMLONGOPT_HOST, optional_argument, 0, PLATFORM_LONGOPT_HOST}, \\\n      {PMLONGOPT_TIMEZONE, optional_argument, 0, PLATFORM_LONGOPT_TIMEZONE}, \\\n      {PMLONGOPT_HOSTZONE, optional_argument, 0, PLATFORM_LONGOPT_HOSTZONE}, \\\n\nvoid Platform_longOptionsUsage(const char* name);\n\nCommandLineStatus Platform_getLongOption(int opt, int argc, char** argv);\n\nextern pmOptions opts;\n\nsize_t Platform_addMetric(Metric id, const char* name);\n\nvoid Platform_getFileDescriptors(double* used, double* max);\n\nvoid Platform_gettime_realtime(struct timeval* tv, uint64_t* msec);\n\nvoid Platform_gettime_monotonic(uint64_t* msec);\n\nHashtable* Platform_dynamicMeters(void);\n\nvoid Platform_dynamicMetersDone(Hashtable* meters);\n\nvoid Platform_dynamicMeterInit(Meter* meter);\n\nvoid Platform_dynamicMeterUpdateValues(Meter* meter);\n\nvoid Platform_dynamicMeterDisplay(const Meter* meter, RichString* out);\n\nHashtable* Platform_dynamicColumns(void);\n\nvoid Platform_dynamicColumnsDone(Hashtable* columns);\n\nconst char* Platform_dynamicColumnName(unsigned int key);\n\nbool Platform_dynamicColumnWriteField(const Process* proc, RichString* str, unsigned int key);\n\nHashtable* Platform_dynamicScreens(void);\n\nvoid Platform_defaultDynamicScreens(Settings* settings);\n\nvoid Platform_addDynamicScreen(ScreenSettings* ss);\n\nvoid Platform_addDynamicScreenAvailableColumns(Panel* availableColumns, const char* screen);\n\nvoid Platform_dynamicScreensDone(Hashtable* screens);\n\nvoid Platform_updateTables(Machine* host);\n\n#endif\n"
  },
  {
    "path": "pcp/ProcessField.h",
    "content": "#ifndef HEADER_PCPProcessField\n#define HEADER_PCPProcessField\n/*\nhtop - pcp/ProcessField.h\n(C) 2014 Hisham H. Muhammad\n(C) 2021 htop dev team\n(C) 2020-2021 Red Hat, Inc.  All Rights Reserved.\nReleased under the GNU GPLv2+, see the COPYING file\nin the source distribution for its full text.\n*/\n\n\n#define PLATFORM_PROCESS_FIELDS  \\\n   CMINFLT = 11,                 \\\n   CMAJFLT = 13,                 \\\n   UTIME = 14,                   \\\n   STIME = 15,                   \\\n   CUTIME = 16,                  \\\n   CSTIME = 17,                  \\\n   M_SHARE = 41,                 \\\n   M_TRS = 42,                   \\\n   M_DRS = 43,                   \\\n   M_LRS = 44,                   \\\n   M_DT = 45,                    \\\n   CTID = 100,                   \\\n   RCHAR = 103,                  \\\n   WCHAR = 104,                  \\\n   SYSCR = 105,                  \\\n   SYSCW = 106,                  \\\n   RBYTES = 107,                 \\\n   WBYTES = 108,                 \\\n   CNCLWB = 109,                 \\\n   IO_READ_RATE = 110,           \\\n   IO_WRITE_RATE = 111,          \\\n   IO_RATE = 112,                \\\n   CGROUP = 113,                 \\\n   OOM = 114,                    \\\n   PERCENT_CPU_DELAY = 116,      \\\n   PERCENT_IO_DELAY = 117,       \\\n   PERCENT_SWAP_DELAY = 118,     \\\n   M_PSS = 119,                  \\\n   M_SWAP = 120,                 \\\n   M_PSSWP = 121,                \\\n   CTXT = 122,                   \\\n   SECATTR = 123,                \\\n   AUTOGROUP_ID = 127,           \\\n   AUTOGROUP_NICE = 128,         \\\n   CCGROUP = 129,                \\\n   CONTAINER = 130,              \\\n   M_PRIV = 131,                 \\\n   // End of list\n\n\n#endif /* HEADER_PCPProcessField */\n"
  },
  {
    "path": "pcp/columns/container",
    "content": "#\n# pcp-htop(1) configuration file - see pcp-htop(5)\n#\n\n[container]\nheading = Container\ncaption = CONTAINER\nwidth = -12\nmetric = proc.id.container\ndescription = Name of processes container via cgroup heuristics\n"
  },
  {
    "path": "pcp/columns/delayacct",
    "content": "#\n# pcp-htop(1) configuration file - see pcp-htop(5)\n#\n\n[blkio]\nheading = BLKIOD\ncaption = BLKIO_TIME\nwidth = 6\nmetric = proc.psinfo.delayacct_blkio_time\ndescription = Aggregated block I/O delays\n"
  },
  {
    "path": "pcp/columns/fdcount",
    "content": "#\n# pcp-htop(1) configuration file - see pcp-htop(5)\n#\n\n[fds]\nheading = FDS\ncaption = FDCOUNT\nwidth = 4\nmetric = proc.fd.count\ndescription = Open file descriptors\n"
  },
  {
    "path": "pcp/columns/gpu_memory",
    "content": "#\n# pcp-htop(1) configuration file - see pcp-htop(5)\n#\n\n[drm_memory_cpu]\nheading = DCPU\nwidth = 6\nmetric = proc.fdinfo.drm_memory_cpu\ndescription = CPU memory which can be used by the GPU to store buffer objects\n\n[drm_memory_gtt]\nheading = DGTT\nwidth = 6\nmetric = proc.fdinfo.drm_memory_gtt\ndescription = GTT memory which can be used by the GPU to store buffer objects\n\n[drm_memory_vram]\nheading = DVRAM\nwidth = 6\nmetric = proc.fdinfo.drm_memory_vram\ndescription = VRAM memory which can be used by the GPU to store buffer objects\n\n[drm_shared_cpu]\nheading = DCPU_SHARE\nwidth = 6\nmetric = proc.fdinfo.drm_shared_cpu\ndescription = CPU memory which can be used by the GPU to store buffer objects, and is shared with another file\n\n[drm_shared_gtt]\nheading = DGTT_SHARE\nwidth = 6\nmetric = proc.fdinfo.drm_shared_gtt\ndescription = GTT memory which can be used by the GPU to store buffer objects, and is shared with another file\n\n[drm_shared_vram]\nheading = DVRAM_SHARE\nwidth = 6\nmetric = proc.fdinfo.drm_shared_vram\ndescription = VRAM memory which can be used by the GPU to store buffer objects, and is shared with another file\n\n[amd_evicted_visible_vram]\nheading = AMD_EVVRAM\nwidth = 6\nmetric = proc.fdinfo.amd_evicted_visible_vram\ndescription = Sum of evicted buffers due to CPU access\n\n[amd_evicted_vram]\nheading = AMD_EVRAM\nwidth = 6\nmetric = proc.fdinfo.amd_evicted_vram\ndescription = Sum of evicted buffers, includes visible VRAM\n\n[amd_memory_visible_vram]\nheading = AMD_VVRAM\nwidth = 6\nmetric = proc.fdinfo.amd_memory_visible_vram\ndescription = Current visible VRAM usage\n\n[amd_requested_gtt]\nheading = AMD_RGTT\nwidth = 6\nmetric = proc.fdinfo.amd_requested_gtt\ndescription = How much GTT memory userspace asked for\n\n[amd_requested_visible_vram]\nheading = AMD_RVVRAM\nwidth = 6\nmetric = proc.fdinfo.amd_requested_visible_vram\ndescription = How much visible VRAM userspace asked for\n\n[amd_requested_vram]\nheading = AMD_RVRAM\nwidth = 6\nmetric = proc.fdinfo.amd_requested_vram\ndescription = How much VRAM userspace asked for, includes visible VRAM\n"
  },
  {
    "path": "pcp/columns/guest",
    "content": "#\n# pcp-htop(1) configuration file - see pcp-htop(5)\n#\n\n[guest]\nheading = GUEST\ncaption = GUEST_TIME\nwidth = 6\nmetric = proc.psinfo.guest_time\ndescription = Guest time for the process\n\n[cguest]\nheading = CGUEST\ncaption = CGUEST_TIME\nwidth = 6\nmetric = proc.psinfo.guest_time + proc.psinfo.cguest_time\ndescription = Cumulative guest time for the process and its children\n"
  },
  {
    "path": "pcp/columns/memory",
    "content": "#\n# pcp-htop(1) configuration file - see pcp-htop(5)\n#\n\n[vmdata]\nheading = VDATA\nwidth = 6\nmetric = proc.memory.vmdata\ndescription = Virtual memory used for data\n\n[vmstack]\nheading = VSTACK\nwidth = -6\nmetric = proc.memory.vmstack\ndescription = Virtual memory used for stack\n\n[vmexe]\nheading = VEXEC\nwidth = 6\nmetric = proc.memory.vmexe\ndescription = Virtual memory used for non-library executable code\n\n[vmlib]\nheading = VLIBS\nwidth = 6\nmetric = proc.memory.vmlib\ndescription = Virtual memory used for libraries\n\n[vmswap]\nheading = VSWAP\nwidth = 6\nmetric = proc.memory.vmswap\ndescription = Virtual memory size currently swapped out\n\n[vmlock]\nheading = VLOCK\nwidth = 6\nmetric = proc.memory.vmlock\ndescription = Locked virtual memory\n"
  },
  {
    "path": "pcp/columns/sched",
    "content": "#\n# pcp-htop(1) configuration file - see pcp-htop(5)\n#\n\n[rundelay]\nheading = RUNQ\ncaption = RUN_DELAY\nwidth = 4\nmetric = proc.schedstat.run_delay\ndescription = Run queue time\n"
  },
  {
    "path": "pcp/columns/swap",
    "content": "#\n# pcp-htop(1) configuration file - see pcp-htop(5)\n#\n\n[swap]\nheading = SWAP\nwidth = 5\nmetric = proc.psinfo.nswap\ndescription = Count of swap operations for the process\n\n[cswap]\nheading = CSWAP\nwidth = 5\nmetric = proc.psinfo.nswap + proc.psinfo.cnswap\ndescription = Cumulative swap operations for the process and its children\n"
  },
  {
    "path": "pcp/columns/tcp",
    "content": "#\n# pcp-htop(1) configuration file - see pcp-htop(5)\n#\n\n[tcp_send_packets]\nheading = TCPS\ncaption = TCP_SEND\nwidth = 6\nmetric = defined(bpf.proc.net.tcp.send.packets) ? bpf.proc.net.tcp.send.packets : novalue()\ndescription = Count of TCP packets sent\n\n[tcp_send_bytes]\nheading = TCPSB\ncaption = TCP_SEND_BYTES\nwidth = 6\nmetric = defined(bpf.proc.net.tcp.send.bytes) ? bpf.proc.net.tcp.send.bytes : novalue()\ndescription = Cumulative bytes sent via TCP\n\n[tcp_recv_packets]\nheading = TCPR\ncaption = TCP_RECV\nwidth = 6\nmetric = defined(bpf.proc.net.tcp.recv.packets) ? bpf.proc.net.tcp.recv.packets : novalue()\ndescription = Count of TCP packets received\n\n[tcp_recv_bytes]\nheading = TCPRB\ncaption = TCP_RECV_BYTES\nwidth = 6\nmetric = defined(bpf.proc.net.tcp.recv.bytes) ? bpf.proc.net.tcp.recv.bytes : novalue()\ndescription = Cumulative bytes received via TCP\n"
  },
  {
    "path": "pcp/columns/udp",
    "content": "#\n# pcp-htop(1) configuration file - see pcp-htop(5)\n#\n\n[udp_send_packets]\nheading = UDPS\ncaption = UDP_SEND\nwidth = 6\nmetric = defined(bpf.proc.net.udp.send.packets) ? bpf.proc.net.udp.send.packets : novalue()\ndescription = Count of UDP packets sent\n\n[udp_send_bytes]\nheading = UDPSB\ncaption = UDP_SEND_BYTES\nwidth = 6\nmetric = defined(bpf.proc.net.udp.send.bytes) ? bpf.proc.net.udp.send.bytes : novalue()\ndescription = Cumulative bytes sent via UDP\n\n[udp_recv_packets]\nheading = UDPR\ncaption = UDP_RECV\nwidth = 6\nmetric = defined(bpf.proc.net.udp.recv.packets) ? bpf.proc.net.udp.recv.packets : novalue()\ndescription = Count of UDP packets received\n\n[udp_recv_bytes]\nheading = UDPRB\ncaption = UDP_RECV_BYTES\nwidth = 6\nmetric = defined(bpf.proc.net.udp.recv.bytes) ? bpf.proc.net.udp.recv.bytes : novalue()\ndescription = Cumulative bytes received via UDP\n"
  },
  {
    "path": "pcp/columns/wchan",
    "content": "#\n# pcp-htop(1) configuration file - see pcp-htop(5)\n#\n\n[wchan]\nheading = WCHAN\ncaption = WCHAN_ADDRESS\nwidth = 8\nmetric = proc.psinfo.wchan\ndescription = Wait channel, kernel address process is blocked or sleeping on\n\n[wchans]\nheading = WCHANS\ncaption = WCHAN_SYMBOL\nwidth = -12\nmetric = proc.psinfo.wchan_s\ndescription = Wait channel, kernel symbol process is blocked or sleeping on\n"
  },
  {
    "path": "pcp/meters/entropy",
    "content": "#\n# pcp-htop(1) configuration file - see pcp-htop(5)\n#\n\n[entropy]\ncaption = Entropy\ndescription = Entropy pool\navail.metric = kernel.all.entropy.avail / kernel.all.entropy.poolsize * 100\navail.label = avail\navail.suffix = %\n"
  },
  {
    "path": "pcp/meters/freespace",
    "content": "#\n# pcp-htop(1) configuration file - see pcp-htop(5)\n#\n\n[freespace]\ncaption = Freespace\ndescription = Filesystem space\nused.metric = sum(filesys.used)\nused.color = blue\nfree.metric = sum(filesys.free)\nfree.color = green\n"
  },
  {
    "path": "pcp/meters/gpu",
    "content": "#\n# pcp-htop(1) configuration file - see pcp-htop(5)\n#\n\n[amd_gpu_load]\ncaption = AMD GPU load\ndescription = AMD GPU load\nload.metric = defined(amdgpu.gpu.load) ? amdgpu.gpu.load : novalue()\nload.suffix = %\n\n[amd_gpu_average_power]\ncaption = AMD GPU average power\ndescription = AMD GPU average power in Watts\naverage_power.metric = defined(amdgpu.gpu.average_power) ? amdgpu.gpu.average_power : novalue()\naverage_power.suffix = W\n\n[amd_gpu_memory]\ntype = bar\ncaption = AMD GPU memory\ndescription = Allocated frame buffer memory\nused.metric = defined(amdgpu.memory.used) ? amdgpu.memory.used : novalue()\nused.color = red\ntotal.metric = defined(amdgpu.memory.total) ? amdgpu.memory.total : novalue()\ntotal.color: blue\n\n[amd_gpu_clock]\ncaption = AMD GPU clock\ndescription = The GPU clock speed in MHz\nclock.metric = defined(amdgpu.gpu.clock) ? amdgpu.gpu.clock : novalue()\nclock.label = MHz\n\n[amd_gpu_memory_clock]\ncaption = AMD GPU memory clock\ndescription = The GDDRx memory clock speed in MHz\nclock.metric = defined(amdgpu.memory.clock) ? amdgpu.memory.clock : novalue()\nclock.label = MHz\n\n[amd_gpu_temp]\ncaption = AMD GPU temperature\ndescription = The GPU temperature in degrees Celsius\ntemp.metric = defined(amdgpu.gpu.temperature) ? amdgpu.gpu.temperature / 1000  : novalue()\ntemp.label = °C\n"
  },
  {
    "path": "pcp/meters/ipc",
    "content": "#\n# pcp-htop(1) configuration file - see pcp-htop(5)\n#\n\n[ipc]\ncaption = SysV IPC\ndescription = SysV IPC counts\nmsg.metric = ipc.msg.used_queues\nmsg.color = blue\nsem.metric = ipc.sem.used_sem\nsem.color = green\nshm.metric = ipc.shm.used_ids\nshm.color = cyan\n"
  },
  {
    "path": "pcp/meters/locks",
    "content": "#\n# pcp-htop(1) configuration file - see pcp-htop(5)\n#\n\n[locks]\ncaption = File locks\ndescription = VFS file locks\nposix.metric = vfs.locks.posix.count\nposix.color = blue\nflock.metric = vfs.locks.flock.count\nflock.color = green\nreadlock.metric = vfs.locks.posix.read + vfs.locks.flock.read\nreadlock.color = red\nwritelock.metric = vfs.locks.posix.write + vfs.locks.flock.write\nwritelock.color = yellow\n"
  },
  {
    "path": "pcp/meters/memcache",
    "content": "#\n# pcp-htop(1) configuration file - see pcp-htop(5)\n#\n\n[memcache]\ncaption = Memcache\ndescription = Memcache Hits\nhit.metric = defined(memcache.hits) ? sum(memcache.hits) : novalue()\nhit.color = green\nmiss.metric = defined(memcache.misses) ? sum(memcache.misses) : novalue()\nmiss.color = blue\n"
  },
  {
    "path": "pcp/meters/mysql",
    "content": "#\n# pcp-htop(1) configuration file - see pcp-htop(5)\n#\n\n[mysql_io]\ncaption = MySQL I/O\ndescription = MySQL throughput\nrecv.metric = defined(mysql.status.bytes_received) ? mysql.status.bytes_received : novalue()\nrecv.color = green\nsent.metric = defined(mysql.status.bytes_sent) ? mysql.status.bytes_sent : novalue()\nsent.color = blue\n\n[mysql_keys]\ncaption = MySQL keys\ndescription = MySQL key status\nkey_blocks_used.metric = defined(mysql.status.key_blocks_used) ? mysql.status.key_blocks_used : novalue()\nkey_blocks_used.color = yellow\nkey_blocks_used.label = used\nkey_reads.metric = defined(mysql.status.key_reads) ? mysql.status.key_reads : novalue()\nkey_reads.label = read\nkey_reads.color = green\nkey_writes.metric = defined(mysql.status.key_writes) ? mysql.status.key_writes : novalue()\nkey_writes.label = writ\nkey_writes.color = blue\nkey_read_requests.metric = defined(mysql.status.key_read_requests) ? mysql.status.key_read_requests : novalue()\nkey_read_requests.label = rreq\nkey_read_requests.color = green\nkey_write_requests.metric = defined(mysql.status.key_write_requests) ? mysql.status.key_write_requests : novalue()\nkey_write_requests.label = wreq\nkey_write_requests.color = blue\n\n[innodb_buffer]\ncaption = InnoDB pool\ndescription = InnoDB buffer pool\ncreated.metric = defined(mysql.status.innodb_pages_created) ? mysql.status.innodb_pages_created : novalue()\ncreated.label = cr\ncreated.color = yellow\nread.metric = defined(mysql.status.innodb_pages_read) ? mysql.status.innodb_pages_read : novalue()\nread.label = rd\nread.color = green\nwritten.metric = defined(mysql.status.innodb_pages_written) ? mysql.status.innodb_pages_written : novalue()\nwritten.label = wr\nwritten.color = red\n\n[innodb_io]\ncaption = InnoDB I/O\ndescription = InnoDB I/O operations\nread.metric = defined(mysql.status.innodb_data_read) ? mysql.status.innodb_data_read : novalue()\nread.label = rd\nread.color = green\nwritten.metric = defined(mysql.status.innodb_data.writes) ? mysql.status.innodb_data.writes : novalue()\nwritten.label = wr\nwritten.color = blue\nsync.metric = defined(mysql.status.innodb_data_fsyncs) ? mysql.status.innodb_data_fsyncs : novalue()\nsync.label = sync\nsync.color = cyan\n\n[innodb_ops]\ncaption = InnoDB ops\ndescription = InnoDB operations\ninserted.metric = defined(mysql.status.innodb_rows_inserted) ? mysql.status.innodb_rows_inserted : novalue()\ninserted.label = ins\ninserted.color = blue\nupdated.metric = defined(mysql.status.innodb_rows_updated) ? mysql.status.innodb_rows_updated : novalue()\nupdated.label = upd\nupdated.color = cyan\ndeleted.metric = defined(mysql.status.innodb_rows_deleted) ? mysql.status.innodb_rows_deleted : novalue()\ndeleted.label = del\ndeleted.color = red\nread.metric = defined(mysql.status.innodb_rows_read) ? mysql.status.innodb_rows_read : novalue()\nread.label = rd\nread.color = green\n"
  },
  {
    "path": "pcp/meters/postfix",
    "content": "#\n# pcp-htop(1) configuration file - see pcp-htop(5)\n#\n\n[postfix]\ncaption = Postfix\nincoming.metric = defined(postfix.queues.incoming) ? sum(postfix.queues.incoming) : novalue()\nincoming.color = green\nincoming.label = in\nactive.metric = defined(postfix.queues.active) ? sum(postfix.queues.active) : novalue()\nactive.color = blue\nactive.label = act\ndeferred.metric = defined(postfix.queues.deferred) ? sum(postfix.queues.deferred) : novalue()\ndeferred.color = cyan\ndeferred.label = dfr\nbounce.metric = defined(postfix.queues.maildrop) ? sum(postfix.queues.maildrop) : novalue()\nbounce.color = red\nbounce.label = bnc\nhold.metric = defined(postfix.queues.hold) ? sum(postfix.queues.hold) : novalue()\nhold.color = yellow\n"
  },
  {
    "path": "pcp/meters/redis",
    "content": "#\n# pcp-htop(1) configuration file - see pcp-htop(5)\n#\n\n[redisxact]\ncaption = Redis xact\ndescription = Redis transactions\ntps.metric = defined(redis.instantaneous_ops_per_sec) ? redis.instantaneous_ops_per_sec : novalue()\ntps.color = green\n\n[redismem]\ncaption = Redis mem\ndescription = Redis memory\nlua.metric = defined(redis.used_memory_lua) ? redis.used_memory_lua : novalue()\nlua.color = magenta\nused.metric = defined(redis.used_memory) ? redis.used_memory : novalue()\nused.color = blue\n\n[redisclient]\ncaption = Redis clients\ndescription = Redis clients\ntype = bar\nblocked.metric = defined(redis.blocked_clients) ? redis.blocked_clients : novalue()\nblocked.color = blue\nblocked.label = blk\nclients.metric = defined(redis.connected_clients) ? redis.connected_clients : novalue()\nclients.color = green\nclients.label = conn\n\n[redisconn]\ncaption = Redis conn\ndescription = Redis connections\ntype = bar\nreject.metric = defined(redis.rejected_connections) ? redis.rejected_connections : novalue()\nreject.color = magenta\nreject.label = fail/s\ntotal.metric = defined(redis.total_connections_received) ? redis.total_connections_received : novalue()\ntotal.color = blue\ntotal.label = conn/s\n"
  },
  {
    "path": "pcp/meters/tcp",
    "content": "#\n# pcp-htop(1) configuration file - see pcp-htop(5)\n#\n\n[tcp]\ncaption = TCP\ndescription = TCP sockets\nlisten.metric = network.tcpconn.listen\nlisten.color = green\nlisten.label = lis\nactive.metric = network.tcpconn.established\nactive.color = blue\nactive.label = act\nsyn.metric = network.tcpconn.syn_sent + network.tcpconn.syn_recv + network.tcpconn.last_ack\nsyn.color = cyan\nwait.metric = network.tcpconn.time_wait\nwait.color = red\nwait.label = tim\nclose.metric = network.tcpconn.fin_wait1 + network.tcpconn.fin_wait2 + network.tcpconn.close + network.tcpconn.close_wait + network.tcpconn.closing\nclose.color = yellow\nclose.label = clo\n"
  },
  {
    "path": "pcp/screens/biosnoop",
    "content": "#\n# pcp-htop(1) configuration file - see pcp-htop(5)\n#\n\n[biosnoop]\nheading = BioSnoop\ncaption = BPF block I/O snoop\ndefault = false\n\npid.heading = PID\npid.caption = Process identifier\npid.metric = defined(bpf.biosnoop.pid) ? bpf.biosnoop.pid : novalue()\npid.format = process\n\ndisk.heading = DISK\ndisk.caption = Device name\ndisk.width = -7\ndisk.metric = defined(bpf.biosnoop.disk) ? bpf.biosnoop.disk : novalue()\n\nrwbs.heading = TYPE\nrwbs.caption = I/O type string\nrwbs.width = -4\nrwbs.metric = defined(bpf.biosnoop.rwbs) ? bpf.biosnoop.rwbs : novalue()\n\nbytes.heading = BYTES\nbytes.caption = I/O size in bytes\nbytes.metric = defined(bpf.biosnoop.bytes) ? bpf.biosnoop.bytes : novalue()\n\nlat.heading = LAT\nlat.caption = I/O latency\nlat.metric = defined(bpf.biosnoop.lat) ? bpf.biosnoop.lat : novalue()\n\nsector.heading = SECTOR\nsector.caption = Device sector\nsector.metric = defined(bpf.biosnoop.sector) ? bpf.biosnoop.sector : novalue()\n\ncomm.heading = Command\ncomm.caption = Process command name\ncomm.width = -16\ncomm.metric = defined(bpf.biosnoop.comm) ? bpf.biosnoop.comm : novalue()\ncomm.format = process\n"
  },
  {
    "path": "pcp/screens/cgroups",
    "content": "#\n# pcp-htop(1) configuration file - see pcp-htop(5)\n#\n\n[cgroups]\nheading = CGroups\ncaption = Control Groups\ndefault = true\n\nuser_cpu.heading = UTIME\nuser_cpu.caption = User CPU Time\nuser_cpu.metric = 1000 * rate(cgroup.cpu.stat.user)\nuser_cpu.width = 6\n\nsystem_cpu.heading = STIME\nsystem_cpu.caption = Kernel CPU Time\nsystem_cpu.metric = 1000 * rate(cgroup.cpu.stat.system)\nsystem_cpu.width = 6\n\ncpu_usage.heading = CPU%\ncpu_usage.caption = CPU Utilization\ncpu_usage.metric = 100 * (rate(cgroup.cpu.stat.usage) / hinv.ncpu)\ncpu_usage.format = percent\n\ncpu_psi.heading = CPU-PSI\ncpu_psi.caption = CPU Pressure Stall Information\ncpu_psi.metric = 1000 * rate(cgroup.pressure.cpu.some.total)\ncpu_psi.width = 7\n\nmem_psi.heading = MEM-PSI\nmem_psi.caption = Memory Pressure Stall Information\nmem_psi.metric = 1000 * rate(cgroup.pressure.memory.some.total)\nmem_psi.width = 7\n\nio_psi.heading = I/O-PSI\nio_psi.caption = I/O Pressure Stall Information\nio_psi.metric = 1000 * rate(cgroup.pressure.io.some.total)\nio_psi.width = 7\n\nname.heading = Control group\nname.caption = Control group name\nname.width = -64\nname.metric = cgroup.cpu.stat.system\nname.instances = true\nname.format = cgroup\n"
  },
  {
    "path": "pcp/screens/cgroupsio",
    "content": "#\n# pcp-htop(1) configuration file - see pcp-htop(5)\n#\n\n[cgroupsio]\nheading = CGroupsIO\ncaption = Control Groups I/O\ndefault = false\n\niops.heading = IOPS\niops.caption = I/O operations\niops.metric = rate(cgroup.io.stat.rios) + rate(cgroup.io.stat.wios) + rate(cgroup.io.stat.dios)\n\nreadops.heading = RDIO\nreadops.caption = Read operations\nreadops.metric = rate(cgroup.io.stat.rios)\nreadops.default = false\n\nwriteops.heading = WRIO\nwriteops.caption = Write operations\nwriteops.metric = rate(cgroup.io.stat.wios)\nwriteops.default = false\n\ndirectops.heading = DIO\ndirectops.caption = Direct I/O operations\ndirectops.metric = rate(cgroup.io.stat.dios)\ndirectops.default = false\n\ntotalbytes.heading = R/W/D\ntotalbytes.caption = Disk throughput\ntotalbytes.metric = rate(cgroup.io.stat.rbytes) + rate(cgroup.io.stat.wbytes) + rate(cgroup.io.stat.dbytes)\n\nreadbytes.heading = RBYTE\nreadbytes.caption = Disk read throughput\nreadbytes.metric = rate(cgroup.io.stat.rbytes)\n\nwritebytes.heading = WBYTE\nwritebytes.caption = Disk throughput\nwritebytes.metric = rate(cgroup.io.stat.wbytes)\n\ndirectio.heading = DBYTE\ndirectio.caption = Direct I/O throughput\ndirectio.metric = rate(cgroup.io.stat.dbytes)\n\nname.heading = Control group device\nname.caption = Control group device\nname.width = -64\nname.metric = cgroup.io.stat.rbytes\nname.instances = true\n"
  },
  {
    "path": "pcp/screens/cgroupsmem",
    "content": "#\n# pcp-htop(1) configuration file - see pcp-htop(5)\n#\n\n[cgroupsmem]\nheading = CGroupsMem\ncaption = Control Groups Memory\ndefault = false\n\ncurrent.heading = MEM\ncurrent.caption = Current memory\ncurrent.metric = cgroup.memory.current\n\nusage.heading = USAGE\nusage.caption = Memory usage\nusage.metric = cgroup.memory.usage\n\ncontainer.heading = CONTAINER\ncontainer.caption = Container Name\ncontainer.metric = cgroup.memory.id.container\n\nresident.heading = RSS\nresident.metric = cgroup.memory.stat.rss\n\ncresident.heading = CRSS\ncresident.metric = cgroup.memory.stat.total.rss\n\nanonmem.heading = ANON\nanonmem.metric = cgroup.memory.stat.anon\n\nfilemem.heading = FILE\nfilemem.metric = cgroup.memory.stat.file\n\nshared.heading = SHMEM\nshared.metric = cgroup.memory.stat.shmem\n\nswap.heading = SWAP\nswap.metric = cgroup.memory.stat.swap\n\npgfault.heading = FAULTS\npgfault.metric = cgroup.memory.stat.pgfault\n\nname.heading = Control group\nname.caption = Control group name\nname.width = -64\nname.metric = cgroup.memory.current\nname.instances = true\nname.format = cgroup\n"
  },
  {
    "path": "pcp/screens/devices",
    "content": "#\n# pcp-htop(1) configuration file - see pcp-htop(5)\n#\n\n[disks]\nheading = Disks\ncaption = Disk devices\n\ndiskdev.heading = Device\ndiskdev.metric = disk.dev.read\ndiskdev.instances = true\ndiskdev.format = device\ndiskdev.width = -8\n\ntotal.heading = TPS\ntotal.metric = rate(disk.dev.read) + rate(disk.dev.write) + rate(disk.dev.discard)\ntotal.caption = Rate of read requests\n\nread.heading = RR/S\nread.metric = rate(disk.dev.read)\nread.caption = Rate of read requests\n\nread_bytes.heading = RRB/S\nread_bytes.metric = rate(disk.dev.read_bytes)\nread_bytes.caption = Read throughput from the device\n\nread_merge.heading = RRQM/S\nread_merge.metric = rate(disk.dev.read_merge)\nread_merge.caption = Rate reads merged before queued\nread_merge.default = false\n\nread_merge_pct.heading = RRQM%\nread_merge_pct.metric = 100 * rate(disk.dev.read_merge) / rate(disk.dev.read)\nread_merge_pct.caption = Percentage reads merged before queued\nread_merge_pct.format = percent\n\nread_await.heading = RAWAIT\nread_await.metric = delta(disk.dev.read_rawactive) / delta(disk.dev.read)\nread_await.caption = Average time read requests queued and serviced\nread_await.default = false\n\nread_avqsz.heading = RARQSZ\nread_avqsz.metric = rescale(delta(disk.dev.read_bytes), \"kbyte\") / delta(disk.dev.read)\nread_avqsz.caption = Average I/O request size for reads to the device\nread_avqsz.default = false\n\nwrite.heading = WR/S\nwrite.metric = rate(disk.dev.write)\nwrite.caption = Rate of write requests\n\nwrite_bytes.heading = WRB/S\nwrite_bytes.metric = rate(disk.dev.write_bytes)\nwrite_bytes.caption = Write throughput to the device\n\nwrite_merge.heading = WRQM/S\nwrite_merge.metric = rate(disk.dev.write_merge)\nwrite_merge.caption = Rate writes merged before queued\nwrite_merge.default = false\n\nwrite_merge_pct.heading = WRQM%\nwrite_merge_pct.metric = 100 * rate(disk.dev.write_merge)  / rate(disk.dev.write)\nwrite_merge_pct.caption = Percentage writes merged before queued\nwrite_merge_pct.format = percent\n\nwrite_await.heading = WAWAIT\nwrite_await.metric = delta(disk.dev.write_rawactive) / delta(disk.dev.write)\nwrite_await.caption = Average time write requests queued and serviced\nwrite_await.default = false\n\nwrite_avqsz.heading = WARQSZ\nwrite_avqsz.metric = rescale(delta(disk.dev.write_bytes), \"kbyte\") / delta(disk.dev.write)\nwrite_avqsz.caption = Average I/O request size for writes to the device\nwrite_avqsz.default = false\n\ndiscard.heading = DR/S\ndiscard.metric = rate(disk.dev.discard)\ndiscard.caption = Rate of discard requests\n\ndiscard_bytes.heading = DRB/S\ndiscard_bytes.metric = rate(disk.dev.discard_bytes)\ndiscard_bytes.caption = Discard request throughput\ndiscard_bytes.default = false\n\ndiscard_merge.heading = DRQM/S\ndiscard_merge.metric = rate(disk.dev.discard_merge)\ndiscard_merge.caption = Rate discards merged before queued\ndiscard_merge.default = false\n\ndiscard_merge_pct.heading = DRQM%\ndiscard_merge_pct.metric = 100 * rate(disk.dev.discard_merge)  / rate(disk.dev.discard)\ndiscard_merge_pct.caption = Percentage discards merged before queued\ndiscard_merge_pct.format = percent\ndiscard_merge_pct.default = false\n\ndiscard_await.heading = DAWAIT\ndiscard_await.metric = delta(disk.dev.discard_rawactive) / delta(disk.dev.discard)\ndiscard_await.caption = Average time discard requests queued and serviced\ndiscard_await.default = false\n\ndiscard_avqsz.heading = DARQSZ\ndiscard_avqsz.metric = rescale(delta(disk.dev.discard_bytes), \"kbyte\") / delta(disk.dev.discard)\ndiscard_avqsz.caption = Average I/O request size for discards to the device\ndiscard_avqsz.default = false\n\nflush.heading = F/S\nflush.metric = rate(disk.dev.flush)\nflush.default = false\nflush.caption = Flushes per second\n\nflush_await.heading = FAWAIT\nflush_await.metric = delta(disk.dev.flush_rawactive) / delta(disk.dev.flush)\nflush_await.caption = Average time that flush requests were queued and serviced\nflush_await.default = false\n\nqlen.heading = AQU-SZ\nqlen.metric = rate(disk.dev.read_rawactive) + rate(disk.dev.write_rawactive)\nqlen.caption = Average read and write I/O queue length to the device\n\nutil.heading = UTIL%\nutil.metric = 100 * rate(disk.dev.avactive)\nutil.caption = Percentage of time device was busy processing requests\nutil.format = percent\n"
  },
  {
    "path": "pcp/screens/execsnoop",
    "content": "#\n# pcp-htop(1) configuration file - see pcp-htop(5)\n#\n\n[execsnoop]\nheading = ExecSnoop\ncaption = BPF exec(2) syscall snoop\ndefault = false\n\npid.heading = PID\npid.caption = Process Identifier\npid.metric = defined(bpf.execsnoop.pid) ? bpf.execsnoop.pid : novalue()\npid.format = process\n\nppid.heading = PPID\nppid.caption = Parent Process\nppid.metric = defined(bpf.execsnoop.ppid) ? bpf.execsnoop.ppid : novalue()\nppid.format = process\n\nuid.heading = UID\nuid.caption = User Identifier\nuid.metric = defined(bpf.execsnoop.uid) ? bpf.execsnoop.uid : novalue()\n\ncomm.heading = COMM\ncomm.caption = Command\ncomm.width = -16\ncomm.metric = defined(bpf.execsnoop.comm) ? bpf.execsnoop.comm : novalue()\ncomm.format = command\n\nret.heading = RET\nret.caption = Return Code\nret.metric = defined(bpf.execsnoop.ret) ? bpf.execsnoop.ret : novalue()\n\npath.heading = Arguments\npath.caption = Arguments\npath.width = -12\npath.metric = defined(bpf.execsnoop.args) ? bpf.execsnoop.args : novalue()\n"
  },
  {
    "path": "pcp/screens/exitsnoop",
    "content": "#\n# pcp-htop(1) configuration file - see pcp-htop(5)\n#\n\n[exitsnoop]\nheading = ExitSnoop\ncaption = BPF process exit(2) snoop\ndefault = false\n\npid.heading = PID\npid.caption = Process Identifier\npid.metric = defined(bpf.exitsnoop.pid) ? bpf.exitsnoop.pid : novalue()\npid.format = process\n\nppid.heading = PPID\nppid.caption = Parent Process\nppid.metric = defined(bpf.exitsnoop.ppid) ? bpf.exitsnoop.ppid : novalue()\nppid.format = process\n\ntid.heading = TID\ntid.caption = Task Identifier\ntid.metric = defined(bpf.exitsnoop.tid) ? bpf.exitsnoop.tid : novalue()\ntid.format = process\ntid.default = false\n\nsignal.heading = SIG\nsignal.caption = Signal number\nsignal.metric = defined(bpf.exitsnoop.sig) ? bpf.exitsnoop.sig : novalue()\n\nexit.heading = EXIT\nexit.caption = Exit Code\nexit.metric = defined(bpf.exitsnoop.exit_code) ? bpf.exitsnoop.exit_code : novalue()\n\ncore.heading = CORE\ncore.caption = Dumped core\ncore.metric = defined(bpf.exitsnoop.coredump) ? bpf.exitsnoop.coredump : novalue()\ncore.default = false\n\nage.heading = AGE\nage.caption = Process age\nage.metric = defined(bpf.exitsnoop.age) ? bpf.exitsnoop.age : novalue()\nage.default = false\n\ncomm.heading = Command\ncomm.caption = COMM\ncomm.width = -16\ncomm.metric = defined(bpf.exitsnoop.comm) ? bpf.exitsnoop.comm : novalue()\ncomm.format = command\n"
  },
  {
    "path": "pcp/screens/filesystems",
    "content": "#\n# pcp-htop(1) configuration file - see pcp-htop(5)\n#\n\n[filesystems]\nheading = Filesystems\ncaption = Mounted block device filesystems\n\nblockdev.heading = Device\nblockdev.metric = filesys.mountdir\nblockdev.instances = true\nblockdev.width = -14\n\nblocksize.heading = BSIZE\nblocksize.metric = filesys.blocksize\nblocksize.default = false\n\ncapacity.heading = SIZE\ncapacity.metric = filesys.capacity\n\nused.heading = USED\nused.metric = filesys.used\n\nfree.heading = FREE\nfree.metric = filesys.free\nfree.default = false\n\navail.heading = AVAIL\navail.metric = filesys.avail\n\nfull.heading = USE%\nfull.metric = filesys.full\nfull.format = percent\n\nusedfiles.heading = USEDF\nusedfiles.metric = filesys.usedfiles\nusedfiles.default = false\n\nfreefiles.heading = FREEF\nfreefiles.metric = filesys.freefiles\nfreefiles.default = false\n\nmaxfiles.heading = MAXF\nmaxfiles.metric = filesys.maxfiles\nmaxfiles.default = false\n\nmountdir.heading = Mount point\nmountdir.metric = filesys.mountdir\nmountdir.format = path\nmountdir.width = -33\n"
  },
  {
    "path": "pcp/screens/opensnoop",
    "content": "#\n# pcp-htop(1) configuration file - see pcp-htop(5)\n#\n\n[opensnoop]\nheading = OpenSnoop\ncaption = BPF open(2) syscall snoop\ndefault = false\n\npid.heading = PID\npid.metric = defined(bpf.opensnoop.pid) ? bpf.opensnoop.pid : novalue()\npid.format = process\n\ncomm.heading = COMM\ncomm.metric = defined(bpf.opensnoop.comm) ? bpf.opensnoop.comm : novalue()\ncomm.format = command\n\nfd.heading = FD\nfd.metric = defined(bpf.opensnoop.fd) ? bpf.opensnoop.fd : novalue()\n\nerr.heading = ERR\nerr.metric = defined(bpf.opensnoop.err) ? bpf.opensnoop.err : novalue()\n\nfile.heading = File name\nfile.width = -32\nfile.metric = defined(bpf.opensnoop.fname) ? bpf.opensnoop.fname : novalue()\nfile.format = path\n"
  },
  {
    "path": "pcp-htop.5.in",
    "content": ".TH \"PCP-HTOP\" \"5\" \"2026\" \"@PACKAGE_STRING@\" \"File Formats\"\n.SH \"NAME\"\n\\f3pcp-htop\\f1 \\- pcp-htop configuration file\n.SH \"DESCRIPTION\"\n.B pcp-htop\nis a customizable performance metrics reporting tool.\nIt has a dynamic architecture, where a set of configuration files\nprovide additional, optional meters and columns to extend the fixed\nset of display options provided by regular\n.BR htop .\n.LP\nThese configuration files can be provided from both system-wide\nlocations (first\n.I @sysconfdir@/pcp/htop\nthen\n.IR @datarootdir@/pcp/htop )\nand below the user's home directory (usually\n.IR ~/.config/htop ).\nWithin these locations the\n.I meters\nand\n.I columns\nare scanned for dynamic Meter and Column specifications.\n.LP\nMeters are displayed in the top part of the\n.B pcp-htop\nwindow, and columns are displayed in the lower part.\nMeters tend to display system-wide metrics, and Columns\ndisplay metrics about individual processes.\n.LP\nThe formats are similar but have slightly different requirements.\nBoth formats follow the common ini-style. Blank lines are ignored.\nLines starting with the \"#\" character are treated as comments.\n.SH \"METERS\"\nThe following is an example configuration for a new Redis meter:\n.LP\n.ft CR\n.nf\n.in +0.5i\n[redisclient]\ncaption = Redis clients\ntype = bar\nblocked.metric = redis.blocked_clients\nblocked.color = blue\nblocked.label = blk\nclients.metric = redis.connected_clients\nclients.color = green\nclients.label = conn\n.in\n.fi\n.ft 1\n.LP\nA configuration file can contain multiple meter definitions.\nEach definition begins with a identifying name enclosed by\nsquare brackets \\-\n.I redisclient\nin this example.\nThe name is used internally within\n.B pcp-htop\nand must be unique, must begin with an alphabetic character,\nand may subsequently only contain alphanumeric characters or\nthe underscore character.\nNo whitespace or other characters are allowed.\n.LP\nThere are several parameters that define the way the meter\nwill be displayed to the user.\n.TP 5\n.B caption\nThis value is displayed on the Setup screen once the meter\nhas been selected.\nA truncated version of the\n.I caption\nwill also be displayed (followed by a colon) on the primary\ndisplay while the meter is updating.\n.TP\n.B description\nThis can be used to provide more detail during the meter\nselection process on the Setup screen, and if present it is\ndisplayed in the \"Available Meters\" column.\nIf not present, the\n.B caption\nwill be used for this.\nIf neither is present, the internal (mandatory)\n.B name\nwill be used.\n.TP\n.B type\nThis setting allows a preferred default meter type to be specified.\nThe associated value must be one of\n.IR bar ,\n.IR text ,\n.IR graph ,\nor\n.IR led .\nIf no value is provided for a dynamic meter, the default value of\n.IR text\nwill be used.\n.TP\n.B maximum\nA numeric value can also be set to size the meter, such that\nvalues (e.g. for a\n.I bar\ntype meter display) will be scaled within range zero to\n.IR maximum .\n.LP\nThe remaining definition syntax describes the individual\nmetric(s) which will be used to animate the meter.\nOne or more metrics must be specified for each meter and\nthere are several properties associated with each.\nOnce again, these metrics must be named (the same rules\ndescribed above for meters apply here) and the following\nproperties can be configured:\n.TP 5\n.B name.metric\nThis is the only mandatory field and associates a PCP metric\nwith the meter.\nValues sampled for each metric at runtime provide the\nanimation visible in the\n.B pcp-htop\ndisplay.\nThe metric specification can be either a PCP metric name as\nlisted by\n.BR pminfo (1)\nor a \"derived\" metric expression.\nThe format for derived metric expressions is described on the\n.BR pmRegisterDerived (3)\nmanual page.\n.TP\n.B name.color\nSetting color to be used when rendering metric values.\nPossible values are\n.IR red ,\n.IR green ,\n.IR blue ,\n.IR cyan ,\n.IR magenta ,\n.IR yellow ,\n.IR gray ,\n.I darkgray\nor\n.IR white .\n.TP\n.B name.label\nAn optional, short label to display before the metric value.\nThe \":\" character will be appended to the\n.I label\nbefore the metric value part of the display.\n.TP\n.B name.suffix\nAn optional, short suffix to display after the metric value.\nCommonly used to indicate values as a percentage using a \"%\"\n.I suffix\nvalue and to provide the base unit of measurement.\nNote that since PCP maintains units for metrics, for those\nmetrics that have dimension in \"space\" (bytes, kilobytes,\nmegabytes, etc), a suffix will be automatically appended.\n.SH \"COLUMNS\"\nThe following is an example configuration for a new column\nshowing open file descriptors for each process:\n.LP\n.ft CR\n.nf\n.in +0.5i\n[openfds]\nheading = FDS\ncaption = FDCOUNT\ndescription = Open file descriptors\nmetric = proc.fd.count\nwidth = 3\n.in\n.fi\n.ft 1\n.LP\nA configuration file can contain multiple column definitions.\nEach definition begins with a identifying name enclosed\nby square brackets \\-\n.I openfds\nin this example, and the same rules apply as described above\nfor meter names.\n.LP\nEach column must specify a metric.\nOptional parameters can also be set.\n.TP 5\n.B metric\nAs with meters, the metric value must be either a PCP metric\nname as listed by\n.BR pminfo (1)\nor a derived metric.\nThe metric must have an instance domain (set of values) and\nthat instance domain must map to the set of processes with\nthe instance identifier being PIDs (process identifiers).\nTypically this will be metrics from the\n.I proc\nor\n.I hotproc\nnamespace (\\c\n.BR pmdaproc (1)),\nbut metrics from other domains (\\c\n.BR pmdabpf (1),\netc) that have per-process values are equally applicable.\n.TP\n.B width\nColumn width to use when displaying values for the metric.\nA negative value can be used to specify left alignment.\nAn upper column limit of 28 characters is enforced.\nThe default column width is 5 characters.\n.TP\n.B heading\nThe short title that will be displayed at the head of the\ncolumn \\- usually a short, cryptic, all uppercase string.\n.TP\n.B caption\nA short identifying word presented to users on the Setup\nscreen under both the Available and Active Columns lists.\n.TP\n.B description\nText that assists users to understand the meaning of this\ncolumn when it is being presented via the Setup screen in\nthe Available Columns list.\n.SH \"SEE ALSO\"\n.BR pcp-htop (1),\n.BR pminfo (1),\n.BR pmcd (1),\n.BR pmdaproc (1),\n.BR pmdabpf (1)\nand\n.BR pmRegisterDerived (3).\n.SH \"AUTHORS\"\n.B htop\nwas originally developed by Hisham Muhammad.\nNowadays it is maintained by the community at <htop@groups.io>.\n.LP\n.B pcp-htop\nis maintained as a collaboration between the <htop@groups.io> and <pcp@groups.io>\ncommunities, and forms part of the Performance Co-Pilot suite of tools.\n"
  },
  {
    "path": "pcp-htop.c",
    "content": "/*\nhtop - pcp-htop.c\n(C) 2004-2011 Hisham H. Muhammad\n(C) 2020-2021 htop dev team\nReleased under the GNU GPLv2+, see the COPYING file\nin the source distribution for its full text.\n*/\n\n#include \"config.h\" // IWYU pragma: keep\n\n#include <pcp/pmapi.h>\n\n#include \"CommandLine.h\"\n#include \"Platform.h\"\n\n\nconst char* program = \"pcp-htop\";\n\nint main(int argc, char** argv) {\n   pmSetProgname(program);\n\n   /* extract environment variables */\n   opts.flags |= PM_OPTFLAG_ENV_ONLY;\n   (void)pmGetOptions(argc, argv, &opts);\n\n   return CommandLine_run(argc, argv);\n}\n"
  },
  {
    "path": "scripts/htop_suppressions.valgrind",
    "content": "{\n   <ncurses internal memory>\n   Memcheck:Leak\n   match-leak-kinds: possible,reachable\n   ...\n   fun:doupdate_sp\n   fun:wrefresh\n}\n\n{\n   <ncurses internal memory>\n   Memcheck:Leak\n   match-leak-kinds: possible,reachable\n   ...\n   fun:newterm_sp\n   fun:newterm\n   fun:initscr\n   fun:CRT_init\n}\n\n{\n   <ncurses internal memory>\n   Memcheck:Leak\n   match-leak-kinds: reachable\n   ...\n   obj:*/libtinfo*\n   fun:CRT_init\n}\n\n{\n   <ncurses internal memory>\n   Memcheck:Leak\n   match-leak-kinds: reachable\n   ...\n   obj:*/libncurses*\n   fun:CRT_init\n}\n\n{\n   <ncurses internal memory>\n   Memcheck:Leak\n   match-leak-kinds: possible,reachable\n   ...\n   obj:*/libncurses*\n   fun:CRT_setColors\n   fun:CRT_init\n}\n\n{\n   <devstat internal memory>\n   Memcheck:Leak\n   match-leak-kinds: possible,reachable\n   ...\n   obj:*/libdevstat*\n   ...\n   fun:Platform_getDiskIO\n}\n"
  },
  {
    "path": "scripts/run_valgrind.sh",
    "content": "#!/bin/sh\n\nSCRIPT=$(readlink -f \"$0\")\nSCRIPTDIR=$(dirname \"$SCRIPT\")\n\nvalgrind --leak-check=full --show-reachable=yes --show-leak-kinds=all --track-fds=yes --errors-for-leak-kinds=all --track-origins=yes --suppressions=\"${SCRIPTDIR}/htop_suppressions.valgrind\" \"${SCRIPTDIR}/../htop\"\n"
  },
  {
    "path": "solaris/Platform.c",
    "content": "/*\nhtop - solaris/Platform.c\n(C) 2014 Hisham H. Muhammad\n(C) 2015 David C. Hunt\n(C) 2017,2018 Guy M. Broome\nReleased under the GNU GPLv2+, see the COPYING file\nin the source distribution for its full text.\n*/\n\n#include \"config.h\" // IWYU pragma: keep\n\n#include \"solaris/Platform.h\"\n\n#include <kstat.h>\n#include <math.h>\n#include <string.h>\n#include <time.h>\n#include <utmpx.h>\n#include <sys/loadavg.h>\n#include <sys/resource.h>\n#include <sys/types.h>\n#include <sys/time.h>\n#include <sys/var.h>\n\n#include \"Macros.h\"\n#include \"Meter.h\"\n#include \"CPUMeter.h\"\n#include \"MemoryMeter.h\"\n#include \"MemorySwapMeter.h\"\n#include \"SwapMeter.h\"\n#include \"TasksMeter.h\"\n#include \"LoadAverageMeter.h\"\n#include \"ClockMeter.h\"\n#include \"DateTimeMeter.h\"\n#include \"HostnameMeter.h\"\n#include \"SysArchMeter.h\"\n#include \"UptimeMeter.h\"\n#include \"XUtils.h\"\n\n#include \"solaris/SolarisMachine.h\"\n\n#include \"zfs/ZfsArcMeter.h\"\n#include \"zfs/ZfsCompressedArcMeter.h\"\n\n\nconst ScreenDefaults Platform_defaultScreens[] = {\n   {\n      .name = \"Default\",\n      .columns = \"PID LWPID USER PRIORITY NICE M_VIRT M_RESIDENT STATE PERCENT_CPU PERCENT_MEM TIME Command\",\n      .sortKey = \"PERCENT_CPU\",\n   },\n};\n\nconst unsigned int Platform_numberOfDefaultScreens = ARRAYSIZE(Platform_defaultScreens);\n\nconst SignalItem Platform_signals[] = {\n   { .name = \" 0 Cancel\",      .number =  0 },\n   { .name = \" 1 SIGHUP\",      .number =  1 },\n   { .name = \" 2 SIGINT\",      .number =  2 },\n   { .name = \" 3 SIGQUIT\",     .number =  3 },\n   { .name = \" 4 SIGILL\",      .number =  4 },\n   { .name = \" 5 SIGTRAP\",     .number =  5 },\n   { .name = \" 6 SIGABRT/IOT\", .number =  6 },\n   { .name = \" 7 SIGEMT\",      .number =  7 },\n   { .name = \" 8 SIGFPE\",      .number =  8 },\n   { .name = \" 9 SIGKILL\",     .number =  9 },\n   { .name = \"10 SIGBUS\",      .number = 10 },\n   { .name = \"11 SIGSEGV\",     .number = 11 },\n   { .name = \"12 SIGSYS\",      .number = 12 },\n   { .name = \"13 SIGPIPE\",     .number = 13 },\n   { .name = \"14 SIGALRM\",     .number = 14 },\n   { .name = \"15 SIGTERM\",     .number = 15 },\n   { .name = \"16 SIGUSR1\",     .number = 16 },\n   { .name = \"17 SIGUSR2\",     .number = 17 },\n   { .name = \"18 SIGCHLD/CLD\", .number = 18 },\n   { .name = \"19 SIGPWR\",      .number = 19 },\n   { .name = \"20 SIGWINCH\",    .number = 20 },\n   { .name = \"21 SIGURG\",      .number = 21 },\n   { .name = \"22 SIGPOLL/IO\",  .number = 22 },\n   { .name = \"23 SIGSTOP\",     .number = 23 },\n   { .name = \"24 SIGTSTP\",     .number = 24 },\n   { .name = \"25 SIGCONT\",     .number = 25 },\n   { .name = \"26 SIGTTIN\",     .number = 26 },\n   { .name = \"27 SIGTTOU\",     .number = 27 },\n   { .name = \"28 SIGVTALRM\",   .number = 28 },\n   { .name = \"29 SIGPROF\",     .number = 29 },\n   { .name = \"30 SIGXCPU\",     .number = 30 },\n   { .name = \"31 SIGXFSZ\",     .number = 31 },\n   { .name = \"32 SIGWAITING\",  .number = 32 },\n   { .name = \"33 SIGLWP\",      .number = 33 },\n   { .name = \"34 SIGFREEZE\",   .number = 34 },\n   { .name = \"35 SIGTHAW\",     .number = 35 },\n   { .name = \"36 SIGCANCEL\",   .number = 36 },\n   { .name = \"37 SIGLOST\",     .number = 37 },\n   { .name = \"38 SIGXRES\",     .number = 38 },\n   { .name = \"39 SIGJVM1\",     .number = 39 },\n   { .name = \"40 SIGJVM2\",     .number = 40 },\n   { .name = \"41 SIGINFO\",     .number = 41 },\n};\n\nconst unsigned int Platform_numberOfSignals = ARRAYSIZE(Platform_signals);\n\nenum {\n   MEMORY_CLASS_USED = 0,\n   MEMORY_CLASS_LOCKED,\n}; // N.B. the chart will display categories in this order\n\nconst MemoryClass Platform_memoryClasses[] = {\n   [MEMORY_CLASS_USED] = { .label = \"used\", .countsAsUsed = true, .countsAsCache = false, .color = MEMORY_1 },\n   [MEMORY_CLASS_LOCKED] = { .label = \"locked\", .countsAsUsed = true, .countsAsCache = true, .color = MEMORY_2 },\n};\n\nconst unsigned int Platform_numberOfMemoryClasses = ARRAYSIZE(Platform_memoryClasses);\n\nconst MeterClass* const Platform_meterTypes[] = {\n   &CPUMeter_class,\n   &ClockMeter_class,\n   &DateMeter_class,\n   &DateTimeMeter_class,\n   &LoadAverageMeter_class,\n   &LoadMeter_class,\n   &MemoryMeter_class,\n   &SwapMeter_class,\n   &MemorySwapMeter_class,\n   &TasksMeter_class,\n   &BatteryMeter_class,\n   &HostnameMeter_class,\n   &SysArchMeter_class,\n   &UptimeMeter_class,\n   &SecondsUptimeMeter_class,\n   &AllCPUsMeter_class,\n   &AllCPUs2Meter_class,\n   &AllCPUs4Meter_class,\n   &AllCPUs8Meter_class,\n   &LeftCPUsMeter_class,\n   &RightCPUsMeter_class,\n   &LeftCPUs2Meter_class,\n   &RightCPUs2Meter_class,\n   &LeftCPUs4Meter_class,\n   &RightCPUs4Meter_class,\n   &LeftCPUs8Meter_class,\n   &RightCPUs8Meter_class,\n   &ZfsArcMeter_class,\n   &ZfsCompressedArcMeter_class,\n   &BlankMeter_class,\n   NULL\n};\n\nbool Platform_init(void) {\n   /* no platform-specific setup needed */\n   return true;\n}\n\nvoid Platform_done(void) {\n   /* no platform-specific cleanup needed */\n}\n\nvoid Platform_setBindings(Htop_Action* keys) {\n   /* no platform-specific key bindings */\n   (void) keys;\n}\n\nint Platform_getUptime(void) {\n   int boot_time = 0;\n   int curr_time = time(NULL);\n   struct utmpx* ent;\n\n   while (( ent = getutxent() )) {\n      if ( String_eq(\"system boot\", ent->ut_line )) {\n         boot_time = ent->ut_tv.tv_sec;\n      }\n   }\n\n   endutxent();\n\n   return (curr_time - boot_time);\n}\n\nvoid Platform_getLoadAverage(double* one, double* five, double* fifteen) {\n   double plat_loadavg[3];\n   if (getloadavg( plat_loadavg, 3 ) < 0) {\n      *one = NAN;\n      *five = NAN;\n      *fifteen = NAN;\n      return;\n   }\n   *one = plat_loadavg[LOADAVG_1MIN];\n   *five = plat_loadavg[LOADAVG_5MIN];\n   *fifteen = plat_loadavg[LOADAVG_15MIN];\n}\n\npid_t Platform_getMaxPid(void) {\n   int vproc = 32778; // Reasonable Solaris default\n\n   kstat_ctl_t* kc = kstat_open();\n   if (kc != NULL) {\n      kstat_t* kshandle = kstat_lookup_wrapper(kc, \"unix\", 0, \"var\");\n      if (kshandle != NULL) {\n         kstat_read(kc, kshandle, NULL);\n\n         kvar_t* ksvar = kshandle->ks_data;\n         if (ksvar && ksvar->v_proc > 0) {\n            vproc = ksvar->v_proc;\n         }\n      }\n      kstat_close(kc);\n   }\n\n   return vproc;\n}\n\ndouble Platform_setCPUValues(Meter* this, unsigned int cpu) {\n   const Machine* host = this->host;\n   const SolarisMachine* shost = (const SolarisMachine*) host;\n   unsigned int cpus = host->existingCPUs;\n   const CPUData* cpuData = NULL;\n\n   if (cpus == 1) {\n      // single CPU box has everything in spl->cpus[0]\n      cpuData = &(shost->cpus[0]);\n   } else {\n      cpuData = &(shost->cpus[cpu]);\n   }\n\n   if (!cpuData->online) {\n      this->curItems = 0;\n      return NAN;\n   }\n\n   double percent;\n   double* v = this->values;\n\n   v[CPU_METER_NICE]   = cpuData->nicePercent;\n   v[CPU_METER_NORMAL] = cpuData->userPercent;\n   if (host->settings->detailedCPUTime) {\n      v[CPU_METER_KERNEL]  = cpuData->systemPercent;\n      v[CPU_METER_IRQ]     = cpuData->irqPercent;\n      this->curItems = 4;\n   } else {\n      v[CPU_METER_KERNEL] = cpuData->systemAllPercent;\n      this->curItems = 3;\n   }\n\n   percent = sumPositiveValues(v, this->curItems);\n   percent = MINIMUM(percent, 100.0);\n\n   v[CPU_METER_FREQUENCY] = cpuData->frequency;\n   v[CPU_METER_TEMPERATURE] = NAN;\n\n   return percent;\n}\n\nvoid Platform_setMemoryValues(Meter* this) {\n   const Machine* host = this->host;\n   const SolarisMachine* shost = (const SolarisMachine*) host;\n   this->total = host->totalMem;\n   this->values[MEMORY_CLASS_USED] = shost->usedMem;\n   this->values[MEMORY_CLASS_LOCKED] = shost->lockedMem;\n}\n\nvoid Platform_setSwapValues(Meter* this) {\n   const Machine* host = this->host;\n   this->total = host->totalSwap;\n   this->values[SWAP_METER_USED] = host->usedSwap;\n}\n\nvoid Platform_setZfsArcValues(Meter* this) {\n   const SolarisMachine* shost = (const SolarisMachine*) this->host;\n\n   ZfsArcMeter_readStats(this, &shost->zfs);\n}\n\nvoid Platform_setZfsCompressedArcValues(Meter* this) {\n   const SolarisMachine* shost = (const SolarisMachine*) this->host;\n\n   ZfsCompressedArcMeter_readStats(this, &shost->zfs);\n}\n\nstatic int Platform_buildenv(void* accum, struct ps_prochandle* Phandle, uintptr_t addr, const char* str) {\n   envAccum* accump = accum;\n   (void) Phandle;\n   (void) addr;\n\n   size_t thissz = strlen(str);\n\n   while ((thissz + 2) > (accump->capacity - accump->size)) {\n      if (accump->capacity > (SIZE_MAX / 2))\n         return 1;\n\n      accump->capacity *= 2;\n      accump->env = xRealloc(accump->env, accump->capacity);\n   }\n\n   strlcpy( accump->env + accump->size, str, accump->capacity - accump->size);\n   strncpy( accump->env + accump->size + thissz + 1, \"\\n\", 2);\n\n   accump->size += thissz + 1;\n   return 0;\n}\n\nchar* Platform_getProcessEnv(pid_t pid) {\n   envAccum envBuilder;\n   pid_t realpid = pid / 1024;\n   int graberr;\n   struct ps_prochandle* Phandle;\n\n   if ((Phandle = Pgrab(realpid, PGRAB_RDONLY, &graberr)) == NULL) {\n      return NULL;\n   }\n\n   envBuilder.capacity = 4096;\n   envBuilder.size     = 0;\n   envBuilder.env      = xMalloc(envBuilder.capacity);\n\n   (void) Penv_iter(Phandle, Platform_buildenv, &envBuilder);\n\n   Prelease(Phandle, 0);\n\n   strncpy( envBuilder.env + envBuilder.size, \"\\0\", 1);\n\n   return xRealloc(envBuilder.env, envBuilder.size + 1);\n}\n\nFileLocks_ProcessData* Platform_getProcessLocks(pid_t pid) {\n   (void)pid;\n   return NULL;\n}\n\nvoid Platform_getFileDescriptors(double* used, double* max) {\n   *used = NAN;\n   *max = NAN;\n}\n\nbool Platform_getDiskIO(DiskIOData* data) {\n   // TODO\n   (void)data;\n   return false;\n}\n\nbool Platform_getNetworkIO(NetworkIOData* data) {\n   // TODO\n   (void)data;\n   return false;\n}\n\nvoid Platform_getBattery(double* percent, ACPresence* isOnAC) {\n   *percent = NAN;\n   *isOnAC = AC_ERROR;\n}\n"
  },
  {
    "path": "solaris/Platform.h",
    "content": "#ifndef HEADER_Platform\n#define HEADER_Platform\n/*\nhtop - solaris/Platform.h\n(C) 2014 Hisham H. Muhammad\n(C) 2015 David C. Hunt\n(C) 2017,2018 Guy M. Broome\nReleased under the GNU GPLv2+, see the COPYING file\nin the source distribution for its full text.\n*/\n\n#include <kstat.h>\n\n/* On OmniOS /usr/include/sys/regset.h redefines ERR to 13 - \\r, breaking the Enter key.\n * Since ncurses macros use the ERR macro, we cannot use another name.\n */\n#undef ERR\n#include <libproc.h>\n#undef ERR\n#define ERR (-1)\n\n#include <signal.h>\n#include <stdbool.h>\n\n#include <sys/mkdev.h>\n#include <sys/proc.h>\n#include <sys/types.h>\n\n#include \"Action.h\"\n#include \"BatteryMeter.h\"\n#include \"CommandLine.h\"\n#include \"DiskIOMeter.h\"\n#include \"Hashtable.h\"\n#include \"MemoryMeter.h\"\n#include \"NetworkIOMeter.h\"\n#include \"ProcessLocksScreen.h\"\n#include \"SignalsPanel.h\"\n#include \"generic/gettime.h\"\n#include \"generic/hostname.h\"\n#include \"generic/uname.h\"\n\n\n#define  kill(pid, signal) kill(pid / 1024, signal)\n\ntypedef struct var kvar_t;\n\ntypedef struct envAccum_ {\n   size_t capacity;\n   size_t size;\n   size_t bytes;\n   char* env;\n} envAccum;\n\nextern const ScreenDefaults Platform_defaultScreens[];\n\nextern const unsigned int Platform_numberOfDefaultScreens;\n\nextern const SignalItem Platform_signals[];\n\nextern const unsigned int Platform_numberOfSignals;\n\nextern const MemoryClass Platform_memoryClasses[];\n\nextern const unsigned int Platform_numberOfMemoryClasses;\n\nextern const MeterClass* const Platform_meterTypes[];\n\nbool Platform_init(void);\n\nvoid Platform_done(void);\n\nvoid Platform_setBindings(Htop_Action* keys);\n\nint Platform_getUptime(void);\n\nvoid Platform_getLoadAverage(double* one, double* five, double* fifteen);\n\npid_t Platform_getMaxPid(void);\n\ndouble Platform_setCPUValues(Meter* this, unsigned int cpu);\n\nvoid Platform_setMemoryValues(Meter* this);\n\nvoid Platform_setSwapValues(Meter* this);\n\nvoid Platform_setZfsArcValues(Meter* this);\n\nvoid Platform_setZfsCompressedArcValues(Meter* this);\n\nchar* Platform_getProcessEnv(pid_t pid);\n\nFileLocks_ProcessData* Platform_getProcessLocks(pid_t pid);\n\nvoid Platform_getFileDescriptors(double* used, double* max);\n\nbool Platform_getDiskIO(DiskIOData* data);\n\nbool Platform_getNetworkIO(NetworkIOData* data);\n\nvoid Platform_getBattery(double* percent, ACPresence* isOnAC);\n\nstatic inline void Platform_getHostname(char* buffer, size_t size) {\n   Generic_hostname(buffer, size);\n}\n\nstatic inline const char* Platform_getRelease(void) {\n   return Generic_uname();\n}\n\nstatic inline const char* Platform_getFailedState(void) {\n   return NULL;\n}\n\n#define PLATFORM_LONG_OPTIONS\n\nstatic inline void Platform_longOptionsUsage(ATTR_UNUSED const char* name) { }\n\nstatic inline CommandLineStatus Platform_getLongOption(ATTR_UNUSED int opt, ATTR_UNUSED int argc, ATTR_UNUSED char** argv) {\n   return STATUS_ERROR_EXIT;\n}\n\nstatic inline void Platform_gettime_realtime(struct timeval* tv, uint64_t* msec) {\n   Generic_gettime_realtime(tv, msec);\n}\n\nstatic inline void Platform_gettime_monotonic(uint64_t* msec) {\n   Generic_gettime_monotonic(msec);\n}\n\nstatic inline void* kstat_data_lookup_wrapper(kstat_t* ksp, const char* name) {\nIGNORE_WCASTQUAL_BEGIN\n   return kstat_data_lookup(ksp, (char*)name);\nIGNORE_WCASTQUAL_END\n}\n\nstatic inline kstat_t* kstat_lookup_wrapper(kstat_ctl_t* kc, const char* ks_module, int ks_instance, const char* ks_name) {\nIGNORE_WCASTQUAL_BEGIN\n   return kstat_lookup(kc, (char*)ks_module, ks_instance, (char*)ks_name);\nIGNORE_WCASTQUAL_END\n}\n\nstatic inline Hashtable* Platform_dynamicMeters(void) {\n   return NULL;\n}\n\nstatic inline void Platform_dynamicMetersDone(ATTR_UNUSED Hashtable* table) { }\n\nstatic inline void Platform_dynamicMeterInit(ATTR_UNUSED Meter* meter) { }\n\nstatic inline void Platform_dynamicMeterUpdateValues(ATTR_UNUSED Meter* meter) { }\n\nstatic inline void Platform_dynamicMeterDisplay(ATTR_UNUSED const Meter* meter, ATTR_UNUSED RichString* out) { }\n\nstatic inline Hashtable* Platform_dynamicColumns(void) {\n   return NULL;\n}\n\nstatic inline void Platform_dynamicColumnsDone(ATTR_UNUSED Hashtable* table) { }\n\nstatic inline const char* Platform_dynamicColumnName(ATTR_UNUSED unsigned int key) {\n   return NULL;\n}\n\nstatic inline bool Platform_dynamicColumnWriteField(ATTR_UNUSED const Process* proc, ATTR_UNUSED RichString* str, ATTR_UNUSED unsigned int key) {\n   return false;\n}\n\nstatic inline Hashtable* Platform_dynamicScreens(void) {\n   return NULL;\n}\n\nstatic inline void Platform_defaultDynamicScreens(ATTR_UNUSED Settings* settings) { }\n\nstatic inline void Platform_addDynamicScreen(ATTR_UNUSED ScreenSettings* ss) { }\n\nstatic inline void Platform_addDynamicScreenAvailableColumns(ATTR_UNUSED Panel* availableColumns, ATTR_UNUSED const char* screen) { }\n\nstatic inline void Platform_dynamicScreensDone(ATTR_UNUSED Hashtable* screens) { }\n\n#endif\n"
  },
  {
    "path": "solaris/ProcessField.h",
    "content": "#ifndef HEADER_SolarisProcessField\n#define HEADER_SolarisProcessField\n/*\nhtop - solaris/ProcessField.h\n(C) 2020 htop dev team\nReleased under the GNU GPLv2+, see the COPYING file\nin the source distribution for its full text.\n*/\n\n\n#define PLATFORM_PROCESS_FIELDS  \\\n   ZONEID = 100,                 \\\n   ZONE  = 101,                  \\\n   PROJID = 102,                 \\\n   TASKID = 103,                 \\\n   POOLID = 104,                 \\\n   CONTID = 105,                 \\\n   LWPID = 106,                  \\\n                                 \\\n   DUMMY_BUMP_FIELD = CWD,       \\\n   // End of list\n\n\n#endif /* HEADER_SolarisProcessField */\n"
  },
  {
    "path": "solaris/SolarisMachine.c",
    "content": "/*\nhtop - SolarisMachine.c\n(C) 2014 Hisham H. Muhammad\n(C) 2017,2018 Guy M. Broome\nReleased under the GNU GPLv2+, see the COPYING file\nin the source distribution for its full text.\n*/\n\n#include \"config.h\" // IWYU pragma: keep\n\n#include \"solaris/SolarisMachine.h\"\n\n#include <unistd.h>\n#include <stdlib.h>\n#include <sys/types.h>\n#include <sys/user.h>\n#include <limits.h>\n#include <string.h>\n#include <procfs.h>\n#include <errno.h>\n#include <pwd.h>\n#include <math.h>\n#include <time.h>\n\n#include \"CRT.h\"\n#include \"solaris/Platform.h\"\n\n\nstatic void SolarisMachine_updateCPUcount(SolarisMachine* this) {\n   Machine* super = &this->super;\n   long int s;\n   bool change = false;\n\n   s = sysconf(_SC_NPROCESSORS_CONF);\n   if (s < 1)\n      CRT_fatalError(\"Cannot get existing CPU count by sysconf(_SC_NPROCESSORS_CONF)\");\n\n   if (s != super->existingCPUs) {\n      if (s == 1) {\n         this->cpus = xRealloc(this->cpus, sizeof(CPUData));\n         this->cpus[0].online = true;\n      } else {\n         this->cpus = xReallocArray(this->cpus, s + 1, sizeof(CPUData));\n         this->cpus[0].online = true; /* average is always \"online\" */\n         for (int i = 1; i < s + 1; i++) {\n            this->cpus[i].online = false;\n         }\n      }\n\n      change = true;\n      super->existingCPUs = s;\n   }\n\n   s = sysconf(_SC_NPROCESSORS_ONLN);\n   if (s < 1)\n      CRT_fatalError(\"Cannot get active CPU count by sysconf(_SC_NPROCESSORS_ONLN)\");\n\n   if (s != super->activeCPUs) {\n      change = true;\n      super->activeCPUs = s;\n   }\n\n   if (change) {\n      kid_t update_kid = kstat_chain_update(this->kd);\n      if (update_kid < 0)\n         CRT_fatalError(\"Cannot update kstat chain\");\n   }\n}\n\n\nstatic void SolarisMachine_scanCPUTime(SolarisMachine* this) {\n   Machine* super = &this->super;\n   unsigned int activeCPUs = super->activeCPUs;\n   unsigned int existingCPUs = super->existingCPUs;\n   kstat_t* cpuinfo = NULL;\n   kstat_named_t* idletime = NULL;\n   kstat_named_t* intrtime = NULL;\n   kstat_named_t* krnltime = NULL;\n   kstat_named_t* usertime = NULL;\n   kstat_named_t* cpu_freq = NULL;\n   double idlebuf = 0;\n   double intrbuf = 0;\n   double krnlbuf = 0;\n   double userbuf = 0;\n   int arrskip = 0;\n\n   assert(existingCPUs > 0);\n   assert(this->kd);\n\n   if (existingCPUs > 1) {\n      // Store values for the stats loop one extra element up in the array\n      // to leave room for the average to be calculated afterwards\n      arrskip++;\n   }\n\n   // Calculate per-CPU statistics first\n   for (unsigned int i = 0; i < existingCPUs; i++) {\n      CPUData* cpuData = &(this->cpus[i + arrskip]);\n\n      if ((cpuinfo = kstat_lookup_wrapper(this->kd, \"cpu\", i, \"sys\")) != NULL) {\n         cpuData->online = true;\n         if (kstat_read(this->kd, cpuinfo, NULL) != -1) {\n            idletime = kstat_data_lookup_wrapper(cpuinfo, \"cpu_nsec_idle\");\n            intrtime = kstat_data_lookup_wrapper(cpuinfo, \"cpu_nsec_intr\");\n            krnltime = kstat_data_lookup_wrapper(cpuinfo, \"cpu_nsec_kernel\");\n            usertime = kstat_data_lookup_wrapper(cpuinfo, \"cpu_nsec_user\");\n         }\n      } else {\n         cpuData->online = false;\n         continue;\n      }\n\n      assert( (idletime != NULL) && (intrtime != NULL)\n           && (krnltime != NULL) && (usertime != NULL) );\n\n      if (super->settings->showCPUFrequency) {\n         if ((cpuinfo = kstat_lookup_wrapper(this->kd, \"cpu_info\", i, NULL)) != NULL) {\n            if (kstat_read(this->kd, cpuinfo, NULL) != -1) {\n               cpu_freq = kstat_data_lookup_wrapper(cpuinfo, \"current_clock_Hz\");\n            }\n         }\n\n         assert( cpu_freq != NULL );\n      }\n\n      uint64_t totaltime = (idletime->value.ui64 - cpuData->lidle)\n                         + (intrtime->value.ui64 - cpuData->lintr)\n                         + (krnltime->value.ui64 - cpuData->lkrnl)\n                         + (usertime->value.ui64 - cpuData->luser);\n\n      // Calculate percentages of deltas since last reading\n      cpuData->userPercent      = ((usertime->value.ui64 - cpuData->luser) / (double)totaltime) * 100.0;\n      cpuData->nicePercent      = (double)0.0; // Not implemented on Solaris\n      cpuData->systemPercent    = ((krnltime->value.ui64 - cpuData->lkrnl) / (double)totaltime) * 100.0;\n      cpuData->irqPercent       = ((intrtime->value.ui64 - cpuData->lintr) / (double)totaltime) * 100.0;\n      cpuData->systemAllPercent = cpuData->systemPercent + cpuData->irqPercent;\n      cpuData->idlePercent      = ((idletime->value.ui64 - cpuData->lidle) / (double)totaltime) * 100.0;\n      // Store current values to use for the next round of deltas\n      cpuData->luser            = usertime->value.ui64;\n      cpuData->lkrnl            = krnltime->value.ui64;\n      cpuData->lintr            = intrtime->value.ui64;\n      cpuData->lidle            = idletime->value.ui64;\n      // Add frequency in MHz\n      cpuData->frequency        = super->settings->showCPUFrequency ? (double)cpu_freq->value.ui64 / 1E6 : NAN;\n      // Accumulate the current percentages into buffers for later average calculation\n      if (existingCPUs > 1) {\n         userbuf               += cpuData->userPercent;\n         krnlbuf               += cpuData->systemPercent;\n         intrbuf               += cpuData->irqPercent;\n         idlebuf               += cpuData->idlePercent;\n      }\n   }\n\n   if (existingCPUs > 1) {\n      CPUData* cpuData          = &(this->cpus[0]);\n      cpuData->userPercent      = userbuf / activeCPUs;\n      cpuData->nicePercent      = (double)0.0; // Not implemented on Solaris\n      cpuData->systemPercent    = krnlbuf / activeCPUs;\n      cpuData->irqPercent       = intrbuf / activeCPUs;\n      cpuData->systemAllPercent = cpuData->systemPercent + cpuData->irqPercent;\n      cpuData->idlePercent      = idlebuf / activeCPUs;\n   }\n}\n\nstatic void SolarisMachine_scanMemoryInfo(SolarisMachine* this) {\n   Machine*            super = &this->super;\n   kstat_t*            meminfo = NULL;\n   struct swaptable*   sl = NULL;\n   struct swapent*     swapdev = NULL;\n   uint64_t            totalswap = 0;\n   uint64_t            totalfree = 0;\n   int                 ksrphyserr = -1;\n   int                 nswap = 0;\n   char*               spath = NULL;\n   char*               spathbase = NULL;\n\n   // Part 1 - physical memory\n   if (this->kd != NULL) {\n      // The ptr `meminfo` is invalidated when the kstat chain is updated by\n      // `kstat_chain_update` (in `SolarisMachine_updateCPUcount`). So it needs\n      // to be re-read on every memory update.\n      meminfo = kstat_lookup_wrapper(this->kd, \"unix\", 0, \"system_pages\");\n   }\n   if (meminfo != NULL) {\n      ksrphyserr = kstat_read(this->kd, meminfo, NULL);\n   }\n   if (ksrphyserr != -1) {\n      kstat_named_t* physmem = kstat_data_lookup_wrapper(meminfo, \"physmem\");\n      kstat_named_t* pagesfree = kstat_data_lookup_wrapper(meminfo, \"pagesfree\");\n      kstat_named_t* pagestotal = kstat_data_lookup_wrapper(meminfo, \"pagestotal\");\n      kstat_named_t* pageslocked = kstat_data_lookup_wrapper(meminfo, \"pageslocked\");\n\n      super->totalMem = physmem->value.ui64 * this->pageSizeKB;\n      this->usedMem = (pagestotal->value.ui64 - pageslocked->value.ui64 - pagesfree->value.ui64) * this->pageSizeKB;\n      this->lockedMem = pageslocked->value.ui64 * this->pageSizeKB;\n   } else {\n      // Fall back to basic sysconf if kstat isn't working\n      super->totalMem = sysconf(_SC_PHYS_PAGES) * this->pageSize;\n      this->usedMem = super->totalMem - (sysconf(_SC_AVPHYS_PAGES) * this->pageSize);\n      this->lockedMem = 0;\n   }\n\n   // Part 2 - swap\n   nswap = swapctl(SC_GETNSWP, NULL);\n   if (nswap > 0) {\n      sl = xMalloc((nswap * sizeof(swapent_t)) + sizeof(int));\n   }\n   if (sl != NULL) {\n      spathbase = xMalloc( nswap * MAXPATHLEN );\n   }\n   if (spathbase != NULL) {\n      spath = spathbase;\n      swapdev = sl->swt_ent;\n      for (int i = 0; i < nswap; i++, swapdev++) {\n         swapdev->ste_path = spath;\n         spath += MAXPATHLEN;\n      }\n      sl->swt_n = nswap;\n   }\n   nswap = swapctl(SC_LIST, sl);\n   if (nswap > 0) {\n      swapdev = sl->swt_ent;\n      for (int i = 0; i < nswap; i++, swapdev++) {\n         totalswap += swapdev->ste_pages;\n         totalfree += swapdev->ste_free;\n      }\n   }\n   free(spathbase);\n   free(sl);\n   super->totalSwap = totalswap * this->pageSizeKB;\n   super->usedSwap  = super->totalSwap - (totalfree * this->pageSizeKB);\n}\n\nstatic void SolarisMachine_scanZfsArcstats(SolarisMachine* this) {\n   kstat_named_t       *cur_kstat;\n   kstat_t             *arcstats;\n   int                 ksrphyserr;\n\n   if (this->kd == NULL)\n      return;\n\n   arcstats = kstat_lookup_wrapper(this->kd, \"zfs\", 0, \"arcstats\");\n   if (arcstats == NULL)\n      return;\n\n   ksrphyserr = kstat_read(this->kd, arcstats, NULL);\n   if (ksrphyserr == -1)\n      return;\n\n   cur_kstat = kstat_data_lookup_wrapper( arcstats, \"size\" );\n   this->zfs.size = cur_kstat->value.ui64 / 1024;\n   this->zfs.enabled = this->zfs.size > 0 ? 1 : 0;\n\n   cur_kstat = kstat_data_lookup_wrapper( arcstats, \"c_max\" );\n   this->zfs.max = cur_kstat->value.ui64 / 1024;\n\n   cur_kstat = kstat_data_lookup_wrapper( arcstats, \"mfu_size\" );\n   this->zfs.MFU = cur_kstat != NULL ? cur_kstat->value.ui64 / 1024 : 0;\n\n   cur_kstat = kstat_data_lookup_wrapper( arcstats, \"mru_size\" );\n   this->zfs.MRU = cur_kstat != NULL ? cur_kstat->value.ui64 / 1024 : 0;\n\n   cur_kstat = kstat_data_lookup_wrapper( arcstats, \"anon_size\" );\n   this->zfs.anon = cur_kstat != NULL ? cur_kstat->value.ui64 / 1024 : 0;\n\n   cur_kstat = kstat_data_lookup_wrapper( arcstats, \"hdr_size\" );\n   this->zfs.header = cur_kstat != NULL ? cur_kstat->value.ui64 / 1024 : 0;\n\n   cur_kstat = kstat_data_lookup_wrapper( arcstats, \"other_size\" );\n   this->zfs.other = cur_kstat != NULL ? cur_kstat->value.ui64 / 1024 : 0;\n\n   if ((cur_kstat = kstat_data_lookup_wrapper( arcstats, \"compressed_size\" )) != NULL) {\n      this->zfs.compressed = cur_kstat->value.ui64 / 1024;\n      this->zfs.isCompressed = 1;\n\n      cur_kstat = kstat_data_lookup_wrapper( arcstats, \"uncompressed_size\" );\n      this->zfs.uncompressed = cur_kstat->value.ui64 / 1024;\n   } else {\n      this->zfs.isCompressed = 0;\n   }\n}\n\nvoid Machine_scan(Machine* super) {\n   SolarisMachine* this = (SolarisMachine*) super;\n\n   SolarisMachine_updateCPUcount(this);\n   SolarisMachine_scanCPUTime(this);\n   SolarisMachine_scanMemoryInfo(this);\n   SolarisMachine_scanZfsArcstats(this);\n}\n\nMachine* Machine_new(UsersTable* usersTable, uid_t userId) {\n   SolarisMachine* this = xCalloc(1, sizeof(SolarisMachine));\n   Machine* super = &this->super;\n\n   Machine_init(super, usersTable, userId);\n\n   long pageSize = sysconf(_SC_PAGESIZE);\n   if (pageSize <= 0)\n      CRT_fatalError(\"Cannot get pagesize by sysconf(_SC_PAGESIZE)\");\n   this->pageSize = (size_t)pageSize;\n   this->pageSizeKB = this->pageSize / ONE_K;\n\n   this->kd = kstat_open();\n   if (!this->kd)\n      CRT_fatalError(\"Cannot open kstat handle\");\n\n   SolarisMachine_updateCPUcount(this);\n\n   return super;\n}\n\nvoid Machine_delete(Machine* super) {\n   SolarisMachine* this = (SolarisMachine*) super;\n\n   Machine_done(super);\n\n   free(this->cpus);\n   if (this->kd) {\n      kstat_close(this->kd);\n   }\n   free(this);\n}\n\nbool Machine_isCPUonline(const Machine* super, unsigned int id) {\n   assert(id < super->existingCPUs);\n\n   const SolarisMachine* this = (const SolarisMachine*) super;\n\n   return (super->existingCPUs == 1) ? true : this->cpus[id + 1].online;\n}\n"
  },
  {
    "path": "solaris/SolarisMachine.h",
    "content": "#ifndef HEADER_SolarisMachine\n#define HEADER_SolarisMachine\n/*\nhtop - SolarisMachine.h\n(C) 2014 Hisham H. Muhammad\n(C) 2017,2018 Guy M. Broome\nReleased under the GNU GPLv2+, see the COPYING file\nin the source distribution for its full text.\n*/\n\n#include <kstat.h>\n#include <stdbool.h>\n#include <stddef.h>\n#include <stdint.h>\n#include <sys/param.h>\n#include <sys/resource.h>\n#include <sys/swap.h>\n#include <sys/sysconf.h>\n#include <sys/sysinfo.h>\n#include <sys/uio.h>\n\n#include \"Hashtable.h\"\n#include \"Machine.h\"\n#include \"UsersTable.h\"\n\n#include \"zfs/ZfsArcStats.h\"\n\n\n#define ZONE_ERRMSGLEN 1024\nextern char zone_errmsg[ZONE_ERRMSGLEN];\n\ntypedef struct CPUData_ {\n   double userPercent;\n   double nicePercent;\n   double systemPercent;\n   double irqPercent;\n   double idlePercent;\n   double systemAllPercent;\n   double frequency;\n   uint64_t luser;\n   uint64_t lkrnl;\n   uint64_t lintr;\n   uint64_t lidle;\n   bool online;\n} CPUData;\n\ntypedef struct SolarisMachine_ {\n   Machine super;\n\n   kstat_ctl_t* kd;\n   CPUData* cpus;\n\n   size_t pageSize;\n   size_t pageSizeKB;\n\n   memory_t usedMem;\n   memory_t lockedMem;\n\n   ZfsArcStats zfs;\n} SolarisMachine;\n\n#endif\n"
  },
  {
    "path": "solaris/SolarisProcess.c",
    "content": "/*\nhtop - SolarisProcess.c\n(C) 2015 Hisham H. Muhammad\n(C) 2017,2018 Guy M. Broome\nReleased under the GNU GPLv2+, see the COPYING file\nin the source distribution for its full text.\n*/\n\n#include \"config.h\" // IWYU pragma: keep\n\n#include \"solaris/SolarisProcess.h\"\n\n#include <stdlib.h>\n#include <string.h>\n#include <unistd.h>\n#include <sys/syscall.h>\n\n#include \"ProcessTable.h\"\n#include \"CRT.h\"\n\n#include \"solaris/Platform.h\"\n\n\nconst ProcessFieldData Process_fields[LAST_PROCESSFIELD] = {\n   [0] = { .name = \"\", .title = NULL, .description = NULL, .flags = 0, },\n   [PID] = { .name = \"PID\", .title = \"PID\", .description = \"Process/thread ID\", .flags = 0, .pidColumn = true, },\n   [COMM] = { .name = \"Command\", .title = \"Command \", .description = \"Command line (insert as last column only)\", .flags = 0, },\n   [STATE] = { .name = \"STATE\", .title = \"S \", .description = \"Process state (S sleeping, R running, O onproc, Z zombie, T stopped, W waiting)\", .flags = 0, },\n   [PPID] = { .name = \"PPID\", .title = \"PPID\", .description = \"Parent process ID\", .flags = 0, .pidColumn = true, },\n   [PGRP] = { .name = \"PGRP\", .title = \"PGRP\", .description = \"Process group ID\", .flags = 0, .pidColumn = true, },\n   [SESSION] = { .name = \"SESSION\", .title = \"SID\", .description = \"Process's session ID\", .flags = 0, .pidColumn = true, },\n   [TTY] = { .name = \"TTY\", .title = \"TTY      \", .description = \"Controlling terminal\", .flags = 0, },\n   //[TPGID] = { .name = \"TPGID\", .title = \"TPGID\", .description = \"Process ID of the fg process group of the controlling terminal\", .flags = 0, .pidColumn = true, },\n   //[MINFLT] = { .name = \"MINFLT\", .title = \"     MINFLT \", .description = \"Number of minor faults which have not required loading a memory page from disk\", .flags = 0, .defaultSortDesc = true, },\n   //[MAJFLT] = { .name = \"MAJFLT\", .title = \"     MAJFLT \", .description = \"Number of major faults which have required loading a memory page from disk\", .flags = 0, .defaultSortDesc = true, },\n   [PRIORITY] = { .name = \"PRIORITY\", .title = \"PRI \", .description = \"Kernel's internal priority for the process\", .flags = 0, },\n   [NICE] = { .name = \"NICE\", .title = \" NI \", .description = \"Nice value (the higher the value, the more it lets other processes take priority)\", .flags = 0, },\n   [STARTTIME] = { .name = \"STARTTIME\", .title = \"START \", .description = \"Time the process was started\", .flags = 0, },\n   [ELAPSED] = { .name = \"ELAPSED\", .title = \"ELAPSED  \", .description = \"Time since the process was started\", .flags = 0, },\n   [PROCESSOR] = { .name = \"PROCESSOR\", .title = \"CPU \", .description = \"Id of the CPU the process last executed on\", .flags = 0, },\n   [M_VIRT] = { .name = \"M_VIRT\", .title = \" VIRT \", .description = \"Total program size in virtual memory\", .flags = 0, .defaultSortDesc = true, },\n   [M_RESIDENT] = { .name = \"M_RESIDENT\", .title = \"  RES \", .description = \"Resident set size, size of the text and data sections, plus stack usage\", .flags = 0, .defaultSortDesc = true, },\n   [ST_UID] = { .name = \"ST_UID\", .title = \"UID\", .description = \"User ID of the process owner\", .flags = 0, },\n   [PERCENT_CPU] = { .name = \"PERCENT_CPU\", .title = \" CPU%\", .description = \"Percentage of the CPU time the process used in the last sampling\", .flags = 0, .defaultSortDesc = true, .autoWidth = true, .autoTitleRightAlign = true, },\n   [PERCENT_NORM_CPU] = { .name = \"PERCENT_NORM_CPU\", .title = \"NCPU%\", .description = \"Normalized percentage of the CPU time the process used in the last sampling (normalized by cpu count)\", .flags = 0, .defaultSortDesc = true, .autoWidth = true, },\n   [PERCENT_MEM] = { .name = \"PERCENT_MEM\", .title = \"MEM% \", .description = \"Percentage of the memory the process is using, based on resident memory size\", .flags = 0, .defaultSortDesc = true, },\n   [USER] = { .name = \"USER\", .title = \"USER       \", .description = \"Username of the process owner (or user ID if name cannot be determined)\", .flags = 0, },\n   [TIME] = { .name = \"TIME\", .title = \"  TIME+  \", .description = \"Total time the process has spent in user and system time\", .flags = 0, .defaultSortDesc = true, },\n   [NLWP] = { .name = \"NLWP\", .title = \"NLWP \", .description = \"Number of threads in the process\", .flags = 0, },\n   [TGID] = { .name = \"TGID\", .title = \"TGID\", .description = \"Thread group ID (i.e. process ID)\", .flags = 0, .pidColumn = true, },\n   [PROC_COMM] = { .name = \"COMM\", .title = \"COMM            \", .description = \"comm string of the process\", .flags = 0, },\n   [PROC_EXE] = { .name = \"EXE\", .title = \"EXE             \", .description = \"Basename of exe of the process\", .flags = 0, },\n   [CWD] = { .name = \"CWD\", .title = \"CWD                       \", .description = \"The current working directory of the process\", .flags = PROCESS_FLAG_CWD, },\n   [ZONEID] = { .name = \"ZONEID\", .title = \"ZONEID\", .description = \"Zone ID\", .flags = 0, .pidColumn = true, },\n   [ZONE] = { .name = \"ZONE\", .title = \"ZONE             \", .description = \"Zone name\", .flags = 0, },\n   [PROJID] = { .name = \"PROJID\", .title = \"PRJID\", .description = \"Project ID\", .flags = 0, .pidColumn = true, },\n   [TASKID] = { .name = \"TASKID\", .title = \"TSKID\", .description = \"Task ID\", .flags = 0, .pidColumn = true, },\n   [POOLID] = { .name = \"POOLID\", .title = \"POLID\", .description = \"Pool ID\", .flags = 0, .pidColumn = true, },\n   [CONTID] = { .name = \"CONTID\", .title = \"CNTID\", .description = \"Contract ID\", .flags = 0, .pidColumn = true, },\n   [LWPID] = { .name = \"LWPID\", .title = \"LWPID\", .description = \"LWP ID\", .flags = 0, .pidColumn = true, },\n};\n\nProcess* SolarisProcess_new(const Machine* host) {\n   SolarisProcess* this = xCalloc(1, sizeof(SolarisProcess));\n   Object_setClass(this, Class(SolarisProcess));\n   Process_init(&this->super, host);\n   return (Process*)this;\n}\n\nvoid Process_delete(Object* cast) {\n   SolarisProcess* sp = (SolarisProcess*) cast;\n   Process_done((Process*)cast);\n   free(sp->zname);\n   free(sp);\n}\n\nstatic void SolarisProcess_rowWriteField(const Row* super, RichString* str, ProcessField field) {\n   const SolarisProcess* sp = (const SolarisProcess*) super;\n\n   char buffer[256]; buffer[255] = '\\0';\n   int attr = CRT_colors[DEFAULT_COLOR];\n   size_t n = sizeof(buffer) - 1;\n\n   switch (field) {\n   // add Solaris-specific fields here\n   case ZONEID: xSnprintf(buffer, n, \"%*d \", Process_pidDigits, sp->zoneid); break;\n   case PROJID: xSnprintf(buffer, n, \"%*d \", Process_pidDigits, sp->projid); break;\n   case TASKID: xSnprintf(buffer, n, \"%*d \", Process_pidDigits, sp->taskid); break;\n   case POOLID: xSnprintf(buffer, n, \"%*d \", Process_pidDigits, sp->poolid); break;\n   case CONTID: xSnprintf(buffer, n, \"%*d \", Process_pidDigits, sp->contid); break;\n   case ZONE: Row_printLeftAlignedField(str, attr, sp->zname ? sp->zname : \"global\", ZONENAME_MAX/4); return;\n   case PID: xSnprintf(buffer, n, \"%*d \", Process_pidDigits, sp->realpid); break;\n   case PPID: xSnprintf(buffer, n, \"%*d \", Process_pidDigits, sp->realppid); break;\n   case TGID: xSnprintf(buffer, n, \"%*d \", Process_pidDigits, sp->realtgid); break;\n   case LWPID: xSnprintf(buffer, n, \"%*d \", Process_pidDigits, sp->lwpid); break;\n   default:\n      Process_writeField(&sp->super, str, field);\n      return;\n   }\n\n   RichString_appendWide(str, attr, buffer);\n}\n\nstatic int SolarisProcess_compareByKey(const Process* v1, const Process* v2, ProcessField key) {\n   const SolarisProcess* p1 = (const SolarisProcess*)v1;\n   const SolarisProcess* p2 = (const SolarisProcess*)v2;\n\n   switch (key) {\n   case ZONEID:\n      return SPACESHIP_NUMBER(p1->zoneid, p2->zoneid);\n   case PROJID:\n      return SPACESHIP_NUMBER(p1->projid, p2->projid);\n   case TASKID:\n      return SPACESHIP_NUMBER(p1->taskid, p2->taskid);\n   case POOLID:\n      return SPACESHIP_NUMBER(p1->poolid, p2->poolid);\n   case CONTID:\n      return SPACESHIP_NUMBER(p1->contid, p2->contid);\n   case ZONE:\n      return strcmp(p1->zname ? p1->zname : \"global\", p2->zname ? p2->zname : \"global\");\n   case PID:\n      return SPACESHIP_NUMBER(p1->realpid, p2->realpid);\n   case PPID:\n      return SPACESHIP_NUMBER(p1->realppid, p2->realppid);\n   case LWPID:\n      return SPACESHIP_NUMBER(p1->lwpid, p2->lwpid);\n   default:\n      return Process_compareByKey_Base(v1, v2, key);\n   }\n}\n\nconst ProcessClass SolarisProcess_class = {\n   .super = {\n      .super = {\n         .extends = Class(Process),\n         .display = Row_display,\n         .delete = Process_delete,\n         .compare = Process_compare\n      },\n      .isHighlighted = Process_rowIsHighlighted,\n      .isVisible = Process_rowIsVisible,\n      .matchesFilter = Process_rowMatchesFilter,\n      .compareByParent = Process_compareByParent,\n      .sortKeyString = Process_rowGetSortKey,\n      .writeField = SolarisProcess_rowWriteField\n   },\n   .compareByKey = SolarisProcess_compareByKey\n};\n"
  },
  {
    "path": "solaris/SolarisProcess.h",
    "content": "#ifndef HEADER_SolarisProcess\n#define HEADER_SolarisProcess\n/*\nhtop - SolarisProcess.h\n(C) 2015 Hisham H. Muhammad\n(C) 2017,2018 Guy M. Broome\nReleased under the GNU GPLv2+, see the COPYING file\nin the source distribution for its full text.\n*/\n\n#include <zone.h>\n#include <sys/proc.h>\n\n/* On OmniOS /usr/include/sys/regset.h redefines ERR to 13 - \\r, breaking the Enter key.\n * Since ncurses macros use the ERR macro, we cannot use another name.\n */\n#undef ERR\n#include <libproc.h>\n#undef ERR\n#define ERR (-1)\n\n#include \"Machine.h\"\n#include \"Process.h\"\n\n\ntypedef struct SolarisProcess_ {\n   Process    super;\n   zoneid_t   zoneid;\n   char*      zname;\n   taskid_t   taskid;\n   projid_t   projid;\n   poolid_t   poolid;\n   ctid_t     contid;\n   pid_t      realpid;\n   pid_t      realppid;\n   pid_t      realtgid;\n   pid_t      lwpid;\n} SolarisProcess;\n\nextern const ProcessClass SolarisProcess_class;\n\nextern const ProcessFieldData Process_fields[LAST_PROCESSFIELD];\n\nProcess* SolarisProcess_new(const Machine* host);\n\nvoid Process_delete(Object* cast);\n\n#endif\n"
  },
  {
    "path": "solaris/SolarisProcessTable.c",
    "content": "/*\nhtop - SolarisProcessTable.c\n(C) 2014 Hisham H. Muhammad\n(C) 2017,2018 Guy M. Broome\nReleased under the GNU GPLv2+, see the COPYING file\nin the source distribution for its full text.\n*/\n\n#include \"config.h\" // IWYU pragma: keep\n\n#include \"solaris/SolarisProcessTable.h\"\n\n#include <unistd.h>\n#include <stdlib.h>\n#include <sys/types.h>\n#include <sys/user.h>\n#include <limits.h>\n#include <string.h>\n#include <procfs.h>\n#include <errno.h>\n#include <pwd.h>\n#include <math.h>\n#include <time.h>\n\n#include \"CRT.h\"\n#include \"solaris/Platform.h\"\n#include \"solaris/SolarisMachine.h\"\n#include \"solaris/SolarisProcess.h\"\n\n\n#define GZONE \"global    \"\n#define UZONE \"unknown   \"\n\nstatic char* SolarisProcessTable_readZoneName(kstat_ctl_t* kd, SolarisProcess* sproc) {\n   char* zname;\n\n   if ( sproc->zoneid == 0 ) {\n      zname = xStrdup(GZONE);\n   } else if ( kd == NULL ) {\n      zname = xStrdup(UZONE);\n   } else {\n      kstat_t* ks = kstat_lookup_wrapper( kd, \"zones\", sproc->zoneid, NULL );\n      zname = xStrdup(ks == NULL ? UZONE : ks->ks_name);\n   }\n\n   return zname;\n}\n\nProcessTable* ProcessTable_new(Machine* host, Hashtable* pidMatchList) {\n   SolarisProcessTable* this = xCalloc(1, sizeof(SolarisProcessTable));\n   Object_setClass(this, Class(ProcessTable));\n\n   ProcessTable* super = &this->super;\n   ProcessTable_init(super, Class(SolarisProcess), host, pidMatchList);\n\n   return super;\n}\n\nvoid ProcessTable_delete(Object* cast) {\n   SolarisProcessTable* this = (SolarisProcessTable*) cast;\n   ProcessTable_done(&this->super);\n   free(this);\n}\n\nstatic void SolarisProcessTable_updateExe(pid_t pid, Process* proc) {\n   char path[32];\n   xSnprintf(path, sizeof(path), \"/proc/%d/path/a.out\", pid);\n\n   char target[PATH_MAX];\n   ssize_t ret = readlink(path, target, sizeof(target) - 1);\n   if (ret <= 0)\n      return;\n\n   target[ret] = '\\0';\n   Process_updateExe(proc, target);\n}\n\nstatic void SolarisProcessTable_updateCwd(pid_t pid, Process* proc) {\n   char path[32];\n   xSnprintf(path, sizeof(path), \"/proc/%d/cwd\", pid);\n\n   char target[PATH_MAX];\n   ssize_t ret = readlink(path, target, sizeof(target) - 1);\n   if (ret <= 0)\n      return;\n\n   target[ret] = '\\0';\n   free_and_xStrdup(&proc->procCwd, target);\n}\n\n/* Taken from: https://docs.oracle.com/cd/E19253-01/817-6223/6mlkidlom/index.html#tbl-sched-state */\nstatic inline ProcessState SolarisProcessTable_getProcessState(char state) {\n   switch (state) {\n      case 'S': return SLEEPING;\n      case 'R': return RUNNABLE;\n      case 'O': return RUNNING;\n      case 'Z': return ZOMBIE;\n      case 'T': return STOPPED;\n      case 'I': return IDLE;\n      default: return UNKNOWN;\n   }\n}\n\n/* NOTE: the following is a callback function of type proc_walk_f\n *       and MUST conform to the appropriate definition in order\n *       to work.  See libproc(3LIB) on a Solaris or Illumos\n *       system for more info.\n */\n\nstatic int SolarisProcessTable_walkproc(psinfo_t* _psinfo, lwpsinfo_t* _lwpsinfo, void* listptr) {\n   bool preExisting;\n   pid_t getpid;\n\n   // Setup process list\n   ProcessTable* pt = (ProcessTable*) listptr;\n   const Machine* host = pt->super.host;\n   const SolarisMachine* shost = (const SolarisMachine*) host;\n\n   id_t lwpid_real = _lwpsinfo->pr_lwpid;\n   if (lwpid_real > 1023) {\n      return 0;\n   }\n\n   pid_t lwpid   = (_psinfo->pr_pid * 1024) + lwpid_real;\n   bool onMasterLWP = (_lwpsinfo->pr_lwpid == _psinfo->pr_lwp.pr_lwpid);\n   if (onMasterLWP) {\n      getpid = _psinfo->pr_pid * 1024;\n   } else {\n      getpid = lwpid;\n   }\n\n   Process* proc            = ProcessTable_getProcess(pt, getpid, &preExisting, SolarisProcess_new);\n   SolarisProcess* sproc    = (SolarisProcess*) proc;\n   const Settings* settings = host->settings;\n\n   // Common code pass 1\n   proc->super.show         = false;\n   sproc->taskid            = _psinfo->pr_taskid;\n   sproc->projid            = _psinfo->pr_projid;\n   sproc->poolid            = _psinfo->pr_poolid;\n   sproc->contid            = _psinfo->pr_contract;\n   proc->priority           = _lwpsinfo->pr_pri;\n   proc->nice               = _lwpsinfo->pr_nice - NZERO;\n   proc->processor          = _lwpsinfo->pr_onpro;\n   proc->state              = SolarisProcessTable_getProcessState(_lwpsinfo->pr_sname);\n   // NOTE: This 'percentage' is a 16-bit BINARY FRACTIONS where 1.0 = 0x8000\n   // Source: https://docs.oracle.com/cd/E19253-01/816-5174/proc-4/index.html\n   // (accessed on 18 November 2017)\n   proc->percent_mem        = ((uint16_t)_psinfo->pr_pctmem / (double)32768) * (double)100.0;\n   proc->pgrp               = _psinfo->pr_pgid;\n   proc->nlwp               = _psinfo->pr_nlwp;\n   proc->session            = _psinfo->pr_sid;\n\n   proc->tty_nr             = _psinfo->pr_ttydev;\n   const char* name = (_psinfo->pr_ttydev != PRNODEV) ? ttyname(_psinfo->pr_ttydev) : NULL;\n   if (!name) {\n      free(proc->tty_name);\n      proc->tty_name = NULL;\n   } else {\n      free_and_xStrdup(&proc->tty_name, name);\n   }\n\n   proc->m_resident         = _psinfo->pr_rssize;  // KB\n   proc->m_virt             = _psinfo->pr_size;    // KB\n\n   if (proc->st_uid != _psinfo->pr_euid) {\n      proc->st_uid          = _psinfo->pr_euid;\n      proc->user            = UsersTable_getRef(host->usersTable, proc->st_uid);\n   }\n\n   if (!preExisting) {\n      sproc->realpid        = _psinfo->pr_pid;\n      sproc->lwpid          = lwpid_real;\n      sproc->zoneid         = _psinfo->pr_zoneid;\n      sproc->zname          = SolarisProcessTable_readZoneName(shost->kd, sproc);\n      SolarisProcessTable_updateExe(_psinfo->pr_pid, proc);\n\n      Process_updateComm(proc, _psinfo->pr_fname);\n      Process_updateCmdline(proc, _psinfo->pr_psargs, 0, 0);\n\n      if (settings->ss->flags & PROCESS_FLAG_CWD) {\n         SolarisProcessTable_updateCwd(_psinfo->pr_pid, proc);\n      }\n   }\n\n   // End common code pass 1\n\n   if (onMasterLWP) { // Are we on the representative LWP?\n      Process_setParent(proc, (_psinfo->pr_ppid * 1024));\n      Process_setThreadGroup(proc, (_psinfo->pr_ppid * 1024));\n      sproc->realppid       = _psinfo->pr_ppid;\n      sproc->realtgid       = _psinfo->pr_ppid;\n\n      // See note above (in common section) about this BINARY FRACTION\n      proc->percent_cpu     = ((uint16_t)_psinfo->pr_pctcpu / (double)32768) * (double)100.0;\n      Process_updateCPUFieldWidths(proc->percent_cpu);\n\n      proc->time            = _psinfo->pr_time.tv_sec * 100 + _psinfo->pr_time.tv_nsec / 10000000;\n      if (!preExisting) { // Tasks done only for NEW processes\n         proc->isUserlandThread = false;\n         proc->starttime_ctime = _psinfo->pr_start.tv_sec;\n      }\n\n      // Update proc and thread counts based on settings\n      if (proc->isKernelThread && !settings->hideKernelThreads) {\n         pt->kernelThreads += proc->nlwp;\n         pt->totalTasks += proc->nlwp + 1;\n         if (proc->state == RUNNING) {\n            pt->runningTasks++;\n         }\n      } else if (!proc->isKernelThread) {\n         if (proc->state == RUNNING) {\n            pt->runningTasks++;\n         }\n         if (settings->hideUserlandThreads) {\n            pt->totalTasks++;\n         } else {\n            pt->userlandThreads += proc->nlwp;\n            pt->totalTasks += proc->nlwp + 1;\n         }\n      }\n      proc->super.show = !(settings->hideKernelThreads && proc->isKernelThread);\n   } else { // We are not in the master LWP, so jump to the LWP handling code\n      proc->percent_cpu        = ((uint16_t)_lwpsinfo->pr_pctcpu / (double)32768) * (double)100.0;\n      Process_updateCPUFieldWidths(proc->percent_cpu);\n\n      proc->time               = _lwpsinfo->pr_time.tv_sec * 100 + _lwpsinfo->pr_time.tv_nsec / 10000000;\n      if (!preExisting) { // Tasks done only for NEW LWPs\n         proc->isUserlandThread    = true;\n         Process_setParent(proc, _psinfo->pr_pid * 1024);\n         Process_setThreadGroup(proc, _psinfo->pr_pid * 1024);\n         sproc->realppid       = _psinfo->pr_pid;\n         sproc->realtgid       = _psinfo->pr_pid;\n         proc->starttime_ctime = _lwpsinfo->pr_start.tv_sec;\n      }\n\n      // Top-level process only gets this for the representative LWP\n      if (proc->isKernelThread && !settings->hideKernelThreads) {\n         proc->super.show = true;\n      }\n      if (!proc->isKernelThread && !settings->hideUserlandThreads) {\n         proc->super.show = true;\n      }\n   } // Top-level LWP or subordinate LWP\n\n   // Common code pass 2\n\n   if (!preExisting) {\n      if ((sproc->realppid <= 0) && !(sproc->realpid <= 1)) {\n         proc->isKernelThread = true;\n      } else {\n         proc->isKernelThread = false;\n      }\n\n      Process_fillStarttimeBuffer(proc);\n      ProcessTable_add(pt, proc);\n   }\n\n   proc->super.updated = true;\n\n   // End common code pass 2\n\n   return 0;\n}\n\nvoid ProcessTable_goThroughEntries(ProcessTable* super) {\n   super->kernelThreads = 1;\n   proc_walk(&SolarisProcessTable_walkproc, super, PR_WALK_LWP);\n}\n"
  },
  {
    "path": "solaris/SolarisProcessTable.h",
    "content": "#ifndef HEADER_SolarisProcessTable\n#define HEADER_SolarisProcessTable\n/*\nhtop - SolarisProcessTable.h\n(C) 2014 Hisham H. Muhammad\n(C) 2017,2018 Guy M. Broome\nReleased under the GNU GPLv2+, see the COPYING file\nin the source distribution for its full text.\n*/\n\n#include <kstat.h>\n#include <stdbool.h>\n#include <stdint.h>\n#include <sys/param.h>\n#include <sys/uio.h>\n#include <sys/resource.h>\n#include <sys/sysconf.h>\n#include <sys/sysinfo.h>\n\n#include \"Hashtable.h\"\n#include \"ProcessTable.h\"\n#include \"UsersTable.h\"\n\n#include \"solaris/SolarisProcess.h\"\n\n\ntypedef struct SolarisProcessTable_ {\n   ProcessTable super;\n} SolarisProcessTable;\n\n#endif\n"
  },
  {
    "path": "test_spec.lua",
    "content": "#!/usr/bin/env lua\n\nlocal VISUALDELAY = os.getenv(\"VISUALDELAY\")\n\nlocal visual = VISUALDELAY or false\nlocal visual_delay = VISUALDELAY and (tonumber(VISUALDELAY)) or 0.1\nlocal short_delay = 0.3\nlocal long_delay = 1\n\nlocal unistd = require(\"posix.unistd\")\nlocal time = require(\"posix.time\")\nlocal curses = require(\"posix.curses\")\nlocal rote = require(\"rote\")\n\nlocal rt = rote.RoteTerm(24, 80)\n\n--[[\nlocal function os_execread(cmd)\n   local fd = io.popen(cmd, \"r\")\n   local out = fd:read(\"*a\")\n   fd:close()\n   return (out:gsub(\"\\n$\", \"\"))\nend\n]]\n--local branch = os_execread(\"git branch | grep '*'\"):sub(3)\n--print(\"Running in branch \"..branch)\n\nos.execute(\"make coverage\")\nos.execute(\"rm -f *.gcda */*.gcda\")\nos.execute(\"rm -f coverage.info test.htoprc\")\nos.execute(\"rm -rf lcov\")\nos.execute(\"killall htop\")\nos.execute(\"ps aux | grep '[s]leep 12345' | awk '{print $2}' | xargs kill 2> /dev/null\")\n\nos.execute(\"cp ./default.htoprc ./test.htoprc\")\nrt:forkPty(\"LC_ALL=C HTOPRC=./test.htoprc ./htop 2> htop-valgrind.txt\")\n\nlocal stdscr, term_win\n-- Curses initialization needed even when not in visual mode\n-- because luaposix only initializes KEY_* constants after initscr().\nstdscr = curses.initscr()\nif visual then\n   curses.echo(false)\n   curses.start_color()\n   curses.raw(true)\n   curses.halfdelay(1)\n   stdscr:keypad(true)\n   term_win = curses.newwin(24, 80, 0, 0)\n   local function makePair(foreground, background)\n      return background * 8 + 7 - foreground\n   end\n   -- initialize the color pairs the way rt:draw() expects it\n   for foreground = 0, 7 do\n      for background = 0, 7 do\n         if foreground ~= 7 or background ~= 0 then\n            local pair = makePair(foreground, background)\n            curses.init_pair(pair, foreground, background)\n         end\n      end\n   end\nelse\n   curses.endwin()\nend\n\nlocal function show(key)\n   rt:update()\n   if visual then\n      rt:draw(term_win, 0, 0)\n      if key then\n         term_win:mvaddstr(0, 0, tostring(key))\n      end\n      term_win:refresh()\n\n      delay(visual_delay)\n   end\nend\n\nlocal function send(key, times, quick)\n   if times == 0 then return end\n   for _ = 1, times or 1 do\n      delay(0.003) -- 30ms delay to avoid clobbering Esc sequences\n      if type(key) == \"string\" then\n         for c in key:gmatch('.') do\n            rt:keyPress(string.byte(c))\n         end\n      else\n         rt:keyPress(key)\n      end\n      if not quick then\n         show(key)\n      end\n   end\n   if quick then\n      show(key)\n   end\nend\n\nlocal function string_at(x, y, len)\n   rt:update()\n   local out = {}\n   for i = 1, len do\n      out[#out+1] = rt:cellChar(y-1, x+i-2)\n   end\n   return table.concat(out)\nend\n\nlocal function is_string_at(x, y, str)\n   return string_at(x, y, #str) == str\nend\n\nlocal function check_string_at(x, y, str)\n   return { str, string_at(x, y, #str) }\nend\n\nlocal ESC = \"\\27\\27\"\n\nfunction delay(t)\n   time.nanosleep({ tv_sec = math.floor(t), tv_nsec = (t - math.floor(t)) * 1000000000 })\nend\n\ndelay(2) -- give some time for htop to initialize.\nrt:update()\n\nlocal y_panelhdr = (function()\n   for y = 1, 24 do\n      if is_string_at(3, y, \"PID\") then\n         return y\n      end\n   end\nend)() or 1\n\nassert.not_equal(y_panelhdr, 1)\n\nlocal x_metercol2 = 41\n\nshow()\n\nos.execute(\"sleep 12345 &\")\n\nlocal function terminated()\n   return not os.execute(\"ps aux | grep -q '\\\\./[h]top'\")\nend\n\nlocal function running_it(desc, fn)\n   it(desc, function()\n      assert(not terminated())\n      show()\n      fn()\n      assert(not terminated())\n   end)\nend\n\nlocal function check(t)\n   return t[1], t[2]\nend\n\nlocal attrs = {\n   black_on_cyan = 6,\n   red_on_cyan = 22,\n   white_on_black = 176,\n   yellow_on_black = 112,\n}\n\nlocal function find_selected_y(from)\n   rt:update()\n   for y = from or (y_panelhdr + 1), rt:rows() - 1 do\n      local attr = rt:cellAttr(y-1, 1)\n      if attr == attrs.black_on_cyan then\n         return y\n      end\n   end\n   return y_panelhdr + 1\nend\n\nlocal function find_command_x()\n   for x = 1, 80 do\n      if is_string_at(x, y_panelhdr, \"Command\") then\n         return x\n      end\n   end\n   return 64\nend\n\nlocal function set_display_option(n)\n   send(\"S\")\n   send(curses.KEY_DOWN)\n   send(curses.KEY_RIGHT)\n   send(curses.KEY_DOWN, n, \"quick\")\n   send(\"\\n\")\n   send(curses.KEY_F10)\nend\n\ndescribe(\"htop test suite\", function()\n\n   running_it(\"performs incremental filter\", function()\n      send(\"\\\\\")\n      send(\"x\\127bux\\127sted\") -- test backspace\n      send(\"\\n\")\n      delay(short_delay)\n      rt:update()\n      local pid = (\"      \"..tostring(unistd.getpid())):sub(-5)\n      local ourpid = check_string_at(1, y_panelhdr + 1, pid)\n      send(\"\\\\\")\n      send(ESC)\n      send(curses.KEY_F5)\n      send(curses.KEY_HOME)\n      delay(short_delay)\n      rt:update()\n      local initpid = check_string_at(1, y_panelhdr + 1, \"    1\")\n      delay(short_delay)\n      rt:update()\n      send(curses.KEY_F5)\n      assert.equal(check(ourpid))\n      assert.equal(check(initpid))\n   end)\n\n   running_it(\"performs incremental search\", function()\n      send(curses.KEY_HOME)\n      send(\"/\")\n      send(\"busted\")\n      local attr = rt:cellAttr(rt:rows() - 1, 30)\n      delay(short_delay)\n      local line = find_selected_y()\n      local pid = (\"      \"..tostring(unistd.getpid())):sub(-5)\n      assert.equal(attr, attrs.black_on_cyan)\n      local ourpid = check_string_at(1, line, pid)\n      send(\"\\n\")\n      send(curses.KEY_HOME)\n      assert.equal(check(ourpid))\n   end)\n\n   running_it(\"performs pid search\", function()\n      send(curses.KEY_F5)\n      send(curses.KEY_END)\n      send(\"1\")\n      delay(short_delay)\n      local line = find_selected_y()\n      local initpid = check_string_at(1, line, \"    1\")\n      send(curses.KEY_F5)\n      assert.equal(check(initpid))\n   end)\n\n\n   running_it(\"horizontal scroll\", function()\n      local h_scroll = 20\n      send(curses.KEY_F5)\n      delay(short_delay)\n      local str1 = string_at(1+h_scroll, y_panelhdr+1, 5)\n      send(curses.KEY_RIGHT)\n      delay(short_delay)\n      local str2 = string_at(1, y_panelhdr+1, 5)\n      send(curses.KEY_LEFT)\n      delay(short_delay)\n      local str3 = string_at(1+h_scroll, y_panelhdr+1, 5)\n      send(curses.KEY_LEFT)\n      delay(short_delay)\n      local str4 = string_at(1+h_scroll, y_panelhdr+1, 5)\n      send(curses.KEY_F5)\n      assert.equal(str1, str2)\n      assert.equal(str2, str3)\n      assert.equal(str3, str4)\n   end)\n\n   running_it(\"kills a process\", function()\n      send(curses.KEY_HOME)\n      send(\"\\\\\")\n      send(\"sleep 12345\")\n      local attr = rt:cellAttr(rt:rows() - 1, 30)\n      assert.equal(attr, attrs.black_on_cyan)\n      send(\"\\n\")\n      delay(short_delay)\n      rt:update()\n      local col = find_command_x()\n      local procname = check_string_at(col, y_panelhdr + 1, \"sleep 12345\")\n      send(\"k\")\n      send(\"\\n\")\n      send(\"\\\\\")\n      send(ESC)\n      delay(short_delay)\n      assert.equal(check(procname))\n      assert.not_equal((os.execute(\"ps aux | grep -q '[s]leep 12345'\")), true)\n   end)\n\n   running_it(\"runs strace\", function()\n      send(curses.KEY_HOME)\n      send(\"/\")\n      send(\"busted\")\n      send(\"\\n\")\n      send(\"s\")\n      delay(long_delay)\n      send(ESC)\n   end)\n\n   running_it(\"runs lsof\", function()\n      send(curses.KEY_HOME)\n      send(\"/\")\n      send(\"busted\")\n      send(\"\\n\")\n      send(\"l\")\n      delay(long_delay)\n      send(ESC)\n   end)\n\n   running_it(\"performs filtering in lsof\", function()\n      send(curses.KEY_HOME)\n      send(\"/\")\n      send(\"htop\")\n      send(\"\\n\")\n      send(\"l\")\n      send(curses.KEY_F4)\n      send(\"pipe\")\n      delay(long_delay)\n      local pipefd = check_string_at(1, 3, \"    3\")\n      send(ESC)\n      assert.equal(check(pipefd))\n   end)\n\n   running_it(\"performs search in lsof\", function()\n      send(curses.KEY_HOME)\n      send(\"/\")\n      send(\"htop\")\n      send(\"\\n\")\n      send(\"l\")\n      send(curses.KEY_F3)\n      send(\"pipe\")\n      delay(long_delay)\n      local line = find_selected_y(3)\n      local pipefd = check_string_at(1, line, \"    3\")\n      send(ESC)\n      assert.equal(check(pipefd))\n   end)\n\n\n   running_it(\"cycles through meter modes in the default meters\", function()\n      send(\"S\")\n      for _ = 1, 2 do\n         send(curses.KEY_RIGHT)\n         for _ = 1, 3 do\n            send(\"\\n\", 4)\n            send(curses.KEY_DOWN)\n         end\n      end\n      send(ESC)\n   end)\n\n   running_it(\"show process of a user\", function()\n      send(curses.KEY_F5)\n      send(\"u\")\n      send(curses.KEY_DOWN)\n      delay(short_delay)\n      rt:update()\n      local chosen = string_at(1, y_panelhdr + 2, 9)\n      send(\"\\n\")\n      send(curses.KEY_HOME)\n      delay(short_delay)\n      rt:update()\n      local shown = string_at(7, y_panelhdr + 1, 9)\n      send(\"u\")\n      send(\"\\n\")\n      send(curses.KEY_HOME)\n      delay(short_delay)\n      rt:update()\n      local inituser = string_at(7, y_panelhdr + 1, 9)\n      send(curses.KEY_F5)\n      assert.equal(shown, chosen)\n      assert.equal(inituser, \"root     \")\n   end)\n\n   running_it(\"performs failing search\", function()\n      send(curses.KEY_HOME)\n      send(\"/\")\n      send(\"xxxxxxxxxx\")\n      delay(short_delay)\n      rt:update()\n      local attr = rt:cellAttr(rt:rows() - 1, 30)\n      assert.equal(attr, attrs.red_on_cyan)\n      send(\"\\n\")\n   end)\n\n   running_it(\"cycles through search\", function()\n      send(curses.KEY_HOME)\n      send(\"/\")\n      send(\"sh\")\n      local lastpid\n      local pidpairs = {}\n      for _ = 1, 3 do\n         send(curses.KEY_F3)\n         local line = find_selected_y()\n         local pid = string_at(1, line, 5)\n         if lastpid then\n            pidpairs[#pidpairs + 1] = { lastpid, pid }\n            lastpid = pid\n         end\n      end\n      send(curses.KEY_HOME)\n      for _, pair in pairs(pidpairs) do\n         assert.not_equal(pair[1], pair[2])\n      end\n   end)\n\n   running_it(\"visits each setup screen\", function()\n      send(\"S\")\n      send(curses.KEY_DOWN, 3)\n      send(curses.KEY_F10)\n   end)\n\n   running_it(\"adds and removes PPID column\", function()\n      send(\"S\")\n      send(curses.KEY_DOWN, 3)\n      send(curses.KEY_RIGHT, 2)\n      send(curses.KEY_DOWN, 2)\n      send(\"\\n\")\n      send(curses.KEY_F10)\n      delay(short_delay)\n      local ppid = check_string_at(2, y_panelhdr, \"PPID\")\n      send(\"S\")\n      send(curses.KEY_DOWN, 3)\n      send(curses.KEY_RIGHT, 1)\n      send(curses.KEY_DC)\n      send(curses.KEY_F10)\n      delay(short_delay)\n      local not_ppid = check_string_at(2, y_panelhdr, \"PPID\")\n      assert.equal(check(ppid))\n      assert.not_equal(check(not_ppid))\n   end)\n\n   running_it(\"changes CPU affinity for a process\", function()\n      send(\"a\")\n      send(\" \\n\")\n      send(ESC)\n   end)\n\n   running_it(\"renices for a process\", function()\n      send(\"/\")\n      send(\"busted\")\n      send(\"\\n\")\n      local line = find_selected_y()\n      local before = check_string_at(22, line, \" 0\")\n      send(curses.KEY_F8)\n      delay(short_delay)\n      local after = check_string_at(22, line, \" 1\")\n      assert.equal(check(before))\n      assert.equal(check(after))\n   end)\n\n   running_it(\"tries to lower nice for a process\", function()\n      send(\"/\")\n      send(\"busted\")\n      send(\"\\n\")\n      local line = find_selected_y()\n      local before = string_at(22, line, 2)\n      send(curses.KEY_F7)\n      delay(short_delay)\n      local after = string_at(22, line, 2)\n      assert.equal(before, after) -- no permissions\n   end)\n\n   running_it(\"invert sort order\", function()\n      local cpu_col = 45\n      send(\"P\")\n      send(\"I\")\n      send(curses.KEY_HOME)\n      delay(short_delay)\n      local zerocpu = check_string_at(cpu_col, y_panelhdr + 1, \" 0.0\")\n      send(\"I\")\n      delay(short_delay)\n      local nonzerocpu = check_string_at(cpu_col, y_panelhdr + 1, \" 0.0\")\n      assert.equal(check(zerocpu))\n      assert.not_equal(check(nonzerocpu))\n   end)\n\n   running_it(\"changes IO priority for a process\", function()\n      send(\"/\")\n      send(\"htop\")\n      send(\"\\n\")\n      send(\"i\")\n      send(curses.KEY_END)\n      send(\"\\n\")\n      send(ESC)\n   end)\n\n   running_it(\"shows help\", function()\n      send(curses.KEY_F1)\n      send(\"\\n\")\n      set_display_option(9)\n      send(curses.KEY_F1)\n      send(\"\\n\")\n      set_display_option(9)\n   end)\n\n   running_it(\"moves meters around\", function()\n      send(\"S\")\n      send(curses.KEY_RIGHT)\n      send(curses.KEY_UP)\n      send(\"\\n\")\n      send(curses.KEY_DOWN)\n      send(curses.KEY_UP)\n      send(curses.KEY_RIGHT)\n      send(curses.KEY_RIGHT)\n      send(curses.KEY_LEFT)\n      send(curses.KEY_LEFT)\n      send(\"\\n\")\n      send(curses.KEY_F10)\n   end)\n\n   local meters = {\n      { name = \"clock\", down = 0, string = \"Time\" },\n      { name = \"load\", down = 2, string = \"Load\" },\n      { name = \"battery\", down = 7, string = \"Battery\" },\n      { name = \"hostname\", down = 8, string = \"Hostname\" },\n      { name = \"memory\", down = 3, string = \"Mem\" },\n      { name = \"CPU average\", down = 16, string = \"Avg\" },\n   }\n\n   running_it(\"checks various CPU meters\", function()\n      send(\"S\")\n      send(curses.KEY_RIGHT, 3)\n      send(curses.KEY_DOWN, 9, \"quick\")\n      for _ = 9, 14 do\n         send(\"\\n\")\n         send(\"\\n\")\n         send(curses.KEY_DC)\n         send(curses.KEY_RIGHT)\n         send(curses.KEY_DOWN)\n      end\n   end)\n\n   for _, item in ipairs(meters) do\n      running_it(\"adds and removes a \"..item.name..\" widget\", function()\n         send(\"S\")\n         send(curses.KEY_RIGHT, 3)\n         send(curses.KEY_DOWN, item.down)\n         send(\"\\n\")\n         send(curses.KEY_UP, 4)\n         send(\"\\n\")\n         send(curses.KEY_F4, 4) -- cycle through meter modes\n         delay(short_delay)\n         rt:update()\n         local with = check_string_at(x_metercol2, 2, item.string)\n         send(curses.KEY_DC)\n         delay(short_delay)\n         local without = check_string_at(x_metercol2, 2, item.string)\n         send(curses.KEY_F10)\n         assert.equal(check(with))\n         assert.not_equal(check(without))\n      end)\n   end\n\n   running_it(\"goes through themes\", function()\n      send(curses.KEY_F2)\n      send(curses.KEY_DOWN, 2)\n      send(curses.KEY_RIGHT)\n      for _ = 1, 6 do\n         send(\"\\n\")\n         send(curses.KEY_DOWN)\n      end\n      send(curses.KEY_UP, 6)\n      send(\"\\n\")\n      send(curses.KEY_F10)\n   end)\n\n   local display_options = {\n      { name = \"tree view\", down = 0 },\n      { name = \"shadow other user's process\", down = 1 },\n      { name = \"hide kernel threads\", down = 2 },\n      { name = \"hide userland threads\", down = 3 },\n      { name = \"display threads in different color\", down = 4 },\n      { name = \"show custom thread names\", down = 5 },\n      { name = \"highlight basename\", down = 6 },\n      { name = \"highlight large numbers\", down = 7 },\n      { name = \"leave margin around header\", down = 8 },\n      { name = \"use detailed CPU time\", down = 9 },\n      { name = \"count from zero\", down = 10 },\n      { name = \"update process names\", down = 11 },\n      { name = \"guest time in CPU%\", down = 12 },\n   }\n\n   for _, item in ipairs(display_options) do\n      running_it(\"checks display option to \"..item.name, function()\n         for _ = 1, 2 do\n            set_display_option(item.down)\n            delay(short_delay)\n         end\n      end)\n   end\n\n   running_it(\"shows detailed CPU with guest time\", function()\n      for _ = 1, 2 do\n         send(\"S\")\n         send(curses.KEY_DOWN)\n         send(curses.KEY_RIGHT)\n         send(curses.KEY_DOWN, 9)\n         send(\"\\n\")\n         send(curses.KEY_DOWN, 3)\n         send(\"\\n\")\n         send(curses.KEY_LEFT)\n         send(curses.KEY_UP)\n         send(curses.KEY_RIGHT)\n         send(curses.KEY_F4, 4) -- cycle through CPU meter modes\n         send(curses.KEY_F10)\n         delay(short_delay)\n      end\n   end)\n\n   running_it(\"expands and collapses tree\", function()\n      send(curses.KEY_F5) -- tree view\n      send(curses.KEY_HOME)\n      send(curses.KEY_DOWN) -- second process in the tree\n      send(\"-\")\n      send(\"+\")\n      send(curses.KEY_F5)\n   end)\n\n   running_it(\"sets sort key\", function()\n      send(\".\")\n      send(\"\\n\")\n   end)\n\n   running_it(\"tags all children\", function()\n      send(curses.KEY_F5) -- tree view\n      send(curses.KEY_HOME) -- ensure we're at init\n      send(\"c\")\n      local taggedattrs = {}\n      rt:update()\n      for y = y_panelhdr + 2, 23 do\n         table.insert(taggedattrs, rt:cellAttr(y-1, 4))\n      end\n      delay(short_delay)\n      send(\"U\")\n      local untaggedattrs = {}\n      rt:update()\n      for y = y_panelhdr + 2, 23 do\n         table.insert(untaggedattrs, rt:cellAttr(y-1, 4))\n      end\n      send(curses.KEY_F5)\n\n      for _, taggedattr in ipairs(taggedattrs) do\n         assert.equal(attrs.yellow_on_black, taggedattr)\n      end\n      for _, untaggedattr in ipairs(untaggedattrs) do\n         assert.equal(attrs.white_on_black, untaggedattr)\n      end\n   end)\n\n   for i = 1, 62 do\n      running_it(\"show column \"..i, function()\n         send(\"S\")\n         send(curses.KEY_END)\n         send(curses.KEY_RIGHT, 1)\n         if i > 1 then\n            send(curses.KEY_DC)\n         end\n         send(curses.KEY_RIGHT, 1)\n         local down = i\n         while down > 13 do\n            send(curses.KEY_NPAGE)\n            down = down - 13\n         end\n         send(curses.KEY_DOWN, down, \"quick\")\n         send(\"\\n\")\n         send(curses.KEY_F10)\n         if i == 62 then\n            send(\"S\")\n            send(curses.KEY_END)\n            send(curses.KEY_RIGHT, 1)\n            if i > 1 then\n               send(curses.KEY_DC)\n            end\n            send(curses.KEY_F10)\n         end\n      end)\n   end\n\n   it(\"finally quits\", function()\n      assert(not terminated())\n      send(\"q\")\n      while not terminated() do\n         unistd.sleep(1)\n         send(\"q\")\n      end\n      assert(terminated())\n      if visual then\n         curses.endwin()\n      end\n      os.execute(\"make lcov && xdg-open lcov/index.html\")\n   end)\nend)\n"
  },
  {
    "path": "unsupported/Platform.c",
    "content": "/*\nhtop - unsupported/Platform.c\n(C) 2014 Hisham H. Muhammad\n(C) 2015 David C. Hunt\nReleased under the GNU GPLv2+, see the COPYING file\nin the source distribution for its full text.\n*/\n\n#include \"config.h\" // IWYU pragma: keep\n\n#include \"unsupported/Platform.h\"\n\n#include <math.h>\n\n#include \"CPUMeter.h\"\n#include \"ClockMeter.h\"\n#include \"DateTimeMeter.h\"\n#include \"FileDescriptorMeter.h\"\n#include \"HostnameMeter.h\"\n#include \"LoadAverageMeter.h\"\n#include \"Macros.h\"\n#include \"MemoryMeter.h\"\n#include \"MemorySwapMeter.h\"\n#include \"SwapMeter.h\"\n#include \"SysArchMeter.h\"\n#include \"TasksMeter.h\"\n#include \"UptimeMeter.h\"\n\n\nconst ScreenDefaults Platform_defaultScreens[] = {\n   {\n      .name = \"Main\",\n      .columns = \"PID USER PRIORITY NICE M_VIRT M_RESIDENT STATE PERCENT_CPU PERCENT_MEM TIME Command\",\n      .sortKey = \"PERCENT_CPU\",\n   },\n};\n\nconst unsigned int Platform_numberOfDefaultScreens = ARRAYSIZE(Platform_defaultScreens);\n\nconst SignalItem Platform_signals[] = {\n   { .name = \" 0 Cancel\",    .number =  0 },\n};\n\nconst unsigned int Platform_numberOfSignals = ARRAYSIZE(Platform_signals);\n\nenum {\n   MEMORY_CLASS_USED = 0,\n   MEMORY_CLASS_CACHED,\n}; // N.B. the chart will display categories in this order\n\nconst MemoryClass Platform_memoryClasses[] = {\n   [MEMORY_CLASS_USED] = { .label = \"used\", .countsAsUsed = true, .countsAsCache = false, .color = MEMORY_1 },\n   [MEMORY_CLASS_CACHED] = { .label = \"cached\", .countsAsUsed = false, .countsAsCache = true, .color = MEMORY_2 },\n};\n\nconst unsigned int Platform_numberOfMemoryClasses = ARRAYSIZE(Platform_memoryClasses);\n\nconst MeterClass* const Platform_meterTypes[] = {\n   &CPUMeter_class,\n   &ClockMeter_class,\n   &DateMeter_class,\n   &DateTimeMeter_class,\n   &LoadAverageMeter_class,\n   &LoadMeter_class,\n   &MemoryMeter_class,\n   &SwapMeter_class,\n   &MemorySwapMeter_class,\n   &TasksMeter_class,\n   &BatteryMeter_class,\n   &HostnameMeter_class,\n   &SysArchMeter_class,\n   &UptimeMeter_class,\n   &SecondsUptimeMeter_class,\n   &AllCPUsMeter_class,\n   &AllCPUs2Meter_class,\n   &AllCPUs4Meter_class,\n   &AllCPUs8Meter_class,\n   &LeftCPUsMeter_class,\n   &RightCPUsMeter_class,\n   &LeftCPUs2Meter_class,\n   &RightCPUs2Meter_class,\n   &LeftCPUs4Meter_class,\n   &RightCPUs4Meter_class,\n   &LeftCPUs8Meter_class,\n   &RightCPUs8Meter_class,\n   &FileDescriptorMeter_class,\n   &BlankMeter_class,\n   NULL\n};\n\nstatic const char Platform_unsupported[] = \"unsupported\";\n\nbool Platform_init(void) {\n   /* no platform-specific setup needed */\n   return true;\n}\n\nvoid Platform_done(void) {\n   /* no platform-specific cleanup needed */\n}\n\nvoid Platform_setBindings(Htop_Action* keys) {\n   /* no platform-specific key bindings */\n   (void) keys;\n}\n\nint Platform_getUptime(void) {\n   return 0;\n}\n\nvoid Platform_getLoadAverage(double* one, double* five, double* fifteen) {\n   *one = 0;\n   *five = 0;\n   *fifteen = 0;\n}\n\npid_t Platform_getMaxPid(void) {\n   return INT_MAX;\n}\n\ndouble Platform_setCPUValues(Meter* this, unsigned int cpu) {\n   (void) cpu;\n\n   double* v = this->values;\n   v[CPU_METER_FREQUENCY] = NAN;\n   v[CPU_METER_TEMPERATURE] = NAN;\n\n   this->curItems = 1;\n\n   return 0.0;\n}\n\nvoid Platform_setMemoryValues(Meter* this) {\n   double* v = this->values;\n   v[MEMORY_CLASS_USED] = NAN;\n   v[MEMORY_CLASS_CACHED] = NAN;\n\n   this->curItems = 2;\n}\n\nvoid Platform_setSwapValues(Meter* this) {\n   (void) this;\n}\n\nchar* Platform_getProcessEnv(pid_t pid) {\n   (void) pid;\n   return NULL;\n}\n\nFileLocks_ProcessData* Platform_getProcessLocks(pid_t pid) {\n   (void)pid;\n   return NULL;\n}\n\nvoid Platform_getFileDescriptors(double* used, double* max) {\n   *used = 1337;\n   *max = 4711;\n}\n\nbool Platform_getDiskIO(DiskIOData* data) {\n   (void)data;\n   return false;\n}\n\nbool Platform_getNetworkIO(NetworkIOData* data) {\n   (void)data;\n   return false;\n}\n\nvoid Platform_getBattery(double* percent, ACPresence* isOnAC) {\n   *percent = NAN;\n   *isOnAC = AC_ERROR;\n}\n\nvoid Platform_getHostname(char* buffer, size_t size) {\n   String_safeStrncpy(buffer, Platform_unsupported, size);\n}\n\nconst char* Platform_getRelease(void) {\n   return Platform_unsupported;\n}\n"
  },
  {
    "path": "unsupported/Platform.h",
    "content": "#ifndef HEADER_Platform\n#define HEADER_Platform\n/*\nhtop - unsupported/Platform.h\n(C) 2014 Hisham H. Muhammad\n(C) 2015 David C. Hunt\nReleased under the GNU GPLv2+, see the COPYING file\nin the source distribution for its full text.\n*/\n\n#include <stdbool.h>\n#include <sys/types.h>\n\n#include \"Action.h\"\n#include \"BatteryMeter.h\"\n#include \"DiskIOMeter.h\"\n#include \"Hashtable.h\"\n#include \"MemoryMeter.h\"\n#include \"NetworkIOMeter.h\"\n#include \"ProcessLocksScreen.h\"\n#include \"SignalsPanel.h\"\n#include \"CommandLine.h\"\n#include \"generic/gettime.h\"\n#include \"unsupported/UnsupportedProcess.h\"\n\n\nextern const ScreenDefaults Platform_defaultScreens[];\n\nextern const unsigned int Platform_numberOfDefaultScreens;\n\nextern const SignalItem Platform_signals[];\n\nextern const unsigned int Platform_numberOfSignals;\n\nextern const MemoryClass Platform_memoryClasses[];\n\nextern const unsigned int Platform_numberOfMemoryClasses;\n\nextern const MeterClass* const Platform_meterTypes[];\n\nbool Platform_init(void);\n\nvoid Platform_done(void);\n\nvoid Platform_setBindings(Htop_Action* keys);\n\nint Platform_getUptime(void);\n\nvoid Platform_getLoadAverage(double* one, double* five, double* fifteen);\n\npid_t Platform_getMaxPid(void);\n\ndouble Platform_setCPUValues(Meter* this, unsigned int cpu);\n\nvoid Platform_setMemoryValues(Meter* this);\n\nvoid Platform_setSwapValues(Meter* this);\n\nchar* Platform_getProcessEnv(pid_t pid);\n\nFileLocks_ProcessData* Platform_getProcessLocks(pid_t pid);\n\nvoid Platform_getFileDescriptors(double* used, double* max);\n\nbool Platform_getDiskIO(DiskIOData* data);\n\nbool Platform_getNetworkIO(NetworkIOData* data);\n\nvoid Platform_getBattery(double* percent, ACPresence* isOnAC);\n\nvoid Platform_getHostname(char* buffer, size_t size);\n\nconst char* Platform_getRelease(void);\n\nstatic inline const char* Platform_getFailedState(void) {\n   return NULL;\n}\n\n#define PLATFORM_LONG_OPTIONS\n\nstatic inline void Platform_longOptionsUsage(ATTR_UNUSED const char* name) { }\n\nstatic inline CommandLineStatus Platform_getLongOption(ATTR_UNUSED int opt, ATTR_UNUSED int argc, ATTR_UNUSED char** argv) {\n   return STATUS_ERROR_EXIT;\n}\n\nstatic inline void Platform_gettime_realtime(struct timeval* tv, uint64_t* msec) {\n   Generic_gettime_realtime(tv, msec);\n}\n\nstatic inline void Platform_gettime_monotonic(uint64_t* msec) {\n   Generic_gettime_monotonic(msec);\n}\n\nstatic inline Hashtable* Platform_dynamicMeters(void) {\n   return NULL;\n}\n\nstatic inline void Platform_dynamicMetersDone(ATTR_UNUSED Hashtable* table) { }\n\nstatic inline void Platform_dynamicMeterInit(ATTR_UNUSED Meter* meter) { }\n\nstatic inline void Platform_dynamicMeterUpdateValues(ATTR_UNUSED Meter* meter) { }\n\nstatic inline void Platform_dynamicMeterDisplay(ATTR_UNUSED const Meter* meter, ATTR_UNUSED RichString* out) { }\n\nstatic inline Hashtable* Platform_dynamicColumns(void) {\n   return NULL;\n}\n\nstatic inline void Platform_dynamicColumnsDone(ATTR_UNUSED Hashtable* table) { }\n\nstatic inline const char* Platform_dynamicColumnName(ATTR_UNUSED unsigned int key) {\n   return NULL;\n}\n\nstatic inline bool Platform_dynamicColumnWriteField(ATTR_UNUSED const Process* proc, ATTR_UNUSED RichString* str, ATTR_UNUSED unsigned int key) {\n   return false;\n}\n\nstatic inline Hashtable* Platform_dynamicScreens(void) {\n   return NULL;\n}\n\nstatic inline void Platform_defaultDynamicScreens(ATTR_UNUSED Settings* settings) { }\n\nstatic inline void Platform_addDynamicScreen(ATTR_UNUSED ScreenSettings* ss) { }\n\nstatic inline void Platform_addDynamicScreenAvailableColumns(ATTR_UNUSED Panel* availableColumns, ATTR_UNUSED const char* screen) { }\n\nstatic inline void Platform_dynamicScreensDone(ATTR_UNUSED Hashtable* screens) { }\n\n#endif\n"
  },
  {
    "path": "unsupported/ProcessField.h",
    "content": "#ifndef HEADER_UnsupportedProcessField\n#define HEADER_UnsupportedProcessField\n/*\nhtop - unsupported/ProcessField.h\n(C) 2020 htop dev team\nReleased under the GNU GPLv2+, see the COPYING file\nin the source distribution for its full text.\n*/\n\n\n#define PLATFORM_PROCESS_FIELDS  \\\n   // End of list\n\n\n#endif /* HEADER_UnsupportedProcessField */\n"
  },
  {
    "path": "unsupported/UnsupportedMachine.c",
    "content": "/*\nhtop - UnsupportedMachine.c\n(C) 2014 Hisham H. Muhammad\nReleased under the GNU GPLv2+, see the COPYING file\nin the source distribution for its full text.\n*/\n\n#include \"config.h\" // IWYU pragma: keep\n\n#include \"UnsupportedMachine.h\"\n\n#include <stdlib.h>\n#include <string.h>\n\n#include \"Machine.h\"\n\n\nMachine* Machine_new(UsersTable* usersTable, uid_t userId) {\n   UnsupportedMachine* this = xCalloc(1, sizeof(UnsupportedMachine));\n   Machine* super = &this->super;\n\n   Machine_init(super, usersTable, userId);\n\n   super->existingCPUs = 1;\n   super->activeCPUs = 1;\n\n   return super;\n}\n\nvoid Machine_delete(Machine* super) {\n   UnsupportedMachine* this = (UnsupportedMachine*) super;\n   Machine_done(super);\n   free(this);\n}\n\nbool Machine_isCPUonline(const Machine* host, unsigned int id) {\n   assert(id < host->existingCPUs);\n\n   (void) host; (void) id;\n\n   return true;\n}\n\nvoid Machine_scan(Machine* super) {\n   UnsupportedMachine* this = (UnsupportedMachine*) super;\n   super->existingCPUs = 1;\n   super->activeCPUs = 1;\n   super->totalSwap = 0;\n   super->usedSwap = 0;\n   super->cachedSwap = 0;\n   super->totalMem = 0;\n   this->usedMem = 0;\n   this->cachedMem = 0;\n}\n"
  },
  {
    "path": "unsupported/UnsupportedMachine.h",
    "content": "#ifndef HEADER_UnsupportedMachine\n#define HEADER_UnsupportedMachine\n/*\nhtop - UnsupportedMachine.h\n(C) 2014 Hisham H. Muhammad\nReleased under the GNU GPLv2+, see the COPYING file\nin the source distribution for its full text.\n*/\n\n#include \"Machine.h\"\n\n\ntypedef struct UnsupportedMachine_ {\n   Machine super;\n   memory_t usedMem;\n   memory_t cachedMem;\n} UnsupportedMachine;\n\n#endif\n"
  },
  {
    "path": "unsupported/UnsupportedProcess.c",
    "content": "/*\nhtop - UnsupportedProcess.c\n(C) 2015 Hisham H. Muhammad\nReleased under the GNU GPLv2+, see the COPYING file\nin the source distribution for its full text.\n*/\n\n#include \"config.h\" // IWYU pragma: keep\n\n#include \"unsupported/UnsupportedProcess.h\"\n\n#include <stdlib.h>\n\n#include \"CRT.h\"\n#include \"Process.h\"\n\n\nconst ProcessFieldData Process_fields[LAST_PROCESSFIELD] = {\n   [0] = { .name = \"\", .title = NULL, .description = NULL, .flags = 0, },\n   [PID] = { .name = \"PID\", .title = \"PID\", .description = \"Process/thread ID\", .flags = 0, .pidColumn = true, },\n   [COMM] = { .name = \"Command\", .title = \"Command \", .description = \"Command line (insert as last column only)\", .flags = 0, },\n   [STATE] = { .name = \"STATE\", .title = \"S \", .description = \"Process state (S sleeping, R running, D disk, Z zombie, T traced, W paging)\", .flags = 0, },\n   [PPID] = { .name = \"PPID\", .title = \"PPID\", .description = \"Parent process ID\", .flags = 0, .pidColumn = true, },\n   [PGRP] = { .name = \"PGRP\", .title = \"PGRP\", .description = \"Process group ID\", .flags = 0, .pidColumn = true, },\n   [SESSION] = { .name = \"SESSION\", .title = \"SID\", .description = \"Process's session ID\", .flags = 0, .pidColumn = true, },\n   [TTY] = { .name = \"TTY\", .title = \"TTY      \", .description = \"Controlling terminal\", .flags = 0, },\n   [TPGID] = { .name = \"TPGID\", .title = \"TPGID\", .description = \"Process ID of the fg process group of the controlling terminal\", .flags = 0, .pidColumn = true, },\n   [MINFLT] = { .name = \"MINFLT\", .title = \"     MINFLT \", .description = \"Number of minor faults which have not required loading a memory page from disk\", .flags = 0, .defaultSortDesc = true,},\n   [MAJFLT] = { .name = \"MAJFLT\", .title = \"     MAJFLT \", .description = \"Number of major faults which have required loading a memory page from disk\", .flags = 0, .defaultSortDesc = true, },\n   [PRIORITY] = { .name = \"PRIORITY\", .title = \"PRI \", .description = \"Kernel's internal priority for the process\", .flags = 0, },\n   [NICE] = { .name = \"NICE\", .title = \" NI \", .description = \"Nice value (the higher the value, the more it lets other processes take priority)\", .flags = 0, },\n   [STARTTIME] = { .name = \"STARTTIME\", .title = \"START \", .description = \"Time the process was started\", .flags = 0, },\n   [ELAPSED] = { .name = \"ELAPSED\", .title = \"ELAPSED  \", .description = \"Time since the process was started\", .flags = 0, },\n   [PROCESSOR] = { .name = \"PROCESSOR\", .title = \"CPU \", .description = \"Id of the CPU the process last executed on\", .flags = 0, },\n   [M_VIRT] = { .name = \"M_VIRT\", .title = \" VIRT \", .description = \"Total program size in virtual memory\", .flags = 0, .defaultSortDesc = true, },\n   [M_RESIDENT] = { .name = \"M_RESIDENT\", .title = \"  RES \", .description = \"Resident set size, size of the text and data sections, plus stack usage\", .flags = 0, .defaultSortDesc = true, },\n   [ST_UID] = { .name = \"ST_UID\", .title = \"UID\", .description = \"User ID of the process owner\", .flags = 0, },\n   [PERCENT_CPU] = { .name = \"PERCENT_CPU\", .title = \" CPU%\", .description = \"Percentage of the CPU time the process used in the last sampling\", .flags = 0, .defaultSortDesc = true, .autoWidth = true, .autoTitleRightAlign = true, },\n   [PERCENT_NORM_CPU] = { .name = \"PERCENT_NORM_CPU\", .title = \"NCPU%\", .description = \"Normalized percentage of the CPU time the process used in the last sampling (normalized by cpu count)\", .flags = 0, .defaultSortDesc = true, .autoWidth = true, },\n   [PERCENT_MEM] = { .name = \"PERCENT_MEM\", .title = \"MEM% \", .description = \"Percentage of the memory the process is using, based on resident memory size\", .flags = 0, .defaultSortDesc = true, },\n   [USER] = { .name = \"USER\", .title = \"USER       \", .description = \"Username of the process owner (or user ID if name cannot be determined)\", .flags = 0, },\n   [TIME] = { .name = \"TIME\", .title = \"  TIME+  \", .description = \"Total time the process has spent in user and system time\", .flags = 0, .defaultSortDesc = true, },\n   [NLWP] = { .name = \"NLWP\", .title = \"NLWP \", .description = \"Number of threads in the process\", .flags = 0, },\n   [TGID] = { .name = \"TGID\", .title = \"TGID\", .description = \"Thread group ID (i.e. process ID)\", .flags = 0, .pidColumn = true, },\n};\n\nProcess* UnsupportedProcess_new(const Machine* host) {\n   Process* this = xCalloc(1, sizeof(UnsupportedProcess));\n   Object_setClass(this, Class(UnsupportedProcess));\n   Process_init(this, host);\n   return this;\n}\n\nvoid Process_delete(Object* cast) {\n   Process* super = (Process*) cast;\n   Process_done(super);\n   // free platform-specific fields here\n   free(cast);\n}\n\nstatic void UnsupportedProcess_rowWriteField(const Row* super, RichString* str, ProcessField field) {\n   const UnsupportedProcess* up = (const UnsupportedProcess*) super;\n\n   bool coloring = super->host->settings->highlightMegabytes;\n   char buffer[256]; buffer[255] = '\\0';\n   int attr = CRT_colors[DEFAULT_COLOR];\n   size_t n = sizeof(buffer) - 1;\n\n   (void) coloring;\n   (void) n;\n\n   switch (field) {\n   /* Add platform specific fields */\n   default:\n      Process_writeField(&up->super, str, field);\n      return;\n   }\n\n   RichString_appendWide(str, attr, buffer);\n}\n\nstatic int UnsupportedProcess_compareByKey(const Process* v1, const Process* v2, ProcessField key) {\n   const UnsupportedProcess* p1 = (const UnsupportedProcess*)v1;\n   const UnsupportedProcess* p2 = (const UnsupportedProcess*)v2;\n\n   (void) p1;\n   (void) p2;\n\n   switch (key) {\n   /* Add platform specific fields */\n   default:\n      return Process_compareByKey_Base(v1, v2, key);\n   }\n}\n\nconst ProcessClass UnsupportedProcess_class = {\n   .super = {\n      .super = {\n         .extends = Class(Process),\n         .display = Row_display,\n         .delete = Process_delete,\n         .compare = Process_compare\n      },\n      .isHighlighted = Process_rowIsHighlighted,\n      .isVisible = Process_rowIsVisible,\n      .matchesFilter = Process_rowMatchesFilter,\n      .compareByParent = Process_compareByParent,\n      .sortKeyString = Process_rowGetSortKey,\n      .writeField = UnsupportedProcess_rowWriteField\n   },\n   .compareByKey = UnsupportedProcess_compareByKey\n};\n"
  },
  {
    "path": "unsupported/UnsupportedProcess.h",
    "content": "#ifndef HEADER_UnsupportedProcess\n#define HEADER_UnsupportedProcess\n/*\nhtop - UnsupportedProcess.h\n(C) 2015 Hisham H. Muhammad\nReleased under the GNU GPLv2+, see the COPYING file\nin the source distribution for its full text.\n*/\n\n#include \"Machine.h\"\n#include \"Process.h\"\n\n\ntypedef struct UnsupportedProcess_ {\n   Process super;\n\n   /* Add platform specific fields */\n} UnsupportedProcess;\n\n\nextern const ProcessFieldData Process_fields[LAST_PROCESSFIELD];\n\nProcess* UnsupportedProcess_new(const Machine* host);\n\nvoid Process_delete(Object* cast);\n\nextern const ProcessClass UnsupportedProcess_class;\n\n#endif\n"
  },
  {
    "path": "unsupported/UnsupportedProcessTable.c",
    "content": "/*\nhtop - UnsupportedProcessTable.c\n(C) 2014 Hisham H. Muhammad\nReleased under the GNU GPLv2+, see the COPYING file\nin the source distribution for its full text.\n*/\n\n#include \"config.h\" // IWYU pragma: keep\n\n#include \"UnsupportedProcessTable.h\"\n\n#include <stdlib.h>\n#include <string.h>\n\n#include \"ProcessTable.h\"\n#include \"UnsupportedProcess.h\"\n\n\nProcessTable* ProcessTable_new(Machine* host, Hashtable* pidMatchList) {\n   UnsupportedProcessTable* this = xCalloc(1, sizeof(UnsupportedProcessTable));\n   Object_setClass(this, Class(ProcessTable));\n\n   ProcessTable* super = &this->super;\n   ProcessTable_init(super, Class(Process), host, pidMatchList);\n\n   return super;\n}\n\nvoid ProcessTable_delete(Object* cast) {\n   UnsupportedProcessTable* this = (UnsupportedProcessTable*) cast;\n   ProcessTable_done(&this->super);\n   free(this);\n}\n\nvoid ProcessTable_goThroughEntries(ProcessTable* super) {\n   bool preExisting = true;\n   Process* proc;\n\n   proc = ProcessTable_getProcess(super, 1, &preExisting, UnsupportedProcess_new);\n\n   /* Empty values */\n   proc->time = proc->time + 10;\n   Process_setPid(proc, 1);\n   Process_setParent(proc, 1);\n   Process_setThreadGroup(proc, 0);\n\n   Process_updateComm(proc, \"commof16char\");\n   Process_updateCmdline(proc, \"<unsupported architecture>\", 0, 0);\n   Process_updateExe(proc, \"/path/to/executable\");\n\n   const Settings* settings = super->super.host->settings;\n   if (settings->ss->flags & PROCESS_FLAG_CWD) {\n      free_and_xStrdup(&proc->procCwd, \"/current/working/directory\");\n   }\n\n   proc->super.updated = true;\n\n   proc->state = RUNNING;\n   proc->isKernelThread = false;\n   proc->isUserlandThread = false;\n   proc->super.show = true; /* Reflected in settings-> \"hideXXX\" really */\n   proc->pgrp = 0;\n   proc->session = 0;\n   proc->tty_nr = 0;\n   proc->tty_name = NULL;\n   proc->tpgid = 0;\n   proc->processor = 0;\n\n   proc->percent_cpu = 2.5;\n   proc->percent_mem = 2.5;\n   Process_updateCPUFieldWidths(proc->percent_cpu);\n\n   proc->st_uid = 0;\n   proc->user = \"nobody\"; /* Update whenever proc->st_uid is changed */\n\n   proc->priority = 0;\n   proc->nice = 0;\n   proc->nlwp = 1;\n   proc->starttime_ctime = 1433116800; // Jun 01, 2015\n   Process_fillStarttimeBuffer(proc);\n\n   proc->m_virt = 100;\n   proc->m_resident = 100;\n\n   proc->minflt = 20;\n   proc->majflt = 20;\n\n   if (!preExisting)\n      ProcessTable_add(super, proc);\n}\n"
  },
  {
    "path": "unsupported/UnsupportedProcessTable.h",
    "content": "#ifndef HEADER_UnsupportedProcessTable\n#define HEADER_UnsupportedProcessTable\n/*\nhtop - UnsupportedProcessTable.h\n(C) 2014 Hisham H. Muhammad\nReleased under the GNU GPLv2+, see the COPYING file\nin the source distribution for its full text.\n*/\n\n#include \"ProcessTable.h\"\n\n\ntypedef struct UnsupportedProcessTable_ {\n   ProcessTable super;\n} UnsupportedProcessTable;\n\n#endif\n"
  },
  {
    "path": "zfs/ZfsArcMeter.c",
    "content": "/*\nhtop - ZfsArcMeter.c\n(C) 2004-2011 Hisham H. Muhammad\nReleased under the GNU GPLv2+, see the COPYING file\nin the source distribution for its full text.\n*/\n\n#include \"config.h\" // IWYU pragma: keep\n\n#include \"zfs/ZfsArcMeter.h\"\n\n#include <stddef.h>\n\n#include \"CRT.h\"\n#include \"Object.h\"\n#include \"Platform.h\"\n#include \"RichString.h\"\n\n#include \"zfs/ZfsArcStats.h\"\n\n\nstatic const int ZfsArcMeter_attributes[] = {\n   ZFS_MFU, ZFS_MRU, ZFS_ANON, ZFS_HEADER, ZFS_OTHER\n};\n\nvoid ZfsArcMeter_readStats(Meter* this, const ZfsArcStats* stats) {\n   this->total = stats->max;\n   this->values[0] = stats->MFU;\n   this->values[1] = stats->MRU;\n   this->values[2] = stats->anon;\n   this->values[3] = stats->header;\n   this->values[4] = stats->other;\n\n   // \"Hide\" the last value so it can\n   // only be accessed by index and is not\n   // displayed by the Bar or Graph style\n   this->curItems = 5;\n   this->values[5] = stats->size;\n}\n\nstatic void ZfsArcMeter_updateValues(Meter* this) {\n   char* buffer = this->txtBuffer;\n   size_t size = sizeof(this->txtBuffer);\n   int written;\n   Platform_setZfsArcValues(this);\n\n   written = Meter_humanUnit(buffer, this->values[5], size);\n   METER_BUFFER_CHECK(buffer, size, written);\n\n   METER_BUFFER_APPEND_CHR(buffer, size, '/');\n\n   Meter_humanUnit(buffer, this->total, size);\n}\n\nstatic void ZfsArcMeter_display(const Object* cast, RichString* out) {\n   const Meter* this = (const Meter*)cast;\n\n   if (this->values[5] > 0) {\n      char buffer[50];\n      Meter_humanUnit(buffer, this->total, sizeof(buffer));\n      RichString_appendAscii(out, CRT_colors[METER_VALUE], buffer);\n      Meter_humanUnit(buffer, this->values[5], sizeof(buffer));\n      RichString_appendAscii(out, CRT_colors[METER_TEXT], \" Used:\");\n      RichString_appendAscii(out, CRT_colors[METER_VALUE], buffer);\n      Meter_humanUnit(buffer, this->values[0], sizeof(buffer));\n      RichString_appendAscii(out, CRT_colors[METER_TEXT], \" MFU:\");\n      RichString_appendAscii(out, CRT_colors[ZFS_MFU], buffer);\n      Meter_humanUnit(buffer, this->values[1], sizeof(buffer));\n      RichString_appendAscii(out, CRT_colors[METER_TEXT], \" MRU:\");\n      RichString_appendAscii(out, CRT_colors[ZFS_MRU], buffer);\n      Meter_humanUnit(buffer, this->values[2], sizeof(buffer));\n      RichString_appendAscii(out, CRT_colors[METER_TEXT], \" Anon:\");\n      RichString_appendAscii(out, CRT_colors[ZFS_ANON], buffer);\n      Meter_humanUnit(buffer, this->values[3], sizeof(buffer));\n      RichString_appendAscii(out, CRT_colors[METER_TEXT], \" Hdr:\");\n      RichString_appendAscii(out, CRT_colors[ZFS_HEADER], buffer);\n      Meter_humanUnit(buffer, this->values[4], sizeof(buffer));\n      RichString_appendAscii(out, CRT_colors[METER_TEXT], \" Oth:\");\n      RichString_appendAscii(out, CRT_colors[ZFS_OTHER], buffer);\n   } else {\n      RichString_writeAscii(out, CRT_colors[METER_TEXT], \" \");\n      RichString_appendAscii(out, CRT_colors[FAILED_READ], \"Unavailable\");\n   }\n}\n\nconst MeterClass ZfsArcMeter_class = {\n   .super = {\n      .extends = Class(Meter),\n      .delete = Meter_delete,\n      .display = ZfsArcMeter_display,\n   },\n   .updateValues = ZfsArcMeter_updateValues,\n   .defaultMode = TEXT_METERMODE,\n   .supportedModes = METERMODE_DEFAULT_SUPPORTED,\n   .maxItems = 6,\n   .isPercentChart = true,\n   .total = 100.0,\n   .attributes = ZfsArcMeter_attributes,\n   .name = \"ZFSARC\",\n   .uiName = \"ZFS ARC\",\n   .caption = \"ARC: \"\n};\n"
  },
  {
    "path": "zfs/ZfsArcMeter.h",
    "content": "#ifndef HEADER_ZfsArcMeter\n#define HEADER_ZfsArcMeter\n/*\nhtop - ZfsArcMeter.h\n(C) 2004-2011 Hisham H. Muhammad\nReleased under the GNU GPLv2+, see the COPYING file\nin the source distribution for its full text.\n*/\n\n#include \"zfs/ZfsArcStats.h\"\n\n#include \"Meter.h\"\n\n\nvoid ZfsArcMeter_readStats(Meter* this, const ZfsArcStats* stats);\n\nextern const MeterClass ZfsArcMeter_class;\n\n#endif\n"
  },
  {
    "path": "zfs/ZfsArcStats.h",
    "content": "#ifndef HEADER_ZfsArcStats\n#define HEADER_ZfsArcStats\n/*\nhtop - ZfsArcStats.h\n(C) 2014 Hisham H. Muhammad\nReleased under the GNU GPLv2+, see the COPYING file\nin the source distribution for its full text.\n*/\n\ntypedef struct ZfsArcStats_ {\n   int enabled;\n   int isCompressed;\n   unsigned long long int min;\n   unsigned long long int max;\n   unsigned long long int size;\n   unsigned long long int MFU;\n   unsigned long long int MRU;\n   unsigned long long int anon;\n   unsigned long long int header;\n   unsigned long long int other;\n   unsigned long long int compressed;\n   unsigned long long int uncompressed;\n} ZfsArcStats;\n\n#endif\n"
  },
  {
    "path": "zfs/ZfsCompressedArcMeter.c",
    "content": "/*\nhtop - ZfsCompressedArcMeter.c\n(C) 2004-2011 Hisham H. Muhammad\nReleased under the GNU GPLv2+, see the COPYING file\nin the source distribution for its full text.\n*/\n\n#include \"config.h\" // IWYU pragma: keep\n\n#include \"zfs/ZfsCompressedArcMeter.h\"\n\n#include <stddef.h>\n\n#include \"CRT.h\"\n#include \"Meter.h\"\n#include \"Object.h\"\n#include \"Platform.h\"\n#include \"RichString.h\"\n#include \"XUtils.h\"\n#include \"zfs/ZfsArcStats.h\"\n\n\nstatic const int ZfsCompressedArcMeter_attributes[] = {\n   ZFS_COMPRESSED\n};\n\nvoid ZfsCompressedArcMeter_readStats(Meter* this, const ZfsArcStats* stats) {\n   if ( stats->isCompressed ) {\n      this->total = stats->uncompressed;\n      this->values[0] = stats->compressed;\n   } else {\n      // For uncompressed ARC, report 1:1 ratio\n      this->total = stats->size;\n      this->values[0] = stats->size;\n   }\n}\n\nstatic int ZfsCompressedArcMeter_printRatioString(const Meter* this, char* buffer, size_t size) {\n   if (this->values[0] > 0) {\n      return xSnprintf(buffer, size, \"%.2f:1\", this->total / this->values[0]);\n   }\n\n   return xSnprintf(buffer, size, \"N/A\");\n}\n\nstatic void ZfsCompressedArcMeter_updateValues(Meter* this) {\n   Platform_setZfsCompressedArcValues(this);\n\n   ZfsCompressedArcMeter_printRatioString(this, this->txtBuffer, sizeof(this->txtBuffer));\n}\n\nstatic void ZfsCompressedArcMeter_display(const Object* cast, RichString* out) {\n   const Meter* this = (const Meter*)cast;\n\n   if (this->values[0] > 0) {\n      char buffer[50];\n      int len;\n\n      Meter_humanUnit(buffer, this->total, sizeof(buffer));\n      RichString_appendAscii(out, CRT_colors[METER_VALUE], buffer);\n      RichString_appendAscii(out, CRT_colors[METER_TEXT], \" Uncompressed, \");\n      Meter_humanUnit(buffer, this->values[0], sizeof(buffer));\n      RichString_appendAscii(out, CRT_colors[METER_VALUE], buffer);\n      RichString_appendAscii(out, CRT_colors[METER_TEXT], \" Compressed, \");\n      len = ZfsCompressedArcMeter_printRatioString(this, buffer, sizeof(buffer));\n      RichString_appendnAscii(out, CRT_colors[ZFS_RATIO], buffer, len);\n      RichString_appendAscii(out, CRT_colors[METER_TEXT], \" Ratio\");\n   } else {\n      RichString_writeAscii(out, CRT_colors[METER_TEXT], \" \");\n      RichString_appendAscii(out, CRT_colors[FAILED_READ], \"Compression Unavailable\");\n   }\n}\n\nconst MeterClass ZfsCompressedArcMeter_class = {\n   .super = {\n      .extends = Class(Meter),\n      .delete = Meter_delete,\n      .display = ZfsCompressedArcMeter_display,\n   },\n   .updateValues = ZfsCompressedArcMeter_updateValues,\n   .defaultMode = TEXT_METERMODE,\n   .supportedModes = METERMODE_DEFAULT_SUPPORTED,\n   .maxItems = 1,\n   .isPercentChart = true,\n   .total = 100.0,\n   .attributes = ZfsCompressedArcMeter_attributes,\n   .name = \"ZFSCARC\",\n   .uiName = \"ZFS CARC\",\n   .description = \"ZFS CARC: Compressed ARC statistics\",\n   .caption = \"ARC: \"\n};\n"
  },
  {
    "path": "zfs/ZfsCompressedArcMeter.h",
    "content": "#ifndef HEADER_ZfsCompressedArcMeter\n#define HEADER_ZfsCompressedArcMeter\n/*\nhtop - ZfsCompressedArcMeter.h\n(C) 2004-2011 Hisham H. Muhammad\nReleased under the GNU GPLv2+, see the COPYING file\nin the source distribution for its full text.\n*/\n\n#include \"zfs/ZfsArcStats.h\"\n\n#include \"Meter.h\"\n\n\nvoid ZfsCompressedArcMeter_readStats(Meter* this, const ZfsArcStats* stats);\n\nextern const MeterClass ZfsCompressedArcMeter_class;\n\n#endif\n"
  }
]