Skip to content

Commit df0aeea

Browse files
MichaelCuevasmeta-codesync[bot]
authored andcommitted
Pass Sapling child ACL metadata through backingstore
Summary: Use the existing parent tree child ACL metadata when building Eden TreeEntries from Sapling backingstore. Directory entries whose parent marks them as ACL roots get hasACL=true; other entries get hasACL=false. Reuse the same child ACL list for permission-denied checks so restriction handling stays aligned with the child ACL metadata. If child ACL metadata cannot be read, fail open by logging the error and treating children as unrestricted instead of failing tree conversion. This does not change IndexedLog/cache state; the parent positive child ACL metadata was already cached. Reviewed By: muirdm Differential Revision: D104246342 fbshipit-source-id: 19d21facf4b0911f4c77bda4ecd87b373caa6bd9
1 parent a57dd3c commit df0aeea

9 files changed

Lines changed: 138 additions & 38 deletions

File tree

‎eden/fs/store/FilteredBackingStore.cpp‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -351,7 +351,8 @@ FilteredBackingStore::co_filterImpl(
351351
entry->second.getSize(),
352352
entry->second.getContentSha1(),
353353
entry->second.getContentBlake3(),
354-
entry->second.isRestricted()};
354+
entry->second.isRestricted(),
355+
entry->second.hasACL()};
355356
pathMap.insert(std::pair{relPath.basename().copy(), std::move(treeEntry)});
356357
}
357358

‎eden/fs/store/FilteredBackingStore.h‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,7 @@ class FilteredBackingStore
158158
FRIEND_TEST(
159159
FakeSubstringFilteredBackingStoreTest,
160160
restrictedTreePreservedAfterFiltering);
161+
FRIEND_TEST(FakeSubstringFilteredBackingStoreTest, treeEntryHasAclPreserved);
161162

162163
ImmediateFuture<GetRootTreeResult> getRootTree(
163164
const RootId& rootId,

‎eden/fs/store/git/GitBackingStore.cpp‎

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -222,9 +222,15 @@ TreePtr GitBackingStore::getTreeImpl(const ObjectId& id) {
222222
}
223223
auto entryHash = oid2Hash(git_tree_entry_id(gitEntry));
224224
auto name = PathComponentPiece{entryName};
225-
entries.emplace(name, std::move(entryHash), fileType);
225+
entries.emplace(
226+
name,
227+
std::move(entryHash),
228+
fileType,
229+
/*isRestricted=*/false,
230+
/*hasACL=*/false);
226231
}
227-
return std::make_shared<TreePtr::element_type>(std::move(entries), id);
232+
return std::make_shared<TreePtr::element_type>(
233+
std::move(entries), id, AclRootState::NoAcl);
228234
}
229235

230236
folly::coro::now_task<BackingStore::GetTreeAuxResult>

‎eden/fs/store/test/FilteredBackingStoreTest.cpp‎

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -479,6 +479,30 @@ TEST_F(FakeSubstringFilteredBackingStoreTest, getTree) {
479479
EXPECT_EQ(treeOID, std::move(future5).get(0ms).tree->getObjectId());
480480
}
481481

482+
TEST_F(FakeSubstringFilteredBackingStoreTest, treeEntryHasAclPreserved) {
483+
Tree::container aclChildEntries{kPathMapDefaultCaseSensitive};
484+
auto aclChildTree = Tree{
485+
std::move(aclChildEntries), makeTestId("3004"), AclRootState::AclRoot};
486+
auto rootId = makeTestId("3003");
487+
auto filteredRootId =
488+
FilteredObjectId(RelativePath{""}, kTestFilter1, rootId);
489+
auto* rootDir = wrappedStore_->putTree(rootId, {{"acl_dir", aclChildTree}});
490+
if (rootDir == nullptr) {
491+
ADD_FAILURE() << "failed to create root tree";
492+
return;
493+
}
494+
495+
auto future = filteredStore_->getTree(
496+
ObjectId{filteredRootId.getValue()},
497+
ObjectFetchContext::getNullContext());
498+
rootDir->trigger();
499+
auto filteredTree = std::move(future).get(0ms).tree;
500+
auto aclDir = filteredTree->find("acl_dir"_pc);
501+
502+
ASSERT_NE(aclDir, filteredTree->cend());
503+
EXPECT_EQ(aclDir->second.hasACL(), std::optional<bool>{true});
504+
}
505+
482506
TEST_F(FakeSubstringFilteredBackingStoreTest, getRootTree) {
483507
// Set up one commit with a root tree
484508
auto dir1Id = makeTestId("abc");

‎eden/fs/testharness/FakeBackingStore.cpp‎

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -378,7 +378,11 @@ FakeBackingStore::TreeEntryData::TreeEntryData(
378378
const Tree& tree)
379379
: entry{
380380
PathComponent{name},
381-
TreeEntry{ObjectId{tree.getObjectId()}, TreeEntryType::TREE}} {}
381+
TreeEntry{
382+
ObjectId{tree.getObjectId()},
383+
TreeEntryType::TREE,
384+
tree.isRestricted(),
385+
tree.hasACL()}} {}
382386

