#1927·eza

Combining --only-files with --no-symlinks disables all filtering (missing match arm in filter_child_files)

Author: Poojax21Created Aug 26, 2026Updated Aug 26, 2026

Summary

FileFilter::filter_child_files implements its flag combinations as an exhaustive-looking match over a 4-tuple of booleans, but only 6 of the meaningful 16 combinations are covered. In particular, (OnlyFiles + NoSymlinks) falls through to the catch-all _ => true, so adding --no-symlinks to --only-files silently disables --only-files: directories and other non-file entries are listed again.

Static-analysis finding based on main; not executed here.

Location

  • File: src/fs/filter.rs
  • Function: FileFilter::filter_child_files
  • Relevant code:
rust
match (
    self.flags.contains(&OnlyDirs),
    self.flags.contains(&OnlyFiles),
    self.flags.contains(&NoSymlinks),
    self.flags.contains(&ShowSymlinks),
) {
    (true, false, false, false) => f.is_directory(),
    (true, false, true, false) => f.is_directory(),
    (true, false, false, true) => f.is_directory() || f.points_to_directory(),
    (false, true, false, false) => if is_recurse { true } else {f.is_file() },
    (false, true, false, true) => if is_recurse { true } else { f.is_file() || f.is_link() && !f.points_to_directory()
    },
    (false, false, true, false) => !f.is_link(),
    _ => true,
}

Problem

The arms encode pairwise interactions between the four flags, but several reachable combinations have no arm and therefore mean "keep everything":

  • (false, true, true, false) ? --only-files --no-symlinks
  • (true, false, true, true) ? --only-dirs --no-symlinks --show-symlinks
  • (false, false, true, true) ? --no-symlinks --show-symlinks

Nothing upstream rejects these combinations: --only-files and --no-symlinks are independent options in src/options/parser.rs, both get pushed into FileFilterFlags, and filter_child_files is where the semantics are decided.

Trigger / Reproduction

Based on source reading:

bash
$ mkdir demo && cd demo
$ mkdir subdir && touch file.txt && ln -s subdir link
$ eza --only-files          # -> file.txt            (correct)
$ eza --only-files --no-symlinks   # -> dir, file.txt, link   (unexpected)

Expected for the second command: file.txt only - i.e. at least as restrictive as --only-files alone, additionally excluding symlinks.

Actual: the wildcard arm retains everything, so the listing is identical to plain eza. The more restrictive flag combination relaxes the output.

Expected Behavior

--no-symlinks should compose conjunctively with the other filters, e.g. (false, true, true, false) ? keep entries that pass --only-files and are not symlinks.

Actual Behavior

Any unlisted combination returns true for every entry; the restrictive flags cancel each other out.

Impact

Users who combine these documented options get silently wrong listings (directories shown despite --only-files). Because there is no error or warning, scripts relying on the output can misbehave. The same gap affects the --only-dirs --no-symlinks --show-symlinks and --no-symlinks --show-symlinks combinations.

Suggested Direction

Either decompose the match into independent predicates (apply each active flag's constraint and require all of them), or add the missing arms explicitly. A decomposition also removes the risk class entirely: any future flag would otherwise need up to 2^n new arms.

Evidence

  • files.retain uses the match result directly as the predicate (src/fs/filter.rs, filter_child_files).
  • The parser layer performs no conflict checking for these flags (checked src/options/parser.rs definitions of --only-files, --only-dirs, --no-symlinks, --show-symlinks).
  • Rust reports no exhaustiveness problem because the _ arm makes the match total.