Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions xff/engine/run.cc
Original file line number Diff line number Diff line change
Expand Up @@ -376,6 +376,8 @@ class DryRunFileSystem : public vfs::FileSystem {

absl::StatusOr<std::string> FsType(std::string_view path) const override { return fs_.FsType(path); }

absl::StatusOr<bool> IsCaseSensitive(std::string_view path) const override { return fs_.IsCaseSensitive(path); }

absl::StatusOr<std::string> ReadContent(std::string_view path) const override { return fs_.ReadContent(path); }

absl::Status Remove(std::string_view path) const override {
Expand Down
2 changes: 2 additions & 0 deletions xff/engine/run_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -926,6 +926,8 @@ class NoBtimeFs : public vfs::FileSystem {

absl::StatusOr<std::string> FsType(std::string_view) const override { return std::string("fakefs"); }

absl::StatusOr<bool> IsCaseSensitive(std::string_view) const override { return true; }

absl::StatusOr<std::string> ReadContent(std::string_view) const override { return std::string(); }

private:
Expand Down
2 changes: 2 additions & 0 deletions xff/engine/walk_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,8 @@ class FakeFs : public vfs::FileSystem {
return std::string("fakefs");
} // unused by walk tests

absl::StatusOr<bool> IsCaseSensitive(std::string_view) const override { return true; } // unused by walk tests

absl::StatusOr<std::string> ReadContent(std::string_view) const override {
return std::string();
} // unused by walk tests
Expand Down
11 changes: 11 additions & 0 deletions xff/vfs/filesystem.h
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,17 @@ class FileSystem {
// "apfs", "tmpfs", "nfs". An error if `path` cannot be queried (`statfs`).
virtual absl::StatusOr<std::string> FsType(std::string_view path) const = 0;

// Whether name lookups on the volume holding `path` distinguish case: true =
// case-sensitive (ext4 / xfs and most Linux filesystems, where `foo` and `FOO`
// are distinct files), false = case-folding (APFS / HFS+ in their default
// configuration, NTFS, exFAT, where they are the same file). Backs xff's
// FS-native name matching (`--exact` opts out): the default xff style matches
// `-name` the way the filesystem itself would, so a lookup that the OS would
// satisfy case-insensitively also matches here. Returns an error when the
// volume cannot be probed; the caller falls back to the conservative
// case-sensitive (byte-exact) behaviour, which is always find-faithful.
virtual absl::StatusOr<bool> IsCaseSensitive(std::string_view path) const = 0;

// Reads the entire byte content of the regular file at `path` (xff's content
// predicates -content / -icontent / -rxc / -irxc). Returns an error when the
// path cannot be opened or read; content search treats that as a non-match
Expand Down
27 changes: 27 additions & 0 deletions xff/vfs/local_fs.cc
Original file line number Diff line number Diff line change
Expand Up @@ -262,6 +262,33 @@ absl::StatusOr<std::string> LocalFs::FsType(std::string_view path) const {
#endif
}

absl::StatusOr<bool> LocalFs::IsCaseSensitive(std::string_view path) const {
#if defined(_PC_CASE_SENSITIVE)
// macOS / BSD expose the volume's case rule directly: pathconf returns 1 for a
// case-sensitive volume and 0 for a case-folding one. A -1 return is either an
// error (errno set) or an indeterminate answer (errno left at 0); both fall back
// to the conservative case-sensitive default (error propagated; indeterminate
// reported as case-sensitive), which is always find-faithful.
const std::string path_str(path);
errno = 0;
const long rc = ::pathconf(path_str.c_str(), _PC_CASE_SENSITIVE); // NOLINT(google-runtime-int)
if (rc < 0) {
if (errno != 0) {
return absl::ErrnoToStatus(errno, absl::StrCat("pathconf(_PC_CASE_SENSITIVE, '", path, "')"));
}
return true; // indeterminate -> conservative byte-exact
}
return rc != 0;
#else
// Linux / glibc has no _PC_CASE_SENSITIVE. ext4 / xfs / btrfs are case-sensitive
// by default; per-directory case folding (ext4 +F / statx STATX_ATTR_CASEFOLD) is
// a future refinement. Report case-sensitive, which is also the safe default and
// matches find's byte-exact matching.
(void)path;
return true;
#endif
}

absl::StatusOr<std::string> LocalFs::ReadContent(std::string_view path) const {
const std::string path_str(path);
const int fd = ::open(path_str.c_str(), O_RDONLY);
Expand Down
1 change: 1 addition & 0 deletions xff/vfs/local_fs.h
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ class LocalFs final : public FileSystem {
bool Access(std::string_view path, AccessMode mode) const override;
absl::StatusOr<std::string> ReadLink(std::string_view path) const override;
absl::StatusOr<std::string> FsType(std::string_view path) const override;
absl::StatusOr<bool> IsCaseSensitive(std::string_view path) const override;
absl::StatusOr<std::string> ReadContent(std::string_view path) const override;
};

Expand Down
16 changes: 16 additions & 0 deletions xff/vfs/local_fs_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -146,5 +146,21 @@ TEST_F(LocalFsTest, ReadContentMissingPathErrors) {
EXPECT_THAT(local_fs_.ReadContent(Path("nope")), StatusIs(absl::StatusCode::kNotFound));
}

TEST_F(LocalFsTest, IsCaseSensitiveProbesTheVolume) {
// The probe must succeed on a normal directory. The value depends on the volume
// (case-sensitive ext4 vs case-folding APFS), so it is cross-checked against the
// volume's actual behaviour in the next test rather than hard-coded here.
EXPECT_THAT(local_fs_.IsCaseSensitive(root_.string()), IsOk());
}

TEST_F(LocalFsTest, IsCaseSensitiveAgreesWithTheVolumeBehaviour) {
// file.txt exists in lower case; whether the upper-case name resolves to it is
// exactly the volume's own case rule, so the probe must report the matching
// value: a case-folding volume resolves FILE.TXT (not sensitive), a
// case-sensitive one does not (sensitive).
const bool upper_resolves = fs::exists(root_ / "FILE.TXT");
EXPECT_THAT(local_fs_.IsCaseSensitive(root_.string()), IsOkAndHolds(Eq(!upper_resolves)));
}

} // namespace
} // namespace xff::vfs
Loading