383387
FakeBackingStore::TreeEntryData::TreeEntryData(
384388
folly::StringPiece name,
@@ -387,7 +391,9 @@ FakeBackingStore::TreeEntryData::TreeEntryData(
387391
PathComponent{name},
388392
TreeEntry{
389393
ObjectId{tree->get().getObjectId()},
390-
TreeEntryType::TREE}} {}
394+
TreeEntryType::TREE,
395+
tree->get().isRestricted(),
396+
tree->get().hasACL()}} {}
391397

392398
StoredTree* FakeBackingStore::putTree(
393399
const std::initializer_list<TreeEntryData>& entryArgs) {

‎eden/scm/lib/backingstore/include/ffi.h‎

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,9 @@
1111
#include <folly/container/FBVector.h>
1212
#include <folly/futures/Future.h>
1313
#include <rust/cxx.h>
14+
#include <cstdint>
1415
#include <optional>
16+
#include <stdexcept>
1517
#include <string_view>
1618
#include <utility>
1719

@@ -127,7 +129,8 @@ class TreeBuilder {
127129
rust::Str name,
128130
const std::array<uint8_t, 20>& hg_node,
129131
facebook::eden::TreeEntryType ttype,
130-
bool is_restricted);
132+
bool is_restricted,
133+
bool has_acl);
131134

132135
// Add tree entry with aux data.
133136
void add_entry_with_aux_data(
@@ -137,7 +140,8 @@ class TreeBuilder {
137140
const uint64_t size,
138141
const std::array<uint8_t, 20>& sha1,
139142
const std::array<uint8_t, 32>& blake3,
140-
bool is_restricted);
143+
bool is_restricted,
144+
bool has_acl);
141145

142146
// Set aux data for tree itself (if available).
143147
void set_aux_data(const std::array<uint8_t, 32>& digest, uint64_t size);

‎eden/scm/lib/backingstore/src/ffi.cpp‎

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -92,16 +92,14 @@ void TreeBuilder::add_entry(
9292
rust::Str name,
9393
const std::array<uint8_t, 20>& hg_node,
9494
facebook::eden::TreeEntryType ttype,
95-
bool is_restricted) {
95+
bool is_restricted,
96+
bool has_acl) {
9697
emplace_entry(
9798
name,
9899
facebook::eden::TreeEntry{
99100
make_entry_oid(hg_node, name),
100101
ttype,
101-
std::nullopt,
102-
std::nullopt,
103-
std::nullopt,
104-
is_restricted,
102+
facebook::eden::makeAclRootState(is_restricted, has_acl),
105103
});
106104
}
107105

@@ -112,7 +110,8 @@ void TreeBuilder::add_entry_with_aux_data(
112110
const uint64_t size,
113111
const std::array<uint8_t, 20>& sha1,
114112
const std::array<uint8_t, 32>& blake3,
115-
bool is_restricted) {
113+
bool is_restricted,
114+
bool has_acl) {
116115
emplace_entry(
117116
name,
118117
facebook::eden::TreeEntry{
@@ -121,7 +120,7 @@ void TreeBuilder::add_entry_with_aux_data(
121120
size,
122121
std::optional<facebook::eden::Hash20>(sha1),
123122
std::optional<facebook::eden::Hash32>(blake3),
124-
is_restricted,
123+
facebook::eden::makeAclRootState(is_restricted, has_acl),
125124
});
126125
}
127126

‎eden/scm/lib/backingstore/src/ffi.rs‎

Lines changed: 44 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ use storemodel::TreeItemFlag;
2525
use types::FetchContext;
2626
use types::HgId;
2727
use types::Key;
28+
use types::PathComponentBuf;
2829
use types::RepoPath;
2930
use types::fetch_cause::FetchCause;
3031
use types::fetch_mode::FetchMode;
@@ -206,6 +207,7 @@ pub(crate) mod ffi {
206207
hg_node: &[u8; 20],
207208
ttype: TreeEntryType,
208209
is_restricted: bool,
210+
has_acl: bool,
209211
);
210212

211213
fn add_entry_with_aux_data(
@@ -217,6 +219,7 @@ pub(crate) mod ffi {
217219
sha1: &[u8; 20],
218220
blake3: &[u8; 32],
219221
is_restricted: bool,
222+
has_acl: bool,
220223
);
221224

222225
fn mark_missing(self: Pin<&mut TreeBuilder>);
@@ -510,48 +513,63 @@ pub fn sapling_backingstore_get_tree(
510513
ffi::GetTreeResult { error }
511514
}
512515

513-
/// Build a set of restricted child directory HgIds from the tree's
514-
/// permission_denied_children(). Fail-open: if the initial call or
515-
/// individual entries error, log a warning and treat as unrestricted.
516-
fn build_restricted_set(tree: &dyn TreeEntry) -> HashSet<HgId> {
517-
let iter = match tree.permission_denied_children() {
516+
/// Build a set of restricted child directory HgIds from known ACL children.
517+
/// Fail-open: if the initial check or individual entries error, log a warning
518+
/// and treat as unrestricted.
519+
fn build_restricted_set(
520+
tree: &dyn TreeEntry,
521+
children_with_acl: Vec<(PathComponentBuf, HgId)>,
522+
) -> HashSet<HgId> {
523+
let iter = match tree.filter_permission_denied(children_with_acl) {
518524
Ok(iter) => iter,
519525
Err(e) => {
520526
tracing::warn!(
521527
?e,
522-
"error calling permission_denied_children; treating all children as unrestricted (fail-open)"
528+
"error checking restricted children; treating all children as unrestricted (fail-open)"
523529
);
524530
return HashSet::new();
525531
}
526532
};
527-
iter.filter_map(|r| match r {
533+
534+
iter.filter_map(|entry| match entry {
528535
Ok((_, hgid, _)) => Some(hgid),
529536
Err(e) => {
530-
tracing::warn!(?e, "error checking permission_denied_children");
537+
tracing::warn!(
538+
?e,
539+
"error checking restricted child; treating child as unrestricted (fail-open)"
540+
);
531541
None
532542
}
533543
})
534544
.collect()
535545
}
536546

547+
fn build_acl_sets(tree: &dyn TreeEntry) -> (HashSet<HgId>, HashSet<HgId>) {
548+
let children_with_acl = match tree.children_with_acls() {
549+
Ok(children) => children,
550+
Err(e) => {
551+
tracing::warn!(
552+
?e,
553+
"error reading child ACL metadata; treating all children as unrestricted (fail-open)"
554+
);
555+
return (HashSet::new(), HashSet::new());
556+
}
557+
};
558+
let child_has_acl = children_with_acl.iter().map(|(_, hgid)| *hgid).collect();
559+
let restricted_set = build_restricted_set(tree, children_with_acl);
560+
(child_has_acl, restricted_set)
561+
}
562+
537563
// Convert the `TreeEntry` trait object into an EdenFS Tree by adding each entry to the TreeBuilder
538564
// object.
539565
fn add_tree_to_builder(
540566
mut builder: Pin<&mut ffi::TreeBuilder>,
541567
tree: Arc<dyn TreeEntry>,
542568
) -> anyhow::Result<()> {
543-
// TODO: Make the aux data available in `TreeEntry::iter()` so we don't have to do this HashMap business.
544569
let aux_map: HashMap<HgId, ScmStoreFileAuxData> =
545570
tree.file_aux_iter()?.collect::<anyhow::Result<_>>()?;
546571

547-
// Build a set of child directory IDs that the server denied access to
548-
// (path ACL restriction). These are directories containing .slacl files.
549-
//
550-
// Naming mapping (B2): The Sapling layer uses "has_acl" / "permission_denied"
551-
// to describe directories with access restrictions. EdenFS translates this
552-
// to "is_restricted" / "isRestricted" to describe the access-denied behavior
553-
// from the user's perspective.
554-
let restricted_set: HashSet<HgId> = build_restricted_set(tree.as_ref());
572+
let (child_has_acl, restricted_set) = build_acl_sets(tree.as_ref());
555573

556574
// Pre-allocate the per-entry storage.
557575
if let Some(hint) = tree.size_hint() {
@@ -575,6 +593,7 @@ fn add_tree_to_builder(
575593

576594
let is_restricted =
577595
matches!(flag, TreeItemFlag::Directory) && restricted_set.contains(&node);
596+
let has_acl = matches!(flag, TreeItemFlag::Directory) && child_has_acl.contains(&node);
578597

579598
if let Some(aux) = aux_map.get(&node) {
580599
builder.as_mut().add_entry_with_aux_data(
@@ -585,11 +604,16 @@ fn add_tree_to_builder(
585604
aux.sha1.as_byte_array(),
586605
aux.blake3.as_byte_array(),
587606
is_restricted,
607+
has_acl,
588608
);
589609
} else {
590-
builder
591-
.as_mut()
592-
.add_entry(name.as_str(), node.as_byte_array(), ttype, is_restricted);
610+
builder.as_mut().add_entry(
611+
name.as_str(),
612+
node.as_byte_array(),
613+
ttype,
614+
is_restricted,
615+
has_acl,
616+
);
593617
}
594618
}
595619

‎eden/scm/lib/backingstore/test/TreeBuilderTest.cpp‎

Lines changed: 39 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,14 @@ TEST_F(TreeBuilderTest, AddFileEntry) {
7373
0xf7, 0xf8, 0xf9, 0xfa, 0xfb, 0xfc, 0xfd, 0xfe, 0xff, 0x00};
7474

7575
builder.add_entry_with_aux_data(
76-
fileName, hgNode, entryType, fileSize, sha1Hash, blake3Hash, false);
76+
fileName,
77+
hgNode,
78+
entryType,
79+
fileSize,
80+
sha1Hash,
81+
blake3Hash,
82+
false,
83+
false);
7784

7885
EXPECT_EQ(builder.num_files(), 1);
7986
EXPECT_EQ(builder.num_dirs(), 0);
@@ -107,7 +114,7 @@ TEST_F(TreeBuilderTest, AddDirectoryEntry) {
107114
TreeEntryType entryType = TreeEntryType::TREE;
108115

109116
// Add the directory entry (we don't currently support dir aux data):
110-
builder.add_entry(dirName, hgNode, entryType, false);
117+
builder.add_entry(dirName, hgNode, entryType, false, true);
111118

112119
EXPECT_EQ(builder.num_files(), 0);
113120
EXPECT_EQ(builder.num_dirs(), 1);
@@ -118,12 +125,39 @@ TEST_F(TreeBuilderTest, AddDirectoryEntry) {
118125
auto entry = tree->find(PathComponentPiece{"test_dir"})->second;
119126

120127
EXPECT_EQ(entry.getType(), entryType);
128+
EXPECT_EQ(entry.aclRootState(), AclRootState::AclRoot);
129+
EXPECT_EQ(entry.hasACL(), true);
121130

122131
SlOid parsedOid = facebook::eden::SlOid{entry.getObjectId()};
123132
EXPECT_EQ(parsedOid.node().getBytes(), folly::ByteRange{hgNode});
124133
EXPECT_EQ(parsedOid.path(), path_ + PathComponentPiece{"test_dir"});
125134
}
126135

136+
TEST_F(TreeBuilderTest, AddDirectoryAclRootStates) {
137+
TreeBuilder builder(oid_, caseSensitive_, objectIdFormat_);
138+
139+
std::array<uint8_t, 20> noAclNode = {0x01};
140+
std::array<uint8_t, 20> permittedNode = {0x02};
141+
std::array<uint8_t, 20> restrictedNode = {0x03};
142+
143+
builder.add_entry("no_acl", noAclNode, TreeEntryType::TREE, false, false);
144+
builder.add_entry(
145+
"permitted", permittedNode, TreeEntryType::TREE, false, true);
146+
builder.add_entry(
147+
"restricted", restrictedNode, TreeEntryType::TREE, true, true);
148+
149+
auto tree = builder.build();
150+
EXPECT_EQ(
151+
tree->find(PathComponentPiece{"no_acl"})->second.aclRootState(),
152+
AclRootState::NoAcl);
153+
EXPECT_EQ(
154+
tree->find(PathComponentPiece{"permitted"})->second.aclRootState(),
155+
AclRootState::AclRoot);
156+
EXPECT_EQ(
157+
tree->find(PathComponentPiece{"restricted"})->second.aclRootState(),
158+
AclRootState::RestrictedAclRoot);
159+
}
160+
127161
TEST_F(TreeBuilderTest, AddMultipleEntries) {
128162
TreeBuilder builder(oid_, caseSensitive_, objectIdFormat_);
129163

@@ -134,7 +168,8 @@ TEST_F(TreeBuilderTest, AddMultipleEntries) {
134168
std::array<uint8_t, 20> hgNode = {0};
135169
hgNode[0] = static_cast<uint8_t>(i);
136170

137-
builder.add_entry(fileNameStr, hgNode, TreeEntryType::REGULAR_FILE, false);
171+
builder.add_entry(
172+
fileNameStr, hgNode, TreeEntryType::REGULAR_FILE, false, false);
138173
}
139174

140175
// Add multiple directory entries
@@ -144,7 +179,7 @@ TEST_F(TreeBuilderTest, AddMultipleEntries) {
144179
std::array<uint8_t, 20> hgNode = {0};
145180
hgNode[1] = static_cast<uint8_t>(i);
146181

147-
builder.add_entry(dirNameStr, hgNode, TreeEntryType::TREE, false);
182+
builder.add_entry(dirNameStr, hgNode, TreeEntryType::TREE, false, false);
148183
}
149184

150185
EXPECT_EQ(builder.num_files(), 3);

0 commit comments

Comments
 (0)