Skip to content

feat: Add NTFS MFT Fast Scan - #702

Draft
rankaiyx wants to merge 20 commits into
cboxdoerfer:masterfrom
rankaiyx:feat/ntfs-fast-scan
Draft

feat: Add NTFS MFT Fast Scan#702
rankaiyx wants to merge 20 commits into
cboxdoerfer:masterfrom
rankaiyx:feat/ntfs-fast-scan

Conversation

@rankaiyx

@rankaiyx rankaiyx commented Jun 24, 2026

Copy link
Copy Markdown

Overview

This PR adds NTFS MFT (Master File Table) fast scan support to fsearch. It reads NTFS file systems directly via libntfs-3g API, bypassing slow readdir traversal through FUSE layer.

Goal: Match Everything's indexing speed on Linux. Everything achieves instant search on Windows by reading NTFS MFT directly. This PR brings the same approach to fsearch.

What This Does

NTFS MFT Fast Scan

  • Uses ntfs_mount() and ntfs_mft_record_read() from libntfs-3g
  • Reads block device directly (e.g., /dev/sda1)
  • Much faster than readdir for NTFS partitions
  • ext4/xfs/btrfs still use normal readdir path

Partition Detection

Function fs_detect_ntfs_partitions() detects NTFS partitions by:

  1. Parse /proc/mounts for fuseblk entries (ntfs-3g appears as fuseblk)
  2. Verify each device with blkid -s TYPE to confirm it is NTFS volume
  3. Return intersection of mounted fuseblk + blkid NTFS verification

Also provides fs_path_is_on_ntfs_mount() to check if a given path is on an NTFS mount point.

UI Changes

New NTFS tab in Preferences → Search → Database Preferences:

  • "Enable MFT fast scan" checkbox
  • "Detected NTFS Partitions" label
  • Partition list with Device, Mount Point, Include, Monitor columns
  • Status bar: ntfs_status_libntfs and ntfs_status_root labels

Privilege Architecture

  • pkexec process replacement (not async Polkit API)
  • At startup, before gtk_init():
    • privilege_request_if_needed() checks config for NTFS fast scan setting
    • If enabled and not root, calls execlp("pkexec", exe_path, "--privileged", "--x-display", DISPLAY, NULL)
    • Process replaces itself with root version
    • --privileged flag prevents infinite loop
    • --x-display passes DISPLAY variable through pkexec
  • privilege_restore_xdg_paths(): After elevation, detects original user via PKEXEC_UID/SUDO_UID, restores XDG_CONFIG_HOME/XDG_DATA_HOME to original user's paths
  • strip_privileged_flag() in main.c: Removes --privileged and --x-display from argv so GTK does not complain
  • UI status text (from privilege_get_status_text()):
    • "MFT scan: disabled" — NTFS fast scan disabled in config
    • "MFT scan: enabled (pkexec)" — running as root via pkexec
    • "MFT scan: enabled (sudo)" — running as root via sudo
    • "MFT scan: enabled (root)" — running as root directly
    • "MFT scan: authorization denied" — pkexec available but user denied
    • "MFT scan: pkexec not found" — pkexec not found on system
  • When user enables MFT scan at runtime, dialog says "NTFS MFT fast scan requires root privileges. The feature will take effect after restarting fsearch."

Configuration

[NTFS]
ntfs_fast_scan_enabled=true
partition_1_mountpoint=/mnt/data
partition_1_include=true
partition_1_monitor=true

Files Changed

New Files (7)

  • src/fsearch_privilege.c/h — Privilege module (pkexec re-exec + XDG path restore + status text)
  • src/fsearch_filesystem_detect.c/h — File system type detection
  • src/fsearch_database_scan_ntfs.c/h — NTFS MFT scanner (ported from everything-demo)
  • src/fsearch_database_preferences_widget.ui — NTFS tab UI (187 lines added)

Modified Files (17)

  • src/main.c — Added privilege_request_if_needed(), privilege_restore_xdg_paths(), strip_privileged_flag()
  • src/fsearch_config.c/h — Added ntfs_fast_scan_enabled, FsearchNtfsPartitionConfig, partition config load/save/compare functions
  • src/fsearch_database.c/h — Added NTFS partition config sync, scan skip check
  • src/fsearch_database_index.c — Added ntfs_partitions field, MFT scan integration in fsearch_database_index_scan()
  • src/fsearch_database_index_store.c/h — Added ntfs_partitions threading, setter
  • src/fsearch_database_preferences_widget.c/h — Added NTFS page init, partition list, config get/set, status update
  • src/fsearch_database_file.c/h — Added NTFS partition save/load
  • src/fsearch_database.h — Added NTFS partition config comparison in config_cmp()
  • src/fsearch_preferences_dialog.c — Added NTFS config sync on apply
  • src/meson.build — Added dependency('libntfs-3g', required: true)
  • meson.build — Project-level changes
  • .gitignore — Added local project files

Not Yet Done

1. Periodic Full MFT Scan

Scheduled full MFT scan to correct index drift over time. Currently only scans at startup or when config changes.

2. NTFS Change Monitoring

Use root privilege to set up fanotify/inotify watch tree directly on block device or mount point, for real-time NTFS change detection. Currently relies on FUSE layer monitoring only.

3. libntfs-3g Runtime Dynamic Loading (dlopen)

Currently meson requires libntfs-3g at compile time (required: true). Plan to change to dlopen runtime loading:

  • meson: required: false
  • Use GModule to load libntfs-3g at runtime
  • If library not available, fall back to readdir silently
  • Benefits: Alpine / minimal installs / containers / Flatpak do not need libntfs-3g packaged
  • NTFS MFT scan is speed-up, not core feature, should not block program startup

Testing

  • NTFS test partition: 50MB, 100 test files
  • MFT scan verified: 165/165 records valid, 120 in-use, 3 dirs, 117 files
  • pkexec elevation tested in VM (password-less rule configured)
  • Graceful degradation tested: MFT scan skipped without root, does not block startup

rankaiyx added 17 commits June 25, 2026 11:44
- Add fsearch_privilege.c/h: Polkit integration for root permission request
- Add NTFS config persistence (ntfs_fast_scan_enabled, ntfs_auto_polkit)
- Wire up Polkit authorization on NTFS preferences widget toggle
- Add Polkit policy file for io.github.cboxdoerfer.FSearch.ntfs-scan
- Use org.freedesktop.policykit.exec as action (custom action pending)
- Add polkit-gobject-1 as a build dependency
- Track authorization state at application level (privilege_is_authorized)
- Skip Polkit request if already authorized (fixes repeated dialogs)
- Request authorization on startup with 500ms delay (dialog appears on top)
- Initialize widget status from application-level state
- Protect against NULL callback in on_authorize_finish
- Added fsearch_filesystem_detect.c/h: parses /etc/fstab and /proc/mounts
  to detect ntfs-3g mounted partitions (strict fstype matching)
- Added partition list population in NTFS preferences widget
- Fixed double-free issue with g_ptr_array_new_with_free_func
Add fsearch_database_scan_ntfs.c/h for direct MFT scanning via libntfs-3g.
This bypasses the FUSE layer to index NTFS volumes significantly faster.

Two-phase scan approach:
  Phase 1: Walk all MFT records, extract FILE_NAME_ATTR (name, parent_mft,
           data_size, last_data_change_time, file_attributes)
  Phase 2: Create FsearchDatabaseEntry objects and build parent tree

- Skip NTFS system files (mft_no < 12 and $Extend children)
- Support exclude manager filtering
- Support GCancellable for cancellation
- Add libntfs-3g as a required build dependency
Replace ntfs_mft_record_read() with ntfs_mft_records_read() to fetch
1024 records at a time (~1MB batch). This reduces system call overhead
and improves sequential I/O on mechanical HDDs where the MFT is typically
stored contiguously.

Extract process_mft_record() as a standalone helper function.
Like Everything, index all files during MFT scan and apply
exclusions at search time rather than during indexing. This
simplifies the scan module and avoids O(n*depth) path building
for exclusion checks.
Add NTFS partition configuration persistence to FsearchConfig:
- FsearchNtfsPartitionConfig struct (mountpoint, include, monitor)
- Save/load NTFS partitions in config file [NTFS] section
- fsearch_ntfs_get_partition_config() lookup helper

Integrate MFT scan into fsearch_database_index_scan:
- Check if include path matches an NTFS partition with include=true
- Call db_scan_ntfs() for MFT scanning, fall back to readdir
- fsearch_database_index_set_ntfs_partitions() to pass config

Add fs_path_is_on_ntfs_mount() to resolve mount point for any path.
Pass FsearchConfig.ntfs_partitions from the application layer down
to each FsearchDatabaseIndex, enabling MFT fast scan to actually
trigger during index creation and rescan.

Changes:
- FsearchDatabaseIndexStore: add ntfs_partitions field, pass to
  each index in start() and create_index_for_rescan()
- FsearchDatabase: add ntfs_partitions field, pass to all store
  creation sites (scan, rescan, rescan_sync, load)
- fsearch_database_file_load: accept ntfs_partitions parameter
- fsearch_database_rescan_blocking: accept ntfs_partitions parameter
- fsearch.c: pass config->ntfs_partitions to database creation
- CLI mode (database_scan_in_local_instance): load config and pass
  ntfs_partitions for command-line database updates
NTFS partitions with include==true are now scanned automatically
without needing to be added to the Database include folder list.

This matches Everything's behavior: NTFS whole-disk scanning is a
first-class feature independent of manual folder selection.

Implementation: fsearch_database_index_store_start() now iterates
ntfs_partitions after regular includes, creating indices for those
with include==true that aren't already in the include list.
The root entry was added to the folders array and referenced by child
entries as their parent, but ntfs_scan_context_free() also freed it.
This caused use-after-free when the sort comparison function traversed
parent pointers.

Fix: clear ctx->root_entry before calling ntfs_scan_context_free() so
the entry remains valid for the caller's DynamicArray.
…lignment

sizeof(MFT_RECORD) is 48 bytes (header only) but actual record size
is vol->mft_record_size (typically 1024 bytes). ntfs_mft_records_read
writes records at 1024-byte boundaries, but batch[i] indexed at 48-byte
strides, causing all records except the first in each batch to read
from wrong offsets.

Also move system file skip (MFT 0-11) before used_records counting
and remove redundant is_ntfs_system_file call.
- Add fsearch_ntfs_partition_configs_equal() to compare NTFS configs
- Add ntfs_partitions comparison to config_cmp() so NTFS changes
  trigger database_config_changed
- Add fsearch_database_set_ntfs_partitions() to update database's
  ntfs_partitions pointer without touching the current store
- Add fsearch_database_index_store_set_ntfs_partitions() setter
- Update database_scan skip check to also compare ntfs_partitions,
  preventing skipped scans when only NTFS config changed
- Set ntfs_partitions in on_preferences_dialog_response before
  queuing scan work, so live config changes take effect immediately
- Remove per-file debug logs from NTFS MFT scanner
…FS detection

- Remove parse_fstab() and fstab dependency
- Add verify_ntfs_with_blkid() to confirm NTFS volume type
- fs_detect_ntfs_partitions() now scans /proc/mounts for fuseblk
  entries, then verifies each with blkid TYPE=ntfs
- Supports removable NTFS devices not configured in fstab
- Update header comments to reflect new detection approach
- Remove NTFS awareness from FsearchDatabaseIndex (pure readdir path)
  - Delete ntfs_partitions member and set_ntfs_partitions()
  - Remove MFT scan branch from fsearch_database_index_scan()
- Elevate NTFS MFT scan to FsearchDatabaseIndexStore
  - Store layer iterates NTFS partition configs independently
  - MFT scan results injected via fsearch_database_index_new_with_content()
  - Remove "already_included" check (overlap is user responsibility)
- Two paths now fully independent:
  - NTFS MFT: direct block device read, no exclude filtering
  - readdir: include folder recursion with real-time exclude filtering
- Results merged at Store level into unified database
@rankaiyx
rankaiyx force-pushed the feat/ntfs-fast-scan branch from 5d60c1e to e1d3363 Compare June 25, 2026 05:21
rankaiyx added 3 commits June 25, 2026 14:27
The index store held a non-owning pointer to ntfs_partitions, which
became dangling when the old config was freed (e.g., on preferences
dialog close). The database worker thread could then read freed memory
in fsearch_ntfs_partition_configs_equal, causing heap-use-after-free.

Fix: make the store own its ntfs_partitions reference via
g_ptr_array_ref/unref, matching the pattern used for include_manager
and exclude_manager.
…tion

pkexec clears environment variables for security, including DISPLAY and
XAUTHORITY. DISPLAY was restored via --x-display CLI argument, but
XAUTHORITY (the X11 authentication cookie file path) was missing,
causing 'Authorization required' errors when the elevated process
tried to connect to the X server.

Add --x-authority CLI argument to pass the original user's XAUTHORITY
path to the elevated process, falling back to ~/.Xauthority if the
environment variable is not set.
When FSearch is started via sudo or as root directly, the NTFS MFT
scan would proceed regardless of the ntfs_fast_scan_enabled config
flag, since the privilege module skips elevation checks for root.

Add ntfs_fast_scan_enabled to FsearchDatabase and use a helper to
return NULL for ntfs_partitions when the feature is disabled, ensuring
the store never scans NTFS partitions against the user's configuration.
@derickso

derickso commented Aug 6, 2026

Copy link
Copy Markdown

Is this still being worked on?

@cboxdoerfer

Copy link
Copy Markdown
Owner

I didn't have any time yet to look at the code, but there are some issues with the design which need to be fixed.

  1. The whole app shouldn't be running as root, if root is only needed to parse a file
  2. This needs to be working with Wayland compositors as well, so no dependency on X11

@rankaiyx

rankaiyx commented Aug 7, 2026

Copy link
Copy Markdown
Author

Sorry, I got distracted by other fun stuff lately—but I’m back to working on this now.
Also, congrats on the release of version 0.3.0/0.3.1! That means fewer conflicts going forward.

About “This needs to work with Wayland compositors too—no X11 dependency”:
I'll fix that.

About “The whole app shouldn’t run as root if root is only needed to read one file”:
I thought about using a small helper tool with higher permissions. But here’s why I didn’t:

  1. With root, fanotify can watch a whole mount point without adding folders one by one. That’s faster for huge file systems.
  2. If we use a helper, it still reads all files—even root-only ones on NTFS. Then the main app (running normally) might show files it can’t open. Awkward!
  3. FSearch doesn’t listen on network ports, so remote attacks aren’t a risk.
  4. Root isn’t needed unless you use NTFS or want fanotify’s speed. Users can choose what matters most: features, speed, or safety.
  5. One app is simpler to build and maintain than two parts.
  6. On Windows, Everything either runs with higher rights or as a system service.

Still, if you’d rather avoid giving the whole app root access, we can switch to the helper approach—or find a better middle ground.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants