From 0aa523628ae1140411a3cf6b3ac8b789db4ddea0 Mon Sep 17 00:00:00 2001 From: Jesse Geens Date: Thu, 20 Aug 2026 11:57:28 +0200 Subject: [PATCH 01/10] implement ACLTree, proof-of-concept for namespace dump --- pkg/reconciliation/.gitignore | 1 + pkg/reconciliation/acl_tree.go | 204 +++++++++ pkg/reconciliation/acl_tree_test.go | 594 +++++++++++++++++++++++++++ pkg/reconciliation/deep.go | 152 +++++++ pkg/reconciliation/ns_dump.go | 145 +++++++ pkg/reconciliation/reconciliation.go | 26 ++ pkg/reconciliation/shallow.go | 23 -- 7 files changed, 1122 insertions(+), 23 deletions(-) create mode 100644 pkg/reconciliation/.gitignore create mode 100644 pkg/reconciliation/acl_tree.go create mode 100644 pkg/reconciliation/acl_tree_test.go create mode 100644 pkg/reconciliation/deep.go create mode 100644 pkg/reconciliation/ns_dump.go diff --git a/pkg/reconciliation/.gitignore b/pkg/reconciliation/.gitignore new file mode 100644 index 0000000000..94a2dd146a --- /dev/null +++ b/pkg/reconciliation/.gitignore @@ -0,0 +1 @@ +*.json \ No newline at end of file diff --git a/pkg/reconciliation/acl_tree.go b/pkg/reconciliation/acl_tree.go new file mode 100644 index 0000000000..891d923870 --- /dev/null +++ b/pkg/reconciliation/acl_tree.go @@ -0,0 +1,204 @@ +package reconciliation + +import ( + "path" + "slices" + + "github.com/cs3org/reva/v3/pkg/spaces" + "github.com/cs3org/reva/v3/pkg/storage/utils/acl" +) + +// TODO(jgeens): +// - there is no need to keep the "root ACLs" in every node, +// we can just append them at the end +// - for "wide" trees, we could sort the children and do a binary search +// over them instead of iterating over all children +// - we should guard `Insert` agains concurrent Inserts + +// An ACL Tree represents the tree of ACLs in a space. +// ACLTree's do not *require* in-order insertion, but note that pre-sorting the paths +// that will be inserted (so that parents are always inserted before their children) +// significantly improves its performance. +// +// e.g.: +// BenchmarkInsert/1365_nodes/top_down 10117 µs 1370 KB 16204 allocs +// BenchmarkInsert/1365_nodes/deepest_first 505674 µs 84681 KB 1427558 allocs +type ACLTree struct { + SpaceType spaces.SpaceType + ACLNode +} + +type ACLNode struct { + Path string + MandatoryACLs []acl.Entry + AllowedACLs []acl.Entry + Children []*ACLNode +} + +func NewACLTree() *ACLTree { + return &ACLTree{} +} + +func (n *ACLNode) Insert(i *ACLNode) bool { + i.Path = path.Clean(i.Path) + return n.insert(i) +} + +// The internal-only insert does not clean i's path, so we only need to do this once +// and not again for all its children +func (n *ACLNode) insert(i *ACLNode) bool { + // Node n cannot be a parent of node i + if !n.Matches(i.Path) { + return false + } + + // First, check if any of the children match + for _, c := range n.Children { + if ok := c.insert(i); ok { + return true + } + } + + // Node i matches under Node n but not any of its children. + // That means: + // - if it is under a different path, it becomes a new child + // - if it has the same path, we need to merge the two + + if n.MatchesExact(i.Path) { + // Two nodes are for the same path, so we need to merge + n.ApplyACLs(i.MandatoryACLs, i.AllowedACLs) + n.Children = append(n.Children, i.Children...) + } else { + // Child + // In this case, we should still merge! ACLs are set recursively + // So, we apply n's ACLs to i and then insert it + i.ApplyACLs(n.MandatoryACLs, n.AllowedACLs) + + // Nodes are not guaranteed to be inserted in order, so we may need to insert + // i into any of the children of n instead of in n itself. + var stay, move []*ACLNode + for _, c := range n.Children { + if i.Matches(c.Path) { + // Child, should be removed from n and added to i + // i will run the same check again for its children + move = append(move, c) + } else { + stay = append(stay, c) + } + } + // We need to insert - not append - children, becaus + for _, c := range move { + i.insert(c) + } + n.Children = append(stay, i) + + } + return true +} + +// Return true if this node or any of its children +// match the given path +func (n *ACLNode) Matches(p string) bool { + if p == n.Path { + return true + } + if p == "/" { + return true + } + return fastHasPrefix(p, n.Path) +} + +// faster implementation of the previous check: +// +// `_, ok := strings.CutPrefix(p, n.Path+"/")` +func fastHasPrefix(s, prefix string) bool { + return len(s) > len(prefix) && s[len(prefix)] == '/' && s[:len(prefix)] == prefix +} + +// Return true if the node's path is `p` +func (n *ACLNode) MatchesExact(p string) bool { + return p == n.Path +} + +func (n *ACLNode) Find(p string) (MandatoryACLs, AllowedACLs []acl.Entry, ok bool) { + return n.find(path.Clean(p)) +} + +// The internal-only find does not clean the path p, so we only need to do this once +// and not for every visited node +func (n *ACLNode) find(p string) (MandatoryACLs, AllowedACLs []acl.Entry, ok bool) { + if !n.Matches(p) { + return nil, nil, false + } + + for _, c := range n.Children { + m, a, ok := c.find(p) + if ok { + return m, a, true + } + } + + return n.MandatoryACLs, n.AllowedACLs, true +} + +func (n *ACLNode) ApplyACLs(mandatory, optional []acl.Entry) { + n.MandatoryACLs = mergeACLs(n.MandatoryACLs, mandatory) + n.AllowedACLs = mergeACLs(n.AllowedACLs, optional) + for _, c := range n.Children { + c.ApplyACLs(mandatory, optional) + } +} + +func mergeACLs(a, b []acl.Entry) []acl.Entry { + resultSet := slices.Clone(a) + for _, e := range b { + + // Now we need to check: is there already + // an entry in a for this qualifier + type? + foundMatch := false + for i, c := range resultSet { + if e.Qualifier == c.Qualifier && e.Type == c.Type { + // ACL for the same user: highest permission wins + resultSet[i].Permissions = highestPermission(e.Permissions, c.Permissions) + foundMatch = true + break + } + } + + // Otherwise, no ACL for this user in the resultSet yet, + // so we add it + if !foundMatch { + resultSet = append(resultSet, e) + } + } + + return resultSet +} + +type Permission int + +const ( + permissionNone Permission = iota + + permissionRead + permissionWrite + permissionDeny +) + +var aclPermissions = map[string]Permission{ + "rx": permissionRead, + "rwx": permissionWrite, + "!r!w!x": permissionDeny, // TODO: verify exact ACL for deny +} + +func highestPermission(a, b string) string { + pa, pb := aclPermissions[a], aclPermissions[b] + switch { + case pa == permissionNone && pb == permissionNone: + return a + case pa >= pb: + return a + default: + return b + } +} diff --git a/pkg/reconciliation/acl_tree_test.go b/pkg/reconciliation/acl_tree_test.go new file mode 100644 index 0000000000..07be14a3f2 --- /dev/null +++ b/pkg/reconciliation/acl_tree_test.go @@ -0,0 +1,594 @@ +// Copyright 2018-2026 CERN +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// In applying this license, CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +package reconciliation + +import ( + "fmt" + "slices" + "strings" + "sync" + "testing" + + "github.com/cs3org/reva/v3/pkg/storage/utils/acl" +) + +// sameEntries reports whether got and want hold the same ACL entries. The tree +// gives no order guarantee, so both sides are sorted before the comparison. +func sameEntries(got, want []acl.Entry) bool { + cmp := func(a, b acl.Entry) int { + if c := strings.Compare(a.Type, b.Type); c != 0 { + return c + } + if c := strings.Compare(a.Qualifier, b.Qualifier); c != 0 { + return c + } + return strings.Compare(a.Permissions, b.Permissions) + } + g, w := slices.Clone(got), slices.Clone(want) + slices.SortFunc(g, cmp) + slices.SortFunc(w, cmp) + return slices.Equal(g, w) +} + +// checkFind looks up p and compares both ACL sets of the node that applies. +func checkFind(t *testing.T, tree *ACLTree, p string, wantMandatory, wantAllowed []acl.Entry) { + t.Helper() + mandatory, allowed, ok := tree.Find(p) + if !ok { + t.Fatalf("Find(%q): ok = false, want true", p) + } + if !sameEntries(mandatory, wantMandatory) { + t.Errorf("Find(%q): mandatory = %v, want %v", p, mandatory, wantMandatory) + } + if !sameEntries(allowed, wantAllowed) { + t.Errorf("Find(%q): allowed = %v, want %v", p, allowed, wantAllowed) + } +} + +var ( + aliceRead = acl.Entry{Type: acl.TypeUser, Qualifier: "alice", Permissions: "rx"} + aliceWrite = acl.Entry{Type: acl.TypeUser, Qualifier: "alice", Permissions: "rwx"} + bobRead = acl.Entry{Type: acl.TypeUser, Qualifier: "bob", Permissions: "rx"} + groupRead = acl.Entry{Type: acl.TypeGroup, Qualifier: "cernbox-admins", Permissions: "rx"} + external = acl.Entry{Type: acl.TypeUser, Qualifier: "cboxexternal", Permissions: "rwx"} +) + +// An empty tree has no rule, so every path is found with no ACL at all. +func TestFindOnEmptyTree(t *testing.T) { + tree := NewACLTree() + checkFind(t, tree, "/eos/project/c/cernbox/some/file", nil, nil) +} + +// A rule applies to its own path and to everything below it, and to nothing +// outside of it. +func TestRuleAppliesRecursively(t *testing.T) { + tree := NewACLTree() + tree.Insert(&ACLNode{ + Path: "/eos/project/c/cernbox/shared", + MandatoryACLs: []acl.Entry{aliceRead}, + }) + + checkFind(t, tree, "/eos/project/c/cernbox/shared", []acl.Entry{aliceRead}, nil) + checkFind(t, tree, "/eos/project/c/cernbox/shared/sub/deep", []acl.Entry{aliceRead}, nil) + checkFind(t, tree, "/eos/project/c/cernbox/other", nil, nil) + // a sibling with a common prefix but a different path segment + checkFind(t, tree, "/eos/project/c/cernbox/sharedother", nil, nil) + checkFind(t, tree, "/eos/project/c/cernbox/shared-doc.md", nil, nil) +} + +// A deeper rule adds its entity to the ones inherited from above. +func TestDeeperRuleAddsEntity(t *testing.T) { + tree := NewACLTree() + tree.Insert(&ACLNode{Path: "/eos/project/c/cernbox", MandatoryACLs: []acl.Entry{aliceRead}}) + tree.Insert(&ACLNode{Path: "/eos/project/c/cernbox/sub", MandatoryACLs: []acl.Entry{bobRead}}) + + checkFind(t, tree, "/eos/project/c/cernbox", []acl.Entry{aliceRead}, nil) + checkFind(t, tree, "/eos/project/c/cernbox/sub", []acl.Entry{aliceRead, bobRead}, nil) + checkFind(t, tree, "/eos/project/c/cernbox/sub/deep", []acl.Entry{aliceRead, bobRead}, nil) + checkFind(t, tree, "/eos/project/c/cernbox/elsewhere", []acl.Entry{aliceRead}, nil) +} + +// A deeper rule raises the permission of an entity, and never lowers it. +func TestDeeperRuleRaisesPermission(t *testing.T) { + t.Run("raise", func(t *testing.T) { + tree := NewACLTree() + tree.Insert(&ACLNode{Path: "/eos/project/c/cernbox", MandatoryACLs: []acl.Entry{aliceRead}}) + tree.Insert(&ACLNode{Path: "/eos/project/c/cernbox/sub", MandatoryACLs: []acl.Entry{aliceWrite}}) + + checkFind(t, tree, "/eos/project/c/cernbox", []acl.Entry{aliceRead}, nil) + checkFind(t, tree, "/eos/project/c/cernbox/sub", []acl.Entry{aliceWrite}, nil) + }) + + t.Run("no lowering", func(t *testing.T) { + tree := NewACLTree() + tree.Insert(&ACLNode{Path: "/eos/project/c/cernbox", MandatoryACLs: []acl.Entry{aliceWrite}}) + tree.Insert(&ACLNode{Path: "/eos/project/c/cernbox/sub", MandatoryACLs: []acl.Entry{aliceRead}}) + + checkFind(t, tree, "/eos/project/c/cernbox/sub", []acl.Entry{aliceWrite}, nil) + }) +} + +// Rules do not arrive in tree order, so the result must not depend on the +// insertion order. Insert takes the node over, so every order gets its own +// nodes. +func TestInsertOrderDoesNotMatter(t *testing.T) { + newNodes := func() []*ACLNode { + return []*ACLNode{ + {Path: "/eos/project/c/cernbox", MandatoryACLs: []acl.Entry{aliceRead}}, + {Path: "/eos/project/c/cernbox/a", MandatoryACLs: []acl.Entry{bobRead}}, + {Path: "/eos/project/c/cernbox/a/b", MandatoryACLs: []acl.Entry{groupRead}}, + } + } + + orders := [][]int{ + {0, 1, 2}, + {0, 2, 1}, + {1, 0, 2}, + {1, 2, 0}, + {2, 0, 1}, + {2, 1, 0}, + } + + for _, order := range orders { + t.Run(fmt.Sprint(order), func(t *testing.T) { + nodes := newNodes() + tree := NewACLTree() + for _, i := range order { + tree.Insert(nodes[i]) + } + + checkFind(t, tree, "/eos/project/c/cernbox", []acl.Entry{aliceRead}, nil) + checkFind(t, tree, "/eos/project/c/cernbox/a", []acl.Entry{aliceRead, bobRead}, nil) + checkFind(t, tree, "/eos/project/c/cernbox/a/b", []acl.Entry{aliceRead, bobRead, groupRead}, nil) + checkFind(t, tree, "/eos/project/c/cernbox/a/b/c", []acl.Entry{aliceRead, bobRead, groupRead}, nil) + }) + } +} + +// A path can carry more than one rule. The rules merge into one node, so an +// entity appears one time only. +func TestTwoRulesOnTheSamePath(t *testing.T) { + tree := NewACLTree() + p := "/eos/project/c/cernbox/shared" + tree.Insert(&ACLNode{Path: p, MandatoryACLs: []acl.Entry{aliceRead}}) + tree.Insert(&ACLNode{Path: p, MandatoryACLs: []acl.Entry{bobRead}}) + tree.Insert(&ACLNode{Path: p, MandatoryACLs: []acl.Entry{aliceWrite}}) + + checkFind(t, tree, p, []acl.Entry{aliceWrite, bobRead}, nil) + checkFind(t, tree, p+"/sub", []acl.Entry{aliceWrite, bobRead}, nil) +} + +// A second rule on a path that already has a subtree must reach that subtree +// too. +func TestSecondRuleOnAPathWithChildren(t *testing.T) { + tree := NewACLTree() + tree.Insert(&ACLNode{Path: "/eos/project/c/cernbox", MandatoryACLs: []acl.Entry{aliceRead}}) + tree.Insert(&ACLNode{Path: "/eos/project/c/cernbox/sub", MandatoryACLs: []acl.Entry{bobRead}}) + tree.Insert(&ACLNode{Path: "/eos/project/c/cernbox", MandatoryACLs: []acl.Entry{groupRead}}) + + checkFind(t, tree, "/eos/project/c/cernbox", []acl.Entry{aliceRead, groupRead}, nil) + checkFind(t, tree, "/eos/project/c/cernbox/sub", []acl.Entry{aliceRead, bobRead, groupRead}, nil) +} + +// A rule can sit below a path that has no rule of its own. +func TestGapBetweenRules(t *testing.T) { + tree := NewACLTree() + tree.Insert(&ACLNode{Path: "/eos/project/c/cernbox", MandatoryACLs: []acl.Entry{aliceRead}}) + tree.Insert(&ACLNode{Path: "/eos/project/c/cernbox/a/b/c", MandatoryACLs: []acl.Entry{bobRead}}) + + checkFind(t, tree, "/eos/project/c/cernbox/a", []acl.Entry{aliceRead}, nil) + checkFind(t, tree, "/eos/project/c/cernbox/a/b", []acl.Entry{aliceRead}, nil) + checkFind(t, tree, "/eos/project/c/cernbox/a/b/c", []acl.Entry{aliceRead, bobRead}, nil) +} + +// Find cleans the path it gets, so a caller can pass a path as it comes from +// the namespace. +func TestFindCleansThePath(t *testing.T) { + tree := NewACLTree() + tree.Insert(&ACLNode{Path: "/eos/project/c/cernbox", MandatoryACLs: []acl.Entry{aliceRead}}) + + checkFind(t, tree, "/eos/project/c/cernbox/", []acl.Entry{aliceRead}, nil) + checkFind(t, tree, "/eos/project/c/cernbox/sub/..", []acl.Entry{aliceRead}, nil) + checkFind(t, tree, "/eos/project/c/other/../cernbox/sub", []acl.Entry{aliceRead}, nil) +} + +// Sibling subtrees stay independent. +func TestSiblingsAreIndependent(t *testing.T) { + tree := NewACLTree() + tree.Insert(&ACLNode{Path: "/eos/project/c/cernbox/a", MandatoryACLs: []acl.Entry{aliceRead}}) + tree.Insert(&ACLNode{Path: "/eos/project/c/cernbox/b", MandatoryACLs: []acl.Entry{bobRead}}) + + checkFind(t, tree, "/eos/project/c/cernbox/a/deep", []acl.Entry{aliceRead}, nil) + checkFind(t, tree, "/eos/project/c/cernbox/b/deep", []acl.Entry{bobRead}, nil) + checkFind(t, tree, "/eos/project/c/cernbox", nil, nil) +} + +// Mandatory and allowed ACLs are two independent sets on the same node. +func TestMandatoryAndAllowedAreSeparate(t *testing.T) { + tree := NewACLTree() + tree.Insert(&ACLNode{ + Path: "/eos/project/c/cernbox", + MandatoryACLs: []acl.Entry{aliceRead}, + AllowedACLs: []acl.Entry{external}, + }) + tree.Insert(&ACLNode{ + Path: "/eos/project/c/cernbox/sub", + MandatoryACLs: []acl.Entry{bobRead}, + }) + + checkFind(t, tree, "/eos/project/c/cernbox", []acl.Entry{aliceRead}, []acl.Entry{external}) + checkFind(t, tree, "/eos/project/c/cernbox/sub", []acl.Entry{aliceRead, bobRead}, []acl.Entry{external}) +} + +// The deep job looks up every namespace entry, so it may want to do that with +// several goroutines. Find only reads the tree, so that is safe once the tree +// is built. Run with -race. +func TestFindIsSafeForConcurrentUse(t *testing.T) { + tree := NewACLTree() + tree.Insert(&ACLNode{Path: "/eos/project/c/cernbox", MandatoryACLs: []acl.Entry{aliceRead}}) + tree.Insert(&ACLNode{Path: "/eos/project/c/cernbox/a", MandatoryACLs: []acl.Entry{bobRead}}) + tree.Insert(&ACLNode{Path: "/eos/project/c/cernbox/b", MandatoryACLs: []acl.Entry{groupRead}}) + + paths := []string{ + "/eos/project/c/cernbox", + "/eos/project/c/cernbox/a/file.txt", + "/eos/project/c/cernbox/b/sub/file.txt", + "/eos/project/c/other/file.txt", + } + + var wg sync.WaitGroup + for range 8 { + wg.Add(1) + go func() { + defer wg.Done() + for range 100 { + for _, p := range paths { + tree.Find(p) + } + } + }() + } + wg.Wait() + + // the lookups changed nothing + checkFind(t, tree, "/eos/project/c/cernbox/a/file.txt", []acl.Entry{aliceRead, bobRead}, nil) + checkFind(t, tree, "/eos/project/c/cernbox/b/sub/file.txt", []acl.Entry{aliceRead, groupRead}, nil) +} + +// Matches tells whether the node covers the path: the node itself or anything +// below it. The path must be clean, which is what Find gives it. +func TestMatches(t *testing.T) { + node := &ACLNode{Path: "/eos/project/c/cernbox"} + tests := []struct { + path string + want bool + }{ + {"/eos/project/c/cernbox", true}, + {"/eos/project/c/cernbox/sub", true}, + {"/eos/project/c/cernbox/sub/deep", true}, + {"/eos/project/c/cernboxother", false}, + {"/eos/project/c/cernbox-doc.md", false}, + {"/eos/project/c", false}, + {"/eos/project/c/other", false}, + } + for _, tt := range tests { + if got := node.Matches(tt.path); got != tt.want { + t.Errorf("Matches(%q) = %v, want %v", tt.path, got, tt.want) + } + } +} + +func TestMatchesExact(t *testing.T) { + node := &ACLNode{Path: "/eos/project/c/cernbox"} + tests := []struct { + path string + want bool + }{ + {"/eos/project/c/cernbox", true}, + {"/eos/project/c/cernbox/sub", false}, + {"/eos/project/c", false}, + } + for _, tt := range tests { + if got := node.MatchesExact(tt.path); got != tt.want { + t.Errorf("MatchesExact(%q) = %v, want %v", tt.path, got, tt.want) + } + } +} + +// A node covers a path only when a separator follows the prefix. A file whose +// name starts with the name of a shared folder must stay outside of it. +func TestFastHasPrefix(t *testing.T) { + const shared = "/eos/project/c/cernbox/shared" + + tests := []struct { + s, prefix string + want bool + }{ + {"/eos/project/c/cernbox/shared/notes.md", shared, true}, + {"/eos/project/c/cernbox/shared/sub/notes.md", shared, true}, + // the file sits next to the folder, not in it + {"/eos/project/c/cernbox/shared-doc.md", shared, false}, + {"/eos/project/c/cernbox/sharednotes", shared, false}, + {"/eos/project/c/cernbox/shared2/notes.md", shared, false}, + // the same path is not below itself + {shared, shared, false}, + // shorter than the prefix + {"/eos/project/c/cernbox/sha", shared, false}, + {"/eos/project/c/cernbox", shared, false}, + // the empty prefix is the root node, which covers every path + {shared, "", true}, + // the prefix must be clean, a trailing separator finds nothing + {"/eos/project/c/cernbox/shared/notes.md", shared + "/", false}, + } + + for _, tt := range tests { + if got := fastHasPrefix(tt.s, tt.prefix); got != tt.want { + t.Errorf("fastHasPrefix(%q, %q) = %v, want %v", tt.s, tt.prefix, got, tt.want) + } + } +} + +func TestMergeACLs(t *testing.T) { + tests := []struct { + name string + a, b []acl.Entry + want []acl.Entry + }{ + { + name: "different entities are kept both", + a: []acl.Entry{aliceRead}, + b: []acl.Entry{bobRead}, + want: []acl.Entry{aliceRead, bobRead}, + }, + { + name: "same entity keeps the highest permission", + a: []acl.Entry{aliceRead}, + b: []acl.Entry{aliceWrite}, + want: []acl.Entry{aliceWrite}, + }, + { + name: "the highest permission wins in both directions", + a: []acl.Entry{aliceWrite}, + b: []acl.Entry{aliceRead}, + want: []acl.Entry{aliceWrite}, + }, + { + name: "same qualifier with another type is another entry", + a: []acl.Entry{{Type: acl.TypeUser, Qualifier: "x", Permissions: "rx"}}, + b: []acl.Entry{{Type: acl.TypeGroup, Qualifier: "x", Permissions: "rx"}}, + want: []acl.Entry{ + {Type: acl.TypeUser, Qualifier: "x", Permissions: "rx"}, + {Type: acl.TypeGroup, Qualifier: "x", Permissions: "rx"}, + }, + }, + { + name: "empty second set", + a: []acl.Entry{aliceRead}, + want: []acl.Entry{aliceRead}, + }, + { + name: "empty first set", + b: []acl.Entry{aliceRead}, + want: []acl.Entry{aliceRead}, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := mergeACLs(tt.a, tt.b); !sameEntries(got, tt.want) { + t.Errorf("mergeACLs(%v, %v) = %v, want %v", tt.a, tt.b, got, tt.want) + } + }) + } +} + +// The two input sets belong to other nodes of the tree, so a merge must not +// change them. +func TestMergeACLsDoesNotChangeItsInput(t *testing.T) { + a := []acl.Entry{aliceRead} + b := []acl.Entry{aliceWrite, bobRead} + + mergeACLs(a, b) + + if !sameEntries(a, []acl.Entry{aliceRead}) { + t.Errorf("first set = %v, want %v", a, []acl.Entry{aliceRead}) + } + if !sameEntries(b, []acl.Entry{aliceWrite, bobRead}) { + t.Errorf("second set = %v, want %v", b, []acl.Entry{aliceWrite, bobRead}) + } +} + +func TestHighestPermission(t *testing.T) { + tests := []struct { + a, b, want string + }{ + {"rx", "rwx", "rwx"}, + {"rwx", "rx", "rwx"}, + {"rx", "rx", "rx"}, + {"rwx", "rwx", "rwx"}, + {"rx", "!r!w!x", "!r!w!x"}, + {"!r!w!x", "rwx", "!r!w!x"}, + // an unknown permission loses against a known one + {"", "rx", "rx"}, + {"rx", "", "rx"}, + } + for _, tt := range tests { + if got := highestPermission(tt.a, tt.b); got != tt.want { + t.Errorf("highestPermission(%q, %q) = %q, want %q", tt.a, tt.b, got, tt.want) + } + } +} + +const benchRoot = "/eos/project/c/cernbox" + +// benchPaths returns one path for every node of a full tree with the given +// depth and number of children per node, parents before children. +func benchPaths(depth, children int) []string { + paths := []string{benchRoot} + level := []string{benchRoot} + for range depth { + var next []string + for _, p := range level { + for c := range children { + next = append(next, fmt.Sprintf("%s/d%d", p, c)) + } + } + paths = append(paths, next...) + level = next + } + return paths +} + +// benchNodes puts one rule on every path. Every rule holds another user, so a +// deep node inherits one entry per level above it. +func benchNodes(paths []string) []*ACLNode { + nodes := make([]*ACLNode, len(paths)) + for i, p := range paths { + nodes[i] = &ACLNode{ + Path: p, + MandatoryACLs: []acl.Entry{{Type: acl.TypeUser, Qualifier: fmt.Sprintf("user%d", i), Permissions: "rx"}}, + AllowedACLs: []acl.Entry{external}, + } + } + return nodes +} + +// The cost of building a tree. Shares do not arrive in tree order, so the +// deepest first order is measured as well: it moves nodes and merges ACLs down +// a subtree at every step. +func BenchmarkInsert(b *testing.B) { + shapes := []struct{ depth, children int }{ + {3, 4}, + {4, 4}, + {5, 4}, + } + + for _, s := range shapes { + paths := benchPaths(s.depth, s.children) + + b.Run(fmt.Sprintf("%d nodes/top down", len(paths)), func(b *testing.B) { + b.ReportAllocs() + for b.Loop() { + b.StopTimer() + nodes := benchNodes(paths) + tree := NewACLTree() + b.StartTimer() + + for _, n := range nodes { + tree.Insert(n) + } + } + }) + + b.Run(fmt.Sprintf("%d nodes/deepest first", len(paths)), func(b *testing.B) { + b.ReportAllocs() + for b.Loop() { + b.StopTimer() + nodes := benchNodes(paths) + slices.Reverse(nodes) + tree := NewACLTree() + b.StartTimer() + + for _, n := range nodes { + tree.Insert(n) + } + } + }) + } +} + +// The lookup, which is the operation the tree exists for. +func BenchmarkFind(b *testing.B) { + const depth = 5 + paths := benchPaths(depth, 4) + + tree := NewACLTree() + for _, n := range benchNodes(paths) { + tree.Insert(n) + } + deepest := benchRoot + strings.Repeat("/d0", depth) + + cases := []struct{ name, path string }{ + {"top of the tree", benchRoot}, + {"deepest rule", deepest}, + {"file below the deepest rule", deepest + "/file.txt"}, + {"outside the tree", "/eos/project/c/other/file.txt"}, + } + + for _, c := range cases { + b.Run(fmt.Sprintf("%d nodes/%s", len(paths), c.name), func(b *testing.B) { + b.ReportAllocs() + for b.Loop() { + tree.Find(c.path) + } + }) + } +} + +// A space with about 1000 rules, spread over three levels of 10 children each. +// The last child of every level is the worst case, because Find scans the +// children in order. +func BenchmarkFindTenWide(b *testing.B) { + const depth, children = 3, 10 + paths := benchPaths(depth, children) + + tree := NewACLTree() + for _, n := range benchNodes(paths) { + tree.Insert(n) + } + + first := benchRoot + strings.Repeat("/d0", depth) + last := benchRoot + strings.Repeat(fmt.Sprintf("/d%d", children-1), depth) + + cases := []struct{ name, path string }{ + {"top of the tree", benchRoot}, + {"first rule of every level", first}, + {"last rule of every level", last}, + {"file below the last rule", last + "/file.txt"}, + } + + for _, c := range cases { + b.Run(fmt.Sprintf("%d nodes/%s", len(paths), c.name), func(b *testing.B) { + b.ReportAllocs() + for b.Loop() { + tree.Find(c.path) + } + }) + } +} + +// Find scans the children of a node one by one, so a wide node is the case to +// watch: a directory with many shared children. +func BenchmarkFindWide(b *testing.B) { + for _, children := range []int{10, 100, 1000} { + paths := benchPaths(1, children) + tree := NewACLTree() + for _, n := range benchNodes(paths) { + tree.Insert(n) + } + last := paths[len(paths)-1] + + b.Run(fmt.Sprintf("%d nodes/%d children", len(paths), children), func(b *testing.B) { + b.ReportAllocs() + for b.Loop() { + tree.Find(last) + } + }) + } +} diff --git a/pkg/reconciliation/deep.go b/pkg/reconciliation/deep.go new file mode 100644 index 0000000000..c78cb72938 --- /dev/null +++ b/pkg/reconciliation/deep.go @@ -0,0 +1,152 @@ +// Copyright 2018-2026 CERN +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// In applying this license, CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +package reconciliation + +import ( + "cmp" + "context" + "fmt" + "slices" + + collaborationv1beta1 "github.com/cs3org/go-cs3apis/cs3/sharing/collaboration/v1beta1" + provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" + "github.com/cs3org/reva/v3/pkg/share/manager/sql" + "github.com/cs3org/reva/v3/pkg/spaces" + "github.com/cs3org/reva/v3/pkg/storage/utils/acl" +) + +const JobName = "reconciliation.deep" + +type RunResult struct { + Entries []EntryResult +} + +type EntryResult struct { + Path string + Action ActionKind + ACL acl.Entry +} + +type DeepJob struct { + shareMgr sql.ShareMgr +} + +type RunParameters struct { + SpaceID string + SpaceType spaces.SpaceType +} + +type ShareWithPath struct { + *collaborationv1beta1.Share + Path string +} + +func (s *ShareWithPath) toTreeNode() *ACLNode { + return &ACLNode{ + Path: s.Path, + MandatoryACLs: []acl.Entry{shareToACL(s.Share)}, + } +} + +func (j *DeepJob) run(ctx context.Context, p RunParameters) error { + + // First, construct a tree which contains the "ideal" state + // For this, we: + // 1. Get all the shares in the space + // 2. Resolve all their paths + // 3. Sort them (this makes the tree insertion much faster) + // 4. Construct the tree + shares, err := j.shareMgr.ListShares(ctx, []*collaborationv1beta1.Filter{ + { + Type: collaborationv1beta1.Filter_TYPE_SPACE_ID, + Term: &collaborationv1beta1.Filter_SpaceId{ + SpaceId: p.SpaceID, + }, + }, + }) + if err != nil { + return err + } + + // Resolve the paths + var sharesWithPaths = make([]*ShareWithPath, len(shares)) + for _, s := range shares { + if p, ok := j.getPath(s.ResourceId); ok { + sharesWithPaths = append(sharesWithPaths, &ShareWithPath{ + Share: s, + Path: p, + }) + } + } + + // Sort the shares, + slices.SortFunc(sharesWithPaths, func(a, b *ShareWithPath) int { + return cmp.Compare(a.Path, b.Path) + }) + + // Finally, construct the tree + tree := NewACLTree() + for _, s := range sharesWithPaths { + tree.Insert(s.toTreeNode()) + } + + // Now that we have the ideal state, we need to get the + // actual state of the namespace into something parsable. + // We start by taking a dump of the namespace, and then we + // parse this entry-by-entry + + namespaceDump, err := NewEOSMemoryNSInspect() + if err != nil { + return err + } + + path, err := spaces.DecodeSpaceID(p.SpaceID) + if err != nil { + return err + } + + ns, err := namespaceDump.Dump(path, 0) + + for _, entry := range ns.entries { + fmt.Print(entry.Path) + } + + return nil +} + +func (j *DeepJob) getPath(rid *provider.ResourceId) (string, bool) { + return "", true +} + +func shareToACL(s *collaborationv1beta1.Share) acl.Entry { + var e acl.Entry + switch { + case s.Grantee.Type == provider.GranteeType_GRANTEE_TYPE_GROUP: + e.Type = "egroup" + e.Qualifier = s.Grantee.GetGroupId().GetOpaqueId() + case s.Grantee.Type == provider.GranteeType_GRANTEE_TYPE_USER: + e.Type = "u" + e.Qualifier = s.Grantee.GetUserId().GetOpaqueId() + default: + return e + } + + // TODO(jgeens): set permissions + return e +} diff --git a/pkg/reconciliation/ns_dump.go b/pkg/reconciliation/ns_dump.go new file mode 100644 index 0000000000..9a563b8143 --- /dev/null +++ b/pkg/reconciliation/ns_dump.go @@ -0,0 +1,145 @@ +package reconciliation + +import ( + "bytes" + "encoding/json" + "fmt" + "os/exec" + "path" + "strings" + + "github.com/pkg/errors" +) + +type NSDumpResponse struct { + entries []NameSpaceEntry +} + +type NSDumpClient interface { + Dump(rootPath string, maxDepth int) (NSDumpResponse, error) +} + +type EOSMemoryNSInspect struct { + cfg EOSNSInspectConfig +} + +type EOSNSInspectConfig struct { + maxDepth int + ignoreFiles bool + instance string +} + +type EntryType string + +const ( + EntryTypeFolder EntryType = "folder" + EntryTypeFile EntryType = "file" +) + +type NameSpaceEntry struct { + Ctime string `json:"ctime"` + Flags string `json:"flags"` + Gid string `json:"gid"` + Mtime string `json:"mtime"` + Name string `json:"name"` + Path string `json:"path"` + Stime string `json:"stime"` + Uid string `json:"uid"` + XattrSysAcl string `json:"xattr.sys.acl"` + EntryType EntryType `json:"entryType"` + ID string `json:"id"` +} + +func (e *NameSpaceEntry) UnmarshalJSON(data []byte) error { + type n NameSpaceEntry // no methods, so no infinite recursion + var aux struct { + n + CID *string `json:"cid"` + FID *string `json:"fid"` + } + if err := json.Unmarshal(data, &aux); err != nil { + return err + } + + switch { + case aux.CID != nil && aux.FID != nil: + return errors.New("item: both cid and fid set") + case aux.CID != nil: + aux.ID, aux.EntryType = *aux.CID, EntryTypeFolder + case aux.FID != nil: + aux.ID, aux.EntryType = *aux.FID, EntryTypeFile + default: + return errors.New("item: neither cid nor fid set") + } + + *e = NameSpaceEntry(aux.n) + return nil +} + +func (d *NameSpaceEntry) IsSysFolder() bool { + return d.EntryType == EntryTypeFolder && strings.HasPrefix(d.Name, ".sys.") +} + +func (d *NameSpaceEntry) IsSysFile() bool { + return d.EntryType == EntryTypeFile && strings.HasPrefix(path.Base(path.Dir(d.Path)), ".sys.") +} + +func NewEOSMemoryNSInspect() (*EOSMemoryNSInspect, error) { + return &EOSMemoryNSInspect{}, nil +} + +func (e *EOSMemoryNSInspect) Dump(rootPath string, maxDepth int) (*NSDumpResponse, error) { + + noFilesFlag := "" + if e.cfg.ignoreFiles { + noFilesFlag = " --no-files" + } + + maxDepthFlag := "" + if maxDepth > 0 { + maxDepthFlag = fmt.Sprintf(" --maxdepth %d", maxDepth) + } + + args := fmt.Sprintf( + "scan --path %s %s --members %s-qdb:7777 --password-file /keytabs/%s_keytab --json%s", + rootPath, + maxDepthFlag, + e.cfg.instance, + e.cfg.instance, + noFilesFlag, + ) + + cmd := exec.Command("/usr/bin/eos-ns-inspect", strings.Split(args, " ")...) + + var stdout bytes.Buffer + cmd.Stdout = &stdout + err := cmd.Run() + if err != nil { + return nil, err + } + + return parseNSInspectOutput(&stdout) +} + +func parseNSInspectOutput(data *bytes.Buffer) (*NSDumpResponse, error) { + var raw []map[string]interface{} + if err := json.Unmarshal(data.Bytes(), &raw); err != nil { + return nil, errors.Wrap(err, "failed to unmarshal ns-inspect input") + } + + var entries []NameSpaceEntry + for _, obj := range raw { + var e NameSpaceEntry + b, err := json.Marshal(obj) + if err != nil { + return nil, err + } + err = json.Unmarshal(b, &e) + if err != nil { + return nil, err + } + entries = append(entries, e) + + } + return &NSDumpResponse{entries: entries}, nil +} diff --git a/pkg/reconciliation/reconciliation.go b/pkg/reconciliation/reconciliation.go index c710ddaea2..ef032be110 100644 --- a/pkg/reconciliation/reconciliation.go +++ b/pkg/reconciliation/reconciliation.go @@ -104,3 +104,29 @@ func OpenLog(path string) (*zerolog.Logger, *os.File, error) { log := zerolog.New(f).With().Timestamp().Logger() return &log, f, nil } + +// ActionKind is what the job did to an ACL entry. There is no remove: the job +// only ever adds one that is missing or corrects one that is wrong. +type ActionKind int + +const ( + // ActionAdd adds an entry that is missing. + ActionAdd ActionKind = iota + // ActionUpdate changes the permissions of an entry that is present. + ActionUpdate + ActionDelete +) + +// String returns a human readable name for the action kind. +func (k ActionKind) String() string { + switch k { + case ActionAdd: + return "add" + case ActionUpdate: + return "update" + case ActionDelete: + return "delete" + default: + return "unknown" + } +} diff --git a/pkg/reconciliation/shallow.go b/pkg/reconciliation/shallow.go index c1823254c3..b386d029d0 100644 --- a/pkg/reconciliation/shallow.go +++ b/pkg/reconciliation/shallow.go @@ -144,29 +144,6 @@ type ShallowJob struct { RunOnStart bool } -// ActionKind is what the job did to an ACL entry. There is no remove: the job -// only ever adds one that is missing or corrects one that is wrong. -type ActionKind int - -const ( - // ActionAdd adds an entry that is missing. - ActionAdd ActionKind = iota - // ActionUpdate changes the permissions of an entry that is present. - ActionUpdate -) - -// String returns a human readable name for the action kind. -func (k ActionKind) String() string { - switch k { - case ActionAdd: - return "add" - case ActionUpdate: - return "update" - default: - return "unknown" - } -} - // WrittenGrant records one grant the job wrote, or, in dry-run, would have. type WrittenGrant struct { ShareID string From 985959f09bf66a49a1d2bf29c923ad2da7e5ac8a Mon Sep 17 00:00:00 2001 From: Jesse Geens Date: Thu, 20 Aug 2026 14:40:53 +0200 Subject: [PATCH 02/10] implement basic logic --- pkg/reconciliation/acl_tree.go | 21 +- pkg/reconciliation/acl_tree_test.go | 232 ++++++++++-------- pkg/reconciliation/deep.go | 88 +++++-- pkg/reconciliation/nsdump/chunked_ns_dump.go | 3 + pkg/reconciliation/nsdump/file_ns_dump.go | 33 +++ pkg/reconciliation/nsdump/memory_ns_dump.go | 56 +++++ pkg/reconciliation/{ => nsdump}/ns_dump.go | 65 +---- pkg/storage/{utils => fs/eos}/acl/acl.go | 0 pkg/storage/fs/eos/auth.go | 2 +- pkg/storage/fs/eos/client/binary/eosbinary.go | 2 +- pkg/storage/fs/eos/client/eosclient.go | 2 +- pkg/storage/fs/eos/client/grpc/acl.go | 2 +- pkg/storage/fs/eos/client/grpc/auth.go | 2 +- pkg/storage/fs/eos/client/grpc/file_ops.go | 2 +- pkg/storage/fs/eos/client/grpc/utils.go | 2 +- pkg/storage/fs/eos/client/utils.go | 2 +- pkg/storage/fs/eos/eosfs.go | 2 +- pkg/storage/fs/eos/grant.go | 2 +- pkg/storage/utils/grants/grants.go | 2 +- pkg/storage/utils/localfs/localfs.go | 2 +- 20 files changed, 333 insertions(+), 189 deletions(-) create mode 100644 pkg/reconciliation/nsdump/chunked_ns_dump.go create mode 100644 pkg/reconciliation/nsdump/file_ns_dump.go create mode 100644 pkg/reconciliation/nsdump/memory_ns_dump.go rename pkg/reconciliation/{ => nsdump}/ns_dump.go (60%) rename pkg/storage/{utils => fs/eos}/acl/acl.go (100%) diff --git a/pkg/reconciliation/acl_tree.go b/pkg/reconciliation/acl_tree.go index 891d923870..b1c88817c6 100644 --- a/pkg/reconciliation/acl_tree.go +++ b/pkg/reconciliation/acl_tree.go @@ -5,7 +5,7 @@ import ( "slices" "github.com/cs3org/reva/v3/pkg/spaces" - "github.com/cs3org/reva/v3/pkg/storage/utils/acl" + "github.com/cs3org/reva/v3/pkg/storage/fs/eos/acl" ) // TODO(jgeens): @@ -30,8 +30,8 @@ type ACLTree struct { type ACLNode struct { Path string - MandatoryACLs []acl.Entry - AllowedACLs []acl.Entry + MandatoryACLs []*acl.Entry + AllowedACLs []*acl.Entry Children []*ACLNode } @@ -120,13 +120,13 @@ func (n *ACLNode) MatchesExact(p string) bool { return p == n.Path } -func (n *ACLNode) Find(p string) (MandatoryACLs, AllowedACLs []acl.Entry, ok bool) { +func (n *ACLNode) Find(p string) (MandatoryACLs, AllowedACLs []*acl.Entry, ok bool) { return n.find(path.Clean(p)) } // The internal-only find does not clean the path p, so we only need to do this once // and not for every visited node -func (n *ACLNode) find(p string) (MandatoryACLs, AllowedACLs []acl.Entry, ok bool) { +func (n *ACLNode) find(p string) (MandatoryACLs, AllowedACLs []*acl.Entry, ok bool) { if !n.Matches(p) { return nil, nil, false } @@ -141,7 +141,7 @@ func (n *ACLNode) find(p string) (MandatoryACLs, AllowedACLs []acl.Entry, ok boo return n.MandatoryACLs, n.AllowedACLs, true } -func (n *ACLNode) ApplyACLs(mandatory, optional []acl.Entry) { +func (n *ACLNode) ApplyACLs(mandatory, optional []*acl.Entry) { n.MandatoryACLs = mergeACLs(n.MandatoryACLs, mandatory) n.AllowedACLs = mergeACLs(n.AllowedACLs, optional) for _, c := range n.Children { @@ -149,7 +149,7 @@ func (n *ACLNode) ApplyACLs(mandatory, optional []acl.Entry) { } } -func mergeACLs(a, b []acl.Entry) []acl.Entry { +func mergeACLs(a, b []*acl.Entry) []*acl.Entry { resultSet := slices.Clone(a) for _, e := range b { @@ -159,7 +159,12 @@ func mergeACLs(a, b []acl.Entry) []acl.Entry { for i, c := range resultSet { if e.Qualifier == c.Qualifier && e.Type == c.Type { // ACL for the same user: highest permission wins - resultSet[i].Permissions = highestPermission(e.Permissions, c.Permissions) + + // We don't want to override other references to this ACL, so we make a copy + merged := *c + merged.Permissions = highestPermission(e.Permissions, c.Permissions) + resultSet[i] = &merged + foundMatch = true break } diff --git a/pkg/reconciliation/acl_tree_test.go b/pkg/reconciliation/acl_tree_test.go index 07be14a3f2..a98eb109c5 100644 --- a/pkg/reconciliation/acl_tree_test.go +++ b/pkg/reconciliation/acl_tree_test.go @@ -25,13 +25,13 @@ import ( "sync" "testing" - "github.com/cs3org/reva/v3/pkg/storage/utils/acl" + "github.com/cs3org/reva/v3/pkg/storage/fs/eos/acl" ) // sameEntries reports whether got and want hold the same ACL entries. The tree // gives no order guarantee, so both sides are sorted before the comparison. -func sameEntries(got, want []acl.Entry) bool { - cmp := func(a, b acl.Entry) int { +func sameEntries(got, want []*acl.Entry) bool { + cmp := func(a, b *acl.Entry) int { if c := strings.Compare(a.Type, b.Type); c != 0 { return c } @@ -43,31 +43,56 @@ func sameEntries(got, want []acl.Entry) bool { g, w := slices.Clone(got), slices.Clone(want) slices.SortFunc(g, cmp) slices.SortFunc(w, cmp) - return slices.Equal(g, w) + // the entries hold pointers, so compare what they point to + return slices.EqualFunc(g, w, func(a, b *acl.Entry) bool { return *a == *b }) +} + +// formatEntries prints the entries themselves. A slice of pointers prints as a +// list of addresses, which says nothing in a failure message. +func formatEntries(entries []*acl.Entry) string { + out := make([]string, len(entries)) + for i, e := range entries { + out[i] = fmt.Sprintf("%s:%s:%s", e.Type, e.Qualifier, e.Permissions) + } + return "[" + strings.Join(out, " ") + "]" } // checkFind looks up p and compares both ACL sets of the node that applies. -func checkFind(t *testing.T, tree *ACLTree, p string, wantMandatory, wantAllowed []acl.Entry) { +func checkFind(t *testing.T, tree *ACLTree, p string, wantMandatory, wantAllowed []*acl.Entry) { t.Helper() mandatory, allowed, ok := tree.Find(p) if !ok { t.Fatalf("Find(%q): ok = false, want true", p) } if !sameEntries(mandatory, wantMandatory) { - t.Errorf("Find(%q): mandatory = %v, want %v", p, mandatory, wantMandatory) + t.Errorf("Find(%q): mandatory = %v, want %v", p, formatEntries(mandatory), formatEntries(wantMandatory)) } if !sameEntries(allowed, wantAllowed) { - t.Errorf("Find(%q): allowed = %v, want %v", p, allowed, wantAllowed) + t.Errorf("Find(%q): allowed = %v, want %v", p, formatEntries(allowed), formatEntries(wantAllowed)) } } -var ( - aliceRead = acl.Entry{Type: acl.TypeUser, Qualifier: "alice", Permissions: "rx"} - aliceWrite = acl.Entry{Type: acl.TypeUser, Qualifier: "alice", Permissions: "rwx"} - bobRead = acl.Entry{Type: acl.TypeUser, Qualifier: "bob", Permissions: "rx"} - groupRead = acl.Entry{Type: acl.TypeGroup, Qualifier: "cernbox-admins", Permissions: "rx"} - external = acl.Entry{Type: acl.TypeUser, Qualifier: "cboxexternal", Permissions: "rwx"} -) +// Every fixture gives a new entry. The tree holds pointers, and a merge writes +// through them, so a shared entry would let one tree change another. +func aliceRead() *acl.Entry { + return &acl.Entry{Type: acl.TypeUser, Qualifier: "alice", Permissions: "rx"} +} + +func aliceWrite() *acl.Entry { + return &acl.Entry{Type: acl.TypeUser, Qualifier: "alice", Permissions: "rwx"} +} + +func bobRead() *acl.Entry { + return &acl.Entry{Type: acl.TypeUser, Qualifier: "bob", Permissions: "rx"} +} + +func groupRead() *acl.Entry { + return &acl.Entry{Type: acl.TypeGroup, Qualifier: "cernbox-admins", Permissions: "rx"} +} + +func external() *acl.Entry { + return &acl.Entry{Type: acl.TypeUser, Qualifier: "cboxexternal", Permissions: "rwx"} +} // An empty tree has no rule, so every path is found with no ACL at all. func TestFindOnEmptyTree(t *testing.T) { @@ -81,11 +106,11 @@ func TestRuleAppliesRecursively(t *testing.T) { tree := NewACLTree() tree.Insert(&ACLNode{ Path: "/eos/project/c/cernbox/shared", - MandatoryACLs: []acl.Entry{aliceRead}, + MandatoryACLs: []*acl.Entry{aliceRead()}, }) - checkFind(t, tree, "/eos/project/c/cernbox/shared", []acl.Entry{aliceRead}, nil) - checkFind(t, tree, "/eos/project/c/cernbox/shared/sub/deep", []acl.Entry{aliceRead}, nil) + checkFind(t, tree, "/eos/project/c/cernbox/shared", []*acl.Entry{aliceRead()}, nil) + checkFind(t, tree, "/eos/project/c/cernbox/shared/sub/deep", []*acl.Entry{aliceRead()}, nil) checkFind(t, tree, "/eos/project/c/cernbox/other", nil, nil) // a sibling with a common prefix but a different path segment checkFind(t, tree, "/eos/project/c/cernbox/sharedother", nil, nil) @@ -95,44 +120,57 @@ func TestRuleAppliesRecursively(t *testing.T) { // A deeper rule adds its entity to the ones inherited from above. func TestDeeperRuleAddsEntity(t *testing.T) { tree := NewACLTree() - tree.Insert(&ACLNode{Path: "/eos/project/c/cernbox", MandatoryACLs: []acl.Entry{aliceRead}}) - tree.Insert(&ACLNode{Path: "/eos/project/c/cernbox/sub", MandatoryACLs: []acl.Entry{bobRead}}) + tree.Insert(&ACLNode{Path: "/eos/project/c/cernbox", MandatoryACLs: []*acl.Entry{aliceRead()}}) + tree.Insert(&ACLNode{Path: "/eos/project/c/cernbox/sub", MandatoryACLs: []*acl.Entry{bobRead()}}) - checkFind(t, tree, "/eos/project/c/cernbox", []acl.Entry{aliceRead}, nil) - checkFind(t, tree, "/eos/project/c/cernbox/sub", []acl.Entry{aliceRead, bobRead}, nil) - checkFind(t, tree, "/eos/project/c/cernbox/sub/deep", []acl.Entry{aliceRead, bobRead}, nil) - checkFind(t, tree, "/eos/project/c/cernbox/elsewhere", []acl.Entry{aliceRead}, nil) + checkFind(t, tree, "/eos/project/c/cernbox", []*acl.Entry{aliceRead()}, nil) + checkFind(t, tree, "/eos/project/c/cernbox/sub", []*acl.Entry{aliceRead(), bobRead()}, nil) + checkFind(t, tree, "/eos/project/c/cernbox/sub/deep", []*acl.Entry{aliceRead(), bobRead()}, nil) + checkFind(t, tree, "/eos/project/c/cernbox/elsewhere", []*acl.Entry{aliceRead()}, nil) } // A deeper rule raises the permission of an entity, and never lowers it. func TestDeeperRuleRaisesPermission(t *testing.T) { t.Run("raise", func(t *testing.T) { tree := NewACLTree() - tree.Insert(&ACLNode{Path: "/eos/project/c/cernbox", MandatoryACLs: []acl.Entry{aliceRead}}) - tree.Insert(&ACLNode{Path: "/eos/project/c/cernbox/sub", MandatoryACLs: []acl.Entry{aliceWrite}}) + tree.Insert(&ACLNode{Path: "/eos/project/c/cernbox", MandatoryACLs: []*acl.Entry{aliceRead()}}) + tree.Insert(&ACLNode{Path: "/eos/project/c/cernbox/sub", MandatoryACLs: []*acl.Entry{aliceWrite()}}) - checkFind(t, tree, "/eos/project/c/cernbox", []acl.Entry{aliceRead}, nil) - checkFind(t, tree, "/eos/project/c/cernbox/sub", []acl.Entry{aliceWrite}, nil) + checkFind(t, tree, "/eos/project/c/cernbox", []*acl.Entry{aliceRead()}, nil) + checkFind(t, tree, "/eos/project/c/cernbox/sub", []*acl.Entry{aliceWrite()}, nil) }) t.Run("no lowering", func(t *testing.T) { tree := NewACLTree() - tree.Insert(&ACLNode{Path: "/eos/project/c/cernbox", MandatoryACLs: []acl.Entry{aliceWrite}}) - tree.Insert(&ACLNode{Path: "/eos/project/c/cernbox/sub", MandatoryACLs: []acl.Entry{aliceRead}}) + tree.Insert(&ACLNode{Path: "/eos/project/c/cernbox", MandatoryACLs: []*acl.Entry{aliceWrite()}}) + tree.Insert(&ACLNode{Path: "/eos/project/c/cernbox/sub", MandatoryACLs: []*acl.Entry{aliceRead()}}) - checkFind(t, tree, "/eos/project/c/cernbox/sub", []acl.Entry{aliceWrite}, nil) + checkFind(t, tree, "/eos/project/c/cernbox/sub", []*acl.Entry{aliceWrite()}, nil) }) } +// A rule that raises a permission deeper down must not raise it above. The +// node inherited the entry from its parent, so the two must not end up as one +// entry that both nodes write to. +func TestDeeperRuleDoesNotChangeTheParent(t *testing.T) { + tree := NewACLTree() + tree.Insert(&ACLNode{Path: "/eos/project/c/cernbox", MandatoryACLs: []*acl.Entry{aliceRead()}}) + tree.Insert(&ACLNode{Path: "/eos/project/c/cernbox/sub", MandatoryACLs: []*acl.Entry{bobRead()}}) + tree.Insert(&ACLNode{Path: "/eos/project/c/cernbox/sub", MandatoryACLs: []*acl.Entry{aliceWrite()}}) + + checkFind(t, tree, "/eos/project/c/cernbox", []*acl.Entry{aliceRead()}, nil) + checkFind(t, tree, "/eos/project/c/cernbox/sub", []*acl.Entry{aliceWrite(), bobRead()}, nil) +} + // Rules do not arrive in tree order, so the result must not depend on the // insertion order. Insert takes the node over, so every order gets its own // nodes. func TestInsertOrderDoesNotMatter(t *testing.T) { newNodes := func() []*ACLNode { return []*ACLNode{ - {Path: "/eos/project/c/cernbox", MandatoryACLs: []acl.Entry{aliceRead}}, - {Path: "/eos/project/c/cernbox/a", MandatoryACLs: []acl.Entry{bobRead}}, - {Path: "/eos/project/c/cernbox/a/b", MandatoryACLs: []acl.Entry{groupRead}}, + {Path: "/eos/project/c/cernbox", MandatoryACLs: []*acl.Entry{aliceRead()}}, + {Path: "/eos/project/c/cernbox/a", MandatoryACLs: []*acl.Entry{bobRead()}}, + {Path: "/eos/project/c/cernbox/a/b", MandatoryACLs: []*acl.Entry{groupRead()}}, } } @@ -153,10 +191,10 @@ func TestInsertOrderDoesNotMatter(t *testing.T) { tree.Insert(nodes[i]) } - checkFind(t, tree, "/eos/project/c/cernbox", []acl.Entry{aliceRead}, nil) - checkFind(t, tree, "/eos/project/c/cernbox/a", []acl.Entry{aliceRead, bobRead}, nil) - checkFind(t, tree, "/eos/project/c/cernbox/a/b", []acl.Entry{aliceRead, bobRead, groupRead}, nil) - checkFind(t, tree, "/eos/project/c/cernbox/a/b/c", []acl.Entry{aliceRead, bobRead, groupRead}, nil) + checkFind(t, tree, "/eos/project/c/cernbox", []*acl.Entry{aliceRead()}, nil) + checkFind(t, tree, "/eos/project/c/cernbox/a", []*acl.Entry{aliceRead(), bobRead()}, nil) + checkFind(t, tree, "/eos/project/c/cernbox/a/b", []*acl.Entry{aliceRead(), bobRead(), groupRead()}, nil) + checkFind(t, tree, "/eos/project/c/cernbox/a/b/c", []*acl.Entry{aliceRead(), bobRead(), groupRead()}, nil) }) } } @@ -166,56 +204,56 @@ func TestInsertOrderDoesNotMatter(t *testing.T) { func TestTwoRulesOnTheSamePath(t *testing.T) { tree := NewACLTree() p := "/eos/project/c/cernbox/shared" - tree.Insert(&ACLNode{Path: p, MandatoryACLs: []acl.Entry{aliceRead}}) - tree.Insert(&ACLNode{Path: p, MandatoryACLs: []acl.Entry{bobRead}}) - tree.Insert(&ACLNode{Path: p, MandatoryACLs: []acl.Entry{aliceWrite}}) + tree.Insert(&ACLNode{Path: p, MandatoryACLs: []*acl.Entry{aliceRead()}}) + tree.Insert(&ACLNode{Path: p, MandatoryACLs: []*acl.Entry{bobRead()}}) + tree.Insert(&ACLNode{Path: p, MandatoryACLs: []*acl.Entry{aliceWrite()}}) - checkFind(t, tree, p, []acl.Entry{aliceWrite, bobRead}, nil) - checkFind(t, tree, p+"/sub", []acl.Entry{aliceWrite, bobRead}, nil) + checkFind(t, tree, p, []*acl.Entry{aliceWrite(), bobRead()}, nil) + checkFind(t, tree, p+"/sub", []*acl.Entry{aliceWrite(), bobRead()}, nil) } // A second rule on a path that already has a subtree must reach that subtree // too. func TestSecondRuleOnAPathWithChildren(t *testing.T) { tree := NewACLTree() - tree.Insert(&ACLNode{Path: "/eos/project/c/cernbox", MandatoryACLs: []acl.Entry{aliceRead}}) - tree.Insert(&ACLNode{Path: "/eos/project/c/cernbox/sub", MandatoryACLs: []acl.Entry{bobRead}}) - tree.Insert(&ACLNode{Path: "/eos/project/c/cernbox", MandatoryACLs: []acl.Entry{groupRead}}) + tree.Insert(&ACLNode{Path: "/eos/project/c/cernbox", MandatoryACLs: []*acl.Entry{aliceRead()}}) + tree.Insert(&ACLNode{Path: "/eos/project/c/cernbox/sub", MandatoryACLs: []*acl.Entry{bobRead()}}) + tree.Insert(&ACLNode{Path: "/eos/project/c/cernbox", MandatoryACLs: []*acl.Entry{groupRead()}}) - checkFind(t, tree, "/eos/project/c/cernbox", []acl.Entry{aliceRead, groupRead}, nil) - checkFind(t, tree, "/eos/project/c/cernbox/sub", []acl.Entry{aliceRead, bobRead, groupRead}, nil) + checkFind(t, tree, "/eos/project/c/cernbox", []*acl.Entry{aliceRead(), groupRead()}, nil) + checkFind(t, tree, "/eos/project/c/cernbox/sub", []*acl.Entry{aliceRead(), bobRead(), groupRead()}, nil) } // A rule can sit below a path that has no rule of its own. func TestGapBetweenRules(t *testing.T) { tree := NewACLTree() - tree.Insert(&ACLNode{Path: "/eos/project/c/cernbox", MandatoryACLs: []acl.Entry{aliceRead}}) - tree.Insert(&ACLNode{Path: "/eos/project/c/cernbox/a/b/c", MandatoryACLs: []acl.Entry{bobRead}}) + tree.Insert(&ACLNode{Path: "/eos/project/c/cernbox", MandatoryACLs: []*acl.Entry{aliceRead()}}) + tree.Insert(&ACLNode{Path: "/eos/project/c/cernbox/a/b/c", MandatoryACLs: []*acl.Entry{bobRead()}}) - checkFind(t, tree, "/eos/project/c/cernbox/a", []acl.Entry{aliceRead}, nil) - checkFind(t, tree, "/eos/project/c/cernbox/a/b", []acl.Entry{aliceRead}, nil) - checkFind(t, tree, "/eos/project/c/cernbox/a/b/c", []acl.Entry{aliceRead, bobRead}, nil) + checkFind(t, tree, "/eos/project/c/cernbox/a", []*acl.Entry{aliceRead()}, nil) + checkFind(t, tree, "/eos/project/c/cernbox/a/b", []*acl.Entry{aliceRead()}, nil) + checkFind(t, tree, "/eos/project/c/cernbox/a/b/c", []*acl.Entry{aliceRead(), bobRead()}, nil) } // Find cleans the path it gets, so a caller can pass a path as it comes from // the namespace. func TestFindCleansThePath(t *testing.T) { tree := NewACLTree() - tree.Insert(&ACLNode{Path: "/eos/project/c/cernbox", MandatoryACLs: []acl.Entry{aliceRead}}) + tree.Insert(&ACLNode{Path: "/eos/project/c/cernbox", MandatoryACLs: []*acl.Entry{aliceRead()}}) - checkFind(t, tree, "/eos/project/c/cernbox/", []acl.Entry{aliceRead}, nil) - checkFind(t, tree, "/eos/project/c/cernbox/sub/..", []acl.Entry{aliceRead}, nil) - checkFind(t, tree, "/eos/project/c/other/../cernbox/sub", []acl.Entry{aliceRead}, nil) + checkFind(t, tree, "/eos/project/c/cernbox/", []*acl.Entry{aliceRead()}, nil) + checkFind(t, tree, "/eos/project/c/cernbox/sub/..", []*acl.Entry{aliceRead()}, nil) + checkFind(t, tree, "/eos/project/c/other/../cernbox/sub", []*acl.Entry{aliceRead()}, nil) } // Sibling subtrees stay independent. func TestSiblingsAreIndependent(t *testing.T) { tree := NewACLTree() - tree.Insert(&ACLNode{Path: "/eos/project/c/cernbox/a", MandatoryACLs: []acl.Entry{aliceRead}}) - tree.Insert(&ACLNode{Path: "/eos/project/c/cernbox/b", MandatoryACLs: []acl.Entry{bobRead}}) + tree.Insert(&ACLNode{Path: "/eos/project/c/cernbox/a", MandatoryACLs: []*acl.Entry{aliceRead()}}) + tree.Insert(&ACLNode{Path: "/eos/project/c/cernbox/b", MandatoryACLs: []*acl.Entry{bobRead()}}) - checkFind(t, tree, "/eos/project/c/cernbox/a/deep", []acl.Entry{aliceRead}, nil) - checkFind(t, tree, "/eos/project/c/cernbox/b/deep", []acl.Entry{bobRead}, nil) + checkFind(t, tree, "/eos/project/c/cernbox/a/deep", []*acl.Entry{aliceRead()}, nil) + checkFind(t, tree, "/eos/project/c/cernbox/b/deep", []*acl.Entry{bobRead()}, nil) checkFind(t, tree, "/eos/project/c/cernbox", nil, nil) } @@ -224,16 +262,16 @@ func TestMandatoryAndAllowedAreSeparate(t *testing.T) { tree := NewACLTree() tree.Insert(&ACLNode{ Path: "/eos/project/c/cernbox", - MandatoryACLs: []acl.Entry{aliceRead}, - AllowedACLs: []acl.Entry{external}, + MandatoryACLs: []*acl.Entry{aliceRead()}, + AllowedACLs: []*acl.Entry{external()}, }) tree.Insert(&ACLNode{ Path: "/eos/project/c/cernbox/sub", - MandatoryACLs: []acl.Entry{bobRead}, + MandatoryACLs: []*acl.Entry{bobRead()}, }) - checkFind(t, tree, "/eos/project/c/cernbox", []acl.Entry{aliceRead}, []acl.Entry{external}) - checkFind(t, tree, "/eos/project/c/cernbox/sub", []acl.Entry{aliceRead, bobRead}, []acl.Entry{external}) + checkFind(t, tree, "/eos/project/c/cernbox", []*acl.Entry{aliceRead()}, []*acl.Entry{external()}) + checkFind(t, tree, "/eos/project/c/cernbox/sub", []*acl.Entry{aliceRead(), bobRead()}, []*acl.Entry{external()}) } // The deep job looks up every namespace entry, so it may want to do that with @@ -241,9 +279,9 @@ func TestMandatoryAndAllowedAreSeparate(t *testing.T) { // is built. Run with -race. func TestFindIsSafeForConcurrentUse(t *testing.T) { tree := NewACLTree() - tree.Insert(&ACLNode{Path: "/eos/project/c/cernbox", MandatoryACLs: []acl.Entry{aliceRead}}) - tree.Insert(&ACLNode{Path: "/eos/project/c/cernbox/a", MandatoryACLs: []acl.Entry{bobRead}}) - tree.Insert(&ACLNode{Path: "/eos/project/c/cernbox/b", MandatoryACLs: []acl.Entry{groupRead}}) + tree.Insert(&ACLNode{Path: "/eos/project/c/cernbox", MandatoryACLs: []*acl.Entry{aliceRead()}}) + tree.Insert(&ACLNode{Path: "/eos/project/c/cernbox/a", MandatoryACLs: []*acl.Entry{bobRead()}}) + tree.Insert(&ACLNode{Path: "/eos/project/c/cernbox/b", MandatoryACLs: []*acl.Entry{groupRead()}}) paths := []string{ "/eos/project/c/cernbox", @@ -267,8 +305,8 @@ func TestFindIsSafeForConcurrentUse(t *testing.T) { wg.Wait() // the lookups changed nothing - checkFind(t, tree, "/eos/project/c/cernbox/a/file.txt", []acl.Entry{aliceRead, bobRead}, nil) - checkFind(t, tree, "/eos/project/c/cernbox/b/sub/file.txt", []acl.Entry{aliceRead, groupRead}, nil) + checkFind(t, tree, "/eos/project/c/cernbox/a/file.txt", []*acl.Entry{aliceRead(), bobRead()}, nil) + checkFind(t, tree, "/eos/project/c/cernbox/b/sub/file.txt", []*acl.Entry{aliceRead(), groupRead()}, nil) } // Matches tells whether the node covers the path: the node itself or anything @@ -347,52 +385,52 @@ func TestFastHasPrefix(t *testing.T) { func TestMergeACLs(t *testing.T) { tests := []struct { name string - a, b []acl.Entry - want []acl.Entry + a, b []*acl.Entry + want []*acl.Entry }{ { name: "different entities are kept both", - a: []acl.Entry{aliceRead}, - b: []acl.Entry{bobRead}, - want: []acl.Entry{aliceRead, bobRead}, + a: []*acl.Entry{aliceRead()}, + b: []*acl.Entry{bobRead()}, + want: []*acl.Entry{aliceRead(), bobRead()}, }, { name: "same entity keeps the highest permission", - a: []acl.Entry{aliceRead}, - b: []acl.Entry{aliceWrite}, - want: []acl.Entry{aliceWrite}, + a: []*acl.Entry{aliceRead()}, + b: []*acl.Entry{aliceWrite()}, + want: []*acl.Entry{aliceWrite()}, }, { name: "the highest permission wins in both directions", - a: []acl.Entry{aliceWrite}, - b: []acl.Entry{aliceRead}, - want: []acl.Entry{aliceWrite}, + a: []*acl.Entry{aliceWrite()}, + b: []*acl.Entry{aliceRead()}, + want: []*acl.Entry{aliceWrite()}, }, { name: "same qualifier with another type is another entry", - a: []acl.Entry{{Type: acl.TypeUser, Qualifier: "x", Permissions: "rx"}}, - b: []acl.Entry{{Type: acl.TypeGroup, Qualifier: "x", Permissions: "rx"}}, - want: []acl.Entry{ + a: []*acl.Entry{{Type: acl.TypeUser, Qualifier: "x", Permissions: "rx"}}, + b: []*acl.Entry{{Type: acl.TypeGroup, Qualifier: "x", Permissions: "rx"}}, + want: []*acl.Entry{ {Type: acl.TypeUser, Qualifier: "x", Permissions: "rx"}, {Type: acl.TypeGroup, Qualifier: "x", Permissions: "rx"}, }, }, { name: "empty second set", - a: []acl.Entry{aliceRead}, - want: []acl.Entry{aliceRead}, + a: []*acl.Entry{aliceRead()}, + want: []*acl.Entry{aliceRead()}, }, { name: "empty first set", - b: []acl.Entry{aliceRead}, - want: []acl.Entry{aliceRead}, + b: []*acl.Entry{aliceRead()}, + want: []*acl.Entry{aliceRead()}, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if got := mergeACLs(tt.a, tt.b); !sameEntries(got, tt.want) { - t.Errorf("mergeACLs(%v, %v) = %v, want %v", tt.a, tt.b, got, tt.want) + t.Errorf("mergeACLs(%v, %v) = %v, want %v", formatEntries(tt.a), formatEntries(tt.b), formatEntries(got), formatEntries(tt.want)) } }) } @@ -401,16 +439,16 @@ func TestMergeACLs(t *testing.T) { // The two input sets belong to other nodes of the tree, so a merge must not // change them. func TestMergeACLsDoesNotChangeItsInput(t *testing.T) { - a := []acl.Entry{aliceRead} - b := []acl.Entry{aliceWrite, bobRead} + a := []*acl.Entry{aliceRead()} + b := []*acl.Entry{aliceWrite(), bobRead()} mergeACLs(a, b) - if !sameEntries(a, []acl.Entry{aliceRead}) { - t.Errorf("first set = %v, want %v", a, []acl.Entry{aliceRead}) + if !sameEntries(a, []*acl.Entry{aliceRead()}) { + t.Errorf("first set = %v, want %v", formatEntries(a), formatEntries([]*acl.Entry{aliceRead()})) } - if !sameEntries(b, []acl.Entry{aliceWrite, bobRead}) { - t.Errorf("second set = %v, want %v", b, []acl.Entry{aliceWrite, bobRead}) + if !sameEntries(b, []*acl.Entry{aliceWrite(), bobRead()}) { + t.Errorf("second set = %v, want %v", formatEntries(b), formatEntries([]*acl.Entry{aliceWrite(), bobRead()})) } } @@ -462,8 +500,8 @@ func benchNodes(paths []string) []*ACLNode { for i, p := range paths { nodes[i] = &ACLNode{ Path: p, - MandatoryACLs: []acl.Entry{{Type: acl.TypeUser, Qualifier: fmt.Sprintf("user%d", i), Permissions: "rx"}}, - AllowedACLs: []acl.Entry{external}, + MandatoryACLs: []*acl.Entry{{Type: acl.TypeUser, Qualifier: fmt.Sprintf("user%d", i), Permissions: "rx"}}, + AllowedACLs: []*acl.Entry{external()}, } } return nodes diff --git a/pkg/reconciliation/deep.go b/pkg/reconciliation/deep.go index c78cb72938..6cf90d4d5e 100644 --- a/pkg/reconciliation/deep.go +++ b/pkg/reconciliation/deep.go @@ -21,26 +21,27 @@ package reconciliation import ( "cmp" "context" - "fmt" "slices" collaborationv1beta1 "github.com/cs3org/go-cs3apis/cs3/sharing/collaboration/v1beta1" provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" + "github.com/cs3org/reva/v3/pkg/reconciliation/nsdump" "github.com/cs3org/reva/v3/pkg/share/manager/sql" "github.com/cs3org/reva/v3/pkg/spaces" - "github.com/cs3org/reva/v3/pkg/storage/utils/acl" + "github.com/cs3org/reva/v3/pkg/storage/fs/eos/acl" ) const JobName = "reconciliation.deep" -type RunResult struct { - Entries []EntryResult +type EntryResult struct { } -type EntryResult struct { +type ChangeSet []*Change + +type Change struct { Path string Action ActionKind - ACL acl.Entry + ACL *acl.Entry } type DeepJob struct { @@ -60,7 +61,7 @@ type ShareWithPath struct { func (s *ShareWithPath) toTreeNode() *ACLNode { return &ACLNode{ Path: s.Path, - MandatoryACLs: []acl.Entry{shareToACL(s.Share)}, + MandatoryACLs: []*acl.Entry{shareToACL(s.Share)}, } } @@ -111,7 +112,10 @@ func (j *DeepJob) run(ctx context.Context, p RunParameters) error { // We start by taking a dump of the namespace, and then we // parse this entry-by-entry - namespaceDump, err := NewEOSMemoryNSInspect() + namespaceDumper := nsdump.EOSMemoryNSInspect{} + namespaceDumper.Setup(map[string]any{ + // TODO(jgeens): set config for dump + }) if err != nil { return err } @@ -121,11 +125,10 @@ func (j *DeepJob) run(ctx context.Context, p RunParameters) error { return err } - ns, err := namespaceDump.Dump(path, 0) + dump, err := namespaceDumper.Dump(path, 0) - for _, entry := range ns.entries { - fmt.Print(entry.Path) - } + // Now we do the diff and get the resulting ChangeSet + compare(tree, dump) // changeSet := return nil } @@ -134,7 +137,62 @@ func (j *DeepJob) getPath(rid *provider.ResourceId) (string, bool) { return "", true } -func shareToACL(s *collaborationv1beta1.Share) acl.Entry { +func compare(tree *ACLTree, ns *nsdump.NamespaceDump) ChangeSet { + changeSet := ChangeSet{} + for _, e := range ns.Entries { + changeSet = append(changeSet, compareEntry(tree, e)...) + } + return changeSet +} + +func compareEntry(tree *ACLTree, entry nsdump.NameSpaceEntry) ChangeSet { + m, o, ok := tree.Find(entry.Path) + if !ok { + return nil + } + + actualACLs := parseACLs(entry.XattrSysAcl) + return calculateChangeSet(m, o, actualACLs, entry.Path) + +} + +func parseACLs(sysattr string) []*acl.Entry { + acls, err := acl.Parse(sysattr, acl.ShortTextForm) + if err != nil { + return nil + } + return acls.Entries +} + +func calculateChangeSet(mandatory, optional, actual []*acl.Entry, p string) ChangeSet { + changeSet := ChangeSet{} + + // First calculate missing entries on `actual` + for _, e := range mandatory { + if !slices.Contains(actual, e) { + changeSet = append(changeSet, &Change{ + Path: p, + Action: ActionAdd, + ACL: e, + }) + } + } + + // Then calculate which entries should not be there + for _, e := range actual { + if !slices.Contains(mandatory, e) && !slices.Contains(optional, e) { + changeSet = append(changeSet, &Change{ + Path: p, + Action: ActionDelete, + ACL: e, + }) + } + } + + return changeSet +} + +func shareToACL(s *collaborationv1beta1.Share) *acl.Entry { var e acl.Entry switch { case s.Grantee.Type == provider.GranteeType_GRANTEE_TYPE_GROUP: @@ -144,9 +202,9 @@ func shareToACL(s *collaborationv1beta1.Share) acl.Entry { e.Type = "u" e.Qualifier = s.Grantee.GetUserId().GetOpaqueId() default: - return e + return &e } // TODO(jgeens): set permissions - return e + return &e } diff --git a/pkg/reconciliation/nsdump/chunked_ns_dump.go b/pkg/reconciliation/nsdump/chunked_ns_dump.go new file mode 100644 index 0000000000..8b338ba7ab --- /dev/null +++ b/pkg/reconciliation/nsdump/chunked_ns_dump.go @@ -0,0 +1,3 @@ +package nsdump + +// TODO(jgeens): implement diff --git a/pkg/reconciliation/nsdump/file_ns_dump.go b/pkg/reconciliation/nsdump/file_ns_dump.go new file mode 100644 index 0000000000..7f5b1b206a --- /dev/null +++ b/pkg/reconciliation/nsdump/file_ns_dump.go @@ -0,0 +1,33 @@ +package nsdump + +import ( + "os" + + "github.com/pkg/errors" +) + +type EOSFileNSInspect struct { + file string +} + +func (e *EOSFileNSInspect) Setup(config map[string]any) error { + v, ok := config["file"] + if !ok { + return errors.New("file parameter must be present") + } + s, ok := v.(string) + if !ok { + return errors.New("file parameter must be a string representing a file path") + } + e.file = s + return nil +} + +func (e *EOSFileNSInspect) Dump(rootPath string, maxDepth int) (*NamespaceDump, error) { + contents, err := os.ReadFile(e.file) + if err != nil { + return nil, err + } + + return parseNSInspectOutput(contents) +} diff --git a/pkg/reconciliation/nsdump/memory_ns_dump.go b/pkg/reconciliation/nsdump/memory_ns_dump.go new file mode 100644 index 0000000000..c9b1cb819f --- /dev/null +++ b/pkg/reconciliation/nsdump/memory_ns_dump.go @@ -0,0 +1,56 @@ +package nsdump + +import ( + "bytes" + "fmt" + "os/exec" + "strings" +) + +type EOSMemoryNSInspectConfig struct { + maxDepth int `mapstructure:"maxdepth"` + ignoreFiles bool `mapstructure:"ignorefiles"` + instance string `mapstructure:"instance"` +} + +type EOSMemoryNSInspect struct { + cfg EOSMemoryNSInspectConfig +} + +func (e *EOSMemoryNSInspect) Setup(config map[string]any) error { + // TODO(jgeens): implement + return nil +} + +func (e *EOSMemoryNSInspect) Dump(rootPath string, maxDepth int) (*NamespaceDump, error) { + + noFilesFlag := "" + if e.cfg.ignoreFiles { + noFilesFlag = " --no-files" + } + + maxDepthFlag := "" + if maxDepth > 0 { + maxDepthFlag = fmt.Sprintf(" --maxdepth %d", maxDepth) + } + + args := fmt.Sprintf( + "scan --path %s %s --members %s-qdb:7777 --password-file /keytabs/%s_keytab --json%s", + rootPath, + maxDepthFlag, + e.cfg.instance, + e.cfg.instance, + noFilesFlag, + ) + + cmd := exec.Command("/usr/bin/eos-ns-inspect", strings.Split(args, " ")...) + + var stdout bytes.Buffer + cmd.Stdout = &stdout + err := cmd.Run() + if err != nil { + return nil, err + } + + return parseNSInspectOutput(stdout.Bytes()) +} diff --git a/pkg/reconciliation/ns_dump.go b/pkg/reconciliation/nsdump/ns_dump.go similarity index 60% rename from pkg/reconciliation/ns_dump.go rename to pkg/reconciliation/nsdump/ns_dump.go index 9a563b8143..743a3a77cb 100644 --- a/pkg/reconciliation/ns_dump.go +++ b/pkg/reconciliation/nsdump/ns_dump.go @@ -1,32 +1,20 @@ -package reconciliation +package nsdump import ( - "bytes" "encoding/json" - "fmt" - "os/exec" "path" "strings" "github.com/pkg/errors" ) -type NSDumpResponse struct { - entries []NameSpaceEntry +type NamespaceDump struct { + Entries []NameSpaceEntry } type NSDumpClient interface { - Dump(rootPath string, maxDepth int) (NSDumpResponse, error) -} - -type EOSMemoryNSInspect struct { - cfg EOSNSInspectConfig -} - -type EOSNSInspectConfig struct { - maxDepth int - ignoreFiles bool - instance string + Dump(rootPath string, maxDepth int) (NamespaceDump, error) + Setup(config map[string]any) error } type EntryType string @@ -84,46 +72,9 @@ func (d *NameSpaceEntry) IsSysFile() bool { return d.EntryType == EntryTypeFile && strings.HasPrefix(path.Base(path.Dir(d.Path)), ".sys.") } -func NewEOSMemoryNSInspect() (*EOSMemoryNSInspect, error) { - return &EOSMemoryNSInspect{}, nil -} - -func (e *EOSMemoryNSInspect) Dump(rootPath string, maxDepth int) (*NSDumpResponse, error) { - - noFilesFlag := "" - if e.cfg.ignoreFiles { - noFilesFlag = " --no-files" - } - - maxDepthFlag := "" - if maxDepth > 0 { - maxDepthFlag = fmt.Sprintf(" --maxdepth %d", maxDepth) - } - - args := fmt.Sprintf( - "scan --path %s %s --members %s-qdb:7777 --password-file /keytabs/%s_keytab --json%s", - rootPath, - maxDepthFlag, - e.cfg.instance, - e.cfg.instance, - noFilesFlag, - ) - - cmd := exec.Command("/usr/bin/eos-ns-inspect", strings.Split(args, " ")...) - - var stdout bytes.Buffer - cmd.Stdout = &stdout - err := cmd.Run() - if err != nil { - return nil, err - } - - return parseNSInspectOutput(&stdout) -} - -func parseNSInspectOutput(data *bytes.Buffer) (*NSDumpResponse, error) { +func parseNSInspectOutput(data []byte) (*NamespaceDump, error) { var raw []map[string]interface{} - if err := json.Unmarshal(data.Bytes(), &raw); err != nil { + if err := json.Unmarshal(data, &raw); err != nil { return nil, errors.Wrap(err, "failed to unmarshal ns-inspect input") } @@ -141,5 +92,5 @@ func parseNSInspectOutput(data *bytes.Buffer) (*NSDumpResponse, error) { entries = append(entries, e) } - return &NSDumpResponse{entries: entries}, nil + return &NamespaceDump{Entries: entries}, nil } diff --git a/pkg/storage/utils/acl/acl.go b/pkg/storage/fs/eos/acl/acl.go similarity index 100% rename from pkg/storage/utils/acl/acl.go rename to pkg/storage/fs/eos/acl/acl.go diff --git a/pkg/storage/fs/eos/auth.go b/pkg/storage/fs/eos/auth.go index 1db7e0699c..99a74f24a2 100644 --- a/pkg/storage/fs/eos/auth.go +++ b/pkg/storage/fs/eos/auth.go @@ -35,8 +35,8 @@ import ( "github.com/cs3org/reva/v3/pkg/permissions" "github.com/cs3org/reva/v3/pkg/rgrpc/status" "github.com/cs3org/reva/v3/pkg/service" + "github.com/cs3org/reva/v3/pkg/storage/fs/eos/acl" eosclient "github.com/cs3org/reva/v3/pkg/storage/fs/eos/client" - "github.com/cs3org/reva/v3/pkg/storage/utils/acl" "github.com/cs3org/reva/v3/pkg/utils" ) diff --git a/pkg/storage/fs/eos/client/binary/eosbinary.go b/pkg/storage/fs/eos/client/binary/eosbinary.go index 2ae7fba783..c40d095770 100644 --- a/pkg/storage/fs/eos/client/binary/eosbinary.go +++ b/pkg/storage/fs/eos/client/binary/eosbinary.go @@ -38,8 +38,8 @@ import ( "github.com/cs3org/reva/v3/pkg/storage" "github.com/cs3org/reva/v3/pkg/errtypes" + "github.com/cs3org/reva/v3/pkg/storage/fs/eos/acl" eosclient "github.com/cs3org/reva/v3/pkg/storage/fs/eos/client" - "github.com/cs3org/reva/v3/pkg/storage/utils/acl" "github.com/cs3org/reva/v3/pkg/trace" "github.com/google/uuid" "github.com/pkg/errors" diff --git a/pkg/storage/fs/eos/client/eosclient.go b/pkg/storage/fs/eos/client/eosclient.go index a1f6495fee..9c92e16595 100644 --- a/pkg/storage/fs/eos/client/eosclient.go +++ b/pkg/storage/fs/eos/client/eosclient.go @@ -26,7 +26,7 @@ import ( "github.com/cs3org/reva/v3/pkg/errtypes" "github.com/cs3org/reva/v3/pkg/storage" - "github.com/cs3org/reva/v3/pkg/storage/utils/acl" + "github.com/cs3org/reva/v3/pkg/storage/fs/eos/acl" ) // EOSClient is the interface which enables access to EOS instances through various interfaces. diff --git a/pkg/storage/fs/eos/client/grpc/acl.go b/pkg/storage/fs/eos/client/grpc/acl.go index ef366706a9..d74c2bf546 100644 --- a/pkg/storage/fs/eos/client/grpc/acl.go +++ b/pkg/storage/fs/eos/client/grpc/acl.go @@ -8,8 +8,8 @@ import ( erpc "github.com/cern-eos/go-eosgrpc" "github.com/cs3org/reva/v3/pkg/appctx" "github.com/cs3org/reva/v3/pkg/errtypes" + "github.com/cs3org/reva/v3/pkg/storage/fs/eos/acl" eosclient "github.com/cs3org/reva/v3/pkg/storage/fs/eos/client" - "github.com/cs3org/reva/v3/pkg/storage/utils/acl" "github.com/pkg/errors" ) diff --git a/pkg/storage/fs/eos/client/grpc/auth.go b/pkg/storage/fs/eos/client/grpc/auth.go index c87c28d359..423bb1e39c 100644 --- a/pkg/storage/fs/eos/client/grpc/auth.go +++ b/pkg/storage/fs/eos/client/grpc/auth.go @@ -8,8 +8,8 @@ import ( erpc "github.com/cern-eos/go-eosgrpc" "github.com/cs3org/reva/v3/pkg/appctx" "github.com/cs3org/reva/v3/pkg/errtypes" + "github.com/cs3org/reva/v3/pkg/storage/fs/eos/acl" eosclient "github.com/cs3org/reva/v3/pkg/storage/fs/eos/client" - "github.com/cs3org/reva/v3/pkg/storage/utils/acl" ) // GenerateToken returns a token on behalf of the resource owner to be used by lightweight accounts. diff --git a/pkg/storage/fs/eos/client/grpc/file_ops.go b/pkg/storage/fs/eos/client/grpc/file_ops.go index 7646d51365..5ee005c5c6 100644 --- a/pkg/storage/fs/eos/client/grpc/file_ops.go +++ b/pkg/storage/fs/eos/client/grpc/file_ops.go @@ -10,8 +10,8 @@ import ( erpc "github.com/cern-eos/go-eosgrpc" "github.com/cs3org/reva/v3/pkg/appctx" "github.com/cs3org/reva/v3/pkg/errtypes" + "github.com/cs3org/reva/v3/pkg/storage/fs/eos/acl" eosclient "github.com/cs3org/reva/v3/pkg/storage/fs/eos/client" - "github.com/cs3org/reva/v3/pkg/storage/utils/acl" "github.com/cs3org/reva/v3/pkg/trace" "github.com/pkg/errors" ) diff --git a/pkg/storage/fs/eos/client/grpc/utils.go b/pkg/storage/fs/eos/client/grpc/utils.go index c37411c323..e7be554f6d 100644 --- a/pkg/storage/fs/eos/client/grpc/utils.go +++ b/pkg/storage/fs/eos/client/grpc/utils.go @@ -7,8 +7,8 @@ import ( erpc "github.com/cern-eos/go-eosgrpc" "github.com/cs3org/reva/v3/pkg/errtypes" + "github.com/cs3org/reva/v3/pkg/storage/fs/eos/acl" eosclient "github.com/cs3org/reva/v3/pkg/storage/fs/eos/client" - "github.com/cs3org/reva/v3/pkg/storage/utils/acl" ) // If the error is not nil, take that diff --git a/pkg/storage/fs/eos/client/utils.go b/pkg/storage/fs/eos/client/utils.go index 27c83948d2..49f9c43102 100644 --- a/pkg/storage/fs/eos/client/utils.go +++ b/pkg/storage/fs/eos/client/utils.go @@ -25,7 +25,7 @@ import ( "strings" "github.com/cs3org/reva/v3/pkg/errtypes" - "github.com/cs3org/reva/v3/pkg/storage/utils/acl" + "github.com/cs3org/reva/v3/pkg/storage/fs/eos/acl" ) const ( diff --git a/pkg/storage/fs/eos/eosfs.go b/pkg/storage/fs/eos/eosfs.go index 177e10ea66..e1d63f0a43 100644 --- a/pkg/storage/fs/eos/eosfs.go +++ b/pkg/storage/fs/eos/eosfs.go @@ -45,10 +45,10 @@ import ( "github.com/cs3org/reva/v3/pkg/sharedconf" "github.com/cs3org/reva/v3/pkg/spaces" "github.com/cs3org/reva/v3/pkg/storage" + "github.com/cs3org/reva/v3/pkg/storage/fs/eos/acl" eosclient "github.com/cs3org/reva/v3/pkg/storage/fs/eos/client" eosbinary "github.com/cs3org/reva/v3/pkg/storage/fs/eos/client/binary" eosgrpc "github.com/cs3org/reva/v3/pkg/storage/fs/eos/client/grpc" - "github.com/cs3org/reva/v3/pkg/storage/utils/acl" "github.com/cs3org/reva/v3/pkg/storage/utils/chunking" "github.com/cs3org/reva/v3/pkg/storage/utils/grants" "github.com/cs3org/reva/v3/pkg/utils" diff --git a/pkg/storage/fs/eos/grant.go b/pkg/storage/fs/eos/grant.go index c2b854fdac..c500b1481d 100644 --- a/pkg/storage/fs/eos/grant.go +++ b/pkg/storage/fs/eos/grant.go @@ -29,8 +29,8 @@ import ( provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" "github.com/cs3org/reva/v3/pkg/appctx" "github.com/cs3org/reva/v3/pkg/errtypes" + "github.com/cs3org/reva/v3/pkg/storage/fs/eos/acl" eosclient "github.com/cs3org/reva/v3/pkg/storage/fs/eos/client" - "github.com/cs3org/reva/v3/pkg/storage/utils/acl" "github.com/cs3org/reva/v3/pkg/storage/utils/grants" "github.com/pkg/errors" ) diff --git a/pkg/storage/utils/grants/grants.go b/pkg/storage/utils/grants/grants.go index 1a6b4b28b7..0d64547ffa 100644 --- a/pkg/storage/utils/grants/grants.go +++ b/pkg/storage/utils/grants/grants.go @@ -23,7 +23,7 @@ import ( "strings" provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" - "github.com/cs3org/reva/v3/pkg/storage/utils/acl" + "github.com/cs3org/reva/v3/pkg/storage/fs/eos/acl" "google.golang.org/protobuf/proto" ) diff --git a/pkg/storage/utils/localfs/localfs.go b/pkg/storage/utils/localfs/localfs.go index ae2454a5ec..82f3f23a71 100644 --- a/pkg/storage/utils/localfs/localfs.go +++ b/pkg/storage/utils/localfs/localfs.go @@ -40,7 +40,7 @@ import ( "github.com/cs3org/reva/v3/pkg/errtypes" "github.com/cs3org/reva/v3/pkg/mime" "github.com/cs3org/reva/v3/pkg/storage" - "github.com/cs3org/reva/v3/pkg/storage/utils/acl" + "github.com/cs3org/reva/v3/pkg/storage/fs/eos/acl" "github.com/cs3org/reva/v3/pkg/storage/utils/chunking" "github.com/cs3org/reva/v3/pkg/storage/utils/grants" "github.com/cs3org/reva/v3/pkg/storage/utils/templates" From 8d972a2ae02f632f2be7c70bfa06041043328c5e Mon Sep 17 00:00:00 2001 From: Jesse Geens Date: Thu, 20 Aug 2026 15:55:01 +0200 Subject: [PATCH 03/10] implement tests for runAnalysis --- pkg/reconciliation/.gitignore | 3 +- pkg/reconciliation/acl_tree.go | 2 +- pkg/reconciliation/deep.go | 88 ++- pkg/reconciliation/deep_test.go | 745 ++++++++++++++++++ pkg/reconciliation/nsdump/ns_dump.go | 2 +- pkg/reconciliation/orphan_test.go | 8 +- .../testdata/nsdump_cernbox.json | 236 ++++++ 7 files changed, 1057 insertions(+), 27 deletions(-) create mode 100644 pkg/reconciliation/deep_test.go create mode 100644 pkg/reconciliation/testdata/nsdump_cernbox.json diff --git a/pkg/reconciliation/.gitignore b/pkg/reconciliation/.gitignore index 94a2dd146a..9c9802a1f8 100644 --- a/pkg/reconciliation/.gitignore +++ b/pkg/reconciliation/.gitignore @@ -1 +1,2 @@ -*.json \ No newline at end of file +*.json +!testdata/*.json diff --git a/pkg/reconciliation/acl_tree.go b/pkg/reconciliation/acl_tree.go index b1c88817c6..8d87427e40 100644 --- a/pkg/reconciliation/acl_tree.go +++ b/pkg/reconciliation/acl_tree.go @@ -13,7 +13,7 @@ import ( // we can just append them at the end // - for "wide" trees, we could sort the children and do a binary search // over them instead of iterating over all children -// - we should guard `Insert` agains concurrent Inserts +// - we should guard `Insert` against concurrent Inserts // An ACL Tree represents the tree of ACLs in a space. // ACLTree's do not *require* in-order insertion, but note that pre-sorting the paths diff --git a/pkg/reconciliation/deep.go b/pkg/reconciliation/deep.go index 6cf90d4d5e..e0469b798c 100644 --- a/pkg/reconciliation/deep.go +++ b/pkg/reconciliation/deep.go @@ -23,14 +23,19 @@ import ( "context" "slices" + gateway "github.com/cs3org/go-cs3apis/cs3/gateway/v1beta1" + rpcv1beta1 "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1" collaborationv1beta1 "github.com/cs3org/go-cs3apis/cs3/sharing/collaboration/v1beta1" provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" + "github.com/cs3org/reva/v3/pkg/permissions" "github.com/cs3org/reva/v3/pkg/reconciliation/nsdump" - "github.com/cs3org/reva/v3/pkg/share/manager/sql" "github.com/cs3org/reva/v3/pkg/spaces" "github.com/cs3org/reva/v3/pkg/storage/fs/eos/acl" ) +// TODO(jgeens): +// - check what to do with version folders + const JobName = "reconciliation.deep" type EntryResult struct { @@ -45,7 +50,8 @@ type Change struct { } type DeepJob struct { - shareMgr sql.ShareMgr + shareMgr ShareStore + gw gateway.GatewayAPIClient } type RunParameters struct { @@ -67,6 +73,20 @@ func (s *ShareWithPath) toTreeNode() *ACLNode { func (j *DeepJob) run(ctx context.Context, p RunParameters) error { + namespaceDumper := &nsdump.EOSMemoryNSInspect{} + err := namespaceDumper.Setup(map[string]any{ + // TODO(jgeens): set config for dump + }) + if err != nil { + return err + } + + _, err = j.runAnalysis(ctx, p.SpaceID, namespaceDumper) + return err +} + +func (j *DeepJob) runAnalysis(ctx context.Context, spaceid string, nsdumper nsdump.NSDumpClient) (ChangeSet, error) { + // First, construct a tree which contains the "ideal" state // For this, we: // 1. Get all the shares in the space @@ -77,18 +97,18 @@ func (j *DeepJob) run(ctx context.Context, p RunParameters) error { { Type: collaborationv1beta1.Filter_TYPE_SPACE_ID, Term: &collaborationv1beta1.Filter_SpaceId{ - SpaceId: p.SpaceID, + SpaceId: spaceid, }, }, }) if err != nil { - return err + return nil, err } // Resolve the paths - var sharesWithPaths = make([]*ShareWithPath, len(shares)) + var sharesWithPaths = make([]*ShareWithPath, 0, len(shares)) for _, s := range shares { - if p, ok := j.getPath(s.ResourceId); ok { + if p, ok := j.getPath(ctx, s.ResourceId); ok { sharesWithPaths = append(sharesWithPaths, &ShareWithPath{ Share: s, Path: p, @@ -111,30 +131,33 @@ func (j *DeepJob) run(ctx context.Context, p RunParameters) error { // actual state of the namespace into something parsable. // We start by taking a dump of the namespace, and then we // parse this entry-by-entry - - namespaceDumper := nsdump.EOSMemoryNSInspect{} - namespaceDumper.Setup(map[string]any{ - // TODO(jgeens): set config for dump - }) + path, err := spaces.DecodeSpaceID(spaceid) if err != nil { - return err + return nil, err } - path, err := spaces.DecodeSpaceID(p.SpaceID) + dump, err := nsdumper.Dump(path, 0) if err != nil { - return err + return nil, err } - dump, err := namespaceDumper.Dump(path, 0) - // Now we do the diff and get the resulting ChangeSet - compare(tree, dump) // changeSet := + changeSet := compare(tree, dump) - return nil + return changeSet, nil } -func (j *DeepJob) getPath(rid *provider.ResourceId) (string, bool) { - return "", true +func (j *DeepJob) getPath(ctx context.Context, rid *provider.ResourceId) (string, bool) { + statRes, err := j.gw.Stat(ctx, &provider.StatRequest{ + Ref: &provider.Reference{ResourceId: rid}, + }) + if err != nil { + return "", false + } + if statRes.Status == nil || statRes.Status.Code != rpcv1beta1.Code_CODE_OK { + return "", false + } + return statRes.Info.Path, true } func compare(tree *ACLTree, ns *nsdump.NamespaceDump) ChangeSet { @@ -169,7 +192,7 @@ func calculateChangeSet(mandatory, optional, actual []*acl.Entry, p string) Chan // First calculate missing entries on `actual` for _, e := range mandatory { - if !slices.Contains(actual, e) { + if !aclSetContains(actual, e) { changeSet = append(changeSet, &Change{ Path: p, Action: ActionAdd, @@ -180,7 +203,7 @@ func calculateChangeSet(mandatory, optional, actual []*acl.Entry, p string) Chan // Then calculate which entries should not be there for _, e := range actual { - if !slices.Contains(mandatory, e) && !slices.Contains(optional, e) { + if !aclSetContains(mandatory, e) && !aclSetContains(optional, e) { changeSet = append(changeSet, &Change{ Path: p, Action: ActionDelete, @@ -192,6 +215,14 @@ func calculateChangeSet(mandatory, optional, actual []*acl.Entry, p string) Chan return changeSet } +// We cannot use slices.Contains, because we want to compare +// the actual values, not the pointers +func aclSetContains(set []*acl.Entry, entry *acl.Entry) bool { + return slices.ContainsFunc(set, func(c *acl.Entry) bool { + return *c == *entry + }) +} + func shareToACL(s *collaborationv1beta1.Share) *acl.Entry { var e acl.Entry switch { @@ -205,6 +236,17 @@ func shareToACL(s *collaborationv1beta1.Share) *acl.Entry { return &e } - // TODO(jgeens): set permissions + // TODO(jgeens): we should define named constants for these int values + // TODO(jgeens): we should define named constants for the EOS perms + ocs := permissions.OCSFromCS3Permission(s.Permissions.Permissions) + switch ocs { + case 0: + e.Permissions = "!r!w!x" + case 1: + e.Permissions = "rx" + case 15: + e.Permissions = "rwx" + } + return &e } diff --git a/pkg/reconciliation/deep_test.go b/pkg/reconciliation/deep_test.go new file mode 100644 index 0000000000..78dff19f9d --- /dev/null +++ b/pkg/reconciliation/deep_test.go @@ -0,0 +1,745 @@ +// Copyright 2018-2026 CERN +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// In applying this license, CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +package reconciliation + +import ( + "cmp" + "context" + "errors" + "fmt" + "path" + "slices" + "strconv" + "strings" + "testing" + + grouppb "github.com/cs3org/go-cs3apis/cs3/identity/group/v1beta1" + userpb "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1" + collaboration "github.com/cs3org/go-cs3apis/cs3/sharing/collaboration/v1beta1" + provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" + "github.com/cs3org/reva/v3/pkg/spaces" + + "github.com/cs3org/reva/v3/pkg/reconciliation/nsdump" + "github.com/cs3org/reva/v3/pkg/storage/fs/eos/acl" +) + +// testSpaceRoot is the space the synthetic namespace dump covers. +const testSpaceRoot = "/eos/project/c/cernbox" + +// The entries of testdata/nsdump_cernbox.json. Folder paths carry a trailing +// separator, the way eos-ns-inspect prints them. +const ( + nsRoot = testSpaceRoot + "/" + nsShared = testSpaceRoot + "/shared/" + nsNotes = testSpaceRoot + "/shared/notes.md" + nsTodo = testSpaceRoot + "/shared/todo.md" + nsExternal = testSpaceRoot + "/shared/external.md" + nsInner = testSpaceRoot + "/shared/inner/" + nsPlan = testSpaceRoot + "/shared/inner/plan.md" + nsSharedDoc = testSpaceRoot + "/shared-doc.md" + nsWrongPerms = testSpaceRoot + "/wrongperms/" + nsTeam = testSpaceRoot + "/team/" + nsTeamReport = testSpaceRoot + "/team/report.md" + nsPrivate = testSpaceRoot + "/private/" + nsPrivateFile = testSpaceRoot + "/private/secret.md" +) + +func aclEntry(t, qualifier, permissions string) *acl.Entry { + return &acl.Entry{Type: t, Qualifier: qualifier, Permissions: permissions} +} + +func aliceEntry() *acl.Entry { return aclEntry(acl.TypeUser, "alice", "rx") } +func bobEntry() *acl.Entry { return aclEntry(acl.TypeUser, "bob", "rwx") } +func carolEntry() *acl.Entry { return aclEntry(acl.TypeUser, "carol", "rwx") } +func carolWrong() *acl.Entry { return aclEntry(acl.TypeUser, "carol", "rx") } +func adminsEntry() *acl.Entry { return aclEntry(acl.TypeGroup, "cernbox-admins", "rwx") } +func malloryEntry() *acl.Entry { return aclEntry(acl.TypeUser, "mallory", "rwx") } +func daveEntry() *acl.Entry { return aclEntry(acl.TypeUser, "dave", "rwx") } +func frankEntry() *acl.Entry { return aclEntry(acl.TypeUser, "frank", "rx") } +func externalEntry() *acl.Entry { return aclEntry(acl.TypeUser, "cboxexternal", "rwx") } + +// loadTestDump reads the synthetic namespace through the file dumper, which is +// the same parser the job uses on the output of eos-ns-inspect. +func loadTestDump(t *testing.T) *nsdump.NamespaceDump { + t.Helper() + + dumper := &nsdump.EOSFileNSInspect{} + if err := dumper.Setup(map[string]any{"file": "testdata/nsdump_cernbox.json"}); err != nil { + t.Fatalf("setting up the file dumper: %v", err) + } + + dump, err := dumper.Dump(testSpaceRoot, 0) + if err != nil { + t.Fatalf("reading the namespace dump: %v", err) + } + return dump +} + +// testTree is the ideal state of the synthetic namespace: four shares, plus one +// entry that is allowed anywhere in the space without a share behind it. +func testTree() *ACLTree { + tree := NewACLTree() + tree.Insert(&ACLNode{ + Path: testSpaceRoot, + AllowedACLs: []*acl.Entry{externalEntry()}, + }) + tree.Insert(&ACLNode{ + Path: testSpaceRoot + "/shared", + MandatoryACLs: []*acl.Entry{aliceEntry()}, + }) + tree.Insert(&ACLNode{ + Path: testSpaceRoot + "/shared/inner", + MandatoryACLs: []*acl.Entry{bobEntry()}, + }) + tree.Insert(&ACLNode{ + Path: testSpaceRoot + "/wrongperms", + MandatoryACLs: []*acl.Entry{carolEntry()}, + }) + tree.Insert(&ACLNode{ + Path: testSpaceRoot + "/team", + MandatoryACLs: []*acl.Entry{adminsEntry()}, + }) + return tree +} + +func formatChange(c *Change) string { + return fmt.Sprintf("%s %s %s:%s:%s", c.Action, c.Path, c.ACL.Type, c.ACL.Qualifier, c.ACL.Permissions) +} + +func formatChanges(cs ChangeSet) string { + out := make([]string, len(cs)) + for i, c := range cs { + out[i] = formatChange(c) + } + slices.Sort(out) + return "\n\t" + strings.Join(out, "\n\t") +} + +// sameChanges compares two change sets without regard to their order. +func sameChanges(got, want ChangeSet) bool { + g, w := make([]string, len(got)), make([]string, len(want)) + for i, c := range got { + g[i] = formatChange(c) + } + for i, c := range want { + w[i] = formatChange(c) + } + slices.Sort(g) + slices.Sort(w) + return slices.Equal(g, w) +} + +func checkChanges(t *testing.T, got, want ChangeSet) { + t.Helper() + if !sameChanges(got, want) { + t.Errorf("changes = %s\n\nwant = %s", formatChanges(got), formatChanges(want)) + } +} + +// The fixture must stay what the tests assume: the parser must give every entry +// its kind, and folder paths keep the trailing separator eos-ns-inspect prints. +func TestNamespaceDumpFixture(t *testing.T) { + dump := loadTestDump(t) + + byPath := make(map[string]nsdump.NameSpaceEntry, len(dump.Entries)) + for _, e := range dump.Entries { + byPath[e.Path] = e + } + + for _, p := range []string{nsRoot, nsShared, nsNotes, nsTodo, nsExternal, nsInner, + nsPlan, nsSharedDoc, nsWrongPerms, nsTeam, nsTeamReport, nsPrivate, nsPrivateFile} { + if _, ok := byPath[p]; !ok { + t.Errorf("the dump has no entry for %q", p) + } + } + if len(dump.Entries) != 13 { + t.Errorf("the dump holds %d entries, want 13", len(dump.Entries)) + } + + if got := byPath[nsShared].EntryType; got != nsdump.EntryTypeFolder { + t.Errorf("%q is a %q, want a folder", nsShared, got) + } + if got := byPath[nsNotes].EntryType; got != nsdump.EntryTypeFile { + t.Errorf("%q is a %q, want a file", nsNotes, got) + } + if got := byPath[nsShared].XattrSysAcl; got != "u:alice:rx" { + t.Errorf("%q holds acl %q, want u:alice:rx", nsShared, got) + } + if got := byPath[nsTodo].XattrSysAcl; got != "" { + t.Errorf("%q holds acl %q, want none", nsTodo, got) + } +} + +// The whole comparison, over a namespace that holds one of every case. +func TestCompareAgainstNamespace(t *testing.T) { + dump := loadTestDump(t) + + want := ChangeSet{ + // the file below the share carries no entry at all + {Path: nsTodo, Action: ActionAdd, ACL: aliceEntry()}, + // the deeper share is not on the folder it was made on + {Path: nsInner, Action: ActionAdd, ACL: bobEntry()}, + // a file whose name starts with the name of the shared folder is not + // in the share, so its entry has nothing behind it + {Path: nsSharedDoc, Action: ActionDelete, ACL: aliceEntry()}, + // the right user with the wrong permissions: the entry goes and the + // right one takes its place + {Path: nsWrongPerms, Action: ActionDelete, ACL: carolWrong()}, + {Path: nsWrongPerms, Action: ActionAdd, ACL: carolEntry()}, + // one entry too many below the group share + {Path: nsTeamReport, Action: ActionDelete, ACL: malloryEntry()}, + // a subtree with no share at all + {Path: nsPrivate, Action: ActionDelete, ACL: daveEntry()}, + } + + checkChanges(t, compare(testTree(), dump), want) +} + +// The cases of one entry, without the namespace around them. +func TestCalculateChangeSet(t *testing.T) { + const p = "/eos/project/c/cernbox/shared" + + tests := []struct { + name string + mandatory, optional, actual []*acl.Entry + want ChangeSet + }{ + { + name: "nothing wanted and nothing there", + }, + { + name: "the wanted entry is there", + mandatory: []*acl.Entry{aliceEntry()}, + actual: []*acl.Entry{aliceEntry()}, + }, + { + name: "the wanted entry is missing", + mandatory: []*acl.Entry{aliceEntry()}, + want: ChangeSet{{Path: p, Action: ActionAdd, ACL: aliceEntry()}}, + }, + { + name: "one of two wanted entries is missing", + mandatory: []*acl.Entry{aliceEntry(), bobEntry()}, + actual: []*acl.Entry{aliceEntry()}, + want: ChangeSet{{Path: p, Action: ActionAdd, ACL: bobEntry()}}, + }, + { + name: "an entry nothing asks for", + actual: []*acl.Entry{daveEntry()}, + want: ChangeSet{{Path: p, Action: ActionDelete, ACL: daveEntry()}}, + }, + { + name: "an allowed entry stays", + optional: []*acl.Entry{externalEntry()}, + actual: []*acl.Entry{externalEntry()}, + }, + { + name: "an allowed entry next to a wanted one", + mandatory: []*acl.Entry{aliceEntry()}, + optional: []*acl.Entry{externalEntry()}, + actual: []*acl.Entry{aliceEntry(), externalEntry()}, + }, + { + name: "the right user with the wrong permissions", + mandatory: []*acl.Entry{carolEntry()}, + actual: []*acl.Entry{carolWrong()}, + want: ChangeSet{ + {Path: p, Action: ActionAdd, ACL: carolEntry()}, + {Path: p, Action: ActionDelete, ACL: carolWrong()}, + }, + }, + { + name: "a group entry is another entry than a user entry", + mandatory: []*acl.Entry{adminsEntry()}, + actual: []*acl.Entry{aclEntry(acl.TypeUser, "cernbox-admins", "rwx")}, + want: ChangeSet{ + {Path: p, Action: ActionAdd, ACL: adminsEntry()}, + {Path: p, Action: ActionDelete, ACL: aclEntry(acl.TypeUser, "cernbox-admins", "rwx")}, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := calculateChangeSet(tt.mandatory, tt.optional, tt.actual, p) + checkChanges(t, got, tt.want) + }) + } +} + +func TestParseACLs(t *testing.T) { + tests := []struct { + name string + sysattr string + want []*acl.Entry + }{ + { + name: "one user entry", + sysattr: "u:alice:rx", + want: []*acl.Entry{aliceEntry()}, + }, + { + name: "a user and a group", + sysattr: "u:alice:rx,egroup:cernbox-admins:rwx", + want: []*acl.Entry{aliceEntry(), adminsEntry()}, + }, + { + name: "no acl at all", + sysattr: "", + }, + { + name: "an entry that does not parse", + sysattr: "this is not an acl", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := parseACLs(tt.sysattr); !sameEntries(got, tt.want) { + t.Errorf("parseACLs(%q) = %v, want %v", tt.sysattr, formatEntries(got), formatEntries(tt.want)) + } + }) + } +} + +// testStorageID is the storage the shares of the test space point at. +const testStorageID = "eosproject-c" + +// spaceShare builds a share of the test space with the fields the deep job +// reads: the space it belongs to, the resource it points at, the grantee and +// the permissions. +func spaceShare(id, spaceID, inode, shareWith string, isGroup bool, perms *provider.ResourcePermissions) storedShare { + s := &collaboration.Share{ + Id: &collaboration.ShareId{OpaqueId: id}, + ResourceId: &provider.ResourceId{ + StorageId: testStorageID, + SpaceId: spaceID, + OpaqueId: inode, + }, + Permissions: &collaboration.SharePermissions{Permissions: perms}, + } + if isGroup { + s.Grantee = &provider.Grantee{ + Type: provider.GranteeType_GRANTEE_TYPE_GROUP, + Id: &provider.Grantee_GroupId{GroupId: &grouppb.GroupId{OpaqueId: shareWith}}, + } + } else { + s.Grantee = &provider.Grantee{ + Type: provider.GranteeType_GRANTEE_TYPE_USER, + Id: &provider.Grantee_UserId{UserId: &userpb.UserId{OpaqueId: shareWith}}, + } + } + return storedShare{share: s} +} + +// readOnly and readWrite are what OCSFromCS3Permission reads to decide between +// rx and rwx. +func readOnly() *provider.ResourcePermissions { + return &provider.ResourcePermissions{InitiateFileDownload: true} +} + +func readWrite() *provider.ResourcePermissions { + return &provider.ResourcePermissions{InitiateFileDownload: true, InitiateFileUpload: true} +} + +// recordingDumper keeps the root path a run asked for, which is the space path +// the job decoded out of the space id. +type recordingDumper struct { + nsdump.NSDumpClient + rootPath string +} + +func (d *recordingDumper) Dump(rootPath string, maxDepth int) (*nsdump.NamespaceDump, error) { + d.rootPath = rootPath + return d.NSDumpClient.Dump(rootPath, maxDepth) +} + +// failingDumper stands for a namespace that cannot be read. +type failingDumper struct { + err error +} + +func (d *failingDumper) Setup(config map[string]any) error { return nil } + +func (d *failingDumper) Dump(rootPath string, maxDepth int) (*nsdump.NamespaceDump, error) { + return nil, d.err +} + +// fileDumper reads the synthetic namespace. +func fileDumper(t *testing.T) nsdump.NSDumpClient { + t.Helper() + d := &nsdump.EOSFileNSInspect{} + if err := d.Setup(map[string]any{"file": "testdata/nsdump_cernbox.json"}); err != nil { + t.Fatalf("setting up the file dumper: %v", err) + } + return d +} + +// A whole run over the synthetic namespace, from the shares to the change set. +func TestRunAnalysis(t *testing.T) { + spaceID := spaces.EncodeSpaceID(testSpaceRoot) + otherSpaceID := spaces.EncodeSpaceID("/eos/project/o/other") + + store := &fakeStore{shares: []storedShare{ + spaceShare("1", spaceID, "1001", "alice", false, readOnly()), + spaceShare("2", spaceID, "1002", "bob", false, readWrite()), + spaceShare("3", spaceID, "1003", "carol", false, readWrite()), + spaceShare("4", spaceID, "1004", "cernbox-admins", true, readWrite()), + // a second share of the folder share 4 points at + spaceShare("5", spaceID, "1004", "frank", false, readOnly()), + // the resource is gone, so the path cannot be resolved and the share + // cannot hold up an entry + spaceShare("6", spaceID, "9999", "dave", false, readWrite()), + // another space, which the listing filter keeps out + spaceShare("7", otherSpaceID, "2001", "eve", false, readOnly()), + }} + + gw := &fakeGateway{ + resources: map[string]bool{ + testStorageID + "/1001": true, + testStorageID + "/1002": true, + testStorageID + "/1003": true, + testStorageID + "/1004": true, + }, + paths: map[string]string{ + testStorageID + "/1001": testSpaceRoot + "/shared", + testStorageID + "/1002": testSpaceRoot + "/shared/inner", + testStorageID + "/1003": testSpaceRoot + "/wrongperms", + testStorageID + "/1004": testSpaceRoot + "/team", + }, + } + + dumper := &recordingDumper{NSDumpClient: fileDumper(t)} + j := &DeepJob{shareMgr: store, gw: gw} + + got, err := j.runAnalysis(context.Background(), spaceID, dumper) + if err != nil { + t.Fatalf("runAnalysis: %v", err) + } + + if dumper.rootPath != testSpaceRoot { + t.Errorf("the namespace was read at %q, want %q", dumper.rootPath, testSpaceRoot) + } + + want := ChangeSet{ + // the file below the share holds no entry + {Path: nsTodo, Action: ActionAdd, ACL: aliceEntry()}, + // no share backs this entry, and nothing allows it either + {Path: nsExternal, Action: ActionDelete, ACL: externalEntry()}, + // the deeper share is not on the folder it was made on + {Path: nsInner, Action: ActionAdd, ACL: bobEntry()}, + // the name starts with the name of the shared folder, but it sits + // next to it + {Path: nsSharedDoc, Action: ActionDelete, ACL: aliceEntry()}, + // the right user with the wrong permissions + {Path: nsWrongPerms, Action: ActionAdd, ACL: carolEntry()}, + {Path: nsWrongPerms, Action: ActionDelete, ACL: carolWrong()}, + // two shares on one folder: the group entry is there, the second one + // is missing on the folder and on the file below it + {Path: nsTeam, Action: ActionAdd, ACL: frankEntry()}, + {Path: nsTeamReport, Action: ActionAdd, ACL: frankEntry()}, + {Path: nsTeamReport, Action: ActionDelete, ACL: malloryEntry()}, + // the share of this subtree points at a resource that is gone, so the + // entry stands on nothing + {Path: nsPrivate, Action: ActionDelete, ACL: daveEntry()}, + } + + checkChanges(t, got, want) +} + +func TestRunAnalysisReportsAFailedListing(t *testing.T) { + store := &fakeStore{listErr: errors.New("the share database is down")} + j := &DeepJob{shareMgr: store, gw: &fakeGateway{}} + + _, err := j.runAnalysis(context.Background(), spaces.EncodeSpaceID(testSpaceRoot), fileDumper(t)) + if err == nil { + t.Fatal("runAnalysis: no error, want the listing error") + } +} + +func TestRunAnalysisReportsAFailedDump(t *testing.T) { + j := &DeepJob{shareMgr: &fakeStore{}, gw: &fakeGateway{}} + + dumper := &failingDumper{err: errors.New("eos-ns-inspect failed")} + _, err := j.runAnalysis(context.Background(), spaces.EncodeSpaceID(testSpaceRoot), dumper) + if err == nil { + t.Fatal("runAnalysis: no error, want the dump error") + } +} + +func TestRunAnalysisRejectsABadSpaceID(t *testing.T) { + j := &DeepJob{shareMgr: &fakeStore{}, gw: &fakeGateway{}} + + _, err := j.runAnalysis(context.Background(), "this is not a space id", fileDumper(t)) + if err == nil { + t.Fatal("runAnalysis: no error, want the decoding error") + } +} + +// A share becomes one ACL entry: the grantee is the qualifier, and the +// permissions of the share decide the EOS permissions. +func TestShareToACL(t *testing.T) { + spaceID := spaces.EncodeSpaceID(testSpaceRoot) + + tests := []struct { + name string + share *collaboration.Share + want *acl.Entry + }{ + { + name: "a user with read permissions", + share: spaceShare("1", spaceID, "1001", "alice", false, readOnly()).share, + want: aclEntry(acl.TypeUser, "alice", "rx"), + }, + { + name: "a user with write permissions", + share: spaceShare("2", spaceID, "1001", "bob", false, readWrite()).share, + want: aclEntry(acl.TypeUser, "bob", "rwx"), + }, + { + name: "a group with write permissions", + share: spaceShare("3", spaceID, "1001", "cernbox-admins", true, readWrite()).share, + want: aclEntry(acl.TypeGroup, "cernbox-admins", "rwx"), + }, + { + name: "a share that grants nothing denies", + share: spaceShare("4", spaceID, "1001", "dave", false, &provider.ResourcePermissions{}).share, + want: aclEntry(acl.TypeUser, "dave", "!r!w!x"), + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := shareToACL(tt.share); *got != *tt.want { + t.Errorf("shareToACL() = %s:%s:%s, want %s:%s:%s", + got.Type, got.Qualifier, got.Permissions, + tt.want.Type, tt.want.Qualifier, tt.want.Permissions) + } + }) + } +} + +// The size of the generated space the benchmarks run on: a namespace of a +// hundred thousand entries, with a thousand shares in it. +const ( + benchEntries = 100000 + benchShares = 1000 +) + +// memoryDumper hands out a namespace that is already in memory, so a benchmark +// measures the run and not the reading of a file. +type memoryDumper struct { + dump *nsdump.NamespaceDump +} + +func (d *memoryDumper) Setup(config map[string]any) error { return nil } + +func (d *memoryDumper) Dump(rootPath string, maxDepth int) (*nsdump.NamespaceDump, error) { + return d.dump, nil +} + +// benchFixture is a generated space: its namespace, the shares on it, and what +// the gateway answers for the resources those shares point at. +type benchFixture struct { + dump *nsdump.NamespaceDump + shares []storedShare + resources map[string]bool + paths map[string]string +} + +// buildBenchFixture generates a space of `entries` namespace entries over 1110 +// folders, three levels deep, with a share on each of the first `shares` +// folders. Most entries hold the ACL they should: a namespace that is mostly in +// order is the normal case for a run. A folder in every 25 carries the wrong +// permissions, a file in every 100 carries an entry too many, and a file in +// every 100 carries none at all. +func buildBenchFixture(entries, shares int) *benchFixture { + spaceID := spaces.EncodeSpaceID(testSpaceRoot) + f := &benchFixture{ + dump: &nsdump.NamespaceDump{}, + resources: make(map[string]bool, shares), + paths: make(map[string]string, shares), + } + + // the folders, parents before children + var folders []string + for i := range 10 { + l1 := fmt.Sprintf("%s/g%d", testSpaceRoot, i) + folders = append(folders, l1) + for j := range 10 { + l2 := fmt.Sprintf("%s/s%d", l1, j) + folders = append(folders, l2) + for k := range 10 { + folders = append(folders, fmt.Sprintf("%s/t%d", l2, k)) + } + } + } + + // the entries every folder should hold: the ones of its shared ancestors, + // plus its own share + inherited := make(map[string][]string, len(folders)) + for i, folder := range folders { + own := slices.Clone(inherited[path.Dir(folder)]) + + if i < shares { + inode := strconv.Itoa(100000 + i) + f.resources[testStorageID+"/"+inode] = true + f.paths[testStorageID+"/"+inode] = folder + + var e string + switch { + case i%5 == 0: + f.shares = append(f.shares, spaceShare(strconv.Itoa(i), spaceID, inode, fmt.Sprintf("group%d", i), true, readWrite())) + e = fmt.Sprintf("egroup:group%d:rwx", i) + case i%2 == 0: + f.shares = append(f.shares, spaceShare(strconv.Itoa(i), spaceID, inode, fmt.Sprintf("user%d", i), false, readWrite())) + e = fmt.Sprintf("u:user%d:rwx", i) + default: + f.shares = append(f.shares, spaceShare(strconv.Itoa(i), spaceID, inode, fmt.Sprintf("user%d", i), false, readOnly())) + e = fmt.Sprintf("u:user%d:rx", i) + } + own = append(own, e) + } + inherited[folder] = own + + // a folder in every 25 holds the wrong permissions + actual := slices.Clone(own) + if i%25 == 24 && len(actual) > 0 { + last := actual[len(actual)-1] + if strings.HasSuffix(last, ":rwx") { + last = strings.TrimSuffix(last, ":rwx") + ":rx" + } else { + last = strings.TrimSuffix(last, ":rx") + ":rwx" + } + actual[len(actual)-1] = last + } + + f.dump.Entries = append(f.dump.Entries, nsdump.NameSpaceEntry{ + ID: strconv.Itoa(100000 + i), + Name: path.Base(folder), + Path: folder + "/", + EntryType: nsdump.EntryTypeFolder, + XattrSysAcl: strings.Join(actual, acl.ShortTextForm), + }) + } + + // the files, spread over the folders + for i := range entries - len(folders) { + folder := folders[i%len(folders)] + actual := slices.Clone(inherited[folder]) + + switch { + case i%100 == 0: + actual = append(actual, "u:intruder:rwx") + case i%100 == 50: + actual = nil + } + + f.dump.Entries = append(f.dump.Entries, nsdump.NameSpaceEntry{ + ID: strconv.Itoa(900000 + i), + Name: fmt.Sprintf("file%d.txt", i), + Path: fmt.Sprintf("%s/file%d.txt", folder, i), + EntryType: nsdump.EntryTypeFile, + XattrSysAcl: strings.Join(actual, acl.ShortTextForm), + }) + } + + return f +} + +// benchTree builds the ideal state out of the shares of a fixture, the way +// runAnalysis does. +func benchTree(f *benchFixture) *ACLTree { + paths := make([]*ShareWithPath, 0, len(f.shares)) + for _, s := range f.shares { + rid := s.share.GetResourceId() + paths = append(paths, &ShareWithPath{ + Share: s.share, + Path: f.paths[rid.GetStorageId()+"/"+rid.GetOpaqueId()], + }) + } + slices.SortFunc(paths, func(a, b *ShareWithPath) int { return cmp.Compare(a.Path, b.Path) }) + + tree := NewACLTree() + for _, s := range paths { + tree.Insert(s.toTreeNode()) + } + return tree +} + +// The generated namespace must be mostly in order, the way a real space is. +// A generator that lines up no entry at all would make every entry a change, +// and the benchmarks would measure the wrong work. +func TestBenchFixtureIsMostlyInOrder(t *testing.T) { + f := buildBenchFixture(benchEntries, benchShares) + + if got := len(f.dump.Entries); got != benchEntries { + t.Errorf("the namespace holds %d entries, want %d", got, benchEntries) + } + if got := len(f.shares); got != benchShares { + t.Errorf("the space holds %d shares, want %d", got, benchShares) + } + + changes := compare(benchTree(f), f.dump) + if len(changes) == 0 { + t.Fatal("the namespace is in order everywhere, so the run has no work at all") + } + if share := float64(len(changes)) / float64(len(f.dump.Entries)); share > 0.1 { + t.Errorf("%d of %d entries change (%.0f%%), want under 10%%", + len(changes), len(f.dump.Entries), share*100) + } else { + t.Logf("%d of %d entries change (%.1f%%)", len(changes), len(f.dump.Entries), share*100) + } +} + +// The comparison on its own: one lookup and one ACL diff per namespace entry. +func BenchmarkCompare(b *testing.B) { + f := buildBenchFixture(benchEntries, benchShares) + tree := benchTree(f) + + b.Run(fmt.Sprintf("%d entries/%d shares", len(f.dump.Entries), len(f.shares)), func(b *testing.B) { + b.ReportAllocs() + for b.Loop() { + compare(tree, f.dump) + } + }) +} + +// A whole run: the listing, the path of every share, the tree, and the +// comparison over the namespace. +func BenchmarkRunAnalysis(b *testing.B) { + f := buildBenchFixture(benchEntries, benchShares) + + j := &DeepJob{ + shareMgr: &fakeStore{shares: f.shares}, + gw: &fakeGateway{resources: f.resources, paths: f.paths}, + } + dumper := &memoryDumper{dump: f.dump} + spaceID := spaces.EncodeSpaceID(testSpaceRoot) + ctx := context.Background() + + b.Run(fmt.Sprintf("%d entries/%d shares", len(f.dump.Entries), len(f.shares)), func(b *testing.B) { + b.ReportAllocs() + for b.Loop() { + if _, err := j.runAnalysis(ctx, spaceID, dumper); err != nil { + b.Fatalf("runAnalysis: %v", err) + } + } + }) +} diff --git a/pkg/reconciliation/nsdump/ns_dump.go b/pkg/reconciliation/nsdump/ns_dump.go index 743a3a77cb..a271a204d3 100644 --- a/pkg/reconciliation/nsdump/ns_dump.go +++ b/pkg/reconciliation/nsdump/ns_dump.go @@ -13,7 +13,7 @@ type NamespaceDump struct { } type NSDumpClient interface { - Dump(rootPath string, maxDepth int) (NamespaceDump, error) + Dump(rootPath string, maxDepth int) (*NamespaceDump, error) Setup(config map[string]any) error } diff --git a/pkg/reconciliation/orphan_test.go b/pkg/reconciliation/orphan_test.go index 8500b8d634..1d2710fb41 100644 --- a/pkg/reconciliation/orphan_test.go +++ b/pkg/reconciliation/orphan_test.go @@ -177,7 +177,13 @@ func (f *fakeGateway) Stat(ctx context.Context, in *provider.StatRequest, _ ...g return nil, f.statErr } id := in.GetRef().GetResourceId() - return &provider.StatResponse{Status: status(f.resources[id.StorageId+"/"+id.OpaqueId])}, nil + key := id.StorageId + "/" + id.OpaqueId + // the path is what the deep job reads off a stat; the orphan job only + // looks at the status. + return &provider.StatResponse{ + Status: status(f.resources[key]), + Info: &provider.ResourceInfo{Id: id, Path: f.paths[key]}, + }, nil } func (f *fakeGateway) GetUserByClaim(ctx context.Context, in *userpb.GetUserByClaimRequest, _ ...grpc.CallOption) (*userpb.GetUserByClaimResponse, error) { diff --git a/pkg/reconciliation/testdata/nsdump_cernbox.json b/pkg/reconciliation/testdata/nsdump_cernbox.json new file mode 100644 index 0000000000..191619e5b6 --- /dev/null +++ b/pkg/reconciliation/testdata/nsdump_cernbox.json @@ -0,0 +1,236 @@ +[ + { + "cid": "4000001", + "ctime": "1786473688.310186487", + "flags": "42700", + "gid": "2763", + "mtime": "1786474621.140556072", + "name": "cernbox", + "parent_id": "348", + "path": "/eos/project/c/cernbox/", + "stime": "1786474621.140556072", + "tree_size": "1886117666", + "uid": "173503", + "xattr.sys.eos.btime": "1677602740.101786072" + }, + { + "cid": "4000002", + "ctime": "1786473688.310186487", + "flags": "42700", + "gid": "2763", + "mtime": "1786474621.140556072", + "name": "shared", + "parent_id": "348", + "path": "/eos/project/c/cernbox/shared/", + "stime": "1786474621.140556072", + "tree_size": "1886117666", + "uid": "173503", + "xattr.sys.acl": "u:alice:rx", + "xattr.sys.eos.btime": "1677602740.101786072" + }, + { + "atime": "1784818711.61715410", + "ctime": "1784818711.61714940", + "fid": "110000001", + "flags": "644", + "gid": "2763", + "layout_id": "1048850", + "link_name": "", + "locations": "413,416", + "mtime": "1784818711.62072292", + "name": "notes.md", + "path": "/eos/project/c/cernbox/shared/notes.md", + "pid": "4251882", + "size": "66", + "stime": "0.0", + "uid": "173503", + "unlink_locations": "", + "xattr.sys.acl": "u:alice:rx", + "xattr.sys.eos.btime": "1784818711.61714940", + "xs": "fcd91616" + }, + { + "atime": "1784818711.61715410", + "ctime": "1784818711.61714940", + "fid": "110000002", + "flags": "644", + "gid": "2763", + "layout_id": "1048850", + "link_name": "", + "locations": "413,416", + "mtime": "1784818711.62072292", + "name": "todo.md", + "path": "/eos/project/c/cernbox/shared/todo.md", + "pid": "4251882", + "size": "66", + "stime": "0.0", + "uid": "173503", + "unlink_locations": "", + "xattr.sys.eos.btime": "1784818711.61714940", + "xs": "fcd91616" + }, + { + "atime": "1784818711.61715410", + "ctime": "1784818711.61714940", + "fid": "110000003", + "flags": "644", + "gid": "2763", + "layout_id": "1048850", + "link_name": "", + "locations": "413,416", + "mtime": "1784818711.62072292", + "name": "external.md", + "path": "/eos/project/c/cernbox/shared/external.md", + "pid": "4251882", + "size": "66", + "stime": "0.0", + "uid": "173503", + "unlink_locations": "", + "xattr.sys.acl": "u:alice:rx,u:cboxexternal:rwx", + "xattr.sys.eos.btime": "1784818711.61714940", + "xs": "fcd91616" + }, + { + "cid": "4000003", + "ctime": "1786473688.310186487", + "flags": "42700", + "gid": "2763", + "mtime": "1786474621.140556072", + "name": "inner", + "parent_id": "348", + "path": "/eos/project/c/cernbox/shared/inner/", + "stime": "1786474621.140556072", + "tree_size": "1886117666", + "uid": "173503", + "xattr.sys.acl": "u:alice:rx", + "xattr.sys.eos.btime": "1677602740.101786072" + }, + { + "atime": "1784818711.61715410", + "ctime": "1784818711.61714940", + "fid": "110000004", + "flags": "644", + "gid": "2763", + "layout_id": "1048850", + "link_name": "", + "locations": "413,416", + "mtime": "1784818711.62072292", + "name": "plan.md", + "path": "/eos/project/c/cernbox/shared/inner/plan.md", + "pid": "4251882", + "size": "66", + "stime": "0.0", + "uid": "173503", + "unlink_locations": "", + "xattr.sys.acl": "u:alice:rx,u:bob:rwx", + "xattr.sys.eos.btime": "1784818711.61714940", + "xs": "fcd91616" + }, + { + "atime": "1784818711.61715410", + "ctime": "1784818711.61714940", + "fid": "110000005", + "flags": "644", + "gid": "2763", + "layout_id": "1048850", + "link_name": "", + "locations": "413,416", + "mtime": "1784818711.62072292", + "name": "shared-doc.md", + "path": "/eos/project/c/cernbox/shared-doc.md", + "pid": "4251882", + "size": "66", + "stime": "0.0", + "uid": "173503", + "unlink_locations": "", + "xattr.sys.acl": "u:alice:rx", + "xattr.sys.eos.btime": "1784818711.61714940", + "xs": "fcd91616" + }, + { + "cid": "4000004", + "ctime": "1786473688.310186487", + "flags": "42700", + "gid": "2763", + "mtime": "1786474621.140556072", + "name": "wrongperms", + "parent_id": "348", + "path": "/eos/project/c/cernbox/wrongperms/", + "stime": "1786474621.140556072", + "tree_size": "1886117666", + "uid": "173503", + "xattr.sys.acl": "u:carol:rx", + "xattr.sys.eos.btime": "1677602740.101786072" + }, + { + "cid": "4000005", + "ctime": "1786473688.310186487", + "flags": "42700", + "gid": "2763", + "mtime": "1786474621.140556072", + "name": "team", + "parent_id": "348", + "path": "/eos/project/c/cernbox/team/", + "stime": "1786474621.140556072", + "tree_size": "1886117666", + "uid": "173503", + "xattr.sys.acl": "egroup:cernbox-admins:rwx", + "xattr.sys.eos.btime": "1677602740.101786072" + }, + { + "atime": "1784818711.61715410", + "ctime": "1784818711.61714940", + "fid": "110000006", + "flags": "644", + "gid": "2763", + "layout_id": "1048850", + "link_name": "", + "locations": "413,416", + "mtime": "1784818711.62072292", + "name": "report.md", + "path": "/eos/project/c/cernbox/team/report.md", + "pid": "4251882", + "size": "66", + "stime": "0.0", + "uid": "173503", + "unlink_locations": "", + "xattr.sys.acl": "egroup:cernbox-admins:rwx,u:mallory:rwx", + "xattr.sys.eos.btime": "1784818711.61714940", + "xs": "fcd91616" + }, + { + "cid": "4000006", + "ctime": "1786473688.310186487", + "flags": "42700", + "gid": "2763", + "mtime": "1786474621.140556072", + "name": "private", + "parent_id": "348", + "path": "/eos/project/c/cernbox/private/", + "stime": "1786474621.140556072", + "tree_size": "1886117666", + "uid": "173503", + "xattr.sys.acl": "u:dave:rwx", + "xattr.sys.eos.btime": "1677602740.101786072" + }, + { + "atime": "1784818711.61715410", + "ctime": "1784818711.61714940", + "fid": "110000007", + "flags": "644", + "gid": "2763", + "layout_id": "1048850", + "link_name": "", + "locations": "413,416", + "mtime": "1784818711.62072292", + "name": "secret.md", + "path": "/eos/project/c/cernbox/private/secret.md", + "pid": "4251882", + "size": "66", + "stime": "0.0", + "uid": "173503", + "unlink_locations": "", + "xattr.sys.eos.btime": "1784818711.61714940", + "xs": "fcd91616" + } +] From e097ba139e51263f6c50003515154afb9f9979f2 Mon Sep 17 00:00:00 2001 From: Jesse Geens Date: Thu, 20 Aug 2026 17:31:14 +0200 Subject: [PATCH 04/10] Speed up JSON parsing of the ns dump --- pkg/reconciliation/deep_test.go | 16 +- pkg/reconciliation/nsdump/ns_dump.go | 79 +- pkg/reconciliation/nsdump/ns_dump_test.go | 109 + .../testdata/jgeens_nsdump.json | 4911 +++++++++++++++++ 4 files changed, 5054 insertions(+), 61 deletions(-) create mode 100644 pkg/reconciliation/nsdump/ns_dump_test.go create mode 100644 pkg/reconciliation/testdata/jgeens_nsdump.json diff --git a/pkg/reconciliation/deep_test.go b/pkg/reconciliation/deep_test.go index 78dff19f9d..e9eaed06c9 100644 --- a/pkg/reconciliation/deep_test.go +++ b/pkg/reconciliation/deep_test.go @@ -157,9 +157,9 @@ func checkChanges(t *testing.T, got, want ChangeSet) { func TestNamespaceDumpFixture(t *testing.T) { dump := loadTestDump(t) - byPath := make(map[string]nsdump.NameSpaceEntry, len(dump.Entries)) - for _, e := range dump.Entries { - byPath[e.Path] = e + byPath := make(map[string]*nsdump.NameSpaceEntry, len(dump.Entries)) + for i := range dump.Entries { + byPath[dump.Entries[i].Path] = &dump.Entries[i] } for _, p := range []string{nsRoot, nsShared, nsNotes, nsTodo, nsExternal, nsInner, @@ -172,10 +172,10 @@ func TestNamespaceDumpFixture(t *testing.T) { t.Errorf("the dump holds %d entries, want 13", len(dump.Entries)) } - if got := byPath[nsShared].EntryType; got != nsdump.EntryTypeFolder { + if got := byPath[nsShared].EntryType(); got != nsdump.EntryTypeFolder { t.Errorf("%q is a %q, want a folder", nsShared, got) } - if got := byPath[nsNotes].EntryType; got != nsdump.EntryTypeFile { + if got := byPath[nsNotes].EntryType(); got != nsdump.EntryTypeFile { t.Errorf("%q is a %q, want a file", nsNotes, got) } if got := byPath[nsShared].XattrSysAcl; got != "u:alice:rx" { @@ -631,10 +631,9 @@ func buildBenchFixture(entries, shares int) *benchFixture { } f.dump.Entries = append(f.dump.Entries, nsdump.NameSpaceEntry{ - ID: strconv.Itoa(100000 + i), + CID: strconv.Itoa(100000 + i), Name: path.Base(folder), Path: folder + "/", - EntryType: nsdump.EntryTypeFolder, XattrSysAcl: strings.Join(actual, acl.ShortTextForm), }) } @@ -652,10 +651,9 @@ func buildBenchFixture(entries, shares int) *benchFixture { } f.dump.Entries = append(f.dump.Entries, nsdump.NameSpaceEntry{ - ID: strconv.Itoa(900000 + i), + FID: strconv.Itoa(900000 + i), Name: fmt.Sprintf("file%d.txt", i), Path: fmt.Sprintf("%s/file%d.txt", folder, i), - EntryType: nsdump.EntryTypeFile, XattrSysAcl: strings.Join(actual, acl.ShortTextForm), }) } diff --git a/pkg/reconciliation/nsdump/ns_dump.go b/pkg/reconciliation/nsdump/ns_dump.go index a271a204d3..f16778231c 100644 --- a/pkg/reconciliation/nsdump/ns_dump.go +++ b/pkg/reconciliation/nsdump/ns_dump.go @@ -25,72 +25,47 @@ const ( ) type NameSpaceEntry struct { - Ctime string `json:"ctime"` - Flags string `json:"flags"` - Gid string `json:"gid"` - Mtime string `json:"mtime"` - Name string `json:"name"` - Path string `json:"path"` - Stime string `json:"stime"` - Uid string `json:"uid"` - XattrSysAcl string `json:"xattr.sys.acl"` - EntryType EntryType `json:"entryType"` - ID string `json:"id"` + Ctime string `json:"ctime"` + Flags string `json:"flags"` + Gid string `json:"gid"` + Mtime string `json:"mtime"` + Name string `json:"name"` + Path string `json:"path"` + Stime string `json:"stime"` + Uid string `json:"uid"` + XattrSysAcl string `json:"xattr.sys.acl"` + CID string `json:"cid"` + FID string `json:"fid"` } -func (e *NameSpaceEntry) UnmarshalJSON(data []byte) error { - type n NameSpaceEntry // no methods, so no infinite recursion - var aux struct { - n - CID *string `json:"cid"` - FID *string `json:"fid"` - } - if err := json.Unmarshal(data, &aux); err != nil { - return err +func (e *NameSpaceEntry) EntryType() EntryType { + if e.CID != "" { + return EntryTypeFolder } + return EntryTypeFile +} - switch { - case aux.CID != nil && aux.FID != nil: - return errors.New("item: both cid and fid set") - case aux.CID != nil: - aux.ID, aux.EntryType = *aux.CID, EntryTypeFolder - case aux.FID != nil: - aux.ID, aux.EntryType = *aux.FID, EntryTypeFile - default: - return errors.New("item: neither cid nor fid set") +func (e *NameSpaceEntry) ID() string { + if e.CID != "" { + return e.CID } - - *e = NameSpaceEntry(aux.n) - return nil + return e.FID } -func (d *NameSpaceEntry) IsSysFolder() bool { - return d.EntryType == EntryTypeFolder && strings.HasPrefix(d.Name, ".sys.") +func (e *NameSpaceEntry) IsSysFolder() bool { + return e.EntryType() == EntryTypeFolder && strings.HasPrefix(e.Name, ".sys.") } -func (d *NameSpaceEntry) IsSysFile() bool { - return d.EntryType == EntryTypeFile && strings.HasPrefix(path.Base(path.Dir(d.Path)), ".sys.") +func (e *NameSpaceEntry) IsSysFile() bool { + return e.EntryType() == EntryTypeFile && strings.HasPrefix(path.Base(path.Dir(e.Path)), ".sys.") } func parseNSInspectOutput(data []byte) (*NamespaceDump, error) { - var raw []map[string]interface{} - if err := json.Unmarshal(data, &raw); err != nil { - return nil, errors.Wrap(err, "failed to unmarshal ns-inspect input") - } - var entries []NameSpaceEntry - for _, obj := range raw { - var e NameSpaceEntry - b, err := json.Marshal(obj) - if err != nil { - return nil, err - } - err = json.Unmarshal(b, &e) - if err != nil { - return nil, err - } - entries = append(entries, e) + if err := json.Unmarshal(data, &entries); err != nil { + return nil, errors.Wrap(err, "failed to unmarshal ns-inspect input") } + return &NamespaceDump{Entries: entries}, nil } diff --git a/pkg/reconciliation/nsdump/ns_dump_test.go b/pkg/reconciliation/nsdump/ns_dump_test.go new file mode 100644 index 0000000000..373c48fd87 --- /dev/null +++ b/pkg/reconciliation/nsdump/ns_dump_test.go @@ -0,0 +1,109 @@ +// Copyright 2018-2026 CERN +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// In applying this license, CERN does not waive the privileges and immunities +// granted to it by virtue of its status as an Intergovernmental Organization +// or submit itself to any jurisdiction. + +package nsdump + +import ( + "bytes" + "fmt" + "testing" +) + +// benchDumpJSON builds what an eos-ns-inspect scan prints for n entries: one +// folder for every nine files, under three folder levels. The objects carry the +// key set of a real dump, because the parser walks every key of every object. +// The text is written out directly: a dump of a million entries is hundreds of +// megabytes, which is too much to hold twice. +func benchDumpJSON(n int) []byte { + const root = "/eos/project/c/cernbox" + + var buf bytes.Buffer + buf.Grow(n * 900) + buf.WriteByte('[') + + for i := range n { + if i > 0 { + buf.WriteByte(',') + } + folder := fmt.Sprintf("%s/g%d/s%d/t%d", root, i%10, (i/10)%10, (i/100)%10) + acl := fmt.Sprintf("egroup:group%d:rwx,u:user%d:rx,u:user%d:rwx", i%97, i%89, i%83) + + if i%10 == 0 { + fmt.Fprintf(&buf, `{"cid":"%d","ctime":"1786473688.310186487","flags":"1",`+ + `"gid":"2763","mode":"42700","mtime":"1786474621.140556072","name":"t%d",`+ + `"parent_id":"348","path":"%s/","stime":"1786474621.140556072",`+ + `"tree_size":"1886117666","uid":"173503","xattr.sys.acl":"%s",`+ + `"xattr.sys.allow.oc.sync":"1","xattr.sys.eos.btime":"1677602740.101786072",`+ + `"xattr.sys.forced.atomic":"1","xattr.sys.forced.blocksize":"4k",`+ + `"xattr.sys.forced.checksum":"adler","xattr.sys.forced.layout":"replica",`+ + `"xattr.sys.forced.nstripes":"2","xattr.sys.forced.space":"default",`+ + `"xattr.sys.mask":"700","xattr.sys.mtime.propagation":"1",`+ + `"xattr.sys.owner.auth":"*","xattr.sys.recycle":"/eos/homedev/proc/recycle/",`+ + `"xattr.sys.versioning":"10"}`, + 4000000+i, (i/100)%10, folder, acl) + continue + } + + fmt.Fprintf(&buf, `{"atime":"1784818711.61715410","ctime":"1784818711.61714940",`+ + `"fid":"%d","flags":"644","gid":"2763","layout_id":"1048850","link_name":"",`+ + `"locations":"413,416","mtime":"1784818711.62072292","name":"file%d.txt",`+ + `"path":"%s/file%d.txt","pid":"4251882","size":"66","stime":"0.0","uid":"173503",`+ + `"unlink_locations":"","xattr.sys.acl":"%s",`+ + `"xattr.sys.eos.btime":"1784818711.61714940","xattr.sys.fs.tracking":"+413+416",`+ + `"xattr.sys.fusex.state":"","xattr.sys.utrace":"f81e1fe0-86a6-11f1-afee-fa163e35f83a",`+ + `"xattr.sys.vtrace":"[Thu Jul 23 16:58:31 2026] uid:173503[jgeens] gid:2763[it] `+ + `tident:jgeens.1:1@[2001:1458:d00:16::33d] name:jgeens dn: prot:https `+ + `app:http/reva_write host:cbox-ocisdev-diogo.cern.ch domain:cern.ch geo: sudo:0 `+ + `trace: onbehalf:","xs":"fcd91616"}`, + 110000000+i, i, folder, i, acl) + } + + buf.WriteByte(']') + return buf.Bytes() +} + +// The dump of a whole space goes through this parser one time per run, so its +// cost is the price of getting the namespace in. +func BenchmarkParseNSInspectOutput(b *testing.B) { + // the data is built inside the sub-benchmark, so running one size does not + // generate the others + for _, entries := range []int{1000, 10000, 100000, 1000000} { + b.Run(fmt.Sprintf("%d entries", entries), func(b *testing.B) { + data := benchDumpJSON(entries) + + // a parser that gives up early would measure nothing + dump, err := parseNSInspectOutput(data) + if err != nil { + b.Fatalf("parsing %d entries: %v", entries, err) + } + if len(dump.Entries) != entries { + b.Fatalf("parsed %d entries, want %d", len(dump.Entries), entries) + } + dump = nil + + b.SetBytes(int64(len(data))) + b.ReportAllocs() + b.ResetTimer() + for b.Loop() { + if _, err := parseNSInspectOutput(data); err != nil { + b.Fatal(err) + } + } + }) + } +} diff --git a/pkg/reconciliation/testdata/jgeens_nsdump.json b/pkg/reconciliation/testdata/jgeens_nsdump.json new file mode 100644 index 0000000000..829d6ee8ac --- /dev/null +++ b/pkg/reconciliation/testdata/jgeens_nsdump.json @@ -0,0 +1,4911 @@ +[ +{ + "cid" : "4251882", + "ctime" : "1786473688.310186487", + "flags" : "1", + "gid" : "2763", + "mode" : "42700", + "mtime" : "1786474621.140556072", + "name" : "jgeens", + "parent_id" : "348", + "path" : "/eos/user/j/jgeens/", + "stime" : "1786474621.140556072", + "tree_size" : "1886117666", + "uid" : "173503", + "xattr.sys.acl" : "u:173503:rwx", + "xattr.sys.allow.oc.sync" : "1", + "xattr.sys.eos.btime" : "1677602740.101786072", + "xattr.sys.forced.atomic" : "1", + "xattr.sys.forced.blocksize" : "4k", + "xattr.sys.forced.checksum" : "adler", + "xattr.sys.forced.layout" : "replica", + "xattr.sys.forced.nstripes" : "2", + "xattr.sys.forced.space" : "default", + "xattr.sys.mask" : "700", + "xattr.sys.mtime.propagation" : "1", + "xattr.sys.owner.auth" : "*", + "xattr.sys.recycle" : "/eos/homedev/proc/recycle/", + "xattr.sys.versioning" : "10", + "xattr.user.http://owncloud.org/ns/favorite" : "u:jgeens=1,u:tstcbsa9=1", + "xattr.user.reva.labels.jgeens.favorite" : "1", + "xattr.user.reva.labels.tstcbsa9.favorite" : "1" +}, +{ + "atime" : "1784818711.61715410", + "ctime" : "1784818711.61714940", + "fid" : "119984401", + "flags" : "644", + "gid" : "2763", + "layout_id" : "1048850", + "link_name" : "", + "locations" : "413,416", + "mtime" : "1784818711.62072292", + "name" : ".~lock.New file.odt#", + "path" : "/eos/user/j/jgeens/.~lock.New file.odt#", + "pid" : "4251882", + "size" : "66", + "stime" : "0.0", + "uid" : "173503", + "unlink_locations" : "", + "xattr.sys.acl" : "u:187895:rx", + "xattr.sys.eos.btime" : "1784818711.61714940", + "xattr.sys.fs.tracking" : "+413+416", + "xattr.sys.fusex.state" : "", + "xattr.sys.utrace" : "f81e1fe0-86a6-11f1-afee-fa163e35f83a", + "xattr.sys.vtrace" : "[Thu Jul 23 16:58:31 2026] uid:173503[jgeens] gid:2763[it] tident:jgeens.1:1@[2001:1458:d00:16::33d] name:jgeens dn: prot:https app:http/reva_write host:cbox-ocisdev-diogo.cern.ch domain:cern.ch geo: sudo:0 trace: onbehalf:", + "xs" : "fcd91616" +}, +{ + "atime" : "1780561047.896795701", + "ctime" : "1780561048.252752430", + "fid" : "118164987", + "flags" : "644", + "gid" : "2763", + "layout_id" : "1048850", + "link_name" : "", + "locations" : "420,412", + "mtime" : "1780561047.897379544", + "name" : "Computing at CERN.pptx", + "path" : "/eos/user/j/jgeens/Computing at CERN.pptx", + "pid" : "4251882", + "size" : "70488847", + "stime" : "0.0", + "uid" : "173503", + "unlink_locations" : "", + "xattr.sys.eos.btime" : "1780561047.896795201", + "xattr.sys.fs.tracking" : "+420+412", + "xattr.sys.utrace" : "d31ccce4-5fed-11f1-bf35-fa163e35f83a", + "xattr.sys.vtrace" : "[Thu Jun 4 10:17:27 2026] uid:173503[jgeens] gid:2763[it] tident:jgeens.1:1@[2001:1458:201:e4::100:6cb] name:jgeens dn: prot:https app:http/reva_eurooffice host:cbox-ocisdev-rasmus.cern.ch domain:cern.ch geo: sudo:0 trace: onbehalf:", + "xattr.user.iop.wopi.lastwritetime" : "1780561048", + "xs" : "162118e2" +}, +{ + "atime" : "1784818106.135950653", + "ctime" : "1786449526.929032160", + "fid" : "119984355", + "flags" : "644", + "gid" : "2763", + "layout_id" : "1048850", + "link_name" : "", + "locations" : "418,408", + "mtime" : "1784818106.136152724", + "name" : "New file.odt", + "path" : "/eos/user/j/jgeens/New file.odt", + "pid" : "4251882", + "size" : "5050146", + "stime" : "0.0", + "uid" : "173503", + "unlink_locations" : "", + "xattr.sys.acl" : "u:187895:rx,u:178406:rx!d", + "xattr.sys.app.lock" : "expires:1784820511,type:shared,owner:*:http/reva_collabora", + "xattr.sys.eos.btime" : "1784818106.135950353", + "xattr.sys.fs.tracking" : "+418+408", + "xattr.sys.fusex.state" : "", + "xattr.sys.reva.lockpayload" : "eyJsb2NrX2lkIjoib3BhcXVlbG9ja3Rva2VuOjc5NzM1NmE4LTA1MDAtNGNlYi1hOGEwLWM5NGM4Y2RlN2ViYSBZMjl2YkMxc2IyTnJRVUV4TWpZNU5VUT0iLCJ0eXBlIjoyLCJhcHBfbmFtZSI6IkNvbGxhYm9yYSIsImV4cGlyYXRpb24iOnsic2Vjb25kcyI6MTc4NDgyMDUxMX19", + "xattr.sys.utrace" : "8f8da96a-86a5-11f1-a171-fa163e35f83a", + "xattr.sys.vtrace" : "[Thu Jul 23 16:48:26 2026] uid:173503[jgeens] gid:2763[it] tident:jgeens.1:1@[2001:1458:d00:16::33d] name:jgeens dn: prot:https app:http/reva_collabora host:cbox-ocisdev-diogo.cern.ch domain:cern.ch geo: sudo:0 trace: onbehalf:", + "xs" : "1d78ac12" +}, +{ + "atime" : "1760601994.191612517", + "ctime" : "1782302739.135584958", + "fid" : "110145713", + "flags" : "644", + "gid" : "2763", + "layout_id" : "1048850", + "link_name" : "", + "locations" : "417,420", + "mtime" : "1760601994.191875716", + "name" : "atlas30new.root", + "path" : "/eos/user/j/jgeens/atlas30new.root", + "pid" : "4251882", + "size" : "1107644867", + "stime" : "0.0", + "uid" : "173503", + "unlink_locations" : "", + "xattr.sys.acl" : "u:187895:rx,u:193339:rwx+d", + "xattr.sys.eos.btime" : "1760601994.191611870", + "xattr.sys.fs.tracking" : "+412+421/421/412/415/413", + "xattr.sys.fusex.state" : "", + "xattr.sys.reva.lwshare.7290186@github" : "rx!d", + "xattr.sys.utrace" : "080d07bc-aa67-11f0-958a-fa163e2e9155", + "xattr.sys.vtrace" : "[Thu Oct 16 10:06:34 2025] uid:173503[jgeens] gid:2763[it] tident:jgeens.1:1@[2001:1458:d00:13::100:2be] name:jgeens dn: prot:https app:http/reva_write host:cbox-ocisdev-jesse.cern.ch domain:cern.ch geo: sudo:0 trace: onbehalf:", + "xs" : "1fdb5746" +}, +{ + "atime" : "1760627102.263157852", + "ctime" : "1760627102.263157029", + "fid" : "110155002", + "flags" : "644", + "gid" : "2763", + "layout_id" : "1048850", + "link_name" : "", + "locations" : "420,412", + "mtime" : "1760627102.263448338", + "name" : "bugged-video-Trisha and Nicola - Guess who I bumped into.mp4", + "path" : "/eos/user/j/jgeens/bugged-video-Trisha and Nicola - Guess who I bumped into.mp4", + "pid" : "4251882", + "size" : "472833361", + "stime" : "0.0", + "uid" : "173503", + "unlink_locations" : "", + "xattr.sys.eos.btime" : "1760627102.263157029", + "xattr.sys.fs.tracking" : "+413+416/413/416/414/418/416/409", + "xattr.sys.fusex.state" : "", + "xattr.sys.utrace" : "7da0b390-aaa1-11f0-ba73-fa163e2e9155", + "xattr.sys.vtrace" : "[Thu Oct 16 17:05:02 2025] uid:173503[jgeens] gid:2763[it] tident:jgeens.1:1@[2001:1458:d00:13::100:2be] name:jgeens dn: prot:https app:http/reva_write host:cbox-ocisdev-jesse.cern.ch domain:cern.ch geo: sudo:0 trace: onbehalf:", + "xs" : "c7aa9c01" +}, +{ + "atime" : "1759481971.431195568", + "ctime" : "1759481974.367057416", + "fid" : "109724938", + "flags" : "644", + "gid" : "2763", + "layout_id" : "1048850", + "link_name" : "", + "locations" : "412,417", + "mtime" : "1759481971.431456004", + "name" : "file.docx", + "path" : "/eos/user/j/jgeens/file.docx", + "pid" : "4251882", + "size" : "0", + "stime" : "0.0", + "uid" : "173503", + "unlink_locations" : "", + "xattr.sys.app.lock" : "expires:1759483774,type:shared,owner:*:http/reva_ms365", + "xattr.sys.eos.btime" : "1759481971.431194618", + "xattr.sys.fs.tracking" : "+412+417", + "xattr.sys.fusex.state" : "", + "xattr.sys.reva.lockpayload" : "eyJsb2NrX2lkIjoib3BhcXVlbG9ja3Rva2VuOjc5NzM1NmE4LTA1MDAtNGNlYi1hOGEwLWM5NGM4Y2RlN2ViYSBleUpUSWpvaU16UXlOMlZtWkRZdFlqaGxNUzAwWW1Nd0xXRm1NV1F0TTJZNVpUYzFZMll6T1dKbElpd2lSaUk2TkN3aVJTSTZNaXdpUXlJNklsQkpSVEVpTENKTklqb2lSRUkxVUVWUVJqQXdNREpFUlVVMklpd2lVQ0k2SWpJME9ERkdORVJHTFRNMU9VVXROREJHUVMxQlEwWTBMVE5GUXpSR09FTTJNak0zTlNJc0lrUWlPaUp2Wm1acFkyVmhjSEJ6TG14cGRtVXVZMjl0SW4wPSIsInR5cGUiOjIsImFwcF9uYW1lIjoiTVMzNjUiLCJleHBpcmF0aW9uIjp7InNlY29uZHMiOjE3NTk0ODM3NzR9fQ==", + "xattr.sys.utrace" : "467686ac-a037-11f0-84ae-fa163e2e9155", + "xattr.sys.vtrace" : "[Fri Oct 3 10:59:31 2025] uid:173503[jgeens] gid:2763[it] tident:jgeens.1:1@[2001:1458:d00:3b::100:283] name:jgeens dn: prot:https app:http/reva_write host:cbox-ocistest-01.cern.ch domain:cern.ch geo: sudo:0 trace: onbehalf:", + "xattr.user.iop.wopi.lastwritetime" : "1759481974", + "xs" : "00000001" +}, +{ + "atime" : "1785759945.983141535", + "ctime" : "1785759945.983140755", + "fid" : "120354831", + "flags" : "644", + "gid" : "2763", + "layout_id" : "1048850", + "link_name" : "", + "locations" : "415,409", + "mtime" : "1785759945.983446277", + "name" : "image (2).png", + "path" : "/eos/user/j/jgeens/image (2).png", + "pid" : "4251882", + "size" : "7515", + "stime" : "0.0", + "uid" : "173503", + "unlink_locations" : "", + "xattr.sys.eos.btime" : "1785759945.983140755", + "xattr.sys.fs.tracking" : "+415+409", + "xattr.sys.fusex.state" : "", + "xattr.sys.utrace" : "73d95edc-8f36-11f1-8b80-fa163e35f83a", + "xattr.sys.vtrace" : "[Mon Aug 3 14:25:45 2026] uid:173503[jgeens] gid:2763[it] tident:jgeens.1:1@[2001:1458:d00:13::100:2be] name:jgeens dn: prot:https app:http/reva_write host:cbox-ocisdev-jesse.cern.ch domain:cern.ch geo: sudo:0 trace: onbehalf:", + "xs" : "52a7fdaf" +}, +{ + "atime" : "1786432355.291004122", + "ctime" : "1786473688.367657487", + "fid" : "120596641", + "flags" : "644", + "gid" : "2763", + "layout_id" : "1048850", + "link_name" : "", + "locations" : "413,409", + "mtime" : "1786432355.291314351", + "name" : "lock.odt", + "path" : "/eos/user/j/jgeens/lock.odt", + "pid" : "4251882", + "size" : "0", + "stime" : "0.0", + "uid" : "173503", + "unlink_locations" : "", + "xattr.sys.eos.btime" : "1786432355.291003512", + "xattr.sys.fs.tracking" : "+413+409", + "xattr.sys.fusex.state" : "", + "xattr.sys.utrace" : "07074dd6-9554-11f1-963f-fa163e35f83a", + "xattr.sys.vtrace" : "[Tue Aug 11 09:12:35 2026] uid:173503[jgeens] gid:2763[it] tident:jgeens.1:1@[2001:1458:d00:13::100:2be] name:jgeens dn: prot:https app:http/reva_write host:cbox-ocisdev-jesse.cern.ch domain:cern.ch geo: sudo:0 trace: onbehalf:", + "xs" : "00000001" +}, +{ + "atime" : "1771841389.496987895", + "ctime" : "1784799478.612336388", + "fid" : "114812362", + "flags" : "644", + "gid" : "2763", + "layout_id" : "1048850", + "link_name" : "", + "locations" : "413,416", + "mtime" : "1771841389.497592060", + "name" : "pres.pptx", + "path" : "/eos/user/j/jgeens/pres.pptx", + "pid" : "4251882", + "size" : "28796", + "stime" : "0.0", + "uid" : "173503", + "unlink_locations" : "", + "xattr.sys.acl" : "u:187895:rx,u:193339:rwx+d", + "xattr.sys.app.lock" : "expires:1771843172,type:shared,owner:*:http/reva_ms365", + "xattr.sys.eos.btime" : "1771841389.496987286", + "xattr.sys.fs.tracking" : "+413+416", + "xattr.sys.fusex.state" : "", + "xattr.sys.reva.lockpayload" : "eyJsb2NrX2lkIjoib3BhcXVlbG9ja3Rva2VuOjc5NzM1NmE4LTA1MDAtNGNlYi1hOGEwLWM5NGM4Y2RlN2ViYSBSa2xGTVY4Mk5TMDRPQzAyUkMwek9DMURNeTFHTnkwNE9TMUZSaTFCTlMxQ05DMHhPQzFGT1MxQlJTMDJRUzFHTnkxRVF5MUdPUzFFTUMwMU9DMDBNeTFFUmkwM05DMDJSaTAxT0MwMVJTMHpSQzA1Tnkwd1FpMHlOaTAxTlMwNU55MUJNRjgzWkRRME9URm1ZUzFpTUdZeExUUTFZelV0WVdFNVpTMHdPR1UwTUdFME9XTTFPV1ZmZGpFPSIsInR5cGUiOjIsImFwcF9uYW1lIjoiTVMzNjUiLCJleHBpcmF0aW9uIjp7InNlY29uZHMiOjE3NzE4NDMxNzJ9fQ==", + "xattr.sys.utrace" : "c9b27a52-109f-11f1-8d9b-fa163e2e9155", + "xattr.sys.vtrace" : "[Mon Feb 23 11:09:49 2026] uid:173503[jgeens] gid:2763[it] tident:jgeens.1:1@[2001:1458:d00:3b::100:283] name:jgeens dn: prot:https app:http/reva_ms365 host:cbox-ocistest-01.cern.ch domain:cern.ch geo: sudo:0 trace: onbehalf:", + "xattr.user.iop.wopi.lastwritetime" : "1771841389", + "xs" : "52f5f494" +}, +{ + "atime" : "1743413415.614234482", + "ctime" : "1743413436.251761549", + "fid" : "100378729", + "flags" : "644", + "gid" : "2763", + "layout_id" : "1048850", + "link_name" : "", + "locations" : "418,419", + "mtime" : "1743413415.614511757", + "name" : "presentation.pptx", + "path" : "/eos/user/j/jgeens/presentation.pptx", + "pid" : "4251882", + "size" : "0", + "stime" : "0.0", + "uid" : "173503", + "unlink_locations" : "", + "xattr.sys.app.lock" : "expires:1743415236,type:shared,owner:*:wopi_ms_365_on_cloud", + "xattr.sys.eos.btime" : "1743413415.614231909", + "xattr.sys.fs.tracking" : "+418+414/414-419", + "xattr.sys.fusex.state" : "", + "xattr.sys.utrace" : "c0d8fe78-0e12-11f0-9b77-fa163e2e9155", + "xattr.sys.vtrace" : "[Mon Mar 31 11:30:15 2025] uid:173503[jgeens] gid:2763[it] tident:jgeens.1:1@[2001:1458:d00:1::100:225] name:jgeens dn: prot:https app:http host:cbox-ocisdev-jesse.cern.ch domain:cern.ch geo: sudo:0 trace: onbehalf:", + "xattr.user.iop.lock" : "eyJsb2NrX2lkIjogIm9wYXF1ZWxvY2t0b2tlbjo3OTczNTZhOC0wNTAwLTRjZWItYThhMC1jOTRjOGNkZTdlYmEgUmtsTU1WODFSQzB5UVMweVF5MDFPQzA0T1MxRE15MDBOeTFDTnkwMk15MUVOeTFGUmkwMU9DMUdSaTB3TXkweVJpMUNPUzA1T0MwMU5DMUdSUzA1T1MxRlFTMDNNeTFETVMwek9DMDBOaTB3TkMwM09DMHlNUzAxTkMwek55MHhRUzAzUlY4NVlqa3lZamsxTlMwd1pEVTRMVFJrTVdVdFlqazRaQzB6TWpZME9UZzRaREUxTW1SZmRqRT0iLCAidHlwZSI6IDIsICJhcHBfbmFtZSI6ICJNUyAzNjUgb24gQ2xvdWQiLCAidXNlciI6IHt9LCAiZXhwaXJhdGlvbiI6IHsic2Vjb25kcyI6IDE3NDM0MTUyMzZ9fQ==", + "xattr.user.iop.wopi.lastwritetime" : "1743413436", + "xs" : "00000001" +}, +{ + "atime" : "1759919434.464540324", + "ctime" : "1759919434.464539349", + "fid" : "109889700", + "flags" : "644", + "gid" : "2763", + "layout_id" : "1048850", + "link_name" : "", + "locations" : "418,410", + "mtime" : "1759919434.464814535", + "name" : "s02_preneel_basic_crypto_2025v1.pdf", + "path" : "/eos/user/j/jgeens/s02_preneel_basic_crypto_2025v1.pdf", + "pid" : "4251882", + "size" : "2342523", + "stime" : "0.0", + "uid" : "173503", + "unlink_locations" : "", + "xattr.sys.eos.btime" : "1759919434.464539349", + "xattr.sys.fs.tracking" : "+413+416/416/413", + "xattr.sys.fusex.state" : "", + "xattr.sys.utrace" : "d2c00bee-a431-11f0-b368-fa163e2e9155", + "xattr.sys.vtrace" : "[Wed Oct 8 12:30:34 2025] uid:173503[jgeens] gid:2763[it] tident:jgeens.1:1@[2001:1458:d00:16::33d] name:jgeens dn: prot:https app:http/reva_write host:cbox-ocisdev-diogo.cern.ch domain:cern.ch geo: sudo:0 trace: onbehalf:", + "xs" : "47dda1bc" +}, +{ + "atime" : "1760700846.239522942", + "ctime" : "1760700846.239517497", + "fid" : "110183236", + "flags" : "644", + "gid" : "2763", + "layout_id" : "1048850", + "link_name" : "", + "locations" : "418,410", + "mtime" : "1760700846.239769059", + "name" : "summer_student_tutorial_tracks.root", + "path" : "/eos/user/j/jgeens/summer_student_tutorial_tracks.root", + "pid" : "4251882", + "size" : "15483567", + "stime" : "0.0", + "uid" : "173503", + "unlink_locations" : "", + "xattr.sys.eos.btime" : "1760700846.239517497", + "xattr.sys.fs.tracking" : "+421+411/421/411", + "xattr.sys.fusex.state" : "", + "xattr.sys.utrace" : "30768016-ab4d-11f0-a253-fa163e2e9155", + "xattr.sys.vtrace" : "[Fri Oct 17 13:34:06 2025] uid:173503[jgeens] gid:2763[it] tident:jgeens.1:1@[2001:1458:d00:13::100:2be] name:jgeens dn: prot:https app:http/reva_write host:cbox-ocisdev-jesse.cern.ch domain:cern.ch geo: sudo:0 trace: onbehalf:", + "xs" : "27997779" +}, +{ + "atime" : "1764338038.762259901", + "ctime" : "1771935740.752474069", + "fid" : "111852685", + "flags" : "644", + "gid" : "2763", + "layout_id" : "1048850", + "link_name" : "", + "locations" : "412,417", + "mtime" : "1764338038.762804013", + "name" : "symmetric_crypto_exerises_nov24.pdf", + "path" : "/eos/user/j/jgeens/symmetric_crypto_exerises_nov24.pdf", + "pid" : "4251882", + "size" : "185169", + "stime" : "0.0", + "uid" : "173503", + "unlink_locations" : "", + "xattr.sys.dtrace" : "{uid:173503,gid:2763,tident:jgeens@grpc,prot:,app:,host:,domain:trace:52ad093f-3210-4088-9690-d5810d19225d,onbehalf:}", + "xattr.sys.eos.btime" : "1764338038.762259155", + "xattr.sys.fs.tracking" : "+412+417", + "xattr.sys.fusex.state" : "", + "xattr.sys.recycle.version.key" : "00000000005eacec", + "xattr.sys.utrace" : "b025565e-cc61-11f0-adb3-fa163e2e9155", + "xattr.sys.vtrace" : "[Fri Nov 28 14:53:58 2025] uid:173503[jgeens] gid:2763[it] tident:jgeens.1:1@[2001:1458:d00:13::100:2be] name:jgeens dn: prot:https app:http/reva_write host:cbox-ocisdev-jesse.cern.ch domain:cern.ch geo: sudo:0 trace: onbehalf:", + "xs" : "70e8fa9e" +}, +{ + "atime" : "1786373040.60738749", + "ctime" : "1786373040.60738210", + "fid" : "120572353", + "flags" : "644", + "gid" : "2763", + "layout_id" : "1048850", + "link_name" : "", + "locations" : "418,408", + "mtime" : "1786373040.61054869", + "name" : "test.docx", + "path" : "/eos/user/j/jgeens/test.docx", + "pid" : "4251882", + "size" : "0", + "stime" : "0.0", + "uid" : "173503", + "unlink_locations" : "", + "xattr.sys.eos.btime" : "1786373040.60738210", + "xattr.sys.fs.tracking" : "+418+408", + "xattr.sys.fusex.state" : "", + "xattr.sys.utrace" : "ec656f06-94c9-11f1-9f34-fa163e35f83a", + "xattr.sys.vtrace" : "[Mon Aug 10 16:44:00 2026] uid:173503[jgeens] gid:2763[it] tident:jgeens.1:1@[2001:1458:d00:13::100:2be] name:jgeens dn: prot:https app:http/reva_write host:cbox-ocisdev-jesse.cern.ch domain:cern.ch geo: sudo:0 trace: onbehalf:", + "xs" : "00000001" +}, +{ + "atime" : "1778863881.606510854", + "ctime" : "1778863881.606510355", + "fid" : "117478810", + "flags" : "644", + "gid" : "2763", + "layout_id" : "1048850", + "link_name" : "", + "locations" : "410,408", + "mtime" : "1778863881.606947972", + "name" : "versions.txt", + "path" : "/eos/user/j/jgeens/versions.txt", + "pid" : "4251882", + "size" : "5", + "stime" : "0.0", + "uid" : "173503", + "unlink_locations" : "", + "xattr.sys.acl" : "u:187895:rx,u:103421:rwx+d", + "xattr.sys.eos.btime" : "1778863881.606510355", + "xattr.sys.fs.tracking" : "+410+408", + "xattr.sys.fusex.state" : "", + "xattr.sys.utrace" : "4d2c8062-507e-11f1-abae-fa163e44ab60", + "xattr.sys.vtrace" : "[Fri May 15 18:51:21 2026] uid:173503[jgeens] gid:2763[it] tident:jgeens.1:1@[2001:1458:d00:13::100:2be] name:jgeens dn: prot:https app:http/reva_write host:cbox-ocisdev-jesse.cern.ch domain:cern.ch geo: sudo:0 trace: onbehalf:", + "xs" : "0453015a" +}, +{ + "cid" : "5868258", + "ctime" : "1754407593.524013422", + "flags" : "0", + "gid" : "2763", + "mode" : "40750", + "mtime" : "1754407593.523434413", + "name" : ".space", + "parent_id" : "4251882", + "path" : "/eos/user/j/jgeens/.space/", + "stime" : "0.0", + "tree_size" : "0", + "uid" : "173503", + "xattr.sys.acl" : "u:173503:rwx", + "xattr.sys.allow.oc.sync" : "1", + "xattr.sys.eos.btime" : "1677602740.101786072", + "xattr.sys.forced.atomic" : "1", + "xattr.sys.forced.blocksize" : "4k", + "xattr.sys.forced.checksum" : "adler", + "xattr.sys.forced.layout" : "replica", + "xattr.sys.forced.nstripes" : "2", + "xattr.sys.forced.space" : "default", + "xattr.sys.mask" : "700", + "xattr.sys.mtime.propagation" : "1", + "xattr.sys.owner.auth" : "*", + "xattr.sys.recycle" : "/eos/homedev/proc/recycle/", + "xattr.sys.versioning" : "10" +}, +{ + "cid" : "6850458", + "ctime" : "1784817937.264746487", + "flags" : "0", + "gid" : "2763", + "mode" : "40750", + "mtime" : "1784818711.75718056", + "name" : ".sys.v#..~lock.New file.odt#", + "parent_id" : "4251882", + "path" : "/eos/user/j/jgeens/.sys.v#..~lock.New file.odt#/", + "stime" : "0.0", + "tree_size" : "66", + "uid" : "173503", + "xattr.sys.eos.btime" : "1784817937.264346325" +}, +{ + "atime" : "1784817793.279894522", + "ctime" : "1784817793.279893932", + "fid" : "119984334", + "flags" : "644", + "gid" : "2763", + "layout_id" : "1048850", + "link_name" : "", + "locations" : "418,408", + "mtime" : "1784817793.280284414", + "name" : "1784817793.0726d0ce", + "path" : "/eos/user/j/jgeens/.sys.v#..~lock.New file.odt#/1784817793.0726d0ce", + "pid" : "6850458", + "size" : "66", + "stime" : "0.0", + "uid" : "173503", + "unlink_locations" : "", + "xattr.sys.eos.btime" : "1784817793.279893932", + "xattr.sys.fs.tracking" : "+418+408", + "xattr.sys.fusex.state" : "", + "xattr.sys.tmp.atomic" : ".sys.a#.v#.~lock.New file.odt#.f81e2ae4-86a6-11f1-afee-fa163e35f83a", + "xattr.sys.utrace" : "d513be6c-86a4-11f1-9c3f-fa163e35f83a", + "xattr.sys.vtrace" : "[Thu Jul 23 16:43:13 2026] uid:173503[jgeens] gid:2763[it] tident:jgeens.1:1@[2001:1458:d00:16::33d] name:jgeens dn: prot:https app:http/reva_write host:cbox-ocisdev-diogo.cern.ch domain:cern.ch geo: sudo:0 trace: onbehalf:", + "xs" : "fc8a1610" +}, +{ + "cid" : "6710975", + "ctime" : "1780560999.463820969", + "flags" : "0", + "gid" : "2763", + "mode" : "40750", + "mtime" : "1780560516.179871867", + "name" : ".sys.v#.Computing at CERN.pptx", + "parent_id" : "4251882", + "path" : "/eos/user/j/jgeens/.sys.v#.Computing at CERN.pptx/", + "stime" : "0.0", + "tree_size" : "0", + "uid" : "173503", + "xattr.sys.eos.btime" : "1780560516.179871867", + "xattr.user.iop.wopi.lastwritetime" : "1780560999" +}, +{ + "cid" : "6849278", + "ctime" : "1784818106.200863718", + "flags" : "0", + "gid" : "2763", + "mode" : "40750", + "mtime" : "1784818106.181755353", + "name" : ".sys.v#.New file.odt", + "parent_id" : "4251882", + "path" : "/eos/user/j/jgeens/.sys.v#.New file.odt/", + "stime" : "0.0", + "tree_size" : "40559165", + "uid" : "173503", + "xattr.sys.acl" : "u:187895:rx,u:178406:rx!d", + "xattr.sys.eos.btime" : "1784788964.263114501", + "xattr.user.iop.wopi.lastwritetime" : "1784818106" +}, +{ + "atime" : "1784788964.239510785", + "ctime" : "1784788970.111087939", + "fid" : "119972503", + "flags" : "644", + "gid" : "2763", + "layout_id" : "1048850", + "link_name" : "", + "locations" : "410,408", + "mtime" : "1784788964.240082798", + "name" : "1784788970.0726a297", + "path" : "/eos/user/j/jgeens/.sys.v#.New file.odt/1784788970.0726a297", + "pid" : "6849278", + "size" : "0", + "stime" : "0.0", + "uid" : "173503", + "unlink_locations" : "", + "xattr.sys.app.lock" : "expires:1784790770,type:shared,owner:*:http/reva_collabora", + "xattr.sys.eos.btime" : "1784788964.239510216", + "xattr.sys.fs.tracking" : "+410+408", + "xattr.sys.fusex.state" : "", + "xattr.sys.reva.lockpayload" : "eyJsb2NrX2lkIjoib3BhcXVlbG9ja3Rva2VuOjc5NzM1NmE4LTA1MDAtNGNlYi1hOGEwLWM5NGM4Y2RlN2ViYSBZMjl2YkMxc2IyTnJNV0k0WTJJNE5qaz0iLCJ0eXBlIjoyLCJhcHBfbmFtZSI6IkNvbGxhYm9yYSIsImV4cGlyYXRpb24iOnsic2Vjb25kcyI6MTc4NDc5MDc3MH19", + "xattr.sys.tmp.atomic" : ".sys.a#.v#New file.odt.ce9d60d0-8661-11f1-be00-fa163e35f83a", + "xattr.sys.utrace" : "b5a1497a-8661-11f1-b4f0-fa163e35f83a", + "xattr.sys.vtrace" : "[Thu Jul 23 08:42:44 2026] uid:173503[jgeens] gid:2763[it] tident:jgeens.1:1@[2001:1458:d00:16::33d] name:jgeens dn: prot:https app:http/reva_write host:cbox-ocisdev-diogo.cern.ch domain:cern.ch geo: sudo:0 trace: onbehalf:", + "xs" : "00000001" +}, +{ + "atime" : "1784796440.84613937", + "ctime" : "1784796440.84613467", + "fid" : "119975505", + "flags" : "644", + "gid" : "2763", + "layout_id" : "1048850", + "link_name" : "", + "locations" : "415,413", + "mtime" : "1784796440.84910869", + "name" : "1784796440.0726ae51", + "path" : "/eos/user/j/jgeens/.sys.v#.New file.odt/1784796440.0726ae51", + "pid" : "6849278", + "size" : "10184", + "stime" : "0.0", + "uid" : "173503", + "unlink_locations" : "", + "xattr.sys.acl" : "u:187895:rx,u:178406:rx!d", + "xattr.sys.app.lock" : "expires:1784796728,type:shared,owner:*:http/reva_collabora", + "xattr.sys.eos.btime" : "1784796440.84613467", + "xattr.sys.fs.tracking" : "+415+413", + "xattr.sys.fusex.state" : "", + "xattr.sys.reva.lockpayload" : "eyJsb2NrX2lkIjoib3BhcXVlbG9ja3Rva2VuOjc5NzM1NmE4LTA1MDAtNGNlYi1hOGEwLWM5NGM4Y2RlN2ViYSBZMjl2YkMxc2IyTnJObUUwTURobVpEVT0iLCJ0eXBlIjoyLCJhcHBfbmFtZSI6IkNvbGxhYm9yYSIsImV4cGlyYXRpb24iOnsic2Vjb25kcyI6MTc4NDc5NjcyOH19", + "xattr.sys.tmp.atomic" : ".sys.a#.v#New file.odt.458b4ef8-8673-11f1-81e8-fa163e35f83a", + "xattr.sys.utrace" : "1d94b466-8673-11f1-931f-fa163e35f83a", + "xattr.sys.vtrace" : "[Thu Jul 23 10:47:20 2026] uid:173503[jgeens] gid:2763[it] tident:jgeens.1:1@[2001:1458:d00:16::33d] name:jgeens dn: prot:https app:http/reva_collabora host:cbox-ocisdev-diogo.cern.ch domain:cern.ch geo: sudo:0 trace: onbehalf:", + "xs" : "64f6dfc6" +}, +{ + "atime" : "1784796507.131597309", + "ctime" : "1784797041.545785675", + "fid" : "119975510", + "flags" : "644", + "gid" : "2763", + "layout_id" : "1048850", + "link_name" : "", + "locations" : "418,410", + "mtime" : "1784796507.131909090", + "name" : "1784797041.0726ae56", + "path" : "/eos/user/j/jgeens/.sys.v#.New file.odt/1784797041.0726ae56", + "pid" : "6849278", + "size" : "5195713", + "stime" : "0.0", + "uid" : "173503", + "unlink_locations" : "", + "xattr.sys.acl" : "u:187895:rx,u:178406:rx!d", + "xattr.sys.app.lock" : "expires:1784798841,type:shared,owner:*:http/reva_collabora", + "xattr.sys.eos.btime" : "1784796507.131596919", + "xattr.sys.fs.tracking" : "+415+416/416/415", + "xattr.sys.fusex.state" : "", + "xattr.sys.reva.lockpayload" : "eyJsb2NrX2lkIjoib3BhcXVlbG9ja3Rva2VuOjc5NzM1NmE4LTA1MDAtNGNlYi1hOGEwLWM5NGM4Y2RlN2ViYSBZMjl2YkMxc2IyTnJObUUwTURobVpEVT0iLCJ0eXBlIjoyLCJhcHBfbmFtZSI6IkNvbGxhYm9yYSIsImV4cGlyYXRpb24iOnsic2Vjb25kcyI6MTc4NDc5ODg0MX19", + "xattr.sys.tmp.atomic" : ".sys.a#.v#New file.odt.878faa50-8674-11f1-87dc-fa163e35f83a", + "xattr.sys.utrace" : "458b4296-8673-11f1-81e8-fa163e35f83a", + "xattr.sys.vtrace" : "[Thu Jul 23 10:48:27 2026] uid:173503[jgeens] gid:2763[it] tident:jgeens.1:1@[2001:1458:d00:16::33d] name:jgeens dn: prot:https app:http/reva_collabora host:cbox-ocisdev-diogo.cern.ch domain:cern.ch geo: sudo:0 trace: onbehalf:", + "xs" : "6ce8c258" +}, +{ + "atime" : "1784797047.386484679", + "ctime" : "1784797047.386484299", + "fid" : "119975555", + "flags" : "644", + "gid" : "2763", + "layout_id" : "1048850", + "link_name" : "", + "locations" : "418,414", + "mtime" : "1784797047.386815581", + "name" : "1784797047.0726ae83", + "path" : "/eos/user/j/jgeens/.sys.v#.New file.odt/1784797047.0726ae83", + "pid" : "6849278", + "size" : "5092858", + "stime" : "0.0", + "uid" : "173503", + "unlink_locations" : "", + "xattr.sys.acl" : "u:187895:rx,u:178406:rx!d", + "xattr.sys.app.lock" : "expires:1784798841,type:shared,owner:*:http/reva_collabora", + "xattr.sys.eos.btime" : "1784797047.386484299", + "xattr.sys.fs.tracking" : "+413+416/416/413", + "xattr.sys.fusex.state" : "", + "xattr.sys.reva.lockpayload" : "eyJsb2NrX2lkIjoib3BhcXVlbG9ja3Rva2VuOjc5NzM1NmE4LTA1MDAtNGNlYi1hOGEwLWM5NGM4Y2RlN2ViYSBZMjl2YkMxc2IyTnJObUUwTURobVpEVT0iLCJ0eXBlIjoyLCJhcHBfbmFtZSI6IkNvbGxhYm9yYSIsImV4cGlyYXRpb24iOnsic2Vjb25kcyI6MTc4NDc5ODg0MX19", + "xattr.sys.tmp.atomic" : ".sys.a#.v#New file.odt.b117f6f2-8674-11f1-b578-fa163e35f83a", + "xattr.sys.utrace" : "878f9a10-8674-11f1-87dc-fa163e35f83a", + "xattr.sys.vtrace" : "[Thu Jul 23 10:57:27 2026] uid:173503[jgeens] gid:2763[it] tident:jgeens.1:1@[2001:1458:d00:16::33d] name:jgeens dn: prot:https app:http/reva_collabora host:cbox-ocisdev-diogo.cern.ch domain:cern.ch geo: sudo:0 trace: onbehalf:", + "xs" : "3e84f094" +}, +{ + "atime" : "1784797117.66344927", + "ctime" : "1784797117.66344547", + "fid" : "119975560", + "flags" : "644", + "gid" : "2763", + "layout_id" : "1048850", + "link_name" : "", + "locations" : "411,420", + "mtime" : "1784797117.66749029", + "name" : "1784797117.0726ae88", + "path" : "/eos/user/j/jgeens/.sys.v#.New file.odt/1784797117.0726ae88", + "pid" : "6849278", + "size" : "5030468", + "stime" : "0.0", + "uid" : "173503", + "unlink_locations" : "", + "xattr.sys.acl" : "u:187895:rx,u:178406:rx!d", + "xattr.sys.app.lock" : "expires:1784798841,type:shared,owner:*:http/reva_collabora", + "xattr.sys.eos.btime" : "1784797117.66344547", + "xattr.sys.fs.tracking" : "+411+420", + "xattr.sys.fusex.state" : "", + "xattr.sys.reva.lockpayload" : "eyJsb2NrX2lkIjoib3BhcXVlbG9ja3Rva2VuOjc5NzM1NmE4LTA1MDAtNGNlYi1hOGEwLWM5NGM4Y2RlN2ViYSBZMjl2YkMxc2IyTnJObUUwTURobVpEVT0iLCJ0eXBlIjoyLCJhcHBfbmFtZSI6IkNvbGxhYm9yYSIsImV4cGlyYXRpb24iOnsic2Vjb25kcyI6MTc4NDc5ODg0MX19", + "xattr.sys.tmp.atomic" : ".sys.a#.v#New file.odt.d9394bf4-8674-11f1-87dc-fa163e35f83a", + "xattr.sys.utrace" : "b117e914-8674-11f1-b578-fa163e35f83a", + "xattr.sys.vtrace" : "[Thu Jul 23 10:58:37 2026] uid:173503[jgeens] gid:2763[it] tident:jgeens.1:1@[2001:1458:d00:16::33d] name:jgeens dn: prot:https app:http/reva_collabora host:cbox-ocisdev-diogo.cern.ch domain:cern.ch geo: sudo:0 trace: onbehalf:", + "xs" : "21d23a68" +}, +{ + "atime" : "1784797184.393651148", + "ctime" : "1784797184.393650788", + "fid" : "119975565", + "flags" : "644", + "gid" : "2763", + "layout_id" : "1048850", + "link_name" : "", + "locations" : "418,410", + "mtime" : "1784797184.393948381", + "name" : "1784797184.0726ae8d", + "path" : "/eos/user/j/jgeens/.sys.v#.New file.odt/1784797184.0726ae8d", + "pid" : "6849278", + "size" : "5030905", + "stime" : "0.0", + "uid" : "173503", + "unlink_locations" : "", + "xattr.sys.acl" : "u:187895:rx,u:178406:rx!d", + "xattr.sys.app.lock" : "expires:1784798841,type:shared,owner:*:http/reva_collabora", + "xattr.sys.eos.btime" : "1784797184.393650788", + "xattr.sys.fs.tracking" : "+415+416/416/415", + "xattr.sys.fusex.state" : "", + "xattr.sys.reva.lockpayload" : "eyJsb2NrX2lkIjoib3BhcXVlbG9ja3Rva2VuOjc5NzM1NmE4LTA1MDAtNGNlYi1hOGEwLWM5NGM4Y2RlN2ViYSBZMjl2YkMxc2IyTnJObUUwTURobVpEVT0iLCJ0eXBlIjoyLCJhcHBfbmFtZSI6IkNvbGxhYm9yYSIsImV4cGlyYXRpb24iOnsic2Vjb25kcyI6MTc4NDc5ODg0MX19", + "xattr.sys.tmp.atomic" : ".sys.a#.v#New file.odt.fe0f3a24-8674-11f1-9a3f-fa163e35f83a", + "xattr.sys.utrace" : "d9393f56-8674-11f1-87dc-fa163e35f83a", + "xattr.sys.vtrace" : "[Thu Jul 23 10:59:44 2026] uid:173503[jgeens] gid:2763[it] tident:jgeens.1:1@[2001:1458:d00:16::33d] name:jgeens dn: prot:https app:http/reva_collabora host:cbox-ocisdev-diogo.cern.ch domain:cern.ch geo: sudo:0 trace: onbehalf:", + "xs" : "65a021d6" +}, +{ + "atime" : "1784797246.193659922", + "ctime" : "1784808828.251806759", + "fid" : "119975573", + "flags" : "644", + "gid" : "2763", + "layout_id" : "1048850", + "link_name" : "", + "locations" : "410,408", + "mtime" : "1784797246.194136795", + "name" : "1784808828.0726ae95", + "path" : "/eos/user/j/jgeens/.sys.v#.New file.odt/1784808828.0726ae95", + "pid" : "6849278", + "size" : "5049700", + "stime" : "0.0", + "uid" : "173503", + "unlink_locations" : "", + "xattr.sys.acl" : "u:187895:rx,u:178406:rx!d", + "xattr.sys.app.lock" : "expires:1784810628,type:shared,owner:*:http/reva_collabora", + "xattr.sys.eos.btime" : "1784797246.193659402", + "xattr.sys.fs.tracking" : "+410+408", + "xattr.sys.fusex.state" : "", + "xattr.sys.reva.lockpayload" : "eyJsb2NrX2lkIjoib3BhcXVlbG9ja3Rva2VuOjc5NzM1NmE4LTA1MDAtNGNlYi1hOGEwLWM5NGM4Y2RlN2ViYSBZMjl2YkMxc2IyTnJOR00wWkdVM1lqST0iLCJ0eXBlIjoyLCJhcHBfbmFtZSI6IkNvbGxhYm9yYSIsImV4cGlyYXRpb24iOnsic2Vjb25kcyI6MTc4NDgxMDYyOH19", + "xattr.sys.tmp.atomic" : ".sys.a#.v#New file.odt.20b1ead4-8690-11f1-a4ea-fa163e35f83a", + "xattr.sys.utrace" : "fe0f2ade-8674-11f1-9a3f-fa163e35f83a", + "xattr.sys.vtrace" : "[Thu Jul 23 11:00:46 2026] uid:173503[jgeens] gid:2763[it] tident:jgeens.1:1@[2001:1458:d00:16::33d] name:jgeens dn: prot:https app:http/reva_collabora host:cbox-ocisdev-diogo.cern.ch domain:cern.ch geo: sudo:0 trace: onbehalf:", + "xs" : "35b75b01" +}, +{ + "atime" : "1784808900.714078190", + "ctime" : "1784808900.714077570", + "fid" : "119980057", + "flags" : "644", + "gid" : "2763", + "layout_id" : "1048850", + "link_name" : "", + "locations" : "417,420", + "mtime" : "1784808900.714445891", + "name" : "1784808900.0726c019", + "path" : "/eos/user/j/jgeens/.sys.v#.New file.odt/1784808900.0726c019", + "pid" : "6849278", + "size" : "5049449", + "stime" : "0.0", + "uid" : "173503", + "unlink_locations" : "", + "xattr.sys.acl" : "u:187895:rx,u:178406:rx!d", + "xattr.sys.app.lock" : "expires:1784810628,type:shared,owner:*:http/reva_collabora", + "xattr.sys.eos.btime" : "1784808900.714077570", + "xattr.sys.fs.tracking" : "+417+420", + "xattr.sys.fusex.state" : "", + "xattr.sys.reva.lockpayload" : "eyJsb2NrX2lkIjoib3BhcXVlbG9ja3Rva2VuOjc5NzM1NmE4LTA1MDAtNGNlYi1hOGEwLWM5NGM4Y2RlN2ViYSBZMjl2YkMxc2IyTnJOR00wWkdVM1lqST0iLCJ0eXBlIjoyLCJhcHBfbmFtZSI6IkNvbGxhYm9yYSIsImV4cGlyYXRpb24iOnsic2Vjb25kcyI6MTc4NDgxMDYyOH19", + "xattr.sys.tmp.atomic" : ".sys.a#.v#New file.odt.36261ac0-8690-11f1-a419-fa163e35f83a", + "xattr.sys.utrace" : "20b1d742-8690-11f1-a4ea-fa163e35f83a", + "xattr.sys.vtrace" : "[Thu Jul 23 14:15:00 2026] uid:173503[jgeens] gid:2763[it] tident:jgeens.1:1@[2001:1458:d00:16::33d] name:jgeens dn: prot:https app:http/reva_collabora host:cbox-ocisdev-diogo.cern.ch domain:cern.ch geo: sudo:0 trace: onbehalf:", + "xs" : "ce0f7bca" +}, +{ + "atime" : "1784808936.707675065", + "ctime" : "1784817793.323467969", + "fid" : "119980062", + "flags" : "644", + "gid" : "2763", + "layout_id" : "1048850", + "link_name" : "", + "locations" : "418,410", + "mtime" : "1784808936.708076617", + "name" : "1784817793.0726c01e", + "path" : "/eos/user/j/jgeens/.sys.v#.New file.odt/1784817793.0726c01e", + "pid" : "6849278", + "size" : "5049788", + "stime" : "0.0", + "uid" : "173503", + "unlink_locations" : "", + "xattr.sys.acl" : "u:187895:rx,u:178406:rx!d", + "xattr.sys.app.lock" : "expires:1784819593,type:shared,owner:*:http/reva_collabora", + "xattr.sys.eos.btime" : "1784808936.707674485", + "xattr.sys.fs.tracking" : "+409+416/416/409", + "xattr.sys.fusex.state" : "", + "xattr.sys.reva.lockpayload" : "eyJsb2NrX2lkIjoib3BhcXVlbG9ja3Rva2VuOjc5NzM1NmE4LTA1MDAtNGNlYi1hOGEwLWM5NGM4Y2RlN2ViYSBZMjl2YkMxc2IyTnJRVUV4TWpZNU5VUT0iLCJ0eXBlIjoyLCJhcHBfbmFtZSI6IkNvbGxhYm9yYSIsImV4cGlyYXRpb24iOnsic2Vjb25kcyI6MTc4NDgxOTU5M319", + "xattr.sys.tmp.atomic" : ".sys.a#.v#New file.odt.2b038280-86a5-11f1-a026-fa163e35f83a", + "xattr.sys.utrace" : "362607ba-8690-11f1-a419-fa163e35f83a", + "xattr.sys.vtrace" : "[Thu Jul 23 14:15:36 2026] uid:173503[jgeens] gid:2763[it] tident:jgeens.1:1@[2001:1458:d00:16::33d] name:jgeens dn: prot:https app:http/reva_collabora host:cbox-ocisdev-diogo.cern.ch domain:cern.ch geo: sudo:0 trace: onbehalf:", + "xs" : "f5e63a1a" +}, +{ + "atime" : "1784817937.457269535", + "ctime" : "1784817937.457269125", + "fid" : "119984344", + "flags" : "644", + "gid" : "2763", + "layout_id" : "1048850", + "link_name" : "", + "locations" : "417,420", + "mtime" : "1784817937.457446646", + "name" : "1784817937.0726d0d8", + "path" : "/eos/user/j/jgeens/.sys.v#.New file.odt/1784817937.0726d0d8", + "pid" : "6849278", + "size" : "5050100", + "stime" : "0.0", + "uid" : "173503", + "unlink_locations" : "", + "xattr.sys.acl" : "u:187895:rx,u:178406:rx!d", + "xattr.sys.app.lock" : "expires:1784819593,type:shared,owner:*:http/reva_collabora", + "xattr.sys.eos.btime" : "1784817937.457269125", + "xattr.sys.fs.tracking" : "+413+416/416/413", + "xattr.sys.fusex.state" : "", + "xattr.sys.reva.lockpayload" : "eyJsb2NrX2lkIjoib3BhcXVlbG9ja3Rva2VuOjc5NzM1NmE4LTA1MDAtNGNlYi1hOGEwLWM5NGM4Y2RlN2ViYSBZMjl2YkMxc2IyTnJRVUV4TWpZNU5VUT0iLCJ0eXBlIjoyLCJhcHBfbmFtZSI6IkNvbGxhYm9yYSIsImV4cGlyYXRpb24iOnsic2Vjb25kcyI6MTc4NDgxOTU5M319", + "xattr.sys.tmp.atomic" : ".sys.a#.v#New file.odt.8f8dd53e-86a5-11f1-a171-fa163e35f83a", + "xattr.sys.utrace" : "2b035bd4-86a5-11f1-a026-fa163e35f83a", + "xattr.sys.vtrace" : "[Thu Jul 23 16:45:37 2026] uid:173503[jgeens] gid:2763[it] tident:jgeens.1:1@[2001:1458:d00:16::33d] name:jgeens dn: prot:https app:http/reva_collabora host:cbox-ocisdev-diogo.cern.ch domain:cern.ch geo: sudo:0 trace: onbehalf:", + "xs" : "c193c98c" +}, +{ + "cid" : "6492598", + "ctime" : "1782302738.256741323", + "flags" : "0", + "gid" : "2763", + "mode" : "40750", + "mtime" : "1774268053.319519742", + "name" : ".sys.v#.atlas30new.root", + "parent_id" : "4251882", + "path" : "/eos/user/j/jgeens/.sys.v#.atlas30new.root/", + "stime" : "0.0", + "tree_size" : "0", + "uid" : "173503", + "xattr.sys.acl" : "u:187895:rx,u:193339:rwx+d", + "xattr.sys.eos.btime" : "1774268053.319519742" +}, +{ + "cid" : "6492604", + "ctime" : "1774268053.365932072", + "flags" : "0", + "gid" : "2763", + "mode" : "40750", + "mtime" : "1774268053.365450336", + "name" : ".sys.v#.bugged-video-Trisha and Nicola - Guess who I bumped into.mp4", + "parent_id" : "4251882", + "path" : "/eos/user/j/jgeens/.sys.v#.bugged-video-Trisha and Nicola - Guess who I bumped into.mp4/", + "stime" : "0.0", + "tree_size" : "0", + "uid" : "173503", + "xattr.sys.eos.btime" : "1774268053.365450336" +}, +{ + "cid" : "6492597", + "ctime" : "1774268053.311877166", + "flags" : "0", + "gid" : "2763", + "mode" : "40750", + "mtime" : "1774268053.311370749", + "name" : ".sys.v#.file.docx", + "parent_id" : "4251882", + "path" : "/eos/user/j/jgeens/.sys.v#.file.docx/", + "stime" : "0.0", + "tree_size" : "0", + "uid" : "173503", + "xattr.sys.eos.btime" : "1774268053.311370749" +}, +{ + "cid" : "6874628", + "ctime" : "1785759946.21811638", + "flags" : "0", + "gid" : "2763", + "mode" : "40750", + "mtime" : "1785759946.21168184", + "name" : ".sys.v#.image (2).png", + "parent_id" : "4251882", + "path" : "/eos/user/j/jgeens/.sys.v#.image (2).png/", + "stime" : "0.0", + "tree_size" : "0", + "uid" : "173503", + "xattr.sys.eos.btime" : "1785759946.21168184" +}, +{ + "cid" : "6891262", + "ctime" : "1786470986.602622721", + "flags" : "0", + "gid" : "2763", + "mode" : "40750", + "mtime" : "1786432355.320869614", + "name" : ".sys.v#.lock.odt", + "parent_id" : "4251882", + "path" : "/eos/user/j/jgeens/.sys.v#.lock.odt/", + "stime" : "0.0", + "tree_size" : "0", + "uid" : "173503", + "xattr.sys.eos.btime" : "1786432355.320869614", + "xattr.user.iop.wopi.lastwritetime" : "1786470986" +}, +{ + "cid" : "6492592", + "ctime" : "1782811897.623892170", + "flags" : "0", + "gid" : "2763", + "mode" : "40750", + "mtime" : "1774268053.276639712", + "name" : ".sys.v#.pres.pptx", + "parent_id" : "4251882", + "path" : "/eos/user/j/jgeens/.sys.v#.pres.pptx/", + "stime" : "0.0", + "tree_size" : "0", + "uid" : "173503", + "xattr.sys.acl" : "u:187895:rx,u:193339:rwx+d", + "xattr.sys.eos.btime" : "1774268053.276639712" +}, +{ + "cid" : "6492601", + "ctime" : "1774268053.346608474", + "flags" : "0", + "gid" : "2763", + "mode" : "40750", + "mtime" : "1774268053.346130826", + "name" : ".sys.v#.presentation.pptx", + "parent_id" : "4251882", + "path" : "/eos/user/j/jgeens/.sys.v#.presentation.pptx/", + "stime" : "0.0", + "tree_size" : "0", + "uid" : "173503", + "xattr.sys.eos.btime" : "1774268053.346130826" +}, +{ + "cid" : "6492603", + "ctime" : "1774268053.358493111", + "flags" : "0", + "gid" : "2763", + "mode" : "40750", + "mtime" : "1774268053.358007790", + "name" : ".sys.v#.s02_preneel_basic_crypto_2025v1.pdf", + "parent_id" : "4251882", + "path" : "/eos/user/j/jgeens/.sys.v#.s02_preneel_basic_crypto_2025v1.pdf/", + "stime" : "0.0", + "tree_size" : "0", + "uid" : "173503", + "xattr.sys.eos.btime" : "1774268053.358007790" +}, +{ + "cid" : "6492595", + "ctime" : "1774268053.299350810", + "flags" : "0", + "gid" : "2763", + "mode" : "40750", + "mtime" : "1774268053.298890664", + "name" : ".sys.v#.summer_student_tutorial_tracks.root", + "parent_id" : "4251882", + "path" : "/eos/user/j/jgeens/.sys.v#.summer_student_tutorial_tracks.root/", + "stime" : "0.0", + "tree_size" : "0", + "uid" : "173503", + "xattr.sys.eos.btime" : "1774268053.298890664" +}, +{ + "cid" : "6492600", + "ctime" : "1774268053.339961373", + "flags" : "0", + "gid" : "2763", + "mode" : "40750", + "mtime" : "1774268053.339479994", + "name" : ".sys.v#.symmetric_crypto_exerises_nov24.pdf", + "parent_id" : "4251882", + "path" : "/eos/user/j/jgeens/.sys.v#.symmetric_crypto_exerises_nov24.pdf/", + "stime" : "0.0", + "tree_size" : "0", + "uid" : "173503", + "xattr.sys.eos.btime" : "1774268053.339479994" +}, +{ + "cid" : "6889374", + "ctime" : "1786373040.86929192", + "flags" : "0", + "gid" : "2763", + "mode" : "40750", + "mtime" : "1786373040.86259052", + "name" : ".sys.v#.test.docx", + "parent_id" : "4251882", + "path" : "/eos/user/j/jgeens/.sys.v#.test.docx/", + "stime" : "0.0", + "tree_size" : "0", + "uid" : "173503", + "xattr.sys.eos.btime" : "1786373040.86259052" +}, +{ + "cid" : "6658133", + "ctime" : "1778863858.952437422", + "flags" : "0", + "gid" : "2763", + "mode" : "40750", + "mtime" : "1778863881.623407630", + "name" : ".sys.v#.versions.txt", + "parent_id" : "4251882", + "path" : "/eos/user/j/jgeens/.sys.v#.versions.txt/", + "stime" : "0.0", + "tree_size" : "2", + "uid" : "173503", + "xattr.sys.eos.btime" : "1778863858.951619929" +}, +{ + "atime" : "1778863858.911823788", + "ctime" : "1778863876.957880361", + "fid" : "117478802", + "flags" : "644", + "gid" : "2763", + "layout_id" : "1048850", + "link_name" : "", + "locations" : "409,416", + "mtime" : "1778863858.912287026", + "name" : "1778863876.07009592", + "path" : "/eos/user/j/jgeens/.sys.v#.versions.txt/1778863876.07009592", + "pid" : "6658133", + "size" : "0", + "stime" : "0.0", + "uid" : "173503", + "unlink_locations" : "", + "xattr.sys.acl" : "u:187895:rx,u:103421:rwx+d", + "xattr.sys.eos.btime" : "1778863858.911822888", + "xattr.sys.fs.tracking" : "+409+416", + "xattr.sys.fusex.state" : "", + "xattr.sys.tmp.atomic" : ".sys.a#.v#versions.txt.4bf7fa1e-507e-11f1-ae9a-fa163e44ab60", + "xattr.sys.utrace" : "3fa58e84-507e-11f1-934d-fa163e44ab60", + "xattr.sys.vtrace" : "[Fri May 15 18:50:58 2026] uid:173503[jgeens] gid:2763[it] tident:jgeens.1:1@[2001:1458:d00:13::100:2be] name:jgeens dn: prot:https app:http/reva_write host:cbox-ocisdev-jesse.cern.ch domain:cern.ch geo: sudo:0 trace: onbehalf:", + "xs" : "00000001" +}, +{ + "atime" : "1778863879.584140264", + "ctime" : "1778863879.584139763", + "fid" : "117478809", + "flags" : "644", + "gid" : "2763", + "layout_id" : "1048850", + "link_name" : "", + "locations" : "417,411", + "mtime" : "1778863879.584592912", + "name" : "1778863879.07009599", + "path" : "/eos/user/j/jgeens/.sys.v#.versions.txt/1778863879.07009599", + "pid" : "6658133", + "size" : "2", + "stime" : "0.0", + "uid" : "173503", + "unlink_locations" : "", + "xattr.sys.acl" : "u:187895:rx,u:103421:rwx+d", + "xattr.sys.eos.btime" : "1778863879.584139763", + "xattr.sys.fs.tracking" : "+417+411", + "xattr.sys.fusex.state" : "", + "xattr.sys.tmp.atomic" : ".sys.a#.v#versions.txt.4d2c9156-507e-11f1-abae-fa163e44ab60", + "xattr.sys.utrace" : "4bf7e1f0-507e-11f1-ae9a-fa163e44ab60", + "xattr.sys.vtrace" : "[Fri May 15 18:51:19 2026] uid:173503[jgeens] gid:2763[it] tident:jgeens.1:1@[2001:1458:d00:13::100:2be] name:jgeens dn: prot:https app:http/reva_write host:cbox-ocisdev-jesse.cern.ch domain:cern.ch geo: sudo:0 trace: onbehalf:", + "xs" : "011f00a8" +}, +{ + "cid" : "4902677", + "ctime" : "1778157001.558356409", + "flags" : "0", + "gid" : "2763", + "mode" : "40750", + "mtime" : "1745849876.839460714", + "name" : "00. Work", + "parent_id" : "4251882", + "path" : "/eos/user/j/jgeens/00. Work/", + "stime" : "1782200198.776737385", + "tree_size" : "17", + "uid" : "173503", + "xattr.sys.acl" : "u:173503:rwx", + "xattr.sys.allow.oc.sync" : "1", + "xattr.sys.eos.btime" : "1677602740.101786072", + "xattr.sys.forced.atomic" : "1", + "xattr.sys.forced.blocksize" : "4k", + "xattr.sys.forced.checksum" : "adler", + "xattr.sys.forced.layout" : "replica", + "xattr.sys.forced.nstripes" : "2", + "xattr.sys.forced.space" : "default", + "xattr.sys.mask" : "700", + "xattr.sys.mtime.propagation" : "1", + "xattr.sys.owner.auth" : "*", + "xattr.sys.recycle" : "/eos/homedev/proc/recycle/", + "xattr.sys.reva.lwshare.111857693350341032552@google" : "rx!d", + "xattr.sys.versioning" : "10", + "xattr.user.http://owncloud.org/ns/favorite" : "u:jgeens=1", + "xattr.user.reva.labels.jgeens.favorite" : "1" +}, +{ + "cid" : "4902678", + "ctime" : "1750084037.328269584", + "flags" : "0", + "gid" : "2763", + "mode" : "40750", + "mtime" : "1782200191.103919642", + "name" : "08_Type 3", + "parent_id" : "4902677", + "path" : "/eos/user/j/jgeens/00. Work/08_Type 3/", + "stime" : "1782200198.776737385", + "tree_size" : "17", + "uid" : "173503", + "xattr.sys.acl" : "u:173503:rwx", + "xattr.sys.allow.oc.sync" : "1", + "xattr.sys.eos.btime" : "1677602740.101786072", + "xattr.sys.forced.atomic" : "1", + "xattr.sys.forced.blocksize" : "4k", + "xattr.sys.forced.checksum" : "adler", + "xattr.sys.forced.layout" : "replica", + "xattr.sys.forced.nstripes" : "2", + "xattr.sys.forced.space" : "default", + "xattr.sys.mask" : "700", + "xattr.sys.mtime.propagation" : "1", + "xattr.sys.owner.auth" : "*", + "xattr.sys.recycle" : "/eos/homedev/proc/recycle/", + "xattr.sys.reva.lwshare.111857693350341032552@google" : "rx!d", + "xattr.sys.versioning" : "10" +}, +{ + "atime" : "1755868489.619344429", + "ctime" : "1755868489.619343514", + "fid" : "108344250", + "flags" : "644", + "gid" : "2763", + "layout_id" : "1048850", + "link_name" : "", + "locations" : "415,416", + "mtime" : "1755868489.619616215", + "name" : "file.txt", + "path" : "/eos/user/j/jgeens/00. Work/08_Type 3/file.txt", + "pid" : "4902678", + "size" : "0", + "stime" : "0.0", + "uid" : "173503", + "unlink_locations" : "", + "xattr.sys.eos.btime" : "1755868489.619343514", + "xattr.sys.fs.tracking" : "+415+416", + "xattr.sys.fusex.state" : "", + "xattr.sys.utrace" : "fb771408-7f59-11f0-bc2e-fa163e2e9155", + "xattr.sys.vtrace" : "[Fri Aug 22 15:14:49 2025] uid:173503[jgeens] gid:2763[it] tident:jgeens.1:1@[2001:1458:d00:16::33d] name:jgeens dn: prot:https app:http host:cbox-ocisdev-diogo.cern.ch domain:cern.ch geo: sudo:0 trace: onbehalf:", + "xs" : "00000001" +}, +{ + "cid" : "6747803", + "ctime" : "1782200191.104293084", + "flags" : "0", + "gid" : "2763", + "mode" : "40750", + "mtime" : "1782200191.103919642", + "name" : ".sys.v#.file.txt", + "parent_id" : "4902678", + "path" : "/eos/user/j/jgeens/00. Work/08_Type 3/.sys.v#.file.txt/", + "stime" : "0.0", + "tree_size" : "0", + "uid" : "173503", + "xattr.sys.eos.btime" : "1782200191.103919642" +}, +{ + "cid" : "4902679", + "ctime" : "1750084037.328367211", + "flags" : "0", + "gid" : "2763", + "mode" : "40750", + "mtime" : "1755868535.451839011", + "name" : "08_CERNBox", + "parent_id" : "4902678", + "path" : "/eos/user/j/jgeens/00. Work/08_Type 3/08_CERNBox/", + "stime" : "1782200198.776737385", + "tree_size" : "17", + "uid" : "173503", + "xattr.sys.acl" : "u:173503:rwx", + "xattr.sys.allow.oc.sync" : "1", + "xattr.sys.eos.btime" : "1677602740.101786072", + "xattr.sys.forced.atomic" : "1", + "xattr.sys.forced.blocksize" : "4k", + "xattr.sys.forced.checksum" : "adler", + "xattr.sys.forced.layout" : "replica", + "xattr.sys.forced.nstripes" : "2", + "xattr.sys.forced.space" : "default", + "xattr.sys.mask" : "700", + "xattr.sys.mtime.propagation" : "1", + "xattr.sys.owner.auth" : "*", + "xattr.sys.recycle" : "/eos/homedev/proc/recycle/", + "xattr.sys.reva.lwshare.111857693350341032552@google" : "rx!d", + "xattr.sys.versioning" : "10" +}, +{ + "cid" : "4902680", + "ctime" : "1750084037.328450891", + "flags" : "0", + "gid" : "2763", + "mode" : "40750", + "mtime" : "1745849926.608296044", + "name" : "23-3-xx", + "parent_id" : "4902679", + "path" : "/eos/user/j/jgeens/00. Work/08_Type 3/08_CERNBox/23-3-xx/", + "stime" : "1745849934.151213398", + "tree_size" : "0", + "uid" : "173503", + "xattr.sys.acl" : "u:173503:rwx", + "xattr.sys.allow.oc.sync" : "1", + "xattr.sys.eos.btime" : "1677602740.101786072", + "xattr.sys.forced.atomic" : "1", + "xattr.sys.forced.blocksize" : "4k", + "xattr.sys.forced.checksum" : "adler", + "xattr.sys.forced.layout" : "replica", + "xattr.sys.forced.nstripes" : "2", + "xattr.sys.forced.space" : "default", + "xattr.sys.mask" : "700", + "xattr.sys.mtime.propagation" : "1", + "xattr.sys.owner.auth" : "*", + "xattr.sys.recycle" : "/eos/homedev/proc/recycle/", + "xattr.sys.reva.lwshare.111857693350341032552@google" : "rx!d", + "xattr.sys.versioning" : "10" +}, +{ + "cid" : "4902681", + "ctime" : "1750084037.328536026", + "flags" : "0", + "gid" : "2763", + "mode" : "40750", + "mtime" : "1745849934.151213398", + "name" : "23-3-01-EBD1_17W (B1712)", + "parent_id" : "4902680", + "path" : "/eos/user/j/jgeens/00. Work/08_Type 3/08_CERNBox/23-3-xx/23-3-01-EBD1_17W (B1712)/", + "stime" : "0.0", + "tree_size" : "0", + "uid" : "173503", + "xattr.sys.acl" : "u:173503:rwx", + "xattr.sys.allow.oc.sync" : "1", + "xattr.sys.eos.btime" : "1677602740.101786072", + "xattr.sys.forced.atomic" : "1", + "xattr.sys.forced.blocksize" : "4k", + "xattr.sys.forced.checksum" : "adler", + "xattr.sys.forced.layout" : "replica", + "xattr.sys.forced.nstripes" : "2", + "xattr.sys.forced.space" : "default", + "xattr.sys.mask" : "700", + "xattr.sys.mtime.propagation" : "1", + "xattr.sys.owner.auth" : "*", + "xattr.sys.recycle" : "/eos/homedev/proc/recycle/", + "xattr.sys.reva.lwshare.111857693350341032552@google" : "rx!d", + "xattr.sys.versioning" : "10" +}, +{ + "cid" : "4902682", + "ctime" : "1775812183.646291233", + "flags" : "0", + "gid" : "2763", + "mode" : "40750", + "mtime" : "1745849934.151213398", + "name" : "Shared Folder", + "parent_id" : "4902681", + "path" : "/eos/user/j/jgeens/00. Work/08_Type 3/08_CERNBox/23-3-xx/23-3-01-EBD1_17W (B1712)/Shared Folder/", + "stime" : "0.0", + "tree_size" : "0", + "uid" : "173503", + "xattr.sys.acl" : "u:58679:rxt,u:187895:rx,u:173503:rwx", + "xattr.sys.allow.oc.sync" : "1", + "xattr.sys.eos.btime" : "1677602740.101786072", + "xattr.sys.forced.atomic" : "1", + "xattr.sys.forced.blocksize" : "4k", + "xattr.sys.forced.checksum" : "adler", + "xattr.sys.forced.layout" : "replica", + "xattr.sys.forced.nstripes" : "2", + "xattr.sys.forced.space" : "default", + "xattr.sys.mask" : "700", + "xattr.sys.mtime.propagation" : "1", + "xattr.sys.owner.auth" : "*", + "xattr.sys.recycle" : "/eos/homedev/proc/recycle/", + "xattr.sys.reva.lwshare.111857693350341032552@google" : "rx!d", + "xattr.sys.versioning" : "10" +}, +{ + "cid" : "5915409", + "ctime" : "1755868535.452717028", + "flags" : "0", + "gid" : "2763", + "mode" : "40750", + "mtime" : "1755868535.570060448", + "name" : "TestFolderSharing2", + "parent_id" : "4902679", + "path" : "/eos/user/j/jgeens/00. Work/08_Type 3/08_CERNBox/TestFolderSharing2/", + "stime" : "1782200198.776737385", + "tree_size" : "17", + "uid" : "173503", + "xattr.sys.acl" : "u:173503:rwx", + "xattr.sys.allow.oc.sync" : "1", + "xattr.sys.eos.btime" : "1677602740.101786072", + "xattr.sys.forced.atomic" : "1", + "xattr.sys.forced.blocksize" : "4k", + "xattr.sys.forced.checksum" : "adler", + "xattr.sys.forced.layout" : "replica", + "xattr.sys.forced.nstripes" : "2", + "xattr.sys.forced.space" : "default", + "xattr.sys.mask" : "700", + "xattr.sys.mtime.propagation" : "1", + "xattr.sys.owner.auth" : "*", + "xattr.sys.recycle" : "/eos/homedev/proc/recycle/", + "xattr.sys.reva.lwshare.111857693350341032552@google" : "rx!d", + "xattr.sys.versioning" : "10" +}, +{ + "cid" : "5915411", + "ctime" : "1755868535.570555182", + "flags" : "0", + "gid" : "2763", + "mode" : "40750", + "mtime" : "1755868535.570060448", + "name" : "MyFolder", + "parent_id" : "5915409", + "path" : "/eos/user/j/jgeens/00. Work/08_Type 3/08_CERNBox/TestFolderSharing2/MyFolder/", + "stime" : "0.0", + "tree_size" : "0", + "uid" : "173503", + "xattr.sys.acl" : "u:173503:rwx", + "xattr.sys.allow.oc.sync" : "1", + "xattr.sys.eos.btime" : "1677602740.101786072", + "xattr.sys.forced.atomic" : "1", + "xattr.sys.forced.blocksize" : "4k", + "xattr.sys.forced.checksum" : "adler", + "xattr.sys.forced.layout" : "replica", + "xattr.sys.forced.nstripes" : "2", + "xattr.sys.forced.space" : "default", + "xattr.sys.mask" : "700", + "xattr.sys.mtime.propagation" : "1", + "xattr.sys.owner.auth" : "*", + "xattr.sys.recycle" : "/eos/homedev/proc/recycle/", + "xattr.sys.reva.lwshare.111857693350341032552@google" : "rx!d", + "xattr.sys.versioning" : "10" +}, +{ + "cid" : "5915410", + "ctime" : "1755868535.562557391", + "flags" : "0", + "gid" : "2763", + "mode" : "40750", + "mtime" : "1782200198.776737385", + "name" : "SubFolder", + "parent_id" : "5915409", + "path" : "/eos/user/j/jgeens/00. Work/08_Type 3/08_CERNBox/TestFolderSharing2/SubFolder/", + "stime" : "0.0", + "tree_size" : "17", + "uid" : "173503", + "xattr.sys.acl" : "u:173503:rwx", + "xattr.sys.allow.oc.sync" : "1", + "xattr.sys.eos.btime" : "1677602740.101786072", + "xattr.sys.forced.atomic" : "1", + "xattr.sys.forced.blocksize" : "4k", + "xattr.sys.forced.checksum" : "adler", + "xattr.sys.forced.layout" : "replica", + "xattr.sys.forced.nstripes" : "2", + "xattr.sys.forced.space" : "default", + "xattr.sys.mask" : "700", + "xattr.sys.mtime.propagation" : "1", + "xattr.sys.owner.auth" : "*", + "xattr.sys.recycle" : "/eos/homedev/proc/recycle/", + "xattr.sys.reva.lwshare.111857693350341032552@google" : "rx!d", + "xattr.sys.versioning" : "10" +}, +{ + "atime" : "1755868535.551194193", + "ctime" : "1755868535.551192829", + "fid" : "108344252", + "flags" : "644", + "gid" : "2763", + "layout_id" : "1048850", + "link_name" : "", + "locations" : "417,411", + "mtime" : "1755868535.551446552", + "name" : "File.txt", + "path" : "/eos/user/j/jgeens/00. Work/08_Type 3/08_CERNBox/TestFolderSharing2/SubFolder/File.txt", + "pid" : "5915410", + "size" : "17", + "stime" : "0.0", + "uid" : "173503", + "unlink_locations" : "", + "xattr.sys.eos.btime" : "1755868535.551192829", + "xattr.sys.fs.tracking" : "+417+411", + "xattr.sys.fusex.state" : "", + "xattr.sys.utrace" : "16d7ba18-7f5a-11f0-bd9b-fa163e2e9155", + "xattr.sys.vtrace" : "[Fri Aug 22 15:15:35 2025] uid:173503[jgeens] gid:2763[it] tident:jgeens.1:1@[2001:1458:d00:16::33d] name:jgeens dn: prot:https app:http host:cbox-ocisdev-diogo.cern.ch domain:cern.ch geo: sudo:0 trace: onbehalf:", + "xs" : "36af065e" +}, +{ + "cid" : "6747849", + "ctime" : "1782200198.777310928", + "flags" : "0", + "gid" : "2763", + "mode" : "40750", + "mtime" : "1782200198.776737385", + "name" : ".sys.v#.File.txt", + "parent_id" : "5915410", + "path" : "/eos/user/j/jgeens/00. Work/08_Type 3/08_CERNBox/TestFolderSharing2/SubFolder/.sys.v#.File.txt/", + "stime" : "0.0", + "tree_size" : "0", + "uid" : "173503", + "xattr.sys.eos.btime" : "1782200198.776737385" +}, +{ + "cid" : "5915408", + "ctime" : "1755868515.879924383", + "flags" : "0", + "gid" : "2763", + "mode" : "40750", + "mtime" : "1755868515.879290352", + "name" : "folder", + "parent_id" : "4902679", + "path" : "/eos/user/j/jgeens/00. Work/08_Type 3/08_CERNBox/folder/", + "stime" : "0.0", + "tree_size" : "0", + "uid" : "173503", + "xattr.sys.acl" : "u:173503:rwx", + "xattr.sys.allow.oc.sync" : "1", + "xattr.sys.eos.btime" : "1677602740.101786072", + "xattr.sys.forced.atomic" : "1", + "xattr.sys.forced.blocksize" : "4k", + "xattr.sys.forced.checksum" : "adler", + "xattr.sys.forced.layout" : "replica", + "xattr.sys.forced.nstripes" : "2", + "xattr.sys.forced.space" : "default", + "xattr.sys.mask" : "700", + "xattr.sys.mtime.propagation" : "1", + "xattr.sys.owner.auth" : "*", + "xattr.sys.recycle" : "/eos/homedev/proc/recycle/", + "xattr.sys.reva.lwshare.111857693350341032552@google" : "rx!d", + "xattr.sys.versioning" : "10" +}, +{ + "cid" : "6633639", + "ctime" : "1784879323.393470459", + "flags" : "0", + "gid" : "2763", + "mode" : "40750", + "mtime" : "1778080143.271479930", + "name" : "000-NewFolder", + "parent_id" : "4251882", + "path" : "/eos/user/j/jgeens/000-NewFolder/", + "stime" : "0.0", + "tree_size" : "0", + "uid" : "173503", + "xattr.sys.acl" : "u:173503:rwx,u:187895:rx,u:178406:rx!d", + "xattr.sys.allow.oc.sync" : "1", + "xattr.sys.eos.btime" : "1677602740.101786072", + "xattr.sys.forced.atomic" : "1", + "xattr.sys.forced.blocksize" : "4k", + "xattr.sys.forced.checksum" : "adler", + "xattr.sys.forced.layout" : "replica", + "xattr.sys.forced.nstripes" : "2", + "xattr.sys.forced.space" : "default", + "xattr.sys.mask" : "700", + "xattr.sys.mtime.propagation" : "1", + "xattr.sys.owner.auth" : "*", + "xattr.sys.recycle" : "/eos/homedev/proc/recycle/", + "xattr.sys.versioning" : "10", + "xattr.user.http://owncloud.org/ns/favorite" : "u:jgeens=1,u:tstcbsa9=1", + "xattr.user.reva.labels.jgeens.favorite" : "1" +}, +{ + "cid" : "6633647", + "ctime" : "1784879323.393626430", + "flags" : "0", + "gid" : "2763", + "mode" : "40750", + "mtime" : "1778080143.271479930", + "name" : "Test", + "parent_id" : "6633639", + "path" : "/eos/user/j/jgeens/000-NewFolder/Test/", + "stime" : "0.0", + "tree_size" : "0", + "uid" : "173503", + "xattr.sys.acl" : "u:173503:rwx,u:187895:rx,u:178406:rx!d", + "xattr.sys.allow.oc.sync" : "1", + "xattr.sys.eos.btime" : "1677602740.101786072", + "xattr.sys.forced.atomic" : "1", + "xattr.sys.forced.blocksize" : "4k", + "xattr.sys.forced.checksum" : "adler", + "xattr.sys.forced.layout" : "replica", + "xattr.sys.forced.nstripes" : "2", + "xattr.sys.forced.space" : "default", + "xattr.sys.mask" : "700", + "xattr.sys.mtime.propagation" : "1", + "xattr.sys.owner.auth" : "*", + "xattr.sys.recycle" : "/eos/homedev/proc/recycle/", + "xattr.sys.versioning" : "10", + "xattr.user.http://owncloud.org/ns/favorite" : "u:jgeens=1,u:tstcbsa9=1" +}, +{ + "cid" : "4636585", + "ctime" : "1764755145.240424672", + "flags" : "0", + "gid" : "2763", + "mode" : "40750", + "mtime" : "1782200188.207328905", + "name" : "MyFolder", + "parent_id" : "4251882", + "path" : "/eos/user/j/jgeens/MyFolder/", + "stime" : "1782200190.453936785", + "tree_size" : "29631752", + "uid" : "173503", + "xattr.sys.acl" : "u:173503:rwx,u:178406:rx!d,u:178352:rx!d", + "xattr.sys.allow.oc.sync" : "1", + "xattr.sys.eos.btime" : "1677602740.101786072", + "xattr.sys.forced.atomic" : "1", + "xattr.sys.forced.blocksize" : "4k", + "xattr.sys.forced.checksum" : "adler", + "xattr.sys.forced.layout" : "replica", + "xattr.sys.forced.nstripes" : "2", + "xattr.sys.forced.space" : "default", + "xattr.sys.mask" : "700", + "xattr.sys.mtime.propagation" : "1", + "xattr.sys.owner.auth" : "*", + "xattr.sys.recycle" : "/eos/homedev/proc/recycle/", + "xattr.sys.versioning" : "10" +}, +{ + "atime" : "1764755145.229946690", + "ctime" : "1764756682.764096287", + "fid" : "112013534", + "flags" : "644", + "gid" : "2763", + "layout_id" : "1048850", + "link_name" : "", + "locations" : "421,417", + "mtime" : "1764755145.230479476", + "name" : "MyFolderFile.txt", + "path" : "/eos/user/j/jgeens/MyFolder/MyFolderFile.txt", + "pid" : "4636585", + "size" : "35", + "stime" : "0.0", + "uid" : "173503", + "unlink_locations" : "", + "xattr.sys.acl" : ",u:187895:rx", + "xattr.sys.eos.btime" : "1764755145.229946167", + "xattr.sys.fs.tracking" : "+421+417", + "xattr.sys.fusex.state" : "", + "xattr.sys.utrace" : "d6f9613e-d02c-11f0-84f2-fa163e2e9155", + "xattr.sys.vtrace" : "[Wed Dec 3 10:45:45 2025] uid:173503[jgeens] gid:2763[it] tident:jgeens.1:1@[2001:1458:d00:13::100:2be] name:jgeens dn: prot:https app:http/reva_write host:cbox-ocisdev-jesse.cern.ch domain:cern.ch geo: sudo:0 trace: onbehalf:", + "xs" : "d9630c18" +}, +{ + "atime" : "1743063888.211467219", + "ctime" : "1743073497.191643315", + "fid" : "100240470", + "flags" : "644", + "gid" : "2763", + "layout_id" : "1048850", + "link_name" : "", + "locations" : "413,409", + "mtime" : "1743063888.211802610", + "name" : "subfile.docx", + "path" : "/eos/user/j/jgeens/MyFolder/subfile.docx", + "pid" : "4636585", + "size" : "9756", + "stime" : "0.0", + "uid" : "173503", + "unlink_locations" : "", + "xattr.sys.app.lock" : "expires:1743075297,type:shared,owner:*:wopi_ms_365_on_cloud", + "xattr.sys.eos.btime" : "1743063888.211466632", + "xattr.sys.fs.tracking" : "+413+409", + "xattr.sys.fusex.state" : "", + "xattr.sys.utrace" : "f247e608-0ae4-11f0-912c-fa163e2e9155", + "xattr.sys.vtrace" : "[Thu Mar 27 09:24:48 2025] uid:173503[jgeens] gid:2763[it] tident:root.8:792@cbox-appsdev-02 name:root dn: prot:unix app:wopi_ms_365_on_cloud host:cbox-appsdev-02.cern.ch domain:cern.ch geo: sudo:1 trace: onbehalf:", + "xattr.user.iop.lock" : "eyJsb2NrX2lkIjogIm9wYXF1ZWxvY2t0b2tlbjo3OTczNTZhOC0wNTAwLTRjZWItYThhMC1jOTRjOGNkZTdlYmEgZXlKVElqb2labUl6WkdRNE1ETXRNbU5tWVMwMFpqVTRMV0U0WWpRdFkyWTNORGcyWWprd05HSTBJaXdpUmlJNk5Dd2lSU0k2TWl3aVF5STZJa1pKVERFaUxDSk5Jam9pVkV3elVFVlFSakF3TURBd01FVkZJaXdpVUNJNklrRTFSamM1TVRRMExVRTFNakF0TkRJMVF5MDVORU14TFRnM01ESTFOMEpHTURFNU5pSXNJa1FpT2lKdlptWnBZMlZoY0hCekxteHBkbVV1WTI5dEluMD0iLCAidHlwZSI6IDIsICJhcHBfbmFtZSI6ICJNUyAzNjUgb24gQ2xvdWQiLCAidXNlciI6IHt9LCAiZXhwaXJhdGlvbiI6IHsic2Vjb25kcyI6IDE3NDMwNzUyOTd9fQ==", + "xattr.user.iop.wopi.lastwritetime" : "1743063888", + "xs" : "f2e2893e" +}, +{ + "cid" : "6747791", + "ctime" : "1782200188.205267784", + "flags" : "0", + "gid" : "2763", + "mode" : "40750", + "mtime" : "1782200188.204851201", + "name" : ".sys.v#.MyFolderFile.txt", + "parent_id" : "4636585", + "path" : "/eos/user/j/jgeens/MyFolder/.sys.v#.MyFolderFile.txt/", + "stime" : "0.0", + "tree_size" : "0", + "uid" : "173503", + "xattr.sys.eos.btime" : "1782200188.204851201" +}, +{ + "cid" : "6747792", + "ctime" : "1782200188.207529986", + "flags" : "0", + "gid" : "2763", + "mode" : "40750", + "mtime" : "1782200188.207328905", + "name" : ".sys.v#.subfile.docx", + "parent_id" : "4636585", + "path" : "/eos/user/j/jgeens/MyFolder/.sys.v#.subfile.docx/", + "stime" : "0.0", + "tree_size" : "0", + "uid" : "173503", + "xattr.sys.eos.btime" : "1782200188.207328905" +}, +{ + "cid" : "5868251", + "ctime" : "1764253241.249620878", + "flags" : "0", + "gid" : "2763", + "mode" : "40750", + "mtime" : "1782200190.453936785", + "name" : "uplaods", + "parent_id" : "4636585", + "path" : "/eos/user/j/jgeens/MyFolder/uplaods/", + "stime" : "0.0", + "tree_size" : "29621961", + "uid" : "173503", + "xattr.sys.acl" : "u:173503:rwx", + "xattr.sys.allow.oc.sync" : "1", + "xattr.sys.eos.btime" : "1677602740.101786072", + "xattr.sys.forced.atomic" : "1", + "xattr.sys.forced.blocksize" : "4k", + "xattr.sys.forced.checksum" : "adler", + "xattr.sys.forced.layout" : "replica", + "xattr.sys.forced.nstripes" : "2", + "xattr.sys.forced.space" : "default", + "xattr.sys.mask" : "700", + "xattr.sys.mtime.propagation" : "1", + "xattr.sys.owner.auth" : "*", + "xattr.sys.recycle" : "/eos/homedev/proc/recycle/", + "xattr.sys.versioning" : "10" +}, +{ + "atime" : "1759413722.722501885", + "ctime" : "1759413722.722501120", + "fid" : "109698749", + "flags" : "644", + "gid" : "2763", + "layout_id" : "1048850", + "link_name" : "", + "locations" : "417,420", + "mtime" : "1759413722.722798383", + "name" : "CSC-Report-IT-SD_uhuvj.pptx", + "path" : "/eos/user/j/jgeens/MyFolder/uplaods/CSC-Report-IT-SD_uhuvj.pptx", + "pid" : "5868251", + "size" : "29621954", + "stime" : "0.0", + "uid" : "173503", + "unlink_locations" : "", + "xattr.sys.eos.btime" : "1759413722.722501120", + "xattr.sys.fs.tracking" : "+417+420", + "xattr.sys.fusex.state" : "", + "xattr.sys.utrace" : "5f100d40-9f98-11f0-b5d5-fa163e2e9155", + "xattr.sys.vtrace" : "[Thu Oct 2 16:02:02 2025] uid:173503[jgeens] gid:2763[it] tident:jgeens.1:1@[2001:1458:d00:16::33d] name:jgeens dn: prot:https app:http/reva_write host:cbox-ocisdev-diogo.cern.ch domain:cern.ch geo: sudo:0 trace: onbehalf:", + "xs" : "953d06bd" +}, +{ + "atime" : "1754406531.727435496", + "ctime" : "1754406531.727434284", + "fid" : "107773014", + "flags" : "644", + "gid" : "2763", + "layout_id" : "1048850", + "link_name" : "", + "locations" : "413,416", + "mtime" : "1754406531.727720997", + "name" : "File_vvknf.md", + "path" : "/eos/user/j/jgeens/MyFolder/uplaods/File_vvknf.md", + "pid" : "5868251", + "size" : "7", + "stime" : "0.0", + "uid" : "173503", + "unlink_locations" : "", + "xattr.sys.eos.btime" : "1754406531.727434284", + "xattr.sys.fs.tracking" : "+413+416", + "xattr.sys.fusex.state" : "", + "xattr.sys.utrace" : "18a82c28-720e-11f0-9422-fa163e2e9155", + "xattr.sys.vtrace" : "[Tue Aug 5 17:08:51 2025] uid:173503[jgeens] gid:2763[it] tident:jgeens.1:1@[2001:1458:d00:13::100:2be] name:jgeens dn: prot:https app:http host:cbox-ocisdev-jesse.cern.ch domain:cern.ch geo: sudo:0 trace: onbehalf:", + "xs" : "0b0b02de" +}, +{ + "atime" : "1751457546.177186569", + "ctime" : "1751457586.641146481", + "fid" : "106573667", + "flags" : "640", + "gid" : "2763", + "layout_id" : "1048850", + "link_name" : "", + "locations" : "415,416", + "mtime" : "1751457546.177478893", + "name" : "MyEditableFile.txt", + "path" : "/eos/user/j/jgeens/MyFolder/uplaods/MyEditableFile.txt", + "pid" : "5868251", + "size" : "0", + "stime" : "0.0", + "uid" : "173503", + "unlink_locations" : "", + "xattr.sys.eos.btime" : "1751457546.177185967", + "xattr.sys.fs.tracking" : "+415+416", + "xattr.sys.fusex.state" : "", + "xattr.sys.tmp.atomic" : ".sys.a#.v#MyEditableFile.txt.f6143662-573b-11f0-8921-fa163e2e9155", + "xattr.sys.utrace" : "f44b8e8e-573b-11f0-9804-fa163e2e9155", + "xattr.sys.vtrace" : "[Wed Jul 2 13:59:06 2025] uid:173503[jgeens] gid:2763[it] tident:jgeens.1:1@[2001:1458:d00:16::33d] name:jgeens dn: prot:https app:http host:cbox-ocisdev-diogo.cern.ch domain:cern.ch geo: sudo:0 trace: onbehalf:", + "xs" : "00000001" +}, +{ + "cid" : "6747801", + "ctime" : "1782200190.451532002", + "flags" : "0", + "gid" : "2763", + "mode" : "40750", + "mtime" : "1782200190.451218450", + "name" : ".sys.v#.CSC-Report-IT-SD_uhuvj.pptx", + "parent_id" : "5868251", + "path" : "/eos/user/j/jgeens/MyFolder/uplaods/.sys.v#.CSC-Report-IT-SD_uhuvj.pptx/", + "stime" : "0.0", + "tree_size" : "0", + "uid" : "173503", + "xattr.sys.eos.btime" : "1782200190.451218450" +}, +{ + "cid" : "6747802", + "ctime" : "1782200190.454179666", + "flags" : "0", + "gid" : "2763", + "mode" : "40750", + "mtime" : "1782200190.453936785", + "name" : ".sys.v#.File_vvknf.md", + "parent_id" : "5868251", + "path" : "/eos/user/j/jgeens/MyFolder/uplaods/.sys.v#.File_vvknf.md/", + "stime" : "0.0", + "tree_size" : "0", + "uid" : "173503", + "xattr.sys.eos.btime" : "1782200190.453936785" +}, +{ + "cid" : "6747800", + "ctime" : "1782200190.449091389", + "flags" : "0", + "gid" : "2763", + "mode" : "40750", + "mtime" : "1782200190.448606057", + "name" : ".sys.v#.MyEditableFile.txt", + "parent_id" : "5868251", + "path" : "/eos/user/j/jgeens/MyFolder/uplaods/.sys.v#.MyEditableFile.txt/", + "stime" : "0.0", + "tree_size" : "0", + "uid" : "173503", + "xattr.sys.eos.btime" : "1782200190.448606057" +}, +{ + "cid" : "4766442", + "ctime" : "1764338452.148827317", + "flags" : "0", + "gid" : "2763", + "mode" : "40750", + "mtime" : "1741709680.66055935", + "name" : "MyFolder3", + "parent_id" : "4251882", + "path" : "/eos/user/j/jgeens/MyFolder3/", + "stime" : "0.0", + "tree_size" : "0", + "uid" : "173503", + "xattr.sys.acl" : "u:173503:rwx,u:178352:rx!d", + "xattr.sys.allow.oc.sync" : "1", + "xattr.sys.eos.btime" : "1677602740.101786072", + "xattr.sys.forced.atomic" : "1", + "xattr.sys.forced.blocksize" : "4k", + "xattr.sys.forced.checksum" : "adler", + "xattr.sys.forced.layout" : "replica", + "xattr.sys.forced.nstripes" : "2", + "xattr.sys.forced.space" : "default", + "xattr.sys.mask" : "700", + "xattr.sys.mtime.propagation" : "1", + "xattr.sys.owner.auth" : "*", + "xattr.sys.recycle" : "/eos/homedev/proc/recycle/", + "xattr.sys.versioning" : "10" +}, +{ + "cid" : "6204655", + "ctime" : "1764338458.544086354", + "flags" : "0", + "gid" : "2763", + "mode" : "40750", + "mtime" : "1764338458.543374407", + "name" : "MyFolder3 (1)", + "parent_id" : "4251882", + "path" : "/eos/user/j/jgeens/MyFolder3 (1)/", + "stime" : "0.0", + "tree_size" : "0", + "uid" : "173503", + "xattr.sys.acl" : "u:173503:rwx", + "xattr.sys.allow.oc.sync" : "1", + "xattr.sys.eos.btime" : "1677602740.101786072", + "xattr.sys.forced.atomic" : "1", + "xattr.sys.forced.blocksize" : "4k", + "xattr.sys.forced.checksum" : "adler", + "xattr.sys.forced.layout" : "replica", + "xattr.sys.forced.nstripes" : "2", + "xattr.sys.forced.space" : "default", + "xattr.sys.mask" : "700", + "xattr.sys.mtime.propagation" : "1", + "xattr.sys.owner.auth" : "*", + "xattr.sys.recycle" : "/eos/homedev/proc/recycle/", + "xattr.sys.versioning" : "10", + "xattr.user.http://owncloud.org/ns/favorite" : "u:jgeens=1" +}, +{ + "cid" : "6427291", + "ctime" : "1784293978.955350832", + "flags" : "0", + "gid" : "2763", + "mode" : "40750", + "mtime" : "1784293978.977234147", + "name" : "NewFolder", + "parent_id" : "4251882", + "path" : "/eos/user/j/jgeens/NewFolder/", + "stime" : "0.0", + "tree_size" : "10478766", + "uid" : "173503", + "xattr.sys.acl" : "u:58679:rxt,u:173503:rwx,u:179814:rwx+d,u:187895:rx,u:193339:rwx+d", + "xattr.sys.allow.oc.sync" : "1", + "xattr.sys.eos.btime" : "1677602740.101786072", + "xattr.sys.forced.atomic" : "1", + "xattr.sys.forced.blocksize" : "4k", + "xattr.sys.forced.checksum" : "adler", + "xattr.sys.forced.layout" : "replica", + "xattr.sys.forced.nstripes" : "2", + "xattr.sys.forced.space" : "default", + "xattr.sys.mask" : "700", + "xattr.sys.mtime.propagation" : "1", + "xattr.sys.owner.auth" : "*", + "xattr.sys.recycle" : "/eos/homedev/proc/recycle/", + "xattr.sys.versioning" : "10", + "xattr.user.http://owncloud.org/ns/favorite" : "u:jgeens=1" +}, +{ + "atime" : "1784293978.938285163", + "ctime" : "1784293978.938284533", + "fid" : "119712907", + "flags" : "644", + "gid" : "2763", + "layout_id" : "1048850", + "link_name" : "", + "locations" : "412,417", + "mtime" : "1784293978.938566285", + "name" : "June 20th_emvcv.pdf", + "path" : "/eos/user/j/jgeens/NewFolder/June 20th_emvcv.pdf", + "pid" : "6427291", + "size" : "868658", + "stime" : "0.0", + "uid" : "173503", + "unlink_locations" : "", + "xattr.sys.eos.btime" : "1784293978.938284533", + "xattr.sys.fs.tracking" : "+412+417", + "xattr.sys.fusex.state" : "", + "xattr.sys.utrace" : "3b66491e-81e1-11f1-8971-fa163e35f83a", + "xattr.sys.vtrace" : "[Fri Jul 17 15:12:58 2026] uid:173503[cboxexternal] gid:2763[def-cg] tident:cboxexternal.1:1@[2001:1458:d00:13::100:2be] name:cboxexternal dn: prot:https app:http/reva_write host:cbox-ocisdev-jesse.cern.ch domain:cern.ch geo: sudo:0 trace: onbehalf:", + "xs" : "801e8bbd" +}, +{ + "atime" : "1784293657.840198346", + "ctime" : "1784293657.840197876", + "fid" : "119712882", + "flags" : "644", + "gid" : "2763", + "layout_id" : "1048850", + "link_name" : "", + "locations" : "415,409", + "mtime" : "1784293657.840502547", + "name" : "RISCV_CARD_yzaov.pdf", + "path" : "/eos/user/j/jgeens/NewFolder/RISCV_CARD_yzaov.pdf", + "pid" : "6427291", + "size" : "163377", + "stime" : "0.0", + "uid" : "173503", + "unlink_locations" : "", + "xattr.sys.eos.btime" : "1784293657.840197876", + "xattr.sys.fs.tracking" : "+415+409", + "xattr.sys.fusex.state" : "", + "xattr.sys.utrace" : "7c029a1e-81e0-11f1-b113-fa163e35f83a", + "xattr.sys.vtrace" : "[Fri Jul 17 15:07:37 2026] uid:173503[cboxexternal] gid:2763[def-cg] tident:cboxexternal.1:1@[2001:1458:d00:13::100:2be] name:cboxexternal dn: prot:https app:http/reva_write host:cbox-ocisdev-jesse.cern.ch domain:cern.ch geo: sudo:0 trace: onbehalf:", + "xs" : "21532593" +}, +{ + "atime" : "1784293139.931555012", + "ctime" : "1784293139.931554372", + "fid" : "119712844", + "flags" : "644", + "gid" : "2763", + "layout_id" : "1048850", + "link_name" : "", + "locations" : "418,410", + "mtime" : "1784293139.931873854", + "name" : "Session3-1_ikwso.pdf", + "path" : "/eos/user/j/jgeens/NewFolder/Session3-1_ikwso.pdf", + "pid" : "6427291", + "size" : "2663591", + "stime" : "0.0", + "uid" : "173503", + "unlink_locations" : "", + "xattr.sys.eos.btime" : "1784293139.931554372", + "xattr.sys.fs.tracking" : "+415+416/415/416", + "xattr.sys.fusex.state" : "", + "xattr.sys.utrace" : "47500474-81df-11f1-b113-fa163e35f83a", + "xattr.sys.vtrace" : "[Fri Jul 17 14:58:59 2026] uid:173503[cboxexternal] gid:2763[def-cg] tident:cboxexternal.1:1@[2001:1458:d00:13::100:2be] name:cboxexternal dn: prot:https app:http/reva_write host:cbox-ocisdev-jesse.cern.ch domain:cern.ch geo: sudo:0 trace: onbehalf:", + "xs" : "48a7e65f" +}, +{ + "atime" : "1784293123.543170522", + "ctime" : "1784293123.543170002", + "fid" : "119712842", + "flags" : "644", + "gid" : "2763", + "layout_id" : "1048850", + "link_name" : "", + "locations" : "415,413", + "mtime" : "1784293123.543600485", + "name" : "it-sd-ai-guidelines_mbxmh.pdf", + "path" : "/eos/user/j/jgeens/NewFolder/it-sd-ai-guidelines_mbxmh.pdf", + "pid" : "6427291", + "size" : "68508", + "stime" : "0.0", + "uid" : "173503", + "unlink_locations" : "", + "xattr.sys.eos.btime" : "1784293123.543170002", + "xattr.sys.fs.tracking" : "+415+413", + "xattr.sys.fusex.state" : "", + "xattr.sys.utrace" : "3d8b50b0-81df-11f1-828f-fa163e35f83a", + "xattr.sys.vtrace" : "[Fri Jul 17 14:58:43 2026] uid:173503[cboxexternal] gid:2763[def-cg] tident:cboxexternal.1:1@[2001:1458:d00:13::100:2be] name:cboxexternal dn: prot:https app:http/reva_write host:cbox-ocisdev-jesse.cern.ch domain:cern.ch geo: sudo:0 trace: onbehalf:", + "xs" : "40d3349c" +}, +{ + "atime" : "1784293827.351238561", + "ctime" : "1784293827.351237961", + "fid" : "119712898", + "flags" : "644", + "gid" : "2763", + "layout_id" : "1048850", + "link_name" : "", + "locations" : "420,412", + "mtime" : "1784293827.351554782", + "name" : "pub_exercises_e20_4_xkuey.pdf", + "path" : "/eos/user/j/jgeens/NewFolder/pub_exercises_e20_4_xkuey.pdf", + "pid" : "6427291", + "size" : "6714632", + "stime" : "0.0", + "uid" : "173503", + "unlink_locations" : "", + "xattr.sys.eos.btime" : "1784293827.351237961", + "xattr.sys.fs.tracking" : "+409+416/416/409", + "xattr.sys.fusex.state" : "", + "xattr.sys.utrace" : "e10bed98-81e0-11f1-931f-fa163e35f83a", + "xattr.sys.vtrace" : "[Fri Jul 17 15:10:27 2026] uid:173503[cboxexternal] gid:2763[def-cg] tident:cboxexternal.1:1@[2001:1458:d00:13::100:2be] name:cboxexternal dn: prot:https app:http/reva_write host:cbox-ocisdev-jesse.cern.ch domain:cern.ch geo: sudo:0 trace: onbehalf:", + "xs" : "97b8f13a" +}, +{ + "atime" : "1776760047.296052818", + "ctime" : "1782200229.976490149", + "fid" : "116620415", + "flags" : "644", + "gid" : "2763", + "layout_id" : "1048850", + "link_name" : "", + "locations" : "421,412", + "mtime" : "1776760047.297141772", + "name" : "test.txt", + "path" : "/eos/user/j/jgeens/NewFolder/test.txt", + "pid" : "6427291", + "size" : "0", + "stime" : "0.0", + "uid" : "173503", + "unlink_locations" : "", + "xattr.sys.acl" : "u:187895:rx", + "xattr.sys.eos.btime" : "1776760047.296052193", + "xattr.sys.fs.tracking" : "+420+421/420", + "xattr.sys.fusex.state" : "", + "xattr.sys.utrace" : "ee34cdba-3d5b-11f1-b845-fa163e44ab60", + "xattr.sys.vtrace" : "[Tue Apr 21 10:27:27 2026] uid:173503[jgeens] gid:2763[it] tident:jgeens.1:1@[2001:1458:d00:13::100:2be] name:jgeens dn: prot:https app:http/reva_write host:cbox-ocisdev-jesse.cern.ch domain:cern.ch geo: sudo:0 trace: onbehalf:", + "xs" : "00000001" +}, +{ + "cid" : "6806000", + "ctime" : "1784293978.977566940", + "flags" : "0", + "gid" : "2763", + "mode" : "40750", + "mtime" : "1784293978.977234147", + "name" : ".sys.v#.June 20th_emvcv.pdf", + "parent_id" : "6427291", + "path" : "/eos/user/j/jgeens/NewFolder/.sys.v#.June 20th_emvcv.pdf/", + "stime" : "0.0", + "tree_size" : "0", + "uid" : "173503", + "xattr.sys.eos.btime" : "1784293978.977234147" +}, +{ + "cid" : "6805996", + "ctime" : "1784293657.907020787", + "flags" : "0", + "gid" : "2763", + "mode" : "40750", + "mtime" : "1784293657.906563244", + "name" : ".sys.v#.RISCV_CARD_yzaov.pdf", + "parent_id" : "6427291", + "path" : "/eos/user/j/jgeens/NewFolder/.sys.v#.RISCV_CARD_yzaov.pdf/", + "stime" : "0.0", + "tree_size" : "0", + "uid" : "173503", + "xattr.sys.eos.btime" : "1784293657.906563244" +}, +{ + "cid" : "6805993", + "ctime" : "1784293139.986728075", + "flags" : "0", + "gid" : "2763", + "mode" : "40750", + "mtime" : "1784293139.986382113", + "name" : ".sys.v#.Session3-1_ikwso.pdf", + "parent_id" : "6427291", + "path" : "/eos/user/j/jgeens/NewFolder/.sys.v#.Session3-1_ikwso.pdf/", + "stime" : "0.0", + "tree_size" : "0", + "uid" : "173503", + "xattr.sys.eos.btime" : "1784293139.986382113" +}, +{ + "cid" : "6805992", + "ctime" : "1784293123.579861657", + "flags" : "0", + "gid" : "2763", + "mode" : "40750", + "mtime" : "1784293123.579270274", + "name" : ".sys.v#.it-sd-ai-guidelines_mbxmh.pdf", + "parent_id" : "6427291", + "path" : "/eos/user/j/jgeens/NewFolder/.sys.v#.it-sd-ai-guidelines_mbxmh.pdf/", + "stime" : "0.0", + "tree_size" : "0", + "uid" : "173503", + "xattr.sys.eos.btime" : "1784293123.579270274" +}, +{ + "cid" : "6805999", + "ctime" : "1784293827.459343958", + "flags" : "0", + "gid" : "2763", + "mode" : "40750", + "mtime" : "1784293827.458873906", + "name" : ".sys.v#.pub_exercises_e20_4_xkuey.pdf", + "parent_id" : "6427291", + "path" : "/eos/user/j/jgeens/NewFolder/.sys.v#.pub_exercises_e20_4_xkuey.pdf/", + "stime" : "0.0", + "tree_size" : "0", + "uid" : "173503", + "xattr.sys.eos.btime" : "1784293827.458873906" +}, +{ + "cid" : "6592837", + "ctime" : "1782120440.524884795", + "flags" : "0", + "gid" : "2763", + "mode" : "40750", + "mtime" : "1776760047.324261575", + "name" : ".sys.v#.test.txt", + "parent_id" : "6427291", + "path" : "/eos/user/j/jgeens/NewFolder/.sys.v#.test.txt/", + "stime" : "0.0", + "tree_size" : "0", + "uid" : "173503", + "xattr.sys.acl" : "u:187895:rx", + "xattr.sys.eos.btime" : "1776760047.324261575" +}, +{ + "cid" : "6633640", + "ctime" : "1778078827.994005903", + "flags" : "0", + "gid" : "2763", + "mode" : "40750", + "mtime" : "1778078736.760628883", + "name" : "NewFolder0605", + "parent_id" : "4251882", + "path" : "/eos/user/j/jgeens/NewFolder0605/", + "stime" : "0.0", + "tree_size" : "0", + "uid" : "173503", + "xattr.sys.acl" : "u:173503:rwx", + "xattr.sys.allow.oc.sync" : "1", + "xattr.sys.eos.btime" : "1677602740.101786072", + "xattr.sys.forced.atomic" : "1", + "xattr.sys.forced.blocksize" : "4k", + "xattr.sys.forced.checksum" : "adler", + "xattr.sys.forced.layout" : "replica", + "xattr.sys.forced.nstripes" : "2", + "xattr.sys.forced.space" : "default", + "xattr.sys.mask" : "700", + "xattr.sys.mtime.propagation" : "1", + "xattr.sys.owner.auth" : "*", + "xattr.sys.recycle" : "/eos/homedev/proc/recycle/", + "xattr.sys.versioning" : "10", + "xattr.user.http://owncloud.org/ns/favorite" : "u:tstcbsa9=1,u:jgeens=1" +}, +{ + "cid" : "5740316", + "ctime" : "1750765062.872146609", + "flags" : "0", + "gid" : "2763", + "mode" : "40750", + "mtime" : "1782200199.129275653", + "name" : "NewSharingFolder", + "parent_id" : "4251882", + "path" : "/eos/user/j/jgeens/NewSharingFolder/", + "stime" : "1782200203.60288555", + "tree_size" : "82", + "uid" : "173503", + "xattr.sys.acl" : "u:173503:rwx", + "xattr.sys.allow.oc.sync" : "1", + "xattr.sys.eos.btime" : "1677602740.101786072", + "xattr.sys.forced.atomic" : "1", + "xattr.sys.forced.blocksize" : "4k", + "xattr.sys.forced.checksum" : "adler", + "xattr.sys.forced.layout" : "replica", + "xattr.sys.forced.nstripes" : "2", + "xattr.sys.forced.space" : "default", + "xattr.sys.mask" : "700", + "xattr.sys.mtime.propagation" : "1", + "xattr.sys.owner.auth" : "*", + "xattr.sys.recycle" : "/eos/homedev/proc/recycle/", + "xattr.sys.versioning" : "10" +}, +{ + "atime" : "1751295308.994809152", + "ctime" : "1751457517.781034659", + "fid" : "106494094", + "flags" : "644", + "gid" : "2763", + "layout_id" : "1048850", + "link_name" : "", + "locations" : "417,420", + "mtime" : "1751295308.995473894", + "name" : "MyTxtFile.txt", + "path" : "/eos/user/j/jgeens/NewSharingFolder/MyTxtFile.txt", + "pid" : "5740316", + "size" : "41", + "stime" : "0.0", + "uid" : "173503", + "unlink_locations" : "", + "xattr.sys.acl" : "u:178406:rx!d", + "xattr.sys.eos.btime" : "1751295308.994808616", + "xattr.sys.fs.tracking" : "+417+420", + "xattr.sys.fusex.state" : "", + "xattr.sys.reva.lwshare.111857693350341032552@google" : "rx!d", + "xattr.sys.tmp.atomic" : ".sys.a#.v#MyTxtFile.txt.817fe13c-5684-11f0-8b02-fa163e2e9155", + "xattr.sys.utrace" : "3765e4c4-55c2-11f0-8690-fa163e2e9155", + "xattr.sys.vtrace" : "[Mon Jun 30 16:55:08 2025] uid:173503[jgeens] gid:2763[it] tident:jgeens.1:1@[2001:1458:d00:16::33d] name:jgeens dn: prot:https app:http host:cbox-ocisdev-diogo.cern.ch domain:cern.ch geo: sudo:0 trace: onbehalf:", + "xs" : "3bc80eaa" +}, +{ + "cid" : "6747850", + "ctime" : "1782200199.129639585", + "flags" : "0", + "gid" : "2763", + "mode" : "40750", + "mtime" : "1782200199.129275653", + "name" : ".sys.v#.MyTxtFile.txt", + "parent_id" : "5740316", + "path" : "/eos/user/j/jgeens/NewSharingFolder/.sys.v#.MyTxtFile.txt/", + "stime" : "0.0", + "tree_size" : "0", + "uid" : "173503", + "xattr.sys.eos.btime" : "1782200199.129275653" +}, +{ + "cid" : "5934625", + "ctime" : "1756454894.887230112", + "flags" : "0", + "gid" : "2763", + "mode" : "40750", + "mtime" : "1782200201.52677338", + "name" : "folder", + "parent_id" : "5740316", + "path" : "/eos/user/j/jgeens/NewSharingFolder/folder/", + "stime" : "0.0", + "tree_size" : "0", + "uid" : "173503", + "xattr.sys.acl" : "u:173503:rwx", + "xattr.sys.allow.oc.sync" : "1", + "xattr.sys.eos.btime" : "1677602740.101786072", + "xattr.sys.forced.atomic" : "1", + "xattr.sys.forced.blocksize" : "4k", + "xattr.sys.forced.checksum" : "adler", + "xattr.sys.forced.layout" : "replica", + "xattr.sys.forced.nstripes" : "2", + "xattr.sys.forced.space" : "default", + "xattr.sys.mask" : "700", + "xattr.sys.mtime.propagation" : "1", + "xattr.sys.owner.auth" : "*", + "xattr.sys.recycle" : "/eos/homedev/proc/recycle/", + "xattr.sys.versioning" : "10" +}, +{ + "atime" : "1756454901.912040606", + "ctime" : "1756454901.912040055", + "fid" : "108574310", + "flags" : "644", + "gid" : "2763", + "layout_id" : "1048850", + "link_name" : "", + "locations" : "421,417", + "mtime" : "1756454901.912295723", + "name" : "New file.drawio", + "path" : "/eos/user/j/jgeens/NewSharingFolder/folder/New file.drawio", + "pid" : "5934625", + "size" : "0", + "stime" : "0.0", + "uid" : "173503", + "unlink_locations" : "", + "xattr.sys.eos.btime" : "1756454901.912040055", + "xattr.sys.fs.tracking" : "+421+417", + "xattr.sys.fusex.state" : "", + "xattr.sys.utrace" : "546dd024-84af-11f0-9999-fa163e2e9155", + "xattr.sys.vtrace" : "[Fri Aug 29 10:08:21 2025] uid:173503[jgeens] gid:2763[it] tident:jgeens.1:1@[2001:1458:d00:16::33d] name:jgeens dn: prot:https app:http host:cbox-ocisdev-diogo.cern.ch domain:cern.ch geo: sudo:0 trace: onbehalf:", + "xs" : "00000001" +}, +{ + "cid" : "6747854", + "ctime" : "1782200201.53142922", + "flags" : "0", + "gid" : "2763", + "mode" : "40750", + "mtime" : "1782200201.52677338", + "name" : ".sys.v#.New file.drawio", + "parent_id" : "5934625", + "path" : "/eos/user/j/jgeens/NewSharingFolder/folder/.sys.v#.New file.drawio/", + "stime" : "0.0", + "tree_size" : "0", + "uid" : "173503", + "xattr.sys.eos.btime" : "1782200201.52677338" +}, +{ + "cid" : "5934627", + "ctime" : "1756718292.646286599", + "flags" : "0", + "gid" : "2763", + "mode" : "40750", + "mtime" : "1782200201.382182096", + "name" : "folder2", + "parent_id" : "5740316", + "path" : "/eos/user/j/jgeens/NewSharingFolder/folder2/", + "stime" : "1782200203.60288555", + "tree_size" : "41", + "uid" : "173503", + "xattr.sys.acl" : "u:173503:rwx", + "xattr.sys.allow.oc.sync" : "1", + "xattr.sys.eos.btime" : "1677602740.101786072", + "xattr.sys.forced.atomic" : "1", + "xattr.sys.forced.blocksize" : "4k", + "xattr.sys.forced.checksum" : "adler", + "xattr.sys.forced.layout" : "replica", + "xattr.sys.forced.nstripes" : "2", + "xattr.sys.forced.space" : "default", + "xattr.sys.mask" : "700", + "xattr.sys.mtime.propagation" : "1", + "xattr.sys.owner.auth" : "*", + "xattr.sys.recycle" : "/eos/homedev/proc/recycle/", + "xattr.sys.versioning" : "10" +}, +{ + "atime" : "1756718292.633663520", + "ctime" : "1756718292.633662936", + "fid" : "108675459", + "flags" : "644", + "gid" : "2763", + "layout_id" : "1048850", + "link_name" : "", + "locations" : "421,417", + "mtime" : "1756718292.633953447", + "name" : "MyTxtFile.txt", + "path" : "/eos/user/j/jgeens/NewSharingFolder/folder2/MyTxtFile.txt", + "pid" : "5934627", + "size" : "41", + "stime" : "0.0", + "uid" : "173503", + "unlink_locations" : "", + "xattr.sys.eos.btime" : "1756718292.633662936", + "xattr.sys.fs.tracking" : "+421+417", + "xattr.sys.fusex.state" : "", + "xattr.sys.utrace" : "958870f2-8714-11f0-94db-fa163e2e9155", + "xattr.sys.vtrace" : "[Mon Sep 1 11:18:12 2025] uid:173503[jgeens] gid:2763[it] tident:jgeens.1:1@[2001:1458:d00:16::33d] name:jgeens dn: prot:https app:http host:cbox-ocisdev-diogo.cern.ch domain:cern.ch geo: sudo:0 trace: onbehalf:", + "xs" : "3bc80eaa" +}, +{ + "cid" : "6747855", + "ctime" : "1782200201.382565689", + "flags" : "0", + "gid" : "2763", + "mode" : "40750", + "mtime" : "1782200201.382182096", + "name" : ".sys.v#.MyTxtFile.txt", + "parent_id" : "5934627", + "path" : "/eos/user/j/jgeens/NewSharingFolder/folder2/.sys.v#.MyTxtFile.txt/", + "stime" : "0.0", + "tree_size" : "0", + "uid" : "173503", + "xattr.sys.eos.btime" : "1782200201.382182096" +}, +{ + "cid" : "5943197", + "ctime" : "1756718301.283910502", + "flags" : "0", + "gid" : "2763", + "mode" : "40750", + "mtime" : "1782200203.60288555", + "name" : "folder", + "parent_id" : "5934627", + "path" : "/eos/user/j/jgeens/NewSharingFolder/folder2/folder/", + "stime" : "0.0", + "tree_size" : "0", + "uid" : "173503", + "xattr.sys.acl" : "u:173503:rwx", + "xattr.sys.allow.oc.sync" : "1", + "xattr.sys.eos.btime" : "1677602740.101786072", + "xattr.sys.forced.atomic" : "1", + "xattr.sys.forced.blocksize" : "4k", + "xattr.sys.forced.checksum" : "adler", + "xattr.sys.forced.layout" : "replica", + "xattr.sys.forced.nstripes" : "2", + "xattr.sys.forced.space" : "default", + "xattr.sys.mask" : "700", + "xattr.sys.mtime.propagation" : "1", + "xattr.sys.owner.auth" : "*", + "xattr.sys.recycle" : "/eos/homedev/proc/recycle/", + "xattr.sys.versioning" : "10" +}, +{ + "atime" : "1756718301.394736359", + "ctime" : "1756718301.394735849", + "fid" : "108675460", + "flags" : "644", + "gid" : "2763", + "layout_id" : "1048850", + "link_name" : "", + "locations" : "415,409", + "mtime" : "1756718301.395021507", + "name" : "New file.drawio", + "path" : "/eos/user/j/jgeens/NewSharingFolder/folder2/folder/New file.drawio", + "pid" : "5943197", + "size" : "0", + "stime" : "0.0", + "uid" : "173503", + "unlink_locations" : "", + "xattr.sys.eos.btime" : "1756718301.394735849", + "xattr.sys.fs.tracking" : "+415+409", + "xattr.sys.fusex.state" : "", + "xattr.sys.utrace" : "9ac14b98-8714-11f0-b99c-fa163e2e9155", + "xattr.sys.vtrace" : "[Mon Sep 1 11:18:21 2025] uid:173503[jgeens] gid:2763[it] tident:jgeens.1:1@[2001:1458:d00:16::33d] name:jgeens dn: prot:https app:http host:cbox-ocisdev-diogo.cern.ch domain:cern.ch geo: sudo:0 trace: onbehalf:", + "xs" : "00000001" +}, +{ + "cid" : "6747863", + "ctime" : "1782200203.60628597", + "flags" : "0", + "gid" : "2763", + "mode" : "40750", + "mtime" : "1782200203.60288555", + "name" : ".sys.v#.New file.drawio", + "parent_id" : "5943197", + "path" : "/eos/user/j/jgeens/NewSharingFolder/folder2/folder/.sys.v#.New file.drawio/", + "stime" : "0.0", + "tree_size" : "0", + "uid" : "173503", + "xattr.sys.eos.btime" : "1782200203.60288555" +}, +{ + "cid" : "6787989", + "ctime" : "1783584487.392915613", + "flags" : "0", + "gid" : "2763", + "mode" : "40750", + "mtime" : "1783584506.612979597", + "name" : "Share Hierarchy", + "parent_id" : "4251882", + "path" : "/eos/user/j/jgeens/Share Hierarchy/", + "stime" : "1783584516.802113809", + "tree_size" : "0", + "uid" : "173503", + "xattr.sys.acl" : "u:173503:rwx", + "xattr.sys.allow.oc.sync" : "1", + "xattr.sys.eos.btime" : "1677602740.101786072", + "xattr.sys.forced.atomic" : "1", + "xattr.sys.forced.blocksize" : "4k", + "xattr.sys.forced.checksum" : "adler", + "xattr.sys.forced.layout" : "replica", + "xattr.sys.forced.nstripes" : "2", + "xattr.sys.forced.space" : "default", + "xattr.sys.mask" : "700", + "xattr.sys.mtime.propagation" : "1", + "xattr.sys.owner.auth" : "*", + "xattr.sys.recycle" : "/eos/homedev/proc/recycle/", + "xattr.sys.versioning" : "10", + "xattr.user.http://owncloud.org/ns/favorite" : "u:jgeens=1,u:tstcbsa9=1", + "xattr.user.reva.labels.jgeens.favorite" : "1", + "xattr.user.reva.labels.tstcbsa9.favorite" : "1" +}, +{ + "cid" : "6787990", + "ctime" : "1783602046.832050783", + "flags" : "0", + "gid" : "2763", + "mode" : "40750", + "mtime" : "1783584511.313161204", + "name" : "Parent", + "parent_id" : "6787989", + "path" : "/eos/user/j/jgeens/Share Hierarchy/Parent/", + "stime" : "1783584516.802113809", + "tree_size" : "0", + "uid" : "173503", + "xattr.sys.acl" : "u:173503:rwx,u:187895:rx,u:179814:rx!d", + "xattr.sys.allow.oc.sync" : "1", + "xattr.sys.eos.btime" : "1677602740.101786072", + "xattr.sys.forced.atomic" : "1", + "xattr.sys.forced.blocksize" : "4k", + "xattr.sys.forced.checksum" : "adler", + "xattr.sys.forced.layout" : "replica", + "xattr.sys.forced.nstripes" : "2", + "xattr.sys.forced.space" : "default", + "xattr.sys.mask" : "700", + "xattr.sys.mtime.propagation" : "1", + "xattr.sys.owner.auth" : "*", + "xattr.sys.recycle" : "/eos/homedev/proc/recycle/", + "xattr.sys.versioning" : "10", + "xattr.user.http://owncloud.org/ns/favorite" : "u:jgeens=1,u:tstcbsa9=1", + "xattr.user.reva.labels.jgeens.favorite" : "1", + "xattr.user.reva.labels.tstcbsa9.favorite" : "1" +}, +{ + "cid" : "6787992", + "ctime" : "1783602046.837408142", + "flags" : "0", + "gid" : "2763", + "mode" : "40750", + "mtime" : "1783584516.802113809", + "name" : "Child", + "parent_id" : "6787990", + "path" : "/eos/user/j/jgeens/Share Hierarchy/Parent/Child/", + "stime" : "0.0", + "tree_size" : "0", + "uid" : "173503", + "xattr.sys.acl" : "u:173503:rwx,u:179814:rwx+d,u:187895:rx", + "xattr.sys.allow.oc.sync" : "1", + "xattr.sys.eos.btime" : "1677602740.101786072", + "xattr.sys.forced.atomic" : "1", + "xattr.sys.forced.blocksize" : "4k", + "xattr.sys.forced.checksum" : "adler", + "xattr.sys.forced.layout" : "replica", + "xattr.sys.forced.nstripes" : "2", + "xattr.sys.forced.space" : "default", + "xattr.sys.mask" : "700", + "xattr.sys.mtime.propagation" : "1", + "xattr.sys.owner.auth" : "*", + "xattr.sys.recycle" : "/eos/homedev/proc/recycle/", + "xattr.sys.reva.lwshare.jesse.geens@proton.me" : "rx!d", + "xattr.sys.versioning" : "10", + "xattr.user.http://owncloud.org/ns/favorite" : "u:jgeens=1,u:tstcbsa9=1", + "xattr.user.reva.labels.jgeens.favorite" : "1", + "xattr.user.reva.labels.tstcbsa9.favorite" : "1" +}, +{ + "cid" : "6787993", + "ctime" : "1783602046.837485652", + "flags" : "0", + "gid" : "2763", + "mode" : "40750", + "mtime" : "1783584516.802113809", + "name" : "Grandchild", + "parent_id" : "6787992", + "path" : "/eos/user/j/jgeens/Share Hierarchy/Parent/Child/Grandchild/", + "stime" : "0.0", + "tree_size" : "0", + "uid" : "173503", + "xattr.sys.acl" : "u:173503:rwx,u:179814:rwx+d,u:187895:rx", + "xattr.sys.allow.oc.sync" : "1", + "xattr.sys.eos.btime" : "1677602740.101786072", + "xattr.sys.forced.atomic" : "1", + "xattr.sys.forced.blocksize" : "4k", + "xattr.sys.forced.checksum" : "adler", + "xattr.sys.forced.layout" : "replica", + "xattr.sys.forced.nstripes" : "2", + "xattr.sys.forced.space" : "default", + "xattr.sys.mask" : "700", + "xattr.sys.mtime.propagation" : "1", + "xattr.sys.owner.auth" : "*", + "xattr.sys.recycle" : "/eos/homedev/proc/recycle/", + "xattr.sys.reva.lwshare.jesse.geens@proton.me" : "rx!d", + "xattr.sys.versioning" : "10", + "xattr.user.http://owncloud.org/ns/favorite" : "u:jgeens=1,u:tstcbsa9=1", + "xattr.user.reva.labels.jgeens.favorite" : "1", + "xattr.user.reva.labels.tstcbsa9.favorite" : "1" +}, +{ + "cid" : "6787991", + "ctime" : "1783584506.613664261", + "flags" : "0", + "gid" : "2763", + "mode" : "40750", + "mtime" : "1783584506.612979597", + "name" : "Sibling", + "parent_id" : "6787989", + "path" : "/eos/user/j/jgeens/Share Hierarchy/Sibling/", + "stime" : "0.0", + "tree_size" : "0", + "uid" : "173503", + "xattr.sys.acl" : "u:173503:rwx", + "xattr.sys.allow.oc.sync" : "1", + "xattr.sys.eos.btime" : "1677602740.101786072", + "xattr.sys.forced.atomic" : "1", + "xattr.sys.forced.blocksize" : "4k", + "xattr.sys.forced.checksum" : "adler", + "xattr.sys.forced.layout" : "replica", + "xattr.sys.forced.nstripes" : "2", + "xattr.sys.forced.space" : "default", + "xattr.sys.mask" : "700", + "xattr.sys.mtime.propagation" : "1", + "xattr.sys.owner.auth" : "*", + "xattr.sys.recycle" : "/eos/homedev/proc/recycle/", + "xattr.sys.versioning" : "10", + "xattr.user.http://owncloud.org/ns/favorite" : "u:jgeens=1,u:tstcbsa9=1", + "xattr.user.reva.labels.jgeens.favorite" : "1", + "xattr.user.reva.labels.tstcbsa9.favorite" : "1" +}, +{ + "cid" : "6614612", + "ctime" : "1782223148.345751699", + "flags" : "0", + "gid" : "2763", + "mode" : "40750", + "mtime" : "1777464293.349815168", + "name" : "ShareMe", + "parent_id" : "4251882", + "path" : "/eos/user/j/jgeens/ShareMe/", + "stime" : "1777464299.596752807", + "tree_size" : "0", + "uid" : "173503", + "xattr.sys.acl" : "u:173503:rwx", + "xattr.sys.allow.oc.sync" : "1", + "xattr.sys.eos.btime" : "1677602740.101786072", + "xattr.sys.forced.atomic" : "1", + "xattr.sys.forced.blocksize" : "4k", + "xattr.sys.forced.checksum" : "adler", + "xattr.sys.forced.layout" : "replica", + "xattr.sys.forced.nstripes" : "2", + "xattr.sys.forced.space" : "default", + "xattr.sys.mask" : "700", + "xattr.sys.mtime.propagation" : "1", + "xattr.sys.owner.auth" : "*", + "xattr.sys.recycle" : "/eos/homedev/proc/recycle/", + "xattr.sys.reva.lwshare.schlemba@illinois.edu" : "rx!d", + "xattr.sys.versioning" : "10", + "xattr.user.http://owncloud.org/ns/favorite" : "u:jgeens=1,u:tstcbsa9=1" +}, +{ + "cid" : "6614613", + "ctime" : "1782223148.345957580", + "flags" : "0", + "gid" : "2763", + "mode" : "40750", + "mtime" : "1777464299.596752807", + "name" : "SubFolder", + "parent_id" : "6614612", + "path" : "/eos/user/j/jgeens/ShareMe/SubFolder/", + "stime" : "0.0", + "tree_size" : "0", + "uid" : "173503", + "xattr.sys.acl" : "u:173503:rwx", + "xattr.sys.allow.oc.sync" : "1", + "xattr.sys.eos.btime" : "1677602740.101786072", + "xattr.sys.forced.atomic" : "1", + "xattr.sys.forced.blocksize" : "4k", + "xattr.sys.forced.checksum" : "adler", + "xattr.sys.forced.layout" : "replica", + "xattr.sys.forced.nstripes" : "2", + "xattr.sys.forced.space" : "default", + "xattr.sys.mask" : "700", + "xattr.sys.mtime.propagation" : "1", + "xattr.sys.owner.auth" : "*", + "xattr.sys.recycle" : "/eos/homedev/proc/recycle/", + "xattr.sys.reva.lwshare.schlemba@illinois.edu" : "rx!d", + "xattr.sys.versioning" : "10", + "xattr.user.http://owncloud.org/ns/favorite" : "u:jgeens=1,u:tstcbsa9=1" +}, +{ + "atime" : "1777464299.566342059", + "ctime" : "1777464301.344059082", + "fid" : "116908955", + "flags" : "644", + "gid" : "2763", + "layout_id" : "1048850", + "link_name" : "", + "locations" : "409,416", + "mtime" : "1777464299.566677821", + "name" : "test.md", + "path" : "/eos/user/j/jgeens/ShareMe/SubFolder/test.md", + "pid" : "6614613", + "size" : "0", + "stime" : "0.0", + "uid" : "173503", + "unlink_locations" : "", + "xattr.sys.eos.btime" : "1777464299.566341379", + "xattr.sys.fs.tracking" : "+409+416", + "xattr.sys.fusex.state" : "", + "xattr.sys.utrace" : "a545366c-43c3-11f1-9863-fa163e35f83a", + "xattr.sys.vtrace" : "[Wed Apr 29 14:04:59 2026] uid:173503[jgeens] gid:2763[it] tident:jgeens.1:1@[2001:1458:d00:13::100:2be] name:jgeens dn: prot:https app:http/reva_write host:cbox-ocisdev-jesse.cern.ch domain:cern.ch geo: sudo:0 trace: onbehalf:", + "xattr.user.iop.wopi.lastwritetime" : "1777464301", + "xs" : "00000001" +}, +{ + "cid" : "6614614", + "ctime" : "1782223148.346014301", + "flags" : "0", + "gid" : "2763", + "mode" : "40750", + "mtime" : "1777464299.596752807", + "name" : ".sys.v#.test.md", + "parent_id" : "6614613", + "path" : "/eos/user/j/jgeens/ShareMe/SubFolder/.sys.v#.test.md/", + "stime" : "0.0", + "tree_size" : "0", + "uid" : "173503", + "xattr.sys.eos.btime" : "1777464299.596752807", + "xattr.sys.reva.lwshare.schlemba@illinois.edu" : "rx!d" +}, +{ + "cid" : "4771583", + "ctime" : "1782811947.489186613", + "flags" : "0", + "gid" : "2763", + "mode" : "40750", + "mtime" : "1782120367.351040725", + "name" : "TestFolderSharing2", + "parent_id" : "4251882", + "path" : "/eos/user/j/jgeens/TestFolderSharing2/", + "stime" : "1782200202.217032434", + "tree_size" : "75", + "uid" : "173503", + "xattr.sys.acl" : "u:173503:rwx,u:179814:rx!d,u:187895:rx,u:193339:rwx+d", + "xattr.sys.allow.oc.sync" : "1", + "xattr.sys.eos.btime" : "1677602740.101786072", + "xattr.sys.forced.atomic" : "1", + "xattr.sys.forced.blocksize" : "4k", + "xattr.sys.forced.checksum" : "adler", + "xattr.sys.forced.layout" : "replica", + "xattr.sys.forced.nstripes" : "2", + "xattr.sys.forced.space" : "default", + "xattr.sys.mask" : "700", + "xattr.sys.mtime.propagation" : "1", + "xattr.sys.owner.auth" : "*", + "xattr.sys.recycle" : "/eos/homedev/proc/recycle/", + "xattr.sys.reva.lwshare.111857693350341032552@google" : "rwx+d", + "xattr.sys.versioning" : "10", + "xattr.user.http://owncloud.org/ns/favorite" : "u:jgeens=1" +}, +{ + "atime" : "1759302605.707546543", + "ctime" : "1759302605.707545909", + "fid" : "109656549", + "flags" : "644", + "gid" : "2763", + "layout_id" : "1048850", + "link_name" : "", + "locations" : "413,416", + "mtime" : "1759302605.707896579", + "name" : "testfile3.txt", + "path" : "/eos/user/j/jgeens/TestFolderSharing2/testfile3.txt", + "pid" : "4771583", + "size" : "9", + "stime" : "0.0", + "uid" : "173503", + "unlink_locations" : "", + "xattr.sys.acl" : "u:187895:rx", + "xattr.sys.eos.btime" : "1759302605.707545909", + "xattr.sys.fs.tracking" : "+413+416", + "xattr.sys.fusex.state" : "", + "xattr.sys.utrace" : "a828b9f4-9e95-11f0-866e-fa163e2e9155", + "xattr.sys.vtrace" : "[Wed Oct 1 09:10:05 2025] uid:173503[jgeens] gid:2763[it] tident:http name:jgeens dn: prot:https app:http/reva_write host:[2001:1458:d00:16::33d] domain:localdomain geo: sudo:0 trace: onbehalf:", + "xs" : "10fc035d" +}, +{ + "cid" : "6746617", + "ctime" : "1782120367.351485558", + "flags" : "0", + "gid" : "2763", + "mode" : "40750", + "mtime" : "1782120367.351040725", + "name" : ".sys.v#.testfile3.txt", + "parent_id" : "4771583", + "path" : "/eos/user/j/jgeens/TestFolderSharing2/.sys.v#.testfile3.txt/", + "stime" : "0.0", + "tree_size" : "0", + "uid" : "173503", + "xattr.sys.eos.btime" : "1782120367.351040725" +}, +{ + "cid" : "4885561", + "ctime" : "1782811947.489333123", + "flags" : "0", + "gid" : "2763", + "mode" : "40750", + "mtime" : "1782200201.890428151", + "name" : "MyFolder", + "parent_id" : "4771583", + "path" : "/eos/user/j/jgeens/TestFolderSharing2/MyFolder/", + "stime" : "0.0", + "tree_size" : "18", + "uid" : "173503", + "xattr.sys.acl" : "u:173503:rwx,u:179814:rx!d,u:187895:rx,u:193339:rwx+d", + "xattr.sys.allow.oc.sync" : "1", + "xattr.sys.eos.btime" : "1677602740.101786072", + "xattr.sys.forced.atomic" : "1", + "xattr.sys.forced.blocksize" : "4k", + "xattr.sys.forced.checksum" : "adler", + "xattr.sys.forced.layout" : "replica", + "xattr.sys.forced.nstripes" : "2", + "xattr.sys.forced.space" : "default", + "xattr.sys.mask" : "700", + "xattr.sys.mtime.propagation" : "1", + "xattr.sys.owner.auth" : "*", + "xattr.sys.recycle" : "/eos/homedev/proc/recycle/", + "xattr.sys.reva.lwshare.111857693350341032552@google" : "rwx+d", + "xattr.sys.versioning" : "10" +}, +{ + "atime" : "1759302288.506567026", + "ctime" : "1759302288.506566677", + "fid" : "109656440", + "flags" : "644", + "gid" : "2763", + "layout_id" : "1048850", + "link_name" : "", + "locations" : "421,417", + "mtime" : "1759302288.506843440", + "name" : "file.md", + "path" : "/eos/user/j/jgeens/TestFolderSharing2/MyFolder/file.md", + "pid" : "4885561", + "size" : "0", + "stime" : "0.0", + "uid" : "173503", + "unlink_locations" : "", + "xattr.sys.eos.btime" : "1759302288.506566677", + "xattr.sys.fs.tracking" : "+421+417", + "xattr.sys.fusex.state" : "", + "xattr.sys.utrace" : "eb17bcc0-9e94-11f0-bbf7-fa163e2e9155", + "xattr.sys.vtrace" : "[Wed Oct 1 09:04:48 2025] uid:173503[jgeens] gid:2763[it] tident:http name:jgeens dn: prot:https app:http/reva_write host:[2001:1458:d00:16::33d] domain:localdomain geo: sudo:0 trace: onbehalf:", + "xs" : "00000001" +}, +{ + "atime" : "1759302321.262105446", + "ctime" : "1759302321.262105126", + "fid" : "109656472", + "flags" : "644", + "gid" : "2763", + "layout_id" : "1048850", + "link_name" : "", + "locations" : "417,411", + "mtime" : "1759302321.262358208", + "name" : "file.txt", + "path" : "/eos/user/j/jgeens/TestFolderSharing2/MyFolder/file.txt", + "pid" : "4885561", + "size" : "18", + "stime" : "0.0", + "uid" : "173503", + "unlink_locations" : "", + "xattr.sys.acl" : "u:187895:rx", + "xattr.sys.eos.btime" : "1759302321.262105126", + "xattr.sys.fs.tracking" : "+417+411", + "xattr.sys.fusex.state" : "", + "xattr.sys.utrace" : "fe9dcaf0-9e94-11f0-bbf7-fa163e2e9155", + "xattr.sys.vtrace" : "[Wed Oct 1 09:05:21 2025] uid:173503[jgeens] gid:2763[it] tident:http name:jgeens dn: prot:https app:http/reva_write host:[2001:1458:d00:16::33d] domain:localdomain geo: sudo:0 trace: onbehalf:", + "xs" : "3f8f06d6" +}, +{ + "cid" : "6747857", + "ctime" : "1782200201.890800654", + "flags" : "0", + "gid" : "2763", + "mode" : "40750", + "mtime" : "1782200201.890428151", + "name" : ".sys.v#.file.md", + "parent_id" : "4885561", + "path" : "/eos/user/j/jgeens/TestFolderSharing2/MyFolder/.sys.v#.file.md/", + "stime" : "0.0", + "tree_size" : "0", + "uid" : "173503", + "xattr.sys.eos.btime" : "1782200201.890428151" +}, +{ + "cid" : "6747856", + "ctime" : "1782200201.887645217", + "flags" : "0", + "gid" : "2763", + "mode" : "40750", + "mtime" : "1782200201.887181324", + "name" : ".sys.v#.file.txt", + "parent_id" : "4885561", + "path" : "/eos/user/j/jgeens/TestFolderSharing2/MyFolder/.sys.v#.file.txt/", + "stime" : "0.0", + "tree_size" : "0", + "uid" : "173503", + "xattr.sys.eos.btime" : "1782200201.887181324" +}, +{ + "cid" : "4771584", + "ctime" : "1782811947.489397254", + "flags" : "0", + "gid" : "2763", + "mode" : "40750", + "mtime" : "1782200202.217032434", + "name" : "SubFolder", + "parent_id" : "4771583", + "path" : "/eos/user/j/jgeens/TestFolderSharing2/SubFolder/", + "stime" : "0.0", + "tree_size" : "48", + "uid" : "173503", + "xattr.sys.acl" : "u:173503:rwx,u:179814:rx!d,u:187895:rx,u:193339:rwx+d", + "xattr.sys.allow.oc.sync" : "1", + "xattr.sys.eos.btime" : "1677602740.101786072", + "xattr.sys.forced.atomic" : "1", + "xattr.sys.forced.blocksize" : "4k", + "xattr.sys.forced.checksum" : "adler", + "xattr.sys.forced.layout" : "replica", + "xattr.sys.forced.nstripes" : "2", + "xattr.sys.forced.space" : "default", + "xattr.sys.mask" : "700", + "xattr.sys.mtime.propagation" : "1", + "xattr.sys.owner.auth" : "*", + "xattr.sys.recycle" : "/eos/homedev/proc/recycle/", + "xattr.sys.reva.lwshare.111857693350341032552@google" : "rwx+d", + "xattr.sys.versioning" : "10" +}, +{ + "atime" : "1759306624.5507673", + "ctime" : "1759306624.5507175", + "fid" : "109658073", + "flags" : "644", + "gid" : "2763", + "layout_id" : "1048850", + "link_name" : "", + "locations" : "415,413", + "mtime" : "1759306624.5761305", + "name" : "File (1).txt", + "path" : "/eos/user/j/jgeens/TestFolderSharing2/SubFolder/File (1).txt", + "pid" : "4771584", + "size" : "24", + "stime" : "0.0", + "uid" : "173503", + "unlink_locations" : "", + "xattr.sys.eos.btime" : "1759306624.5507175", + "xattr.sys.fs.tracking" : "+415+413", + "xattr.sys.fusex.state" : "", + "xattr.sys.utrace" : "03405a78-9e9f-11f0-8211-fa163e2e9155", + "xattr.sys.vtrace" : "[Wed Oct 1 10:17:04 2025] uid:173503[jgeens] gid:2763[it] tident:http name:jgeens dn: prot:https app:http/reva_write host:[2001:1458:d00:16::33d] domain:localdomain geo: sudo:0 trace: onbehalf:", + "xs" : "6bd208c6" +}, +{ + "atime" : "1759303965.144520762", + "ctime" : "1759303965.144520273", + "fid" : "109657545", + "flags" : "644", + "gid" : "2763", + "layout_id" : "1048850", + "link_name" : "", + "locations" : "421,417", + "mtime" : "1759303965.144813326", + "name" : "File.txt", + "path" : "/eos/user/j/jgeens/TestFolderSharing2/SubFolder/File.txt", + "pid" : "4771584", + "size" : "24", + "stime" : "0.0", + "uid" : "173503", + "unlink_locations" : "", + "xattr.sys.acl" : "u:187895:rx", + "xattr.sys.eos.btime" : "1759303965.144520273", + "xattr.sys.fs.tracking" : "+421+417", + "xattr.sys.fusex.state" : "", + "xattr.sys.utrace" : "d2724cb8-9e98-11f0-9e5d-fa163e2e9155", + "xattr.sys.vtrace" : "[Wed Oct 1 09:32:45 2025] uid:173503[jgeens] gid:2763[it] tident:http name:jgeens dn: prot:https app:http/reva_write host:[2001:1458:d00:16::33d] domain:localdomain geo: sudo:0 trace: onbehalf:", + "xs" : "6bd208c6" +}, +{ + "cid" : "6747858", + "ctime" : "1782200202.215053514", + "flags" : "0", + "gid" : "2763", + "mode" : "40750", + "mtime" : "1782200202.214664731", + "name" : ".sys.v#.File (1).txt", + "parent_id" : "4771584", + "path" : "/eos/user/j/jgeens/TestFolderSharing2/SubFolder/.sys.v#.File (1).txt/", + "stime" : "0.0", + "tree_size" : "0", + "uid" : "173503", + "xattr.sys.eos.btime" : "1782200202.214664731" +}, +{ + "cid" : "6747859", + "ctime" : "1782200202.217268325", + "flags" : "0", + "gid" : "2763", + "mode" : "40750", + "mtime" : "1782200202.217032434", + "name" : ".sys.v#.File.txt", + "parent_id" : "4771584", + "path" : "/eos/user/j/jgeens/TestFolderSharing2/SubFolder/.sys.v#.File.txt/", + "stime" : "0.0", + "tree_size" : "0", + "uid" : "173503", + "xattr.sys.eos.btime" : "1782200202.217032434" +}, +{ + "cid" : "6694464", + "ctime" : "1783416699.605421959", + "flags" : "0", + "gid" : "2763", + "mode" : "40750", + "mtime" : "1780039780.501298480", + "name" : "Tests", + "parent_id" : "4251882", + "path" : "/eos/user/j/jgeens/Tests/", + "stime" : "1780039786.305899248", + "tree_size" : "0", + "uid" : "173503", + "xattr.sys.acl" : "u:173503:rwx,u:187895:rx,egroup:cernbox-service-ops:rx!d", + "xattr.sys.allow.oc.sync" : "1", + "xattr.sys.eos.btime" : "1677602740.101786072", + "xattr.sys.forced.atomic" : "1", + "xattr.sys.forced.blocksize" : "4k", + "xattr.sys.forced.checksum" : "adler", + "xattr.sys.forced.layout" : "replica", + "xattr.sys.forced.nstripes" : "2", + "xattr.sys.forced.space" : "default", + "xattr.sys.mask" : "700", + "xattr.sys.mtime.propagation" : "1", + "xattr.sys.owner.auth" : "*", + "xattr.sys.recycle" : "/eos/homedev/proc/recycle/", + "xattr.sys.versioning" : "10", + "xattr.user.http://owncloud.org/ns/favorite" : "u:jgeens=1,u:tstcbsa9=1", + "xattr.user.reva.labels.jgeens.favorite" : "1", + "xattr.user.reva.labels.tstcbsa9.favorite" : "1" +}, +{ + "cid" : "6694465", + "ctime" : "1783416699.605629239", + "flags" : "0", + "gid" : "2763", + "mode" : "40750", + "mtime" : "1780039786.305899248", + "name" : "VersionAttrs", + "parent_id" : "6694464", + "path" : "/eos/user/j/jgeens/Tests/VersionAttrs/", + "stime" : "0.0", + "tree_size" : "0", + "uid" : "173503", + "xattr.sys.acl" : "u:173503:rwx,u:187895:rx,egroup:cernbox-service-ops:rx!d", + "xattr.sys.allow.oc.sync" : "1", + "xattr.sys.eos.btime" : "1677602740.101786072", + "xattr.sys.forced.atomic" : "1", + "xattr.sys.forced.blocksize" : "4k", + "xattr.sys.forced.checksum" : "adler", + "xattr.sys.forced.layout" : "replica", + "xattr.sys.forced.nstripes" : "2", + "xattr.sys.forced.space" : "default", + "xattr.sys.mask" : "700", + "xattr.sys.mtime.propagation" : "1", + "xattr.sys.owner.auth" : "*", + "xattr.sys.recycle" : "/eos/homedev/proc/recycle/", + "xattr.sys.versioning" : "10", + "xattr.user.http://owncloud.org/ns/favorite" : "u:jgeens=1,u:tstcbsa9=1", + "xattr.user.reva.labels.jgeens.favorite" : "1", + "xattr.user.reva.labels.tstcbsa9.favorite" : "1" +}, +{ + "atime" : "1780039786.274429484", + "ctime" : "1782200234.951842827", + "fid" : "117950592", + "flags" : "644", + "gid" : "2763", + "layout_id" : "1048850", + "link_name" : "", + "locations" : "412,417", + "mtime" : "1780039786.274751946", + "name" : "favorite.txt", + "path" : "/eos/user/j/jgeens/Tests/VersionAttrs/favorite.txt", + "pid" : "6694465", + "size" : "0", + "stime" : "0.0", + "uid" : "173503", + "unlink_locations" : "", + "xattr.sys.acl" : "u:187895:rx", + "xattr.sys.eos.btime" : "1780039786.274428914", + "xattr.sys.fs.tracking" : "+412+417", + "xattr.sys.fusex.state" : "", + "xattr.sys.reva.lwshare.jesse.geens@proton.me" : "rx!d", + "xattr.sys.utrace" : "2af95fee-5b30-11f1-9709-fa163e35f83a", + "xattr.sys.vtrace" : "[Fri May 29 09:29:46 2026] uid:173503[jgeens] gid:2763[it] tident:jgeens.1:1@[2001:1458:d00:13::100:2be] name:jgeens dn: prot:https app:http/reva_write host:cbox-ocisdev-jesse.cern.ch domain:cern.ch geo: sudo:0 trace: onbehalf:", + "xs" : "00000001" +}, +{ + "cid" : "6694466", + "ctime" : "1780059160.349071567", + "flags" : "0", + "gid" : "2763", + "mode" : "40750", + "mtime" : "1780039786.305899248", + "name" : ".sys.v#.favorite.txt", + "parent_id" : "6694465", + "path" : "/eos/user/j/jgeens/Tests/VersionAttrs/.sys.v#.favorite.txt/", + "stime" : "0.0", + "tree_size" : "0", + "uid" : "173503", + "xattr.sys.acl" : "u:187895:rx", + "xattr.sys.eos.btime" : "1780039786.305899248", + "xattr.sys.reva.lwshare.jesse.geens@proton.me" : "rx!d", + "xattr.user.reva.labels.jgeens.favorite" : "1" +}, +{ + "cid" : "6204654", + "ctime" : "1778079884.584675127", + "flags" : "0", + "gid" : "2763", + "mode" : "40750", + "mtime" : "1782200200.881427030", + "name" : "elvin (1)", + "parent_id" : "4251882", + "path" : "/eos/user/j/jgeens/elvin (1)/", + "stime" : "0.0", + "tree_size" : "0", + "uid" : "173503", + "xattr.sys.acl" : "u:173503:rwx", + "xattr.sys.allow.oc.sync" : "1", + "xattr.sys.eos.btime" : "1677602740.101786072", + "xattr.sys.forced.atomic" : "1", + "xattr.sys.forced.blocksize" : "4k", + "xattr.sys.forced.checksum" : "adler", + "xattr.sys.forced.layout" : "replica", + "xattr.sys.forced.nstripes" : "2", + "xattr.sys.forced.space" : "default", + "xattr.sys.mask" : "700", + "xattr.sys.mtime.propagation" : "1", + "xattr.sys.owner.auth" : "*", + "xattr.sys.recycle" : "/eos/homedev/proc/recycle/", + "xattr.sys.versioning" : "10", + "xattr.user.http://owncloud.org/ns/favorite" : "u:jgeens=1" +}, +{ + "atime" : "1764338422.561605224", + "ctime" : "1764338422.561604298", + "fid" : "111852700", + "flags" : "644", + "gid" : "2763", + "layout_id" : "1048850", + "link_name" : "", + "locations" : "409,416", + "mtime" : "1764338422.562044025", + "name" : "file.txt", + "path" : "/eos/user/j/jgeens/elvin (1)/file.txt", + "pid" : "6204654", + "size" : "0", + "stime" : "0.0", + "uid" : "173503", + "unlink_locations" : "", + "xattr.sys.eos.btime" : "1764338422.561604298", + "xattr.sys.fs.tracking" : "+409+416", + "xattr.sys.fusex.state" : "", + "xattr.sys.utrace" : "94e87ca8-cc62-11f0-9dd8-fa163e2e9155", + "xattr.sys.vtrace" : "[Fri Nov 28 15:00:22 2025] uid:173503[jgeens] gid:2763[it] tident:jgeens.1:1@[2001:1458:d00:17::100:178] name:jgeens dn: prot:https app:http/reva_write host:cbox-ocisdev-rodrigo-old.cern.ch domain:cern.ch geo: sudo:0 trace: onbehalf:", + "xs" : "00000001" +}, +{ + "atime" : "1764338422.496040676", + "ctime" : "1764338422.496039763", + "fid" : "111852699", + "flags" : "644", + "gid" : "2763", + "layout_id" : "1048850", + "link_name" : "", + "locations" : "412,417", + "mtime" : "1764338422.496602431", + "name" : "file1.dat", + "path" : "/eos/user/j/jgeens/elvin (1)/file1.dat", + "pid" : "6204654", + "size" : "0", + "stime" : "0.0", + "uid" : "173503", + "unlink_locations" : "", + "xattr.sys.eos.btime" : "1764338422.496039763", + "xattr.sys.fs.tracking" : "+412+417", + "xattr.sys.fusex.state" : "", + "xattr.sys.utrace" : "94de7a0a-cc62-11f0-adb3-fa163e2e9155", + "xattr.sys.vtrace" : "[Fri Nov 28 15:00:22 2025] uid:173503[jgeens] gid:2763[it] tident:jgeens.1:1@[2001:1458:d00:17::100:178] name:jgeens dn: prot:https app:http/reva_write host:cbox-ocisdev-rodrigo-old.cern.ch domain:cern.ch geo: sudo:0 trace: onbehalf:", + "xs" : "00000001" +}, +{ + "atime" : "1764338422.645022173", + "ctime" : "1764338422.645021215", + "fid" : "111852701", + "flags" : "644", + "gid" : "2763", + "layout_id" : "1048850", + "link_name" : "", + "locations" : "412,417", + "mtime" : "1764338422.645565137", + "name" : "file2.dat", + "path" : "/eos/user/j/jgeens/elvin (1)/file2.dat", + "pid" : "6204654", + "size" : "0", + "stime" : "0.0", + "uid" : "173503", + "unlink_locations" : "", + "xattr.sys.eos.btime" : "1764338422.645021215", + "xattr.sys.fs.tracking" : "+412+417", + "xattr.sys.fusex.state" : "", + "xattr.sys.utrace" : "94f5352e-cc62-11f0-9b2a-fa163e2e9155", + "xattr.sys.vtrace" : "[Fri Nov 28 15:00:22 2025] uid:173503[jgeens] gid:2763[it] tident:jgeens.1:1@[2001:1458:d00:17::100:178] name:jgeens dn: prot:https app:http/reva_write host:cbox-ocisdev-rodrigo-old.cern.ch domain:cern.ch geo: sudo:0 trace: onbehalf:", + "xs" : "00000001" +}, +{ + "cid" : "6747853", + "ctime" : "1782200200.881648951", + "flags" : "0", + "gid" : "2763", + "mode" : "40750", + "mtime" : "1782200200.881427030", + "name" : ".sys.v#.file.txt", + "parent_id" : "6204654", + "path" : "/eos/user/j/jgeens/elvin (1)/.sys.v#.file.txt/", + "stime" : "0.0", + "tree_size" : "0", + "uid" : "173503", + "xattr.sys.eos.btime" : "1782200200.881427030" +}, +{ + "cid" : "6747852", + "ctime" : "1782200200.879852262", + "flags" : "0", + "gid" : "2763", + "mode" : "40750", + "mtime" : "1782200200.879579701", + "name" : ".sys.v#.file1.dat", + "parent_id" : "6204654", + "path" : "/eos/user/j/jgeens/elvin (1)/.sys.v#.file1.dat/", + "stime" : "0.0", + "tree_size" : "0", + "uid" : "173503", + "xattr.sys.eos.btime" : "1782200200.879579701" +}, +{ + "cid" : "6747851", + "ctime" : "1782200200.877720881", + "flags" : "0", + "gid" : "2763", + "mode" : "40750", + "mtime" : "1782200200.877277689", + "name" : ".sys.v#.file2.dat", + "parent_id" : "6204654", + "path" : "/eos/user/j/jgeens/elvin (1)/.sys.v#.file2.dat/", + "stime" : "0.0", + "tree_size" : "0", + "uid" : "173503", + "xattr.sys.eos.btime" : "1782200200.877277689" +}, +{ + "cid" : "5707355", + "ctime" : "1778157236.83383319", + "flags" : "0", + "gid" : "2763", + "mode" : "40777", + "mtime" : "1782200202.723491099", + "name" : "elvin2", + "parent_id" : "4251882", + "path" : "/eos/user/j/jgeens/elvin2/", + "stime" : "0.0", + "tree_size" : "0", + "uid" : "173503", + "xattr.sys.acl" : "u:173503:rwx", + "xattr.sys.allow.oc.sync" : "1", + "xattr.sys.eos.btime" : "1677602740.101786072", + "xattr.sys.forced.atomic" : "1", + "xattr.sys.forced.blocksize" : "4k", + "xattr.sys.forced.checksum" : "adler", + "xattr.sys.forced.layout" : "replica", + "xattr.sys.forced.nstripes" : "2", + "xattr.sys.forced.space" : "default", + "xattr.sys.mask" : "700", + "xattr.sys.mtime.propagation" : "1", + "xattr.sys.owner.auth" : "*", + "xattr.sys.recycle" : "/eos/homedev/proc/recycle/", + "xattr.sys.versioning" : "10", + "xattr.user.reva.labels.jgeens.favorite" : "1" +}, +{ + "atime" : "1755868439.61995341", + "ctime" : "1755868439.61994647", + "fid" : "108344245", + "flags" : "644", + "gid" : "2763", + "layout_id" : "1048850", + "link_name" : "", + "locations" : "421,411", + "mtime" : "1755868439.62263988", + "name" : "file.txt", + "path" : "/eos/user/j/jgeens/elvin2/file.txt", + "pid" : "5707355", + "size" : "0", + "stime" : "0.0", + "uid" : "173503", + "unlink_locations" : "", + "xattr.sys.eos.btime" : "1755868439.61994647", + "xattr.sys.fs.tracking" : "+421+411", + "xattr.sys.fusex.state" : "", + "xattr.sys.utrace" : "dd549d9c-7f59-11f0-8f48-fa163e2e9155", + "xattr.sys.vtrace" : "[Fri Aug 22 15:13:59 2025] uid:173503[jgeens] gid:2763[it] tident:jgeens.1:1@[2001:1458:d00:16::33d] name:jgeens dn: prot:https app:http host:cbox-ocisdev-diogo.cern.ch domain:cern.ch geo: sudo:0 trace: onbehalf:", + "xs" : "00000001" +}, +{ + "atime" : "1749722251.359652239", + "ctime" : "1749722251.359750681", + "fid" : "105786712", + "flags" : "0", + "gid" : "2763", + "layout_id" : "0", + "link_name" : "", + "locations" : "", + "mtime" : "1749722251.359750681", + "name" : "file1.dat", + "path" : "/eos/user/j/jgeens/elvin2/file1.dat", + "pid" : "5707355", + "size" : "0", + "stime" : "0.0", + "uid" : "173503", + "unlink_locations" : "", + "xattr.sys.eos.btime" : "1749722251.359750681", + "xs" : "" +}, +{ + "atime" : "1749722355.464204300", + "ctime" : "1749722355.464311389", + "fid" : "105786716", + "flags" : "0", + "gid" : "2763", + "layout_id" : "0", + "link_name" : "", + "locations" : "", + "mtime" : "1749722355.464311389", + "name" : "file2.dat", + "path" : "/eos/user/j/jgeens/elvin2/file2.dat", + "pid" : "5707355", + "size" : "0", + "stime" : "0.0", + "uid" : "173503", + "unlink_locations" : "", + "xattr.sys.eos.btime" : "1749722355.464311389", + "xs" : "" +}, +{ + "cid" : "6747862", + "ctime" : "1782200202.723841730", + "flags" : "0", + "gid" : "2763", + "mode" : "40750", + "mtime" : "1782200202.723491099", + "name" : ".sys.v#.file.txt", + "parent_id" : "5707355", + "path" : "/eos/user/j/jgeens/elvin2/.sys.v#.file.txt/", + "stime" : "0.0", + "tree_size" : "0", + "uid" : "173503", + "xattr.sys.eos.btime" : "1782200202.723491099" +}, +{ + "cid" : "6747861", + "ctime" : "1782200202.721589399", + "flags" : "0", + "gid" : "2763", + "mode" : "40750", + "mtime" : "1782200202.721383139", + "name" : ".sys.v#.file1.dat", + "parent_id" : "5707355", + "path" : "/eos/user/j/jgeens/elvin2/.sys.v#.file1.dat/", + "stime" : "0.0", + "tree_size" : "0", + "uid" : "173503", + "xattr.sys.eos.btime" : "1782200202.721383139" +}, +{ + "cid" : "6747860", + "ctime" : "1782200202.719396547", + "flags" : "0", + "gid" : "2763", + "mode" : "40750", + "mtime" : "1782200202.719079506", + "name" : ".sys.v#.file2.dat", + "parent_id" : "5707355", + "path" : "/eos/user/j/jgeens/elvin2/.sys.v#.file2.dat/", + "stime" : "0.0", + "tree_size" : "0", + "uid" : "173503", + "xattr.sys.eos.btime" : "1782200202.719079506" +}, +{ + "cid" : "6204659", + "ctime" : "1764338493.559530019", + "flags" : "0", + "gid" : "2763", + "mode" : "40750", + "mtime" : "1782200203.586456064", + "name" : "elvincp", + "parent_id" : "4251882", + "path" : "/eos/user/j/jgeens/elvincp/", + "stime" : "0.0", + "tree_size" : "0", + "uid" : "173503", + "xattr.sys.acl" : "u:173503:rwx", + "xattr.sys.allow.oc.sync" : "1", + "xattr.sys.eos.btime" : "1677602740.101786072", + "xattr.sys.forced.atomic" : "1", + "xattr.sys.forced.blocksize" : "4k", + "xattr.sys.forced.checksum" : "adler", + "xattr.sys.forced.layout" : "replica", + "xattr.sys.forced.nstripes" : "2", + "xattr.sys.forced.space" : "default", + "xattr.sys.mask" : "700", + "xattr.sys.mtime.propagation" : "1", + "xattr.sys.owner.auth" : "*", + "xattr.sys.recycle" : "/eos/homedev/proc/recycle/", + "xattr.sys.versioning" : "10", + "xattr.user.http://owncloud.org/ns/favorite" : "u:jgeens=1" +}, +{ + "atime" : "1764338516.261349127", + "ctime" : "1764338516.261348334", + "fid" : "111852706", + "flags" : "644", + "gid" : "2763", + "layout_id" : "1048850", + "link_name" : "", + "locations" : "413,409", + "mtime" : "1764338516.261905009", + "name" : "file.txt", + "path" : "/eos/user/j/jgeens/elvincp/file.txt", + "pid" : "6204659", + "size" : "0", + "stime" : "0.0", + "uid" : "173503", + "unlink_locations" : "", + "xattr.sys.eos.btime" : "1764338516.261348334", + "xattr.sys.fs.tracking" : "+413+409", + "xattr.sys.fusex.state" : "", + "xattr.sys.utrace" : "ccc1ebfa-cc62-11f0-8ded-fa163e2e9155", + "xattr.sys.vtrace" : "[Fri Nov 28 15:01:56 2025] uid:173503[jgeens] gid:2763[it] tident:jgeens.1:1@[2001:1458:d00:13::100:2be] name:jgeens dn: prot:https app:http/reva_write host:cbox-ocisdev-jesse.cern.ch domain:cern.ch geo: sudo:0 trace: onbehalf:", + "xs" : "00000001" +}, +{ + "atime" : "1764338515.994999719", + "ctime" : "1764338515.994998857", + "fid" : "111852705", + "flags" : "644", + "gid" : "2763", + "layout_id" : "1048850", + "link_name" : "", + "locations" : "412,417", + "mtime" : "1764338515.995547854", + "name" : "file1.dat", + "path" : "/eos/user/j/jgeens/elvincp/file1.dat", + "pid" : "6204659", + "size" : "0", + "stime" : "0.0", + "uid" : "173503", + "unlink_locations" : "", + "xattr.sys.eos.btime" : "1764338515.994998857", + "xattr.sys.fs.tracking" : "+412+417", + "xattr.sys.fusex.state" : "", + "xattr.sys.utrace" : "cc994632-cc62-11f0-a12f-fa163e2e9155", + "xattr.sys.vtrace" : "[Fri Nov 28 15:01:55 2025] uid:173503[jgeens] gid:2763[it] tident:jgeens.1:1@[2001:1458:d00:13::100:2be] name:jgeens dn: prot:https app:http/reva_write host:cbox-ocisdev-jesse.cern.ch domain:cern.ch geo: sudo:0 trace: onbehalf:", + "xs" : "00000001" +}, +{ + "atime" : "1764338505.799863195", + "ctime" : "1764338505.799862450", + "fid" : "111852704", + "flags" : "644", + "gid" : "2763", + "layout_id" : "1048850", + "link_name" : "", + "locations" : "415,409", + "mtime" : "1764338505.800673167", + "name" : "file2.dat", + "path" : "/eos/user/j/jgeens/elvincp/file2.dat", + "pid" : "6204659", + "size" : "0", + "stime" : "0.0", + "uid" : "173503", + "unlink_locations" : "", + "xattr.sys.eos.btime" : "1764338505.799862450", + "xattr.sys.fs.tracking" : "+415+409", + "xattr.sys.fusex.state" : "", + "xattr.sys.utrace" : "c6859d40-cc62-11f0-9cee-fa163e2e9155", + "xattr.sys.vtrace" : "[Fri Nov 28 15:01:45 2025] uid:173503[jgeens] gid:2763[it] tident:jgeens.1:1@[2001:1458:d00:13::100:2be] name:jgeens dn: prot:https app:http/reva_write host:cbox-ocisdev-jesse.cern.ch domain:cern.ch geo: sudo:0 trace: onbehalf:", + "xs" : "00000001" +}, +{ + "cid" : "6747866", + "ctime" : "1782200203.586778776", + "flags" : "0", + "gid" : "2763", + "mode" : "40750", + "mtime" : "1782200203.586456064", + "name" : ".sys.v#.file.txt", + "parent_id" : "6204659", + "path" : "/eos/user/j/jgeens/elvincp/.sys.v#.file.txt/", + "stime" : "0.0", + "tree_size" : "0", + "uid" : "173503", + "xattr.sys.eos.btime" : "1782200203.586456064" +}, +{ + "cid" : "6747865", + "ctime" : "1782200203.584351583", + "flags" : "0", + "gid" : "2763", + "mode" : "40750", + "mtime" : "1782200203.584044272", + "name" : ".sys.v#.file1.dat", + "parent_id" : "6204659", + "path" : "/eos/user/j/jgeens/elvincp/.sys.v#.file1.dat/", + "stime" : "0.0", + "tree_size" : "0", + "uid" : "173503", + "xattr.sys.eos.btime" : "1782200203.584044272" +}, +{ + "cid" : "6747864", + "ctime" : "1782200203.582021901", + "flags" : "0", + "gid" : "2763", + "mode" : "40750", + "mtime" : "1782200203.581559129", + "name" : ".sys.v#.file2.dat", + "parent_id" : "6204659", + "path" : "/eos/user/j/jgeens/elvincp/.sys.v#.file2.dat/", + "stime" : "0.0", + "tree_size" : "0", + "uid" : "173503", + "xattr.sys.eos.btime" : "1782200203.581559129" +}, +{ + "cid" : "4929672", + "ctime" : "1783327961.368531686", + "flags" : "0", + "gid" : "2763", + "mode" : "40750", + "mtime" : "1783327540.643244272", + "name" : "folder", + "parent_id" : "4251882", + "path" : "/eos/user/j/jgeens/folder/", + "stime" : "0.0", + "tree_size" : "0", + "uid" : "173503", + "xattr.sys.acl" : "u:173503:rwx,u:187895:rx,u:178352:rx!d", + "xattr.sys.allow.oc.sync" : "1", + "xattr.sys.eos.btime" : "1677602740.101786072", + "xattr.sys.forced.atomic" : "1", + "xattr.sys.forced.blocksize" : "4k", + "xattr.sys.forced.checksum" : "adler", + "xattr.sys.forced.layout" : "replica", + "xattr.sys.forced.nstripes" : "2", + "xattr.sys.forced.space" : "default", + "xattr.sys.mask" : "700", + "xattr.sys.mtime.propagation" : "1", + "xattr.sys.owner.auth" : "*", + "xattr.sys.recycle" : "/eos/homedev/proc/recycle/", + "xattr.sys.versioning" : "10" +}, +{ + "cid" : "6781895", + "ctime" : "1783328041.79884613", + "flags" : "0", + "gid" : "2763", + "mode" : "40750", + "mtime" : "1783327540.643244272", + "name" : "subfolder", + "parent_id" : "4929672", + "path" : "/eos/user/j/jgeens/folder/subfolder/", + "stime" : "0.0", + "tree_size" : "0", + "uid" : "173503", + "xattr.sys.acl" : "u:173503:rwx,u:187895:rx,u:178352:rwx+d", + "xattr.sys.allow.oc.sync" : "1", + "xattr.sys.eos.btime" : "1677602740.101786072", + "xattr.sys.forced.atomic" : "1", + "xattr.sys.forced.blocksize" : "4k", + "xattr.sys.forced.checksum" : "adler", + "xattr.sys.forced.layout" : "replica", + "xattr.sys.forced.nstripes" : "2", + "xattr.sys.forced.space" : "default", + "xattr.sys.mask" : "700", + "xattr.sys.mtime.propagation" : "1", + "xattr.sys.owner.auth" : "*", + "xattr.sys.recycle" : "/eos/homedev/proc/recycle/", + "xattr.sys.versioning" : "10" +}, +{ + "cid" : "6086386", + "ctime" : "1778836684.625645020", + "flags" : "0", + "gid" : "2763", + "mode" : "40750", + "mtime" : "1774944651.528699807", + "name" : "mypictures", + "parent_id" : "4251882", + "path" : "/eos/user/j/jgeens/mypictures/", + "stime" : "0.0", + "tree_size" : "89684824", + "uid" : "173503", + "xattr.sys.acl" : "u:58679:rxt,u:187895:rx,u:173503:rwx,u:193339:rwx+d", + "xattr.sys.allow.oc.sync" : "1", + "xattr.sys.eos.btime" : "1677602740.101786072", + "xattr.sys.forced.atomic" : "1", + "xattr.sys.forced.blocksize" : "4k", + "xattr.sys.forced.checksum" : "adler", + "xattr.sys.forced.layout" : "replica", + "xattr.sys.forced.nstripes" : "2", + "xattr.sys.forced.space" : "default", + "xattr.sys.mask" : "700", + "xattr.sys.mtime.propagation" : "1", + "xattr.sys.owner.auth" : "*", + "xattr.sys.recycle" : "/eos/homedev/proc/recycle/", + "xattr.sys.reva.lwshare.guest:jesse.geens@proton.me" : "rwx+d", + "xattr.sys.versioning" : "10", + "xattr.user.http://owncloud.org/ns/favorite" : "u:jgeens=1", + "xattr.user.reva.labels.jgeens.favorite" : "1" +}, +{ + "atime" : "1761135602.867615110", + "ctime" : "1761135602.867614343", + "fid" : "110345601", + "flags" : "644", + "gid" : "2763", + "layout_id" : "1048850", + "link_name" : "", + "locations" : "418,414", + "mtime" : "1761135602.867936704", + "name" : "000237750002.jpg", + "path" : "/eos/user/j/jgeens/mypictures/000237750002.jpg", + "pid" : "6086386", + "size" : "4587714", + "stime" : "0.0", + "uid" : "173503", + "unlink_locations" : "", + "xattr.sys.eos.btime" : "1761135602.867614343", + "xattr.sys.fs.tracking" : "+409+416/409/416", + "xattr.sys.fusex.state" : "", + "xattr.sys.utrace" : "6f9b5094-af41-11f0-9860-fa163e2e9155", + "xattr.sys.vtrace" : "[Wed Oct 22 14:20:02 2025] uid:173503[jgeens] gid:2763[it] tident:jgeens.1:1@[2001:1458:d00:13::100:2be] name:jgeens dn: prot:https app:http/reva_write host:cbox-ocisdev-jesse.cern.ch domain:cern.ch geo: sudo:0 trace: onbehalf:", + "xs" : "b9eb1626" +}, +{ + "atime" : "1761135602.759841956", + "ctime" : "1761135602.759841155", + "fid" : "110345600", + "flags" : "644", + "gid" : "2763", + "layout_id" : "1048850", + "link_name" : "", + "locations" : "412,421", + "mtime" : "1761135602.760124477", + "name" : "000237750003.jpg", + "path" : "/eos/user/j/jgeens/mypictures/000237750003.jpg", + "pid" : "6086386", + "size" : "5089619", + "stime" : "0.0", + "uid" : "173503", + "unlink_locations" : "", + "xattr.sys.eos.btime" : "1761135602.759841155", + "xattr.sys.fs.tracking" : "+412+421", + "xattr.sys.fusex.state" : "", + "xattr.sys.utrace" : "6f8ae02e-af41-11f0-9505-fa163e2e9155", + "xattr.sys.vtrace" : "[Wed Oct 22 14:20:02 2025] uid:173503[jgeens] gid:2763[it] tident:jgeens.1:1@[2001:1458:d00:13::100:2be] name:jgeens dn: prot:https app:http/reva_write host:cbox-ocisdev-jesse.cern.ch domain:cern.ch geo: sudo:0 trace: onbehalf:", + "xs" : "d6d31046" +}, +{ + "atime" : "1761135602.476108027", + "ctime" : "1761135602.476107428", + "fid" : "110345599", + "flags" : "644", + "gid" : "2763", + "layout_id" : "1048850", + "link_name" : "", + "locations" : "410,419", + "mtime" : "1761135602.476361285", + "name" : "000237750004.jpg", + "path" : "/eos/user/j/jgeens/mypictures/000237750004.jpg", + "pid" : "6086386", + "size" : "4540299", + "stime" : "0.0", + "uid" : "173503", + "unlink_locations" : "", + "xattr.sys.eos.btime" : "1761135602.476107428", + "xattr.sys.fs.tracking" : "+415+413/413/415", + "xattr.sys.fusex.state" : "", + "xattr.sys.utrace" : "6f5f945a-af41-11f0-b484-fa163e2e9155", + "xattr.sys.vtrace" : "[Wed Oct 22 14:20:02 2025] uid:173503[jgeens] gid:2763[it] tident:jgeens.1:1@[2001:1458:d00:13::100:2be] name:jgeens dn: prot:https app:http/reva_write host:cbox-ocisdev-jesse.cern.ch domain:cern.ch geo: sudo:0 trace: onbehalf:", + "xs" : "3ecd42ff" +}, +{ + "atime" : "1761135602.420678073", + "ctime" : "1761135602.420677517", + "fid" : "110345597", + "flags" : "644", + "gid" : "2763", + "layout_id" : "1048850", + "link_name" : "", + "locations" : "417,411", + "mtime" : "1761135602.420928503", + "name" : "000237750005.jpg", + "path" : "/eos/user/j/jgeens/mypictures/000237750005.jpg", + "pid" : "6086386", + "size" : "5105925", + "stime" : "0.0", + "uid" : "173503", + "unlink_locations" : "", + "xattr.sys.eos.btime" : "1761135602.420677517", + "xattr.sys.fs.tracking" : "+417+411", + "xattr.sys.fusex.state" : "", + "xattr.sys.utrace" : "6f571f96-af41-11f0-829d-fa163e2e9155", + "xattr.sys.vtrace" : "[Wed Oct 22 14:20:02 2025] uid:173503[jgeens] gid:2763[it] tident:jgeens.1:1@[2001:1458:d00:13::100:2be] name:jgeens dn: prot:https app:http/reva_write host:cbox-ocisdev-jesse.cern.ch domain:cern.ch geo: sudo:0 trace: onbehalf:", + "xs" : "5896f22a" +}, +{ + "atime" : "1761135602.47990950", + "ctime" : "1761135602.47990160", + "fid" : "110345596", + "flags" : "644", + "gid" : "2763", + "layout_id" : "1048850", + "link_name" : "", + "locations" : "418,419", + "mtime" : "1761135602.48242753", + "name" : "000237750006.jpg", + "path" : "/eos/user/j/jgeens/mypictures/000237750006.jpg", + "pid" : "6086386", + "size" : "4727305", + "stime" : "0.0", + "uid" : "173503", + "unlink_locations" : "", + "xattr.sys.eos.btime" : "1761135602.47990160", + "xattr.sys.fs.tracking" : "+413+409/413/409", + "xattr.sys.fusex.state" : "", + "xattr.sys.utrace" : "6f1e43b0-af41-11f0-bc98-fa163e2e9155", + "xattr.sys.vtrace" : "[Wed Oct 22 14:20:02 2025] uid:173503[jgeens] gid:2763[it] tident:jgeens.1:1@[2001:1458:d00:13::100:2be] name:jgeens dn: prot:https app:http/reva_write host:cbox-ocisdev-jesse.cern.ch domain:cern.ch geo: sudo:0 trace: onbehalf:", + "xs" : "a723c9f5" +}, +{ + "atime" : "1761135602.7873138", + "ctime" : "1761135602.7872381", + "fid" : "110345595", + "flags" : "644", + "gid" : "2763", + "layout_id" : "1048850", + "link_name" : "", + "locations" : "421,417", + "mtime" : "1761135602.8154201", + "name" : "000237750007.jpg", + "path" : "/eos/user/j/jgeens/mypictures/000237750007.jpg", + "pid" : "6086386", + "size" : "4837900", + "stime" : "0.0", + "uid" : "173503", + "unlink_locations" : "", + "xattr.sys.eos.btime" : "1761135602.7872381", + "xattr.sys.fs.tracking" : "+417+411/417/411/409/415", + "xattr.sys.fusex.state" : "", + "xattr.sys.utrace" : "6f1822c8-af41-11f0-aae6-fa163e2e9155", + "xattr.sys.vtrace" : "[Wed Oct 22 14:20:02 2025] uid:173503[jgeens] gid:2763[it] tident:jgeens.1:1@[2001:1458:d00:13::100:2be] name:jgeens dn: prot:https app:http/reva_write host:cbox-ocisdev-jesse.cern.ch domain:cern.ch geo: sudo:0 trace: onbehalf:", + "xs" : "6c8f638a" +}, +{ + "atime" : "1761135601.862014449", + "ctime" : "1761135601.862013685", + "fid" : "110345594", + "flags" : "644", + "gid" : "2763", + "layout_id" : "1048850", + "link_name" : "", + "locations" : "418,410", + "mtime" : "1761135601.862270408", + "name" : "000237750008.jpg", + "path" : "/eos/user/j/jgeens/mypictures/000237750008.jpg", + "pid" : "6086386", + "size" : "4334057", + "stime" : "0.0", + "uid" : "173503", + "unlink_locations" : "", + "xattr.sys.eos.btime" : "1761135601.862013685", + "xattr.sys.fs.tracking" : "+415+409/409/415/412/417", + "xattr.sys.fusex.state" : "", + "xattr.sys.utrace" : "6f01dba8-af41-11f0-9d3b-fa163e2e9155", + "xattr.sys.vtrace" : "[Wed Oct 22 14:20:01 2025] uid:173503[jgeens] gid:2763[it] tident:jgeens.1:1@[2001:1458:d00:13::100:2be] name:jgeens dn: prot:https app:http/reva_write host:cbox-ocisdev-jesse.cern.ch domain:cern.ch geo: sudo:0 trace: onbehalf:", + "xs" : "80f1ac8e" +}, +{ + "atime" : "1761135601.675646284", + "ctime" : "1761135601.675645628", + "fid" : "110345592", + "flags" : "644", + "gid" : "2763", + "layout_id" : "1048850", + "link_name" : "", + "locations" : "418,414", + "mtime" : "1761135601.675893089", + "name" : "000237750009.jpg", + "path" : "/eos/user/j/jgeens/mypictures/000237750009.jpg", + "pid" : "6086386", + "size" : "4721479", + "stime" : "0.0", + "uid" : "173503", + "unlink_locations" : "", + "xattr.sys.eos.btime" : "1761135601.675645628", + "xattr.sys.fs.tracking" : "+409+416/416/409", + "xattr.sys.fusex.state" : "", + "xattr.sys.utrace" : "6ee5721a-af41-11f0-aea9-fa163e2e9155", + "xattr.sys.vtrace" : "[Wed Oct 22 14:20:01 2025] uid:173503[jgeens] gid:2763[it] tident:jgeens.1:1@[2001:1458:d00:13::100:2be] name:jgeens dn: prot:https app:http/reva_write host:cbox-ocisdev-jesse.cern.ch domain:cern.ch geo: sudo:0 trace: onbehalf:", + "xs" : "5a0f662f" +}, +{ + "atime" : "1761135601.751473325", + "ctime" : "1761135601.751472697", + "fid" : "110345593", + "flags" : "644", + "gid" : "2763", + "layout_id" : "1048850", + "link_name" : "", + "locations" : "418,419", + "mtime" : "1761135601.751677954", + "name" : "000237750010.jpg", + "path" : "/eos/user/j/jgeens/mypictures/000237750010.jpg", + "pid" : "6086386", + "size" : "5958151", + "stime" : "0.0", + "uid" : "173503", + "unlink_locations" : "", + "xattr.sys.eos.btime" : "1761135601.751472697", + "xattr.sys.fs.tracking" : "+412+421/421/412/413/416", + "xattr.sys.fusex.state" : "", + "xattr.sys.utrace" : "6ef106e8-af41-11f0-9c72-fa163e2e9155", + "xattr.sys.vtrace" : "[Wed Oct 22 14:20:01 2025] uid:173503[jgeens] gid:2763[it] tident:jgeens.1:1@[2001:1458:d00:13::100:2be] name:jgeens dn: prot:https app:http/reva_write host:cbox-ocisdev-jesse.cern.ch domain:cern.ch geo: sudo:0 trace: onbehalf:", + "xs" : "15a70153" +}, +{ + "atime" : "1761135601.535990476", + "ctime" : "1761135601.535989815", + "fid" : "110345591", + "flags" : "644", + "gid" : "2763", + "layout_id" : "1048850", + "link_name" : "", + "locations" : "410,419", + "mtime" : "1761135601.536240072", + "name" : "000237750011.jpg", + "path" : "/eos/user/j/jgeens/mypictures/000237750011.jpg", + "pid" : "6086386", + "size" : "4910246", + "stime" : "0.0", + "uid" : "173503", + "unlink_locations" : "", + "xattr.sys.eos.btime" : "1761135601.535989815", + "xattr.sys.fs.tracking" : "+421+417/421/417", + "xattr.sys.fusex.state" : "", + "xattr.sys.utrace" : "6ed02162-af41-11f0-996f-fa163e2e9155", + "xattr.sys.vtrace" : "[Wed Oct 22 14:20:01 2025] uid:173503[jgeens] gid:2763[it] tident:jgeens.1:1@[2001:1458:d00:13::100:2be] name:jgeens dn: prot:https app:http/reva_write host:cbox-ocisdev-jesse.cern.ch domain:cern.ch geo: sudo:0 trace: onbehalf:", + "xs" : "6fd1e958" +}, +{ + "atime" : "1761135601.391108203", + "ctime" : "1761135601.391107649", + "fid" : "110345590", + "flags" : "644", + "gid" : "2763", + "layout_id" : "1048850", + "link_name" : "", + "locations" : "417,420", + "mtime" : "1761135601.391455440", + "name" : "000237750012.jpg", + "path" : "/eos/user/j/jgeens/mypictures/000237750012.jpg", + "pid" : "6086386", + "size" : "5606243", + "stime" : "0.0", + "uid" : "173503", + "unlink_locations" : "", + "xattr.sys.eos.btime" : "1761135601.391107649", + "xattr.sys.fs.tracking" : "+415+413/415/413/411/412/413/409", + "xattr.sys.fusex.state" : "", + "xattr.sys.utrace" : "6eba04b8-af41-11f0-8a97-fa163e2e9155", + "xattr.sys.vtrace" : "[Wed Oct 22 14:20:01 2025] uid:173503[jgeens] gid:2763[it] tident:jgeens.1:1@[2001:1458:d00:13::100:2be] name:jgeens dn: prot:https app:http/reva_write host:cbox-ocisdev-jesse.cern.ch domain:cern.ch geo: sudo:0 trace: onbehalf:", + "xs" : "312fe828" +}, +{ + "atime" : "1761135601.256184419", + "ctime" : "1761135601.256183790", + "fid" : "110345589", + "flags" : "644", + "gid" : "2763", + "layout_id" : "1048850", + "link_name" : "", + "locations" : "418,410", + "mtime" : "1761135601.256465041", + "name" : "000237750013.jpg", + "path" : "/eos/user/j/jgeens/mypictures/000237750013.jpg", + "pid" : "6086386", + "size" : "5224276", + "stime" : "0.0", + "uid" : "173503", + "unlink_locations" : "", + "xattr.sys.eos.btime" : "1761135601.256183790", + "xattr.sys.fs.tracking" : "+412+417/417/412/415/416", + "xattr.sys.fusex.state" : "", + "xattr.sys.utrace" : "6ea57016-af41-11f0-b1f5-fa163e2e9155", + "xattr.sys.vtrace" : "[Wed Oct 22 14:20:01 2025] uid:173503[jgeens] gid:2763[it] tident:jgeens.1:1@[2001:1458:d00:13::100:2be] name:jgeens dn: prot:https app:http/reva_write host:cbox-ocisdev-jesse.cern.ch domain:cern.ch geo: sudo:0 trace: onbehalf:", + "xs" : "c8547d6a" +}, +{ + "atime" : "1761135600.834306174", + "ctime" : "1761135600.834305302", + "fid" : "110345588", + "flags" : "644", + "gid" : "2763", + "layout_id" : "1048850", + "link_name" : "", + "locations" : "418,419", + "mtime" : "1761135600.834559519", + "name" : "000237750014.jpg", + "path" : "/eos/user/j/jgeens/mypictures/000237750014.jpg", + "pid" : "6086386", + "size" : "4034362", + "stime" : "0.0", + "uid" : "173503", + "unlink_locations" : "", + "xattr.sys.eos.btime" : "1761135600.834305302", + "xattr.sys.fs.tracking" : "+415+416/415/416", + "xattr.sys.fusex.state" : "", + "xattr.sys.utrace" : "6e651444-af41-11f0-829d-fa163e2e9155", + "xattr.sys.vtrace" : "[Wed Oct 22 14:20:00 2025] uid:173503[jgeens] gid:2763[it] tident:jgeens.1:1@[2001:1458:d00:13::100:2be] name:jgeens dn: prot:https app:http/reva_write host:cbox-ocisdev-jesse.cern.ch domain:cern.ch geo: sudo:0 trace: onbehalf:", + "xs" : "55fa8b3a" +}, +{ + "atime" : "1761136899.449037089", + "ctime" : "1761136899.449036283", + "fid" : "110346854", + "flags" : "644", + "gid" : "2763", + "layout_id" : "1048850", + "link_name" : "", + "locations" : "417,420", + "mtime" : "1761136899.449360104", + "name" : "000237750021.jpg", + "path" : "/eos/user/j/jgeens/mypictures/000237750021.jpg", + "pid" : "6086386", + "size" : "5609508", + "stime" : "0.0", + "uid" : "173503", + "unlink_locations" : "", + "xattr.sys.eos.btime" : "1761136899.449036283", + "xattr.sys.fs.tracking" : "+413+416/413/416", + "xattr.sys.fusex.state" : "", + "xattr.sys.utrace" : "746dedea-af44-11f0-829d-fa163e2e9155", + "xattr.sys.vtrace" : "[Wed Oct 22 14:41:39 2025] uid:173503[jgeens] gid:2763[it] tident:jgeens.1:1@[2001:1458:d00:13::100:2be] name:jgeens dn: prot:https app:http/reva_write host:cbox-ocisdev-jesse.cern.ch domain:cern.ch geo: sudo:0 trace: onbehalf:", + "xs" : "cc5c8547" +}, +{ + "atime" : "1761136891.796540345", + "ctime" : "1761136891.796539779", + "fid" : "110346853", + "flags" : "644", + "gid" : "2763", + "layout_id" : "1048850", + "link_name" : "", + "locations" : "412,417", + "mtime" : "1761136891.796866133", + "name" : "000237750022.jpg", + "path" : "/eos/user/j/jgeens/mypictures/000237750022.jpg", + "pid" : "6086386", + "size" : "5444752", + "stime" : "0.0", + "uid" : "173503", + "unlink_locations" : "", + "xattr.sys.eos.btime" : "1761136891.796539779", + "xattr.sys.fs.tracking" : "+412+417", + "xattr.sys.fusex.state" : "", + "xattr.sys.utrace" : "6fde41ee-af44-11f0-8a97-fa163e2e9155", + "xattr.sys.vtrace" : "[Wed Oct 22 14:41:31 2025] uid:173503[jgeens] gid:2763[it] tident:jgeens.1:1@[2001:1458:d00:13::100:2be] name:jgeens dn: prot:https app:http/reva_write host:cbox-ocisdev-jesse.cern.ch domain:cern.ch geo: sudo:0 trace: onbehalf:", + "xs" : "70d579bc" +}, +{ + "atime" : "1761136907.399883695", + "ctime" : "1761136907.399882940", + "fid" : "110346855", + "flags" : "644", + "gid" : "2763", + "layout_id" : "1048850", + "link_name" : "", + "locations" : "417,411", + "mtime" : "1761136907.400231444", + "name" : "000237750025.jpg", + "path" : "/eos/user/j/jgeens/mypictures/000237750025.jpg", + "pid" : "6086386", + "size" : "4666746", + "stime" : "0.0", + "uid" : "173503", + "unlink_locations" : "", + "xattr.sys.eos.btime" : "1761136907.399882940", + "xattr.sys.fs.tracking" : "+417+411", + "xattr.sys.fusex.state" : "", + "xattr.sys.utrace" : "792b2046-af44-11f0-a253-fa163e2e9155", + "xattr.sys.vtrace" : "[Wed Oct 22 14:41:47 2025] uid:173503[jgeens] gid:2763[it] tident:jgeens.1:1@[2001:1458:d00:13::100:2be] name:jgeens dn: prot:https app:http/reva_write host:cbox-ocisdev-jesse.cern.ch domain:cern.ch geo: sudo:0 trace: onbehalf:", + "xs" : "f9f16cf4" +}, +{ + "atime" : "1761136929.546220536", + "ctime" : "1761136929.546219425", + "fid" : "110346857", + "flags" : "644", + "gid" : "2763", + "layout_id" : "1048850", + "link_name" : "", + "locations" : "412,421", + "mtime" : "1761136929.546529079", + "name" : "000237750032.jpg", + "path" : "/eos/user/j/jgeens/mypictures/000237750032.jpg", + "pid" : "6086386", + "size" : "4861522", + "stime" : "0.0", + "uid" : "173503", + "unlink_locations" : "", + "xattr.sys.eos.btime" : "1761136929.546219425", + "xattr.sys.fs.tracking" : "+417+411/411/417/418/408", + "xattr.sys.fusex.state" : "", + "xattr.sys.utrace" : "865e676e-af44-11f0-aae6-fa163e2e9155", + "xattr.sys.vtrace" : "[Wed Oct 22 14:42:09 2025] uid:173503[jgeens] gid:2763[it] tident:jgeens.1:1@[2001:1458:d00:13::100:2be] name:jgeens dn: prot:https app:http/reva_write host:cbox-ocisdev-jesse.cern.ch domain:cern.ch geo: sudo:0 trace: onbehalf:", + "xs" : "8728b11b" +}, +{ + "atime" : "1761136921.567144852", + "ctime" : "1761136921.567144193", + "fid" : "110346856", + "flags" : "644", + "gid" : "2763", + "layout_id" : "1048850", + "link_name" : "", + "locations" : "411,412", + "mtime" : "1761136921.567476030", + "name" : "000237750033.jpg", + "path" : "/eos/user/j/jgeens/mypictures/000237750033.jpg", + "pid" : "6086386", + "size" : "5424720", + "stime" : "0.0", + "uid" : "173503", + "unlink_locations" : "", + "xattr.sys.eos.btime" : "1761136921.567144193", + "xattr.sys.fs.tracking" : "+413+416/416/413", + "xattr.sys.fusex.state" : "", + "xattr.sys.utrace" : "819ce46c-af44-11f0-9211-fa163e2e9155", + "xattr.sys.vtrace" : "[Wed Oct 22 14:42:01 2025] uid:173503[jgeens] gid:2763[it] tident:jgeens.1:1@[2001:1458:d00:13::100:2be] name:jgeens dn: prot:https app:http/reva_write host:cbox-ocisdev-jesse.cern.ch domain:cern.ch geo: sudo:0 trace: onbehalf:", + "xs" : "043e964f" +}, +{ + "cid" : "6513150", + "ctime" : "1774944651.481261045", + "flags" : "0", + "gid" : "2763", + "mode" : "40750", + "mtime" : "1774944651.480645464", + "name" : ".sys.v#.000237750002.jpg", + "parent_id" : "6086386", + "path" : "/eos/user/j/jgeens/mypictures/.sys.v#.000237750002.jpg/", + "stime" : "0.0", + "tree_size" : "0", + "uid" : "173503", + "xattr.sys.eos.btime" : "1774944651.480645464" +}, +{ + "cid" : "6513165", + "ctime" : "1774944651.525798074", + "flags" : "0", + "gid" : "2763", + "mode" : "40750", + "mtime" : "1774944651.525269576", + "name" : ".sys.v#.000237750003.jpg", + "parent_id" : "6086386", + "path" : "/eos/user/j/jgeens/mypictures/.sys.v#.000237750003.jpg/", + "stime" : "0.0", + "tree_size" : "0", + "uid" : "173503", + "xattr.sys.eos.btime" : "1774944651.525269576" +}, +{ + "cid" : "6513166", + "ctime" : "1774944651.529210389", + "flags" : "0", + "gid" : "2763", + "mode" : "40750", + "mtime" : "1774944651.528699807", + "name" : ".sys.v#.000237750004.jpg", + "parent_id" : "6086386", + "path" : "/eos/user/j/jgeens/mypictures/.sys.v#.000237750004.jpg/", + "stime" : "0.0", + "tree_size" : "0", + "uid" : "173503", + "xattr.sys.eos.btime" : "1774944651.528699807" +}, +{ + "cid" : "6513152", + "ctime" : "1774944651.487736155", + "flags" : "0", + "gid" : "2763", + "mode" : "40750", + "mtime" : "1774944651.487212086", + "name" : ".sys.v#.000237750005.jpg", + "parent_id" : "6086386", + "path" : "/eos/user/j/jgeens/mypictures/.sys.v#.000237750005.jpg/", + "stime" : "0.0", + "tree_size" : "0", + "uid" : "173503", + "xattr.sys.eos.btime" : "1774944651.487212086" +}, +{ + "cid" : "6513161", + "ctime" : "1774944651.513897848", + "flags" : "0", + "gid" : "2763", + "mode" : "40750", + "mtime" : "1774944651.513423242", + "name" : ".sys.v#.000237750006.jpg", + "parent_id" : "6086386", + "path" : "/eos/user/j/jgeens/mypictures/.sys.v#.000237750006.jpg/", + "stime" : "0.0", + "tree_size" : "0", + "uid" : "173503", + "xattr.sys.eos.btime" : "1774944651.513423242" +}, +{ + "cid" : "6513163", + "ctime" : "1774944651.519334921", + "flags" : "0", + "gid" : "2763", + "mode" : "40750", + "mtime" : "1774944651.518838206", + "name" : ".sys.v#.000237750007.jpg", + "parent_id" : "6086386", + "path" : "/eos/user/j/jgeens/mypictures/.sys.v#.000237750007.jpg/", + "stime" : "0.0", + "tree_size" : "0", + "uid" : "173503", + "xattr.sys.eos.btime" : "1774944651.518838206" +}, +{ + "cid" : "6513159", + "ctime" : "1774944651.508242733", + "flags" : "0", + "gid" : "2763", + "mode" : "40750", + "mtime" : "1774944651.507744482", + "name" : ".sys.v#.000237750008.jpg", + "parent_id" : "6086386", + "path" : "/eos/user/j/jgeens/mypictures/.sys.v#.000237750008.jpg/", + "stime" : "0.0", + "tree_size" : "0", + "uid" : "173503", + "xattr.sys.eos.btime" : "1774944651.507744482" +}, +{ + "cid" : "6513160", + "ctime" : "1774944651.511181139", + "flags" : "0", + "gid" : "2763", + "mode" : "40750", + "mtime" : "1774944651.510692583", + "name" : ".sys.v#.000237750009.jpg", + "parent_id" : "6086386", + "path" : "/eos/user/j/jgeens/mypictures/.sys.v#.000237750009.jpg/", + "stime" : "0.0", + "tree_size" : "0", + "uid" : "173503", + "xattr.sys.eos.btime" : "1774944651.510692583" +}, +{ + "cid" : "6513162", + "ctime" : "1774944651.516639741", + "flags" : "0", + "gid" : "2763", + "mode" : "40750", + "mtime" : "1774944651.516169501", + "name" : ".sys.v#.000237750010.jpg", + "parent_id" : "6086386", + "path" : "/eos/user/j/jgeens/mypictures/.sys.v#.000237750010.jpg/", + "stime" : "0.0", + "tree_size" : "0", + "uid" : "173503", + "xattr.sys.eos.btime" : "1774944651.516169501" +}, +{ + "cid" : "6513158", + "ctime" : "1774944651.505492897", + "flags" : "0", + "gid" : "2763", + "mode" : "40750", + "mtime" : "1774944651.504964305", + "name" : ".sys.v#.000237750011.jpg", + "parent_id" : "6086386", + "path" : "/eos/user/j/jgeens/mypictures/.sys.v#.000237750011.jpg/", + "stime" : "0.0", + "tree_size" : "0", + "uid" : "173503", + "xattr.sys.eos.btime" : "1774944651.504964305" +}, +{ + "cid" : "6513156", + "ctime" : "1774944651.499219867", + "flags" : "0", + "gid" : "2763", + "mode" : "40750", + "mtime" : "1774944651.498740432", + "name" : ".sys.v#.000237750012.jpg", + "parent_id" : "6086386", + "path" : "/eos/user/j/jgeens/mypictures/.sys.v#.000237750012.jpg/", + "stime" : "0.0", + "tree_size" : "0", + "uid" : "173503", + "xattr.sys.eos.btime" : "1774944651.498740432" +}, +{ + "cid" : "6513153", + "ctime" : "1774944651.490727961", + "flags" : "0", + "gid" : "2763", + "mode" : "40750", + "mtime" : "1774944651.490219643", + "name" : ".sys.v#.000237750013.jpg", + "parent_id" : "6086386", + "path" : "/eos/user/j/jgeens/mypictures/.sys.v#.000237750013.jpg/", + "stime" : "0.0", + "tree_size" : "0", + "uid" : "173503", + "xattr.sys.eos.btime" : "1774944651.490219643" +}, +{ + "cid" : "6513154", + "ctime" : "1774944651.493652413", + "flags" : "0", + "gid" : "2763", + "mode" : "40750", + "mtime" : "1774944651.493169537", + "name" : ".sys.v#.000237750014.jpg", + "parent_id" : "6086386", + "path" : "/eos/user/j/jgeens/mypictures/.sys.v#.000237750014.jpg/", + "stime" : "0.0", + "tree_size" : "0", + "uid" : "173503", + "xattr.sys.eos.btime" : "1774944651.493169537" +}, +{ + "cid" : "6513164", + "ctime" : "1774944651.522218123", + "flags" : "0", + "gid" : "2763", + "mode" : "40750", + "mtime" : "1774944651.521670544", + "name" : ".sys.v#.000237750021.jpg", + "parent_id" : "6086386", + "path" : "/eos/user/j/jgeens/mypictures/.sys.v#.000237750021.jpg/", + "stime" : "0.0", + "tree_size" : "0", + "uid" : "173503", + "xattr.sys.eos.btime" : "1774944651.521670544" +}, +{ + "cid" : "6513155", + "ctime" : "1774944651.496366149", + "flags" : "0", + "gid" : "2763", + "mode" : "40750", + "mtime" : "1774944651.495873651", + "name" : ".sys.v#.000237750022.jpg", + "parent_id" : "6086386", + "path" : "/eos/user/j/jgeens/mypictures/.sys.v#.000237750022.jpg/", + "stime" : "0.0", + "tree_size" : "0", + "uid" : "173503", + "xattr.sys.eos.btime" : "1774944651.495873651" +}, +{ + "cid" : "6513151", + "ctime" : "1774944651.484442293", + "flags" : "0", + "gid" : "2763", + "mode" : "40750", + "mtime" : "1774944651.483913487", + "name" : ".sys.v#.000237750025.jpg", + "parent_id" : "6086386", + "path" : "/eos/user/j/jgeens/mypictures/.sys.v#.000237750025.jpg/", + "stime" : "0.0", + "tree_size" : "0", + "uid" : "173503", + "xattr.sys.eos.btime" : "1774944651.483913487" +}, +{ + "cid" : "6513157", + "ctime" : "1774944651.502522018", + "flags" : "0", + "gid" : "2763", + "mode" : "40750", + "mtime" : "1774944651.501962835", + "name" : ".sys.v#.000237750032.jpg", + "parent_id" : "6086386", + "path" : "/eos/user/j/jgeens/mypictures/.sys.v#.000237750032.jpg/", + "stime" : "0.0", + "tree_size" : "0", + "uid" : "173503", + "xattr.sys.eos.btime" : "1774944651.501962835" +}, +{ + "cid" : "6513149", + "ctime" : "1774944651.477428981", + "flags" : "0", + "gid" : "2763", + "mode" : "40750", + "mtime" : "1774944651.476447231", + "name" : ".sys.v#.000237750033.jpg", + "parent_id" : "6086386", + "path" : "/eos/user/j/jgeens/mypictures/.sys.v#.000237750033.jpg/", + "stime" : "0.0", + "tree_size" : "0", + "uid" : "173503", + "xattr.sys.eos.btime" : "1774944651.476447231" +}, +{ + "cid" : "6746858", + "ctime" : "1782135597.201330770", + "flags" : "0", + "gid" : "2763", + "mode" : "40750", + "mtime" : "1782135597.200819187", + "name" : "nextcloudsync", + "parent_id" : "4251882", + "path" : "/eos/user/j/jgeens/nextcloudsync/", + "stime" : "0.0", + "tree_size" : "0", + "uid" : "173503", + "xattr.sys.acl" : "u:173503:rwx", + "xattr.sys.allow.oc.sync" : "1", + "xattr.sys.eos.btime" : "1677602740.101786072", + "xattr.sys.forced.atomic" : "1", + "xattr.sys.forced.blocksize" : "4k", + "xattr.sys.forced.checksum" : "adler", + "xattr.sys.forced.layout" : "replica", + "xattr.sys.forced.nstripes" : "2", + "xattr.sys.forced.space" : "default", + "xattr.sys.mask" : "700", + "xattr.sys.mtime.propagation" : "1", + "xattr.sys.owner.auth" : "*", + "xattr.sys.recycle" : "/eos/homedev/proc/recycle/", + "xattr.sys.versioning" : "10", + "xattr.user.http://owncloud.org/ns/favorite" : "u:jgeens=1,u:tstcbsa9=1", + "xattr.user.reva.labels.jgeens.favorite" : "1", + "xattr.user.reva.labels.tstcbsa9.favorite" : "1" +}, +{ + "cid" : "6004498", + "ctime" : "1778071870.412945025", + "flags" : "0", + "gid" : "2763", + "mode" : "40750", + "mtime" : "1782200204.929062595", + "name" : "ultra-long-folder-name-why-would-you", + "parent_id" : "4251882", + "path" : "/eos/user/j/jgeens/ultra-long-folder-name-why-would-you/", + "stime" : "0.0", + "tree_size" : "0", + "uid" : "173503", + "xattr.sys.acl" : "u:173503:rwx", + "xattr.sys.allow.oc.sync" : "1", + "xattr.sys.eos.btime" : "1677602740.101786072", + "xattr.sys.forced.atomic" : "1", + "xattr.sys.forced.blocksize" : "4k", + "xattr.sys.forced.checksum" : "adler", + "xattr.sys.forced.layout" : "replica", + "xattr.sys.forced.nstripes" : "2", + "xattr.sys.forced.space" : "default", + "xattr.sys.mask" : "700", + "xattr.sys.mtime.propagation" : "1", + "xattr.sys.owner.auth" : "*", + "xattr.sys.recycle" : "/eos/homedev/proc/recycle/", + "xattr.sys.versioning" : "10", + "xattr.user.http://owncloud.org/ns/favorite" : "u:jgeens=1" +}, +{ + "atime" : "1758533298.71680793", + "ctime" : "1758533298.71680190", + "fid" : "109366898", + "flags" : "644", + "gid" : "2763", + "layout_id" : "1048850", + "link_name" : "", + "locations" : "417,411", + "mtime" : "1758533298.71957839", + "name" : "and-then-an-even-longer-file-name-are-you-crazy.txt", + "path" : "/eos/user/j/jgeens/ultra-long-folder-name-why-would-you/and-then-an-even-longer-file-name-are-you-crazy.txt", + "pid" : "6004498", + "size" : "0", + "stime" : "0.0", + "uid" : "173503", + "unlink_locations" : "", + "xattr.sys.eos.btime" : "1758533298.71680190", + "xattr.sys.fs.tracking" : "+417+411", + "xattr.sys.fusex.state" : "", + "xattr.sys.utrace" : "7913b3fe-9796-11f0-9c17-fa163e2e9155", + "xattr.sys.vtrace" : "[Mon Sep 22 11:28:18 2025] uid:173503[jgeens] gid:2763[it] tident:jgeens.1:1@[2001:1458:d00:16::33d] name:jgeens dn: prot:https app:http/reva_write host:cbox-ocisdev-diogo.cern.ch domain:cern.ch geo: sudo:0 trace: onbehalf:", + "xs" : "00000001" +}, +{ + "cid" : "6747867", + "ctime" : "1782200204.929571237", + "flags" : "0", + "gid" : "2763", + "mode" : "40750", + "mtime" : "1782200204.929062595", + "name" : ".sys.v#.and-then-an-even-longer-file-name-are-you-crazy.txt", + "parent_id" : "6004498", + "path" : "/eos/user/j/jgeens/ultra-long-folder-name-why-would-you/.sys.v#.and-then-an-even-longer-file-name-are-you-crazy.txt/", + "stime" : "0.0", + "tree_size" : "0", + "uid" : "173503", + "xattr.sys.eos.btime" : "1782200204.929062595" +}, +{ + "cid" : "6201977", + "ctime" : "1785223865.787581392", + "flags" : "0", + "gid" : "2763", + "mode" : "40750", + "mtime" : "1785223865.810180464", + "name" : "uploads", + "parent_id" : "4251882", + "path" : "/eos/user/j/jgeens/uploads/", + "stime" : "1785223865.810180464", + "tree_size" : "41698055", + "uid" : "173503", + "xattr.sys.acl" : "u:58679:rxt,u:187895:rx,u:173503:rwx,u:4179:rx!d,u:193339:rwx+d", + "xattr.sys.allow.oc.sync" : "1", + "xattr.sys.eos.btime" : "1677602740.101786072", + "xattr.sys.forced.atomic" : "1", + "xattr.sys.forced.blocksize" : "4k", + "xattr.sys.forced.checksum" : "adler", + "xattr.sys.forced.layout" : "replica", + "xattr.sys.forced.nstripes" : "2", + "xattr.sys.forced.space" : "default", + "xattr.sys.mask" : "700", + "xattr.sys.mtime.propagation" : "1", + "xattr.sys.owner.auth" : "*", + "xattr.sys.recycle" : "/eos/homedev/proc/recycle/", + "xattr.sys.reva.lwshare.111857693350341032552@google" : "rwx+d", + "xattr.sys.reva.lwshare.guest:jesse.geens@proton.me" : "rwx+d", + "xattr.sys.versioning" : "10", + "xattr.user.http://owncloud.org/ns/favorite" : "u:jgeens=1" +}, +{ + "atime" : "1767971560.736129166", + "ctime" : "1767971560.736128437", + "fid" : "113265536", + "flags" : "644", + "gid" : "2763", + "layout_id" : "1048850", + "link_name" : "", + "locations" : "418,410", + "mtime" : "1767971560.736436031", + "name" : "000237740001_ygysq.jpg", + "path" : "/eos/user/j/jgeens/uploads/000237740001_ygysq.jpg", + "pid" : "6201977", + "size" : "5622656", + "stime" : "0.0", + "uid" : "173503", + "unlink_locations" : "", + "xattr.sys.eos.btime" : "1767971560.736128437", + "xattr.sys.fs.tracking" : "+412+417/417/412/413/415", + "xattr.sys.fusex.state" : "", + "xattr.sys.utrace" : "a402e10c-ed6d-11f0-8cdc-fa163e2e9155", + "xattr.sys.vtrace" : "[Fri Jan 9 16:12:40 2026] uid:173503[jgeens] gid:2763[it] tident:jgeens.1:1@[2001:1458:d00:13::100:2be] name:jgeens dn: prot:https app:http/reva_write host:cbox-ocisdev-jesse.cern.ch domain:cern.ch geo: sudo:0 trace: onbehalf:", + "xs" : "653e7cd3" +}, +{ + "atime" : "1767971367.466154136", + "ctime" : "1767971367.466153564", + "fid" : "113265527", + "flags" : "644", + "gid" : "2763", + "layout_id" : "1048850", + "link_name" : "", + "locations" : "418,410", + "mtime" : "1767971367.466427614", + "name" : "000237740006_jzfor.jpg", + "path" : "/eos/user/j/jgeens/uploads/000237740006_jzfor.jpg", + "pid" : "6201977", + "size" : "5090002", + "stime" : "0.0", + "uid" : "173503", + "unlink_locations" : "", + "xattr.sys.eos.btime" : "1767971367.466153564", + "xattr.sys.fs.tracking" : "+412+417/412/417", + "xattr.sys.fusex.state" : "", + "xattr.sys.utrace" : "30d03806-ed6d-11f0-a539-fa163e2e9155", + "xattr.sys.vtrace" : "[Fri Jan 9 16:09:27 2026] uid:173503[jgeens] gid:2763[it] tident:jgeens.1:1@[2001:1458:d00:13::100:2be] name:jgeens dn: prot:https app:http/reva_write host:cbox-ocisdev-jesse.cern.ch domain:cern.ch geo: sudo:0 trace: onbehalf:", + "xs" : "129f17ef" +}, +{ + "atime" : "1767971477.300227938", + "ctime" : "1767971477.300227404", + "fid" : "113265531", + "flags" : "644", + "gid" : "2763", + "layout_id" : "1048850", + "link_name" : "", + "locations" : "418,419", + "mtime" : "1767971477.300809811", + "name" : "000237740012_kxfnl.jpg", + "path" : "/eos/user/j/jgeens/uploads/000237740012_kxfnl.jpg", + "pid" : "6201977", + "size" : "4593388", + "stime" : "0.0", + "uid" : "173503", + "unlink_locations" : "", + "xattr.sys.eos.btime" : "1767971477.300227404", + "xattr.sys.fs.tracking" : "+413+409/409/413", + "xattr.sys.fusex.state" : "", + "xattr.sys.utrace" : "72478a82-ed6d-11f0-859f-fa163e2e9155", + "xattr.sys.vtrace" : "[Fri Jan 9 16:11:17 2026] uid:173503[jgeens] gid:2763[it] tident:jgeens.1:1@[2001:1458:d00:13::100:2be] name:jgeens dn: prot:https app:http/reva_write host:cbox-ocisdev-jesse.cern.ch domain:cern.ch geo: sudo:0 trace: onbehalf:", + "xs" : "08eed4f9" +}, +{ + "atime" : "1767971571.721623677", + "ctime" : "1767971571.721622755", + "fid" : "113265537", + "flags" : "644", + "gid" : "2763", + "layout_id" : "1048850", + "link_name" : "", + "locations" : "417,420", + "mtime" : "1767971571.721908296", + "name" : "000237740019_nplyo.jpg", + "path" : "/eos/user/j/jgeens/uploads/000237740019_nplyo.jpg", + "pid" : "6201977", + "size" : "4991277", + "stime" : "0.0", + "uid" : "173503", + "unlink_locations" : "", + "xattr.sys.eos.btime" : "1767971571.721622755", + "xattr.sys.fs.tracking" : "+413+409/413/409", + "xattr.sys.fusex.state" : "", + "xattr.sys.utrace" : "aa8f2030-ed6d-11f0-ad82-fa163e2e9155", + "xattr.sys.vtrace" : "[Fri Jan 9 16:12:51 2026] uid:173503[jgeens] gid:2763[it] tident:jgeens.1:1@[2001:1458:d00:13::100:2be] name:jgeens dn: prot:https app:http/reva_write host:cbox-ocisdev-jesse.cern.ch domain:cern.ch geo: sudo:0 trace: onbehalf:", + "xs" : "f8e3eb89" +}, +{ + "atime" : "1785223865.744382748", + "ctime" : "1785223865.744382058", + "fid" : "120136059", + "flags" : "644", + "gid" : "2763", + "layout_id" : "1048850", + "link_name" : "", + "locations" : "408,419", + "mtime" : "1785223865.744673299", + "name" : "000237750003_avtpp.jpg", + "path" : "/eos/user/j/jgeens/uploads/000237750003_avtpp.jpg", + "pid" : "6201977", + "size" : "5085527", + "stime" : "0.0", + "uid" : "173503", + "unlink_locations" : "", + "xattr.sys.eos.btime" : "1785223865.744382058", + "xattr.sys.fs.tracking" : "+408+419", + "xattr.sys.fusex.state" : "", + "xattr.sys.utrace" : "4b208e82-8a56-11f1-a419-fa163e35f83a", + "xattr.sys.vtrace" : "[Tue Jul 28 09:31:05 2026] uid:173503[cboxexternal] gid:2763[def-cg] tident:cboxexternal.1:1@[2001:1458:d00:13::100:2be] name:cboxexternal dn: prot:https app:http/reva_write host:cbox-ocisdev-jesse.cern.ch domain:cern.ch geo: sudo:0 trace: onbehalf:", + "xs" : "9540cc33" +}, +{ + "atime" : "1760081501.783982305", + "ctime" : "1778157250.261947749", + "fid" : "109951143", + "flags" : "644", + "gid" : "2763", + "layout_id" : "1048850", + "link_name" : "", + "locations" : "412,421", + "mtime" : "1760081501.784245252", + "name" : "Nouveau fichier.md", + "path" : "/eos/user/j/jgeens/uploads/Nouveau fichier.md", + "pid" : "6201977", + "size" : "0", + "stime" : "0.0", + "uid" : "173503", + "unlink_locations" : "", + "xattr.sys.acl" : "u:187895:rx", + "xattr.sys.eos.btime" : "1760081501.783981737", + "xattr.sys.fs.tracking" : "+412+421", + "xattr.sys.fusex.state" : "", + "xattr.sys.utrace" : "2a666166-a5ab-11f0-9d22-fa163e2e9155", + "xattr.sys.vtrace" : "[Fri Oct 10 09:31:41 2025] uid:173503[jgeens] gid:2763[it] tident:jgeens.1:1@[2001:1458:d00:3b::100:283] name:jgeens dn: prot:https app:http/reva_write host:cbox-ocistest-01.cern.ch domain:cern.ch geo: sudo:0 trace: onbehalf:", + "xs" : "00000001" +}, +{ + "atime" : "1784905569.526749024", + "ctime" : "1784905569.526748464", + "fid" : "120018008", + "flags" : "644", + "gid" : "2763", + "layout_id" : "1048850", + "link_name" : "", + "locations" : "411,420", + "mtime" : "1784905569.526979445", + "name" : "PXL_20260717_082238214_xjkfr.jpg", + "path" : "/eos/user/j/jgeens/uploads/PXL_20260717_082238214_xjkfr.jpg", + "pid" : "6201977", + "size" : "2319015", + "stime" : "0.0", + "uid" : "173503", + "unlink_locations" : "", + "xattr.sys.eos.btime" : "1784905569.526748464", + "xattr.sys.fs.tracking" : "+411+420", + "xattr.sys.fusex.state" : "", + "xattr.sys.utrace" : "33cc1a9a-8771-11f1-9f34-fa163e35f83a", + "xattr.sys.vtrace" : "[Fri Jul 24 17:06:09 2026] uid:173503[cboxexternal] gid:2763[def-cg] tident:cboxexternal.1:1@[2001:1458:d00:13::100:2be] name:cboxexternal dn: prot:https app:http/reva_write host:cbox-ocisdev-jesse.cern.ch domain:cern.ch geo: sudo:0 trace: onbehalf:", + "xs" : "474b24e0" +}, +{ + "atime" : "1784893943.150863611", + "ctime" : "1784893943.150863072", + "fid" : "120014938", + "flags" : "644", + "gid" : "2763", + "layout_id" : "1048850", + "link_name" : "", + "locations" : "411,420", + "mtime" : "1784893943.151128572", + "name" : "PXL_20260719_175848292_hbkml.jpg", + "path" : "/eos/user/j/jgeens/uploads/PXL_20260719_175848292_hbkml.jpg", + "pid" : "6201977", + "size" : "65396", + "stime" : "0.0", + "uid" : "173503", + "unlink_locations" : "", + "xattr.sys.eos.btime" : "1784893943.150863072", + "xattr.sys.fs.tracking" : "+411+420", + "xattr.sys.fusex.state" : "", + "xattr.sys.utrace" : "21efedda-8756-11f1-92c9-fa163e35f83a", + "xattr.sys.vtrace" : "[Fri Jul 24 13:52:23 2026] uid:173503[cboxexternal] gid:2763[def-cg] tident:cboxexternal.1:1@[2001:1458:d00:13::100:2be] name:cboxexternal dn: prot:https app:http/reva_write host:cbox-ocisdev-jesse.cern.ch domain:cern.ch geo: sudo:0 trace: onbehalf:", + "xs" : "517a01e1" +}, +{ + "atime" : "1784887084.901965976", + "ctime" : "1784887084.901965426", + "fid" : "120012954", + "flags" : "644", + "gid" : "2763", + "layout_id" : "1048850", + "link_name" : "", + "locations" : "411,420", + "mtime" : "1784887084.902364959", + "name" : "PXL_20260719_175848292_kkqqw.jpg", + "path" : "/eos/user/j/jgeens/uploads/PXL_20260719_175848292_kkqqw.jpg", + "pid" : "6201977", + "size" : "65396", + "stime" : "0.0", + "uid" : "173503", + "unlink_locations" : "", + "xattr.sys.eos.btime" : "1784887084.901965426", + "xattr.sys.fs.tracking" : "+411+420", + "xattr.sys.fusex.state" : "", + "xattr.sys.utrace" : "2a1a2ff8-8746-11f1-afee-fa163e35f83a", + "xattr.sys.vtrace" : "[Fri Jul 24 11:58:04 2026] uid:173503[cboxexternal] gid:2763[def-cg] tident:cboxexternal.1:1@[2001:1458:d00:13::100:2be] name:cboxexternal dn: prot:https app:http/reva_write host:cbox-ocisdev-jesse.cern.ch domain:cern.ch geo: sudo:0 trace: onbehalf:", + "xs" : "517a01e1" +}, +{ + "atime" : "1784880371.473936189", + "ctime" : "1784880371.473935759", + "fid" : "120010051", + "flags" : "644", + "gid" : "2763", + "layout_id" : "1048850", + "link_name" : "", + "locations" : "421,411", + "mtime" : "1784880371.474394002", + "name" : "gridfinity-box-2x1_2h20m_0.20mm_195C_PLA_ENDER3_iocak.gcode", + "path" : "/eos/user/j/jgeens/uploads/gridfinity-box-2x1_2h20m_0.20mm_195C_PLA_ENDER3_iocak.gcode", + "pid" : "6201977", + "size" : "3192393", + "stime" : "0.0", + "uid" : "173503", + "unlink_locations" : "", + "xattr.sys.eos.btime" : "1784880371.473935759", + "xattr.sys.fs.tracking" : "+421+411", + "xattr.sys.fusex.state" : "", + "xattr.sys.utrace" : "889658c8-8736-11f1-92c9-fa163e35f83a", + "xattr.sys.vtrace" : "[Fri Jul 24 10:06:11 2026] uid:173503[cboxexternal] gid:2763[def-cg] tident:cboxexternal.1:1@[2001:1458:d00:13::100:2be] name:cboxexternal dn: prot:https app:http/reva_write host:cbox-ocisdev-jesse.cern.ch domain:cern.ch geo: sudo:0 trace: onbehalf:", + "xs" : "b9e6c234" +}, +{ + "atime" : "1784880372.319329154", + "ctime" : "1784880372.319328294", + "fid" : "120010052", + "flags" : "644", + "gid" : "2763", + "layout_id" : "1048850", + "link_name" : "", + "locations" : "418,408", + "mtime" : "1784880372.319650755", + "name" : "gridfinity-box-2x2-3walls_4h37m_0.20mm_195C_PLA_ENDER3_kghyk.gcode", + "path" : "/eos/user/j/jgeens/uploads/gridfinity-box-2x2-3walls_4h37m_0.20mm_195C_PLA_ENDER3_kghyk.gcode", + "pid" : "6201977", + "size" : "5050349", + "stime" : "0.0", + "uid" : "173503", + "unlink_locations" : "", + "xattr.sys.eos.btime" : "1784880372.319328294", + "xattr.sys.fs.tracking" : "+418+408", + "xattr.sys.fusex.state" : "", + "xattr.sys.utrace" : "891760e4-8736-11f1-afee-fa163e35f83a", + "xattr.sys.vtrace" : "[Fri Jul 24 10:06:12 2026] uid:173503[cboxexternal] gid:2763[def-cg] tident:cboxexternal.1:1@[2001:1458:d00:13::100:2be] name:cboxexternal dn: prot:https app:http/reva_write host:cbox-ocisdev-jesse.cern.ch domain:cern.ch geo: sudo:0 trace: onbehalf:", + "xs" : "f795ccd7" +}, +{ + "atime" : "1771603234.415823907", + "ctime" : "1771603248.782646305", + "fid" : "114723496", + "flags" : "644", + "gid" : "2763", + "layout_id" : "1048850", + "link_name" : "", + "locations" : "412,417", + "mtime" : "1771603234.416429535", + "name" : "test.md", + "path" : "/eos/user/j/jgeens/uploads/test.md", + "pid" : "6201977", + "size" : "0", + "stime" : "0.0", + "uid" : "173503", + "unlink_locations" : "", + "xattr.sys.eos.btime" : "1771603234.415823208", + "xattr.sys.fs.tracking" : "+412+417", + "xattr.sys.fusex.state" : "", + "xattr.sys.utrace" : "4a31c232-0e75-11f1-a9cd-fa163e2e9155", + "xattr.sys.vtrace" : "[Fri Feb 20 17:00:34 2026] uid:173503[jgeens] gid:2763[it] tident:http name:jgeens dn: prot:https app:http/reva_write host:[2001:1458:d00:13::100:2be] domain:localdomain geo: sudo:0 trace: onbehalf:", + "xattr.user.iop.wopi.lastwritetime" : "1771603241", + "xs" : "00000001" +}, +{ + "atime" : "1772548443.877603339", + "ctime" : "1772548446.686258657", + "fid" : "115072428", + "flags" : "644", + "gid" : "2763", + "layout_id" : "1048850", + "link_name" : "", + "locations" : "412,417", + "mtime" : "1772548443.878251324", + "name" : "test2.md", + "path" : "/eos/user/j/jgeens/uploads/test2.md", + "pid" : "6201977", + "size" : "0", + "stime" : "0.0", + "uid" : "173503", + "unlink_locations" : "", + "xattr.sys.eos.btime" : "1772548443.877602557", + "xattr.sys.fs.tracking" : "+412+417", + "xattr.sys.fusex.state" : "", + "xattr.sys.utrace" : "06f300a2-170e-11f1-a09b-fa163e2e9155", + "xattr.sys.vtrace" : "[Tue Mar 3 15:34:03 2026] uid:173503[cboxexternal] gid:2763[def-cg] tident:cboxexternal.1:1@[2001:1458:d00:13::100:2be] name:cboxexternal dn: prot:https app:http/reva_write host:cbox-ocisdev-jesse.cern.ch domain:cern.ch geo: sudo:0 trace: onbehalf:", + "xs" : "00000001" +}, +{ + "cid" : "6570191", + "ctime" : "1776325148.584476233", + "flags" : "0", + "gid" : "2763", + "mode" : "40750", + "mtime" : "1776325148.583998263", + "name" : ".sys.v#.000237740001_ygysq.jpg", + "parent_id" : "6201977", + "path" : "/eos/user/j/jgeens/uploads/.sys.v#.000237740001_ygysq.jpg/", + "stime" : "0.0", + "tree_size" : "0", + "uid" : "173503", + "xattr.sys.eos.btime" : "1776325148.583998263" +}, +{ + "cid" : "6570188", + "ctime" : "1776325148.60516333", + "flags" : "0", + "gid" : "2763", + "mode" : "40750", + "mtime" : "1776325148.59843503", + "name" : ".sys.v#.000237740006_jzfor.jpg", + "parent_id" : "6201977", + "path" : "/eos/user/j/jgeens/uploads/.sys.v#.000237740006_jzfor.jpg/", + "stime" : "0.0", + "tree_size" : "0", + "uid" : "173503", + "xattr.sys.eos.btime" : "1776325148.59843503" +}, +{ + "cid" : "6570190", + "ctime" : "1776325148.580689098", + "flags" : "0", + "gid" : "2763", + "mode" : "40750", + "mtime" : "1776325148.579939457", + "name" : ".sys.v#.000237740012_kxfnl.jpg", + "parent_id" : "6201977", + "path" : "/eos/user/j/jgeens/uploads/.sys.v#.000237740012_kxfnl.jpg/", + "stime" : "0.0", + "tree_size" : "0", + "uid" : "173503", + "xattr.sys.eos.btime" : "1776325148.579939457" +}, +{ + "cid" : "6570189", + "ctime" : "1776325148.63136222", + "flags" : "0", + "gid" : "2763", + "mode" : "40750", + "mtime" : "1776325148.62747659", + "name" : ".sys.v#.000237740019_nplyo.jpg", + "parent_id" : "6201977", + "path" : "/eos/user/j/jgeens/uploads/.sys.v#.000237740019_nplyo.jpg/", + "stime" : "0.0", + "tree_size" : "0", + "uid" : "173503", + "xattr.sys.eos.btime" : "1776325148.62747659" +}, +{ + "cid" : "6859845", + "ctime" : "1785223865.810521676", + "flags" : "0", + "gid" : "2763", + "mode" : "40750", + "mtime" : "1785223865.810180464", + "name" : ".sys.v#.000237750003_avtpp.jpg", + "parent_id" : "6201977", + "path" : "/eos/user/j/jgeens/uploads/.sys.v#.000237750003_avtpp.jpg/", + "stime" : "0.0", + "tree_size" : "0", + "uid" : "173503", + "xattr.sys.eos.btime" : "1785223865.810180464" +}, +{ + "cid" : "6492594", + "ctime" : "1774268053.293496656", + "flags" : "0", + "gid" : "2763", + "mode" : "40750", + "mtime" : "1774268053.293016900", + "name" : ".sys.v#.Nouveau fichier.md", + "parent_id" : "6201977", + "path" : "/eos/user/j/jgeens/uploads/.sys.v#.Nouveau fichier.md/", + "stime" : "0.0", + "tree_size" : "0", + "uid" : "173503", + "xattr.sys.eos.btime" : "1774268053.293016900" +}, +{ + "cid" : "6852915", + "ctime" : "1784905569.590975530", + "flags" : "0", + "gid" : "2763", + "mode" : "40750", + "mtime" : "1784905569.590617008", + "name" : ".sys.v#.PXL_20260717_082238214_xjkfr.jpg", + "parent_id" : "6201977", + "path" : "/eos/user/j/jgeens/uploads/.sys.v#.PXL_20260717_082238214_xjkfr.jpg/", + "stime" : "0.0", + "tree_size" : "0", + "uid" : "173503", + "xattr.sys.eos.btime" : "1784905569.590617008" +}, +{ + "cid" : "6852750", + "ctime" : "1784893943.196232271", + "flags" : "0", + "gid" : "2763", + "mode" : "40750", + "mtime" : "1784893943.195840049", + "name" : ".sys.v#.PXL_20260719_175848292_hbkml.jpg", + "parent_id" : "6201977", + "path" : "/eos/user/j/jgeens/uploads/.sys.v#.PXL_20260719_175848292_hbkml.jpg/", + "stime" : "0.0", + "tree_size" : "0", + "uid" : "173503", + "xattr.sys.eos.btime" : "1784893943.195840049" +}, +{ + "cid" : "6852649", + "ctime" : "1784887084.973563961", + "flags" : "0", + "gid" : "2763", + "mode" : "40750", + "mtime" : "1784887084.973118638", + "name" : ".sys.v#.PXL_20260719_175848292_kkqqw.jpg", + "parent_id" : "6201977", + "path" : "/eos/user/j/jgeens/uploads/.sys.v#.PXL_20260719_175848292_kkqqw.jpg/", + "stime" : "0.0", + "tree_size" : "0", + "uid" : "173503", + "xattr.sys.eos.btime" : "1784887084.973118638" +}, +{ + "cid" : "6852448", + "ctime" : "1784880371.540831742", + "flags" : "0", + "gid" : "2763", + "mode" : "40750", + "mtime" : "1784880371.540347630", + "name" : ".sys.v#.gridfinity-box-2x1_2h20m_0.20mm_195C_PLA_ENDER3_iocak.gcode", + "parent_id" : "6201977", + "path" : "/eos/user/j/jgeens/uploads/.sys.v#.gridfinity-box-2x1_2h20m_0.20mm_195C_PLA_ENDER3_iocak.gcode/", + "stime" : "0.0", + "tree_size" : "0", + "uid" : "173503", + "xattr.sys.eos.btime" : "1784880371.540347630" +}, +{ + "cid" : "6852449", + "ctime" : "1784880372.387005681", + "flags" : "0", + "gid" : "2763", + "mode" : "40750", + "mtime" : "1784880372.386645199", + "name" : ".sys.v#.gridfinity-box-2x2-3walls_4h37m_0.20mm_195C_PLA_ENDER3_kghyk.gcode", + "parent_id" : "6201977", + "path" : "/eos/user/j/jgeens/uploads/.sys.v#.gridfinity-box-2x2-3walls_4h37m_0.20mm_195C_PLA_ENDER3_kghyk.gcode/", + "stime" : "0.0", + "tree_size" : "0", + "uid" : "173503", + "xattr.sys.eos.btime" : "1784880372.386645199" +}, +{ + "cid" : "6570192", + "ctime" : "1776325148.586863939", + "flags" : "0", + "gid" : "2763", + "mode" : "40750", + "mtime" : "1776325148.586445426", + "name" : ".sys.v#.test.md", + "parent_id" : "6201977", + "path" : "/eos/user/j/jgeens/uploads/.sys.v#.test.md/", + "stime" : "0.0", + "tree_size" : "0", + "uid" : "173503", + "xattr.sys.eos.btime" : "1776325148.586445426" +}, +{ + "cid" : "6570193", + "ctime" : "1776325148.589033233", + "flags" : "0", + "gid" : "2763", + "mode" : "40750", + "mtime" : "1776325148.588638339", + "name" : ".sys.v#.test2.md", + "parent_id" : "6201977", + "path" : "/eos/user/j/jgeens/uploads/.sys.v#.test2.md/", + "stime" : "0.0", + "tree_size" : "0", + "uid" : "173503", + "xattr.sys.eos.btime" : "1776325148.588638339" +}, +{ + "cid" : "6434038", + "ctime" : "1776263540.272706426", + "flags" : "0", + "gid" : "2763", + "mode" : "40750", + "mtime" : "1782200206.261464720", + "name" : "folder", + "parent_id" : "6201977", + "path" : "/eos/user/j/jgeens/uploads/folder/", + "stime" : "0.0", + "tree_size" : "5622656", + "uid" : "173503", + "xattr.sys.acl" : "u:58679:rxt,u:187895:rx,u:173503:rwx,u:4179:rx!d,u:193339:rwx+d", + "xattr.sys.allow.oc.sync" : "1", + "xattr.sys.eos.btime" : "1677602740.101786072", + "xattr.sys.forced.atomic" : "1", + "xattr.sys.forced.blocksize" : "4k", + "xattr.sys.forced.checksum" : "adler", + "xattr.sys.forced.layout" : "replica", + "xattr.sys.forced.nstripes" : "2", + "xattr.sys.forced.space" : "default", + "xattr.sys.mask" : "700", + "xattr.sys.mtime.propagation" : "1", + "xattr.sys.owner.auth" : "*", + "xattr.sys.recycle" : "/eos/homedev/proc/recycle/", + "xattr.sys.reva.lwshare.111857693350341032552@google" : "rwx+d", + "xattr.sys.reva.lwshare.guest:jesse.geens@proton.me" : "rwx+d", + "xattr.sys.versioning" : "10", + "xattr.user.http://owncloud.org/ns/favorite" : "u:jgeens=1" +}, +{ + "atime" : "1767971285.633690842", + "ctime" : "1772114797.757475277", + "fid" : "113265522", + "flags" : "644", + "gid" : "2763", + "layout_id" : "1048850", + "link_name" : "", + "locations" : "418,414", + "mtime" : "1767971285.633983599", + "name" : "000237740001_fesbb2.jpg", + "path" : "/eos/user/j/jgeens/uploads/folder/000237740001_fesbb2.jpg", + "pid" : "6434038", + "size" : "5622656", + "stime" : "0.0", + "uid" : "173503", + "unlink_locations" : "", + "xattr.sys.eos.btime" : "1767971285.633690180", + "xattr.sys.fs.tracking" : "+415+413/413/415", + "xattr.sys.fusex.state" : "", + "xattr.sys.utrace" : "0009932a-ed6d-11f0-ad4e-fa163e2e9155", + "xattr.sys.vtrace" : "[Fri Jan 9 16:08:05 2026] uid:173503[jgeens] gid:2763[it] tident:jgeens.1:1@[2001:1458:d00:13::100:2be] name:jgeens dn: prot:https app:http/reva_write host:cbox-ocisdev-jesse.cern.ch domain:cern.ch geo: sudo:0 trace: onbehalf:", + "xs" : "653e7cd3" +}, +{ + "cid" : "6747868", + "ctime" : "1782200206.261929393", + "flags" : "0", + "gid" : "2763", + "mode" : "40750", + "mtime" : "1782200206.261464720", + "name" : ".sys.v#.000237740001_fesbb2.jpg", + "parent_id" : "6434038", + "path" : "/eos/user/j/jgeens/uploads/folder/.sys.v#.000237740001_fesbb2.jpg/", + "stime" : "0.0", + "tree_size" : "0", + "uid" : "173503", + "xattr.sys.eos.btime" : "1782200206.261464720" +}] From 51e4c7d7ab44e2949bf1fbd1e2c486ec904d4366 Mon Sep 17 00:00:00 2001 From: Jesse Geens Date: Wed, 26 Aug 2026 22:54:21 +0200 Subject: [PATCH 05/10] WIP --- pkg/reconciliation/deep.go | 29 ++++++++++++++------- pkg/reconciliation/deep_test.go | 4 +-- pkg/reconciliation/nsdump/file_ns_dump.go | 22 +++++++--------- pkg/reconciliation/nsdump/memory_ns_dump.go | 21 ++++++++------- pkg/reconciliation/nsdump/ns_dump.go | 2 +- 5 files changed, 45 insertions(+), 33 deletions(-) diff --git a/pkg/reconciliation/deep.go b/pkg/reconciliation/deep.go index e0469b798c..cae89bdaf3 100644 --- a/pkg/reconciliation/deep.go +++ b/pkg/reconciliation/deep.go @@ -38,7 +38,9 @@ import ( const JobName = "reconciliation.deep" -type EntryResult struct { +type DeepJob struct { + shareMgr ShareStore + gw gateway.GatewayAPIClient } type ChangeSet []*Change @@ -49,11 +51,6 @@ type Change struct { ACL *acl.Entry } -type DeepJob struct { - shareMgr ShareStore - gw gateway.GatewayAPIClient -} - type RunParameters struct { SpaceID string SpaceType spaces.SpaceType @@ -71,11 +68,25 @@ func (s *ShareWithPath) toTreeNode() *ACLNode { } } -func (j *DeepJob) run(ctx context.Context, p RunParameters) error { +func (j *DeepJob) Run(ctx context.Context, p RunParameters) error { + path, err := spaces.DecodeSpaceID(p.SpaceID) + if err != nil { + return err + } + + statRes, err := j.gw.Stat(ctx, &provider.StatRequest{Ref: &provider.Reference{ + Path: path, + }}) + + if err != nil { + return err + } + + // TODO(jgeens): check statRes status code namespaceDumper := &nsdump.EOSMemoryNSInspect{} - err := namespaceDumper.Setup(map[string]any{ - // TODO(jgeens): set config for dump + err = namespaceDumper.Setup(nsdump.EOSMemoryNSInspectConfig{ + Instance: statRes.Info.Id.StorageId, }) if err != nil { return err diff --git a/pkg/reconciliation/deep_test.go b/pkg/reconciliation/deep_test.go index e9eaed06c9..9346c52dc5 100644 --- a/pkg/reconciliation/deep_test.go +++ b/pkg/reconciliation/deep_test.go @@ -375,7 +375,7 @@ type failingDumper struct { err error } -func (d *failingDumper) Setup(config map[string]any) error { return nil } +func (d *failingDumper) Setup(config any) error { return nil } func (d *failingDumper) Dump(rootPath string, maxDepth int) (*nsdump.NamespaceDump, error) { return nil, d.err @@ -548,7 +548,7 @@ type memoryDumper struct { dump *nsdump.NamespaceDump } -func (d *memoryDumper) Setup(config map[string]any) error { return nil } +func (d *memoryDumper) Setup(config any) error { return nil } func (d *memoryDumper) Dump(rootPath string, maxDepth int) (*nsdump.NamespaceDump, error) { return d.dump, nil diff --git a/pkg/reconciliation/nsdump/file_ns_dump.go b/pkg/reconciliation/nsdump/file_ns_dump.go index 7f5b1b206a..d914e577cb 100644 --- a/pkg/reconciliation/nsdump/file_ns_dump.go +++ b/pkg/reconciliation/nsdump/file_ns_dump.go @@ -1,26 +1,24 @@ package nsdump import ( + "fmt" "os" - - "github.com/pkg/errors" ) type EOSFileNSInspect struct { file string } -func (e *EOSFileNSInspect) Setup(config map[string]any) error { - v, ok := config["file"] - if !ok { - return errors.New("file parameter must be present") - } - s, ok := v.(string) - if !ok { - return errors.New("file parameter must be a string representing a file path") +type EOSFileNSInspectConfig struct { + File string +} + +func (e *EOSFileNSInspect) Setup(config any) error { + if c, ok := config.(EOSFileNSInspectConfig); ok { + e.file = c.File + return nil } - e.file = s - return nil + return fmt.Errorf("requires a config of type %t", EOSFileNSInspectConfig{}) } func (e *EOSFileNSInspect) Dump(rootPath string, maxDepth int) (*NamespaceDump, error) { diff --git a/pkg/reconciliation/nsdump/memory_ns_dump.go b/pkg/reconciliation/nsdump/memory_ns_dump.go index c9b1cb819f..70dc09faf7 100644 --- a/pkg/reconciliation/nsdump/memory_ns_dump.go +++ b/pkg/reconciliation/nsdump/memory_ns_dump.go @@ -8,24 +8,27 @@ import ( ) type EOSMemoryNSInspectConfig struct { - maxDepth int `mapstructure:"maxdepth"` - ignoreFiles bool `mapstructure:"ignorefiles"` - instance string `mapstructure:"instance"` + MaxDepth int `mapstructure:"maxdepth"` + IgnoreFiles bool `mapstructure:"ignorefiles"` + Instance string `mapstructure:"instance"` } type EOSMemoryNSInspect struct { cfg EOSMemoryNSInspectConfig } -func (e *EOSMemoryNSInspect) Setup(config map[string]any) error { - // TODO(jgeens): implement - return nil +func (e *EOSMemoryNSInspect) Setup(config any) error { + if c, ok := config.(EOSMemoryNSInspectConfig); ok { + e.cfg = c + return nil + } + return fmt.Errorf("requires a config of type %t", EOSMemoryNSInspectConfig{}) } func (e *EOSMemoryNSInspect) Dump(rootPath string, maxDepth int) (*NamespaceDump, error) { noFilesFlag := "" - if e.cfg.ignoreFiles { + if e.cfg.IgnoreFiles { noFilesFlag = " --no-files" } @@ -38,8 +41,8 @@ func (e *EOSMemoryNSInspect) Dump(rootPath string, maxDepth int) (*NamespaceDump "scan --path %s %s --members %s-qdb:7777 --password-file /keytabs/%s_keytab --json%s", rootPath, maxDepthFlag, - e.cfg.instance, - e.cfg.instance, + e.cfg.Instance, + e.cfg.Instance, noFilesFlag, ) diff --git a/pkg/reconciliation/nsdump/ns_dump.go b/pkg/reconciliation/nsdump/ns_dump.go index f16778231c..b9b032c875 100644 --- a/pkg/reconciliation/nsdump/ns_dump.go +++ b/pkg/reconciliation/nsdump/ns_dump.go @@ -14,7 +14,7 @@ type NamespaceDump struct { type NSDumpClient interface { Dump(rootPath string, maxDepth int) (*NamespaceDump, error) - Setup(config map[string]any) error + Setup(config any) error } type EntryType string From 87e8d4c010b0e69f88a73d50937073a051d5f18e Mon Sep 17 00:00:00 2001 From: Jesse Geens Date: Tue, 18 Aug 2026 10:02:01 +0200 Subject: [PATCH 06/10] skip space if it has new shares since starting --- plan.md | 350 -------------------------------------------------------- 1 file changed, 350 deletions(-) delete mode 100644 plan.md diff --git a/plan.md b/plan.md deleted file mode 100644 index d1b9985fec..0000000000 --- a/plan.md +++ /dev/null @@ -1,350 +0,0 @@ -# Share reconciliation implementation plan - -## Context - -We run a service called CERNBox, similar to Google Drive. It uses a backend server called Reva. Reva consists of multiple microservices, which are interconnected via standardized API's called the CS3APIs. To do operator-only operations, we have a dedicated tool, called cernboxcop. - -The problem we are trying to solve is the following. Users can share resources with other users. This results in an ACL entry on the storage, as well as an entry in the database. For multiple reasons, these can diverge. The goal is to implement algorithms that fix this divergence. - -Some background info: storage in CERNBox is split up across "spaces". Spaces are completely disjunct. There are two types of spaces to take into account: personal ones (one per user account), and project spaces (for collaborative work between users). - -Shares can also go to different recipients. Specifically, there are -1) CERN user acounts. These go directly in the actual ACLs. -2) Groups. Also go in the ACLs. In the share definition, recipient type (user or group) is determined by share_with_is_group -3) External accounts. Do not go in the native EOS acls, but have a dedicated attribute: `sys.reva.lwshare.=` - -Users can be resolved via the reva gateway. ACLs (native ones, but also lightweight) should all be set via the CS3API. Note that even lightweight ACLs should be set via AddACL. - -We use a three-level approach. -1. The first is to detect shares in the database that are no longer valid. This can be because the file has been deleted, the recipient no longer exists, etc. We mark these invalid shares as "orphan". - -2. The second is to list all spaces. Then for every space, we reconstruct what the ACLs *should* be on all the paths that are shared. Then, we check these paths and set the correctly if there is any difference. - -3. Check the whole namespace. We once again list all the spaces. But then we use `eos-ns-inspect` and compare the whole namespace against what the database tells us - -Note that spaces will have default ACLs. There are global default ACLs that are *allowed* to be anywhere but don't have to (`cbackeosro` and `cboxexternal`). Then for user spaces the owner of the space HAS to be everywhere. And for project spaces, there are three groups (one for readers, writers and admins each) that also have to be everywhere. - -We should have an extremely extensive test suite. This test suite should cover the three levels. Every time, we need tests for: -* every recipient type -* all combo's of ACLs (see share hierarchy for possibilities) -* real eos-ns-inspect output parsing - -Everything should also be very configurable. For example, we want to be able to set path_prefixes and map these to default ACL's, and we want to be able to say if they *can* be there or *should* be there. - -Implementation details: -* We want three levels to run as three different jobs -* Please think about where the best place would be to put the code for the jobs. Perhaps together with the share hierarchy? Or under EOS (although this should also work for other storage drivers)? -* Use Reva's built-in jobs framework for running the jobs -* We already have a half-baked implementation under ~/Code/cernboxcop. This might be useful to inspect for the eos-ns-inspect code. -* Implement a dry_run mode so we can do dry runs on production data without modifying anything - -## Implementation strategy - -### Where the code lives - -Put the reconciliation engine in a new top-level package `pkg/reconciliation`, not under -`pkg/storage/fs/eos` and not folded into `pkg/sharehierarchy`. - -Reasoning: -* The three jobs are cross-cutting: they read the share DB (`pkg/share/manager/sql`), - resolve identities through the gateway, list spaces, and mutate ACLs. None of those - belong to a single storage driver, so `pkg/storage/fs/eos` is the wrong home. The - requirement that this "should also work for other storage drivers" makes the driver - package a dead end. -* `pkg/sharehierarchy` already owns the permission-ordering algebra (`PermLevel`, - `PermLevelFromCS3`, ancestor/descendant resolution). Reconciliation *reuses* that, but - it is a much bigger surface (namespace scanning, orphan detection, ACL diffing, jobs). - Keep `sharehierarchy` as the pure algorithm library and let `pkg/reconciliation` depend - on it. Do not grow `sharehierarchy` into a jobs package. - -The one genuinely EOS-specific piece is reading the EOS namespace for level 3, which is what -`eos-ns-inspect` does. That does **not** live under `pkg/reconciliation`. It belongs with the -rest of the EOS driver under `pkg/storage/fs/eos`, exactly like the existing eos client, -grant, and recycle code. `pkg/reconciliation` only defines the `NamespaceScanner` interface -and a small registry; the EOS driver provides the concrete scanner and registers it from its -own `init()`, the same `Register(name, NewFunc)` + `loader.go` pattern reva already uses for -storage backends (`pkg/storage/fs/registry`). This keeps EOS code with EOS and keeps the -engine free of any EOS import. - -What "read the EOS namespace" means, and why it is not the MGM gRPC API (established while -writing this plan, from the EOS source under `~/Code/eos`): -* `eos-ns-inspect` reads **QDB directly** (QuarkDB: RocksDB behind a Redis-protocol server), - not the MGM. `Find` / the reva `EOSClient.ListWithRegex` gRPC call goes through the running - MGM and is a different operation; it is not a substitute for a whole-namespace audit and - would load the live MGM. So level 3 uses a QDB-reading scanner, not gRPC. -* In QDB the namespace is protobuf blobs: all container MDs under one "locality hash" key, - all file MDs under another, read with QDB-custom commands (`LHGET`/`LHSCAN`), with - parent/child links in standard Redis hashes `:map_conts` and `:map_files` - (`HSCAN`/`HGET`), root container id = 1. Each `FileMdProto` / `ContainerMdProto` - (`proto/namespace/ns_quarkdb/{FileMd,ContainerMd}.proto`) carries the `xattrs` map, where - `sys.acl` and the lightweight `sys.reva.lwshare.` entries live, plus uid/gid/name. - -Decision (see "Open questions"): define the `NamespaceScanner` interface now and, for now, -ship a single EOS implementation behind it that shells out to the binary. A native QDB reader -is a possible future option, not part of this work: -* `eos-nsinspect-binary` (what we build): exec the version-matched `eos-ns-inspect scan ... --json` - binary and parse its output, as cernboxcop does. Least code, no coupling to the QDB on-disk - schema, fastest path to a working level 3. Needs the binary and keytab on the host running - the job. This is the only scanner we implement now. -* Native QDB reader (**could consider later**, not scheduled): a Go QDB reader that does what - the binary does in-process: a redis-protocol client issuing `LHSCAN`/`HSCAN`/`LHGET`, the QDB - **HMAC challenge-response handshake** for auth (QDB does not use plain redis `AUTH`; this is - the fiddliest part, would be ported from `qclient`), generated Go types from the two `.proto` - files, and the flat or tree scan. It would remove the external-binary dependency and be fully - unit-testable with recorded QDB responses, at the cost of tracking the EOS QDB schema across - releases. Worth revisiting only if the on-host binary dependency becomes a real operational - problem. -The point of the `NamespaceScanner` interface is that if that native reader is ever built, it -drops in behind the same interface: level 3 and its tests do not change, only the registered -scanner name in config would. - -Decisions taken (see "Open questions" answers): -* Levels 2 and 3 are separate jobs. They share only the "what should the ACL be" computation - (the `expected_acls.go` + `planner.go` pair below), not their scan or scheduling. -* Orphan detection (level 1) resolves recipients and resources through the gateway (CS3), - not by reading EOS or the DB directly. Driver-agnostic and consistent with the rest. - -Proposed layout. Two files carry the naming that needs explaining up front: -* `expected_acls.go` is the pure function "given the shares and defaults for a space, what - ACL entries *should* exist on each path". This is the piece shared between levels 2 and 3. -* `planner.go` diffs those expected ACLs against the *observed* ACLs and produces a `Plan`: - the ordered list of add/remove/update actions. `applier.go` then executes a `Plan`. - -``` -pkg/reconciliation/ - reconcile.go // shared types: Space, Recipient, ExpectedACL, Plan, Action, Outcome - config.go // Config + ApplyDefaults, path_prefix -> default-ACL rules (can/should) - default_acls.go // default-ACL computation per space type (owner, project egroups, globals) - expected_acls.go // pure: (shares for a space) + defaults -> expected ACL set per path - // (shared by levels 2 and 3; wraps sharehierarchy) - planner.go // pure: expected ACLs vs observed ACLs -> Plan of add/remove/update - applier.go // executes a Plan via the CS3 grant API; honours dry_run - identity.go // recipient resolution + classification (user / group / lightweight) - scanner.go // NamespaceScanner interface + Register/registry (driver-agnostic) - orphans.go // level 1 - space_acls.go // level 2 - namespace.go // level 3 (depends on the interface, never on the EOS impl) - jobs/ - orphans_job.go // rjobs on-demand + periodic registration for level 1 - spaceacls_job.go // level 2 - namespace_job.go // level 3 - -pkg/storage/fs/eos/ // EOS-specific scanner lives with the EOS driver - nsscan_loader.go // registers the scanner in init() (Register pattern) - nsscan_binary.go // eos-nsinspect-binary: exec eos-ns-inspect + JSON parser - // (ported from cernboxcop); the only scanner we build now - testdata/nsinspect/ // captured real eos-ns-inspect JSON (binary scanner tests) - // A native QDB reader (nsscan_qdb.go + a qdb/ client package) could be added here later - // behind the same interface, but is out of scope for now. -``` - -The scanner sits behind the `NamespaceScanner` interface defined in `pkg/reconciliation`. -Level 3 depends only on that interface and looks the scanner up by name from the registry -(config: `scanner = "eos-nsinspect-binary"`). The concrete implementation lives under -`pkg/storage/fs/eos` and registers itself at init time, so `pkg/reconciliation` never imports -the EOS driver. A storage driver that cannot enumerate its namespace simply registers no -scanner, and level 3 is a no-op for its spaces; levels 1 and 2 stay driver-agnostic by going -through CS3. - -### Reuse from cernboxcop - -Port, do not import, from `~/Code/cernboxcop/pkg`: -* `eos/ns_inspect.go`: the `eos-ns-inspect scan ... --json` command builder and the - `CommonEntry` / `DirEntry` / `FileEntry` parser. This is the `eos-nsinspect-binary` scanner - and lands in `pkg/storage/fs/eos` (`nsscan_binary.go`), not in `pkg/reconciliation`. Keep - the `prefetchedData` path: it is what makes real-output parsing testable and enables dry - runs against a captured snapshot. -* `reconciliation/set_operations.go` and `acl_change_set.go`: the ACL set-diff - (add / remove / update) logic is sound and becomes the core of `planner.go`. -* `reconciliation/permission_store.go` and `deep_fs.go`: the "reconstruct expected ACLs by - walking parent shares" idea, reworked to use `sharehierarchy` for the permission ordering - instead of the ad-hoc `rx`/`rwx` string comparison, and to be space-scoped from the start. - -Fixes to make while porting: -* Replace `os/user.Lookup` and hardcoded `/eos/user/...` path math with gateway-based - identity resolution and the space's own root path. -* Do not shell to EOS for mutations. `acl_change_set.go` currently calls the EOS client - directly; route every mutation through the CS3 grant API (see below). -* Space isolation is mandatory. Every DB read filters by `space_id` (`SpaceIDFilter`), and - hierarchy never crosses a space boundary. See the existing memory on share space isolation. - -### Setting ACLs: always through CS3 - -All mutations go through the gateway grant API, never the EOS binary directly: -* Native user/group ACLs and lightweight (external) ACLs are all set with `AddGrant` / - `RemoveGrant` / `UpdateGrant` / `DenyGrant` on the storage provider. The EOS driver - (`pkg/storage/fs/eos/grant.go`) already routes lightweight accounts to the - `sys.reva.lwshare.` xattr and everything else to `sys.acl`, so the reconciler does - not need to know the on-disk encoding. This is what keeps it driver-agnostic and is also - the project rule (lightweight ACLs still go via AddGrant). -* Recipient classification drives the CS3 `Grantee`: - * `share_with_is_group == true` -> `GranteeType_GROUP`. - * external account (lightweight) -> user grantee whose id the driver recognises as - lightweight; the driver picks the xattr path. - * otherwise -> `GranteeType_USER`. -* Reconstruct `ResourcePermissions` from the DB `permissions` (OCS uint8) exactly as - `model.Share.AsCS3Share` does, then map through `sharehierarchy.PermLevelFromCS3` when we - need to compare levels. Permissions=0 is an active deny, not "no share" (see `PermDeny`). - -### The three jobs - -Each level is its own `rjobs` job, registered both on-demand (operator triggers a run, -optionally scoped to one space or user) and periodic (`ScopeLeader`, since they mutate -shared state and must fire once across replicas). Register under stable names, e.g. -`reconciliation.orphans`, `reconciliation.space_acls`, `reconciliation.namespace`. Config -per job comes from `[serverless.services.jobs.on_demand."reconciliation.namespace"]`. - -**Level 1: orphan detection (`orphans.go`).** -List DB shares (`ListModelShares`, including orphans) per space. A share is an orphan when -its resource no longer resolves (gateway Stat returns not-found / in recycling), or its -recipient no longer exists (gateway user/group lookup), or the space is gone. All three -checks go through the gateway (CS3), never a direct EOS or DB read, so this stays -driver-agnostic. Mark with `ShareMgr.MarkAsOrphaned`. No ACL writes, so it is cheap and safe -to run frequently. Public links reuse the same pass via `PublicShareMgr`. - -**Level 2: per-space expected-ACL reconstruction (`space_acls.go`).** -For each space: gather its non-orphan shares, group by grantee, and use `sharehierarchy` to -collapse each grantee's shares into the minimal correct ACL set per path (nearest-ancestor -wins, children with higher perms are re-applied). Add the space's default ACLs (below). -Stat each shared path, diff observed grants against expected with the planner, and apply. -This corrects the shared paths only; it does not walk the whole tree, so it is the routine -reconciler. - -**Level 3: full-namespace sweep (`namespace.go`).** -List spaces, then for each look up the configured `NamespaceScanner` from the registry (the -EOS one shells out to the `eos-ns-inspect` binary, which reads QDB) and scan the whole space -subtree. For every node compute expected ACLs = default ACLs for the space + inherited -share ACLs from the permission store, diff against the scanned `sys.acl` (and lightweight -xattrs), and apply. This is the expensive, authoritative sweep; schedule it `@daily`/`@weekly` -with jitter and `Skip` overlap. It catches drift on paths that no share touches anymore. - -Levels 2 and 3 are separate jobs but share `expected_acls.go` (what the ACLs should be) plus -`planner.go` and `applier.go` (diff and execute). They differ only in how they gather the -observed state (gateway Stat on shared paths vs. full eos-ns-inspect scan) and which node set -they cover. Level 1 shares none of this; it only reads and marks the DB. - -### Default ACLs and configuration - -`Config` (decoded from the job's config section) holds an ordered list of path-prefix rules: - -``` -[[path_prefix]] - prefix = "/eos/user" - [[path_prefix.default_acl]] - type = "u" # u | egroup | lw (see package acl) - qualifier = "{owner}" # may contain {owner} / {project} - permissions = "rwx" - enforcement = "must" # "may" (allowed anywhere) | "must" (required everywhere) - [[path_prefix.default_acl]] - type = "egroup" - qualifier = "cbackeosro" - permissions = "rx" - enforcement = "may" -``` - -A space is governed by the single rule whose prefix is a path prefix of its root. Prefixes may -not overlap (e.g. `/eos/user` vs `/eos/project`), so at most one rule matches and there is no -space_type or priority to reason about. The default ACL entry is given as explicit `type` / -`qualifier` / `permissions` rather than a single opaque token, so it is unambiguous and -validatable at config load. `default_acls.go` resolves the `{owner}` / `{project}` templates in -the qualifier per space. - -Semantics, matching the spec: -* Global defaults (`cbackeosro`, `cboxexternal`): `enforcement = "may"`. Present is fine, - absent is fine; never added, never removed by the reconciler. -* Personal space owner: `enforcement = "must"`, template resolves to the space owner uid. - Missing => add; the planner never removes a "must" entry. -* Project spaces: the readers/writers/admins egroups are three `must` entries, templated - from the project name. - -`default_acls.go` resolves templates (`{owner}`, `{project}`) against the space. The planner -treats `must` entries as always-expected and `may` entries as never-diffed (neither added -nor flagged), so a `may` entry present on disk is left untouched. - -### dry_run mode - -`dry_run` is a `Config` bool threaded into `applier.go`. When set, the applier logs and -records each intended `Action` (path, grantee, before/after) into the job's result `Params` -and the run status, and skips the CS3 call entirely. Level 3 additionally accepts a -`prefetched_scan` path so a captured `eos-ns-inspect` snapshot can be replayed offline, so we -can dry-run against production data without touching EOS or the DB. - -### Test suite - -Tests live beside each file plus an integration layer in `pkg/reconciliation`. Coverage per -the spec, driven by table tests: -* Every recipient type: CERN user, group (`share_with_is_group`), external/lightweight. - Assert each maps to the correct CS3 `Grantee` and, for lightweight, that the driver would - target the `sys.reva.lwshare.` xattr. -* Every ACL combination from the hierarchy: all ordered pairs of `{R, RW, Deny}` for - parent/child on nested paths, plus the re-apply and delete cases already covered by - `sharehierarchy` tests, now asserted end to end as `Plan` actions. -* Default-ACL rules: `may` present/absent (untouched), `must` present/absent (added), and - wrong-perms `must` (updated), for personal and project spaces. -* Real `eos-ns-inspect` output: commit captured JSON under - `pkg/storage/fs/eos/testdata/nsinspect` (personal and project space, files and folders, - sys entries, lightweight xattrs). Assert the binary scanner's parser (EOS-driver test) and, - feeding the scanner output into the engine, that the level-3 planner produces the expected - `Plan` (reconciliation test). Reuse the `prefetchedData` path so no QDB or MGM is needed in - CI. (If a native QDB scanner is ever added, it would get its own recorded-response tests and - a cross-check asserting it yields the identical node set and ACLs as the binary scanner.) -* Orphan detection: deleted resource, recycled resource, missing recipient, missing space. -* dry_run: assert no mutation is issued and the recorded actions match what a live run would - have applied (run planner once, apply in both modes, compare). - - -## Work breakdown - -We build the simplest thing that works first, then deepen. The strategy above describes the -eventual full system; the phases below are the build order. Each phase is a self-contained, -reviewable unit that compiles and has its own tests, and each is useful on its own. A later -phase never blocks an earlier one. - -Progress marker: `[x]` done, `[ ]` todo. - -**Phase 1: orphan job.** `[x]` done. -`orphan.go`: a periodic job that scans the share DB and marks a share orphaned -when its resource or its recipient no longer exists. It lists non-orphan shares via -`ListModelShares(nil, nil, hideOrphans=true)`; for each it checks the resource -(`gateway.Stat` on `{Instance, Inode}`) and the recipient (`GetUserByClaim` for users, -`GetGroupByClaim` for groups), then marks via `MarkAsOrphaned`. A lookup error is never -treated as absence: the share is skipped, never orphaned on uncertainty. `dry_run` reports -what would be marked without mutating. Runs `ScopeLeader` because it mutates shared DB state. -Consumer-defined `ShareStore` and `ExistenceChecker` interfaces keep the logic unit-testable: -`*sql.ShareMgr` satisfies the first; the concrete CS3 gateway-backed `ExistenceChecker` is -built at service-startup wiring time (with the gateway address from config), not in this -package, so phase 1 carries no dead wiring. -Missing-space is folded into the resource check for now (if the space is gone the resource -Stat fails); a dedicated space check can come later. -Tests: resource missing, user recipient missing, group recipient missing, all present, -dry_run marks nothing, lookup error skips (no false orphan), already-orphan shares excluded, -mixed batch, share-reference by id. - -**Phase 2: shallow check (DB only).** `[x]` done, except for the default ACLs. -`shallow.go`: reconcile the ACLs implied by the share DB against what is actually set on -each shared path, without a full-namespace scan. For each non-orphan share it resolves the -path (`gateway.GetPath`) and the recipient (`GetUserByClaim`, so the grantee carries the user -type the storage keys the lightweight xattr off), collapses the shares of one recipient in -one space and runs `sharehierarchy.CheckGrantConsistency` over each of them, the same check -that runs at share creation, so an entry is only written where it escalates beyond every share -above it. It then reads the grants on the node and adds or corrects the entry through the -storage provider grant API. Those calls are deliberately not on the gateway API, since a client -that wants to change access goes through CreateShare; the job asks the gateway which provider -hosts the storage and calls that provider directly, the way the gateway does internally. -It never removes an entry: telling a stray entry from a default ACL needs the whole namespace. -It never writes an entry weaker than one above it either, so a nested share can never lose -access to this job; a share that contradicts the hierarchy is reported, not enforced. -A lookup failure skips the share rather than writing an entry built on a guess. `dry_run`. -Targeted and per-share, so cost scales with the number of shares, not the size of the -namespace. -Still to do: the default ACLs are not applied to the visited paths. Computing them needs the -`Space` (root, type, owner, project) a share belongs to, and nothing enumerates spaces yet. -That enumeration is shared with phase 3 and lands with it, so the default-ACL config and the -per-space computation land with it too. - -**Phase 3: deep FS check (eos-ns-inspect).** `[ ]` -The whole-namespace sweep. Enumerate every node of a space directly from QuarkDB via -`eos-ns-inspect` (which reads the namespace, not the MGM), compare each node's actual -`sys.acl` against what the DB says it should be, and correct drift, including stray entries -that no share justifies. Behind a `NamespaceScanner` interface with the EOS binary scanner as -the first implementation; a native QDB reader could come later. `dry_run`. From a04db4c0e2908dac62c9f30f8bd4d35fa270d6b1 Mon Sep 17 00:00:00 2001 From: Jesse Geens Date: Tue, 4 Aug 2026 11:16:05 +0200 Subject: [PATCH 07/10] implement shallow job --- plan.md | 350 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 350 insertions(+) create mode 100644 plan.md diff --git a/plan.md b/plan.md new file mode 100644 index 0000000000..d1b9985fec --- /dev/null +++ b/plan.md @@ -0,0 +1,350 @@ +# Share reconciliation implementation plan + +## Context + +We run a service called CERNBox, similar to Google Drive. It uses a backend server called Reva. Reva consists of multiple microservices, which are interconnected via standardized API's called the CS3APIs. To do operator-only operations, we have a dedicated tool, called cernboxcop. + +The problem we are trying to solve is the following. Users can share resources with other users. This results in an ACL entry on the storage, as well as an entry in the database. For multiple reasons, these can diverge. The goal is to implement algorithms that fix this divergence. + +Some background info: storage in CERNBox is split up across "spaces". Spaces are completely disjunct. There are two types of spaces to take into account: personal ones (one per user account), and project spaces (for collaborative work between users). + +Shares can also go to different recipients. Specifically, there are +1) CERN user acounts. These go directly in the actual ACLs. +2) Groups. Also go in the ACLs. In the share definition, recipient type (user or group) is determined by share_with_is_group +3) External accounts. Do not go in the native EOS acls, but have a dedicated attribute: `sys.reva.lwshare.=` + +Users can be resolved via the reva gateway. ACLs (native ones, but also lightweight) should all be set via the CS3API. Note that even lightweight ACLs should be set via AddACL. + +We use a three-level approach. +1. The first is to detect shares in the database that are no longer valid. This can be because the file has been deleted, the recipient no longer exists, etc. We mark these invalid shares as "orphan". + +2. The second is to list all spaces. Then for every space, we reconstruct what the ACLs *should* be on all the paths that are shared. Then, we check these paths and set the correctly if there is any difference. + +3. Check the whole namespace. We once again list all the spaces. But then we use `eos-ns-inspect` and compare the whole namespace against what the database tells us + +Note that spaces will have default ACLs. There are global default ACLs that are *allowed* to be anywhere but don't have to (`cbackeosro` and `cboxexternal`). Then for user spaces the owner of the space HAS to be everywhere. And for project spaces, there are three groups (one for readers, writers and admins each) that also have to be everywhere. + +We should have an extremely extensive test suite. This test suite should cover the three levels. Every time, we need tests for: +* every recipient type +* all combo's of ACLs (see share hierarchy for possibilities) +* real eos-ns-inspect output parsing + +Everything should also be very configurable. For example, we want to be able to set path_prefixes and map these to default ACL's, and we want to be able to say if they *can* be there or *should* be there. + +Implementation details: +* We want three levels to run as three different jobs +* Please think about where the best place would be to put the code for the jobs. Perhaps together with the share hierarchy? Or under EOS (although this should also work for other storage drivers)? +* Use Reva's built-in jobs framework for running the jobs +* We already have a half-baked implementation under ~/Code/cernboxcop. This might be useful to inspect for the eos-ns-inspect code. +* Implement a dry_run mode so we can do dry runs on production data without modifying anything + +## Implementation strategy + +### Where the code lives + +Put the reconciliation engine in a new top-level package `pkg/reconciliation`, not under +`pkg/storage/fs/eos` and not folded into `pkg/sharehierarchy`. + +Reasoning: +* The three jobs are cross-cutting: they read the share DB (`pkg/share/manager/sql`), + resolve identities through the gateway, list spaces, and mutate ACLs. None of those + belong to a single storage driver, so `pkg/storage/fs/eos` is the wrong home. The + requirement that this "should also work for other storage drivers" makes the driver + package a dead end. +* `pkg/sharehierarchy` already owns the permission-ordering algebra (`PermLevel`, + `PermLevelFromCS3`, ancestor/descendant resolution). Reconciliation *reuses* that, but + it is a much bigger surface (namespace scanning, orphan detection, ACL diffing, jobs). + Keep `sharehierarchy` as the pure algorithm library and let `pkg/reconciliation` depend + on it. Do not grow `sharehierarchy` into a jobs package. + +The one genuinely EOS-specific piece is reading the EOS namespace for level 3, which is what +`eos-ns-inspect` does. That does **not** live under `pkg/reconciliation`. It belongs with the +rest of the EOS driver under `pkg/storage/fs/eos`, exactly like the existing eos client, +grant, and recycle code. `pkg/reconciliation` only defines the `NamespaceScanner` interface +and a small registry; the EOS driver provides the concrete scanner and registers it from its +own `init()`, the same `Register(name, NewFunc)` + `loader.go` pattern reva already uses for +storage backends (`pkg/storage/fs/registry`). This keeps EOS code with EOS and keeps the +engine free of any EOS import. + +What "read the EOS namespace" means, and why it is not the MGM gRPC API (established while +writing this plan, from the EOS source under `~/Code/eos`): +* `eos-ns-inspect` reads **QDB directly** (QuarkDB: RocksDB behind a Redis-protocol server), + not the MGM. `Find` / the reva `EOSClient.ListWithRegex` gRPC call goes through the running + MGM and is a different operation; it is not a substitute for a whole-namespace audit and + would load the live MGM. So level 3 uses a QDB-reading scanner, not gRPC. +* In QDB the namespace is protobuf blobs: all container MDs under one "locality hash" key, + all file MDs under another, read with QDB-custom commands (`LHGET`/`LHSCAN`), with + parent/child links in standard Redis hashes `:map_conts` and `:map_files` + (`HSCAN`/`HGET`), root container id = 1. Each `FileMdProto` / `ContainerMdProto` + (`proto/namespace/ns_quarkdb/{FileMd,ContainerMd}.proto`) carries the `xattrs` map, where + `sys.acl` and the lightweight `sys.reva.lwshare.` entries live, plus uid/gid/name. + +Decision (see "Open questions"): define the `NamespaceScanner` interface now and, for now, +ship a single EOS implementation behind it that shells out to the binary. A native QDB reader +is a possible future option, not part of this work: +* `eos-nsinspect-binary` (what we build): exec the version-matched `eos-ns-inspect scan ... --json` + binary and parse its output, as cernboxcop does. Least code, no coupling to the QDB on-disk + schema, fastest path to a working level 3. Needs the binary and keytab on the host running + the job. This is the only scanner we implement now. +* Native QDB reader (**could consider later**, not scheduled): a Go QDB reader that does what + the binary does in-process: a redis-protocol client issuing `LHSCAN`/`HSCAN`/`LHGET`, the QDB + **HMAC challenge-response handshake** for auth (QDB does not use plain redis `AUTH`; this is + the fiddliest part, would be ported from `qclient`), generated Go types from the two `.proto` + files, and the flat or tree scan. It would remove the external-binary dependency and be fully + unit-testable with recorded QDB responses, at the cost of tracking the EOS QDB schema across + releases. Worth revisiting only if the on-host binary dependency becomes a real operational + problem. +The point of the `NamespaceScanner` interface is that if that native reader is ever built, it +drops in behind the same interface: level 3 and its tests do not change, only the registered +scanner name in config would. + +Decisions taken (see "Open questions" answers): +* Levels 2 and 3 are separate jobs. They share only the "what should the ACL be" computation + (the `expected_acls.go` + `planner.go` pair below), not their scan or scheduling. +* Orphan detection (level 1) resolves recipients and resources through the gateway (CS3), + not by reading EOS or the DB directly. Driver-agnostic and consistent with the rest. + +Proposed layout. Two files carry the naming that needs explaining up front: +* `expected_acls.go` is the pure function "given the shares and defaults for a space, what + ACL entries *should* exist on each path". This is the piece shared between levels 2 and 3. +* `planner.go` diffs those expected ACLs against the *observed* ACLs and produces a `Plan`: + the ordered list of add/remove/update actions. `applier.go` then executes a `Plan`. + +``` +pkg/reconciliation/ + reconcile.go // shared types: Space, Recipient, ExpectedACL, Plan, Action, Outcome + config.go // Config + ApplyDefaults, path_prefix -> default-ACL rules (can/should) + default_acls.go // default-ACL computation per space type (owner, project egroups, globals) + expected_acls.go // pure: (shares for a space) + defaults -> expected ACL set per path + // (shared by levels 2 and 3; wraps sharehierarchy) + planner.go // pure: expected ACLs vs observed ACLs -> Plan of add/remove/update + applier.go // executes a Plan via the CS3 grant API; honours dry_run + identity.go // recipient resolution + classification (user / group / lightweight) + scanner.go // NamespaceScanner interface + Register/registry (driver-agnostic) + orphans.go // level 1 + space_acls.go // level 2 + namespace.go // level 3 (depends on the interface, never on the EOS impl) + jobs/ + orphans_job.go // rjobs on-demand + periodic registration for level 1 + spaceacls_job.go // level 2 + namespace_job.go // level 3 + +pkg/storage/fs/eos/ // EOS-specific scanner lives with the EOS driver + nsscan_loader.go // registers the scanner in init() (Register pattern) + nsscan_binary.go // eos-nsinspect-binary: exec eos-ns-inspect + JSON parser + // (ported from cernboxcop); the only scanner we build now + testdata/nsinspect/ // captured real eos-ns-inspect JSON (binary scanner tests) + // A native QDB reader (nsscan_qdb.go + a qdb/ client package) could be added here later + // behind the same interface, but is out of scope for now. +``` + +The scanner sits behind the `NamespaceScanner` interface defined in `pkg/reconciliation`. +Level 3 depends only on that interface and looks the scanner up by name from the registry +(config: `scanner = "eos-nsinspect-binary"`). The concrete implementation lives under +`pkg/storage/fs/eos` and registers itself at init time, so `pkg/reconciliation` never imports +the EOS driver. A storage driver that cannot enumerate its namespace simply registers no +scanner, and level 3 is a no-op for its spaces; levels 1 and 2 stay driver-agnostic by going +through CS3. + +### Reuse from cernboxcop + +Port, do not import, from `~/Code/cernboxcop/pkg`: +* `eos/ns_inspect.go`: the `eos-ns-inspect scan ... --json` command builder and the + `CommonEntry` / `DirEntry` / `FileEntry` parser. This is the `eos-nsinspect-binary` scanner + and lands in `pkg/storage/fs/eos` (`nsscan_binary.go`), not in `pkg/reconciliation`. Keep + the `prefetchedData` path: it is what makes real-output parsing testable and enables dry + runs against a captured snapshot. +* `reconciliation/set_operations.go` and `acl_change_set.go`: the ACL set-diff + (add / remove / update) logic is sound and becomes the core of `planner.go`. +* `reconciliation/permission_store.go` and `deep_fs.go`: the "reconstruct expected ACLs by + walking parent shares" idea, reworked to use `sharehierarchy` for the permission ordering + instead of the ad-hoc `rx`/`rwx` string comparison, and to be space-scoped from the start. + +Fixes to make while porting: +* Replace `os/user.Lookup` and hardcoded `/eos/user/...` path math with gateway-based + identity resolution and the space's own root path. +* Do not shell to EOS for mutations. `acl_change_set.go` currently calls the EOS client + directly; route every mutation through the CS3 grant API (see below). +* Space isolation is mandatory. Every DB read filters by `space_id` (`SpaceIDFilter`), and + hierarchy never crosses a space boundary. See the existing memory on share space isolation. + +### Setting ACLs: always through CS3 + +All mutations go through the gateway grant API, never the EOS binary directly: +* Native user/group ACLs and lightweight (external) ACLs are all set with `AddGrant` / + `RemoveGrant` / `UpdateGrant` / `DenyGrant` on the storage provider. The EOS driver + (`pkg/storage/fs/eos/grant.go`) already routes lightweight accounts to the + `sys.reva.lwshare.` xattr and everything else to `sys.acl`, so the reconciler does + not need to know the on-disk encoding. This is what keeps it driver-agnostic and is also + the project rule (lightweight ACLs still go via AddGrant). +* Recipient classification drives the CS3 `Grantee`: + * `share_with_is_group == true` -> `GranteeType_GROUP`. + * external account (lightweight) -> user grantee whose id the driver recognises as + lightweight; the driver picks the xattr path. + * otherwise -> `GranteeType_USER`. +* Reconstruct `ResourcePermissions` from the DB `permissions` (OCS uint8) exactly as + `model.Share.AsCS3Share` does, then map through `sharehierarchy.PermLevelFromCS3` when we + need to compare levels. Permissions=0 is an active deny, not "no share" (see `PermDeny`). + +### The three jobs + +Each level is its own `rjobs` job, registered both on-demand (operator triggers a run, +optionally scoped to one space or user) and periodic (`ScopeLeader`, since they mutate +shared state and must fire once across replicas). Register under stable names, e.g. +`reconciliation.orphans`, `reconciliation.space_acls`, `reconciliation.namespace`. Config +per job comes from `[serverless.services.jobs.on_demand."reconciliation.namespace"]`. + +**Level 1: orphan detection (`orphans.go`).** +List DB shares (`ListModelShares`, including orphans) per space. A share is an orphan when +its resource no longer resolves (gateway Stat returns not-found / in recycling), or its +recipient no longer exists (gateway user/group lookup), or the space is gone. All three +checks go through the gateway (CS3), never a direct EOS or DB read, so this stays +driver-agnostic. Mark with `ShareMgr.MarkAsOrphaned`. No ACL writes, so it is cheap and safe +to run frequently. Public links reuse the same pass via `PublicShareMgr`. + +**Level 2: per-space expected-ACL reconstruction (`space_acls.go`).** +For each space: gather its non-orphan shares, group by grantee, and use `sharehierarchy` to +collapse each grantee's shares into the minimal correct ACL set per path (nearest-ancestor +wins, children with higher perms are re-applied). Add the space's default ACLs (below). +Stat each shared path, diff observed grants against expected with the planner, and apply. +This corrects the shared paths only; it does not walk the whole tree, so it is the routine +reconciler. + +**Level 3: full-namespace sweep (`namespace.go`).** +List spaces, then for each look up the configured `NamespaceScanner` from the registry (the +EOS one shells out to the `eos-ns-inspect` binary, which reads QDB) and scan the whole space +subtree. For every node compute expected ACLs = default ACLs for the space + inherited +share ACLs from the permission store, diff against the scanned `sys.acl` (and lightweight +xattrs), and apply. This is the expensive, authoritative sweep; schedule it `@daily`/`@weekly` +with jitter and `Skip` overlap. It catches drift on paths that no share touches anymore. + +Levels 2 and 3 are separate jobs but share `expected_acls.go` (what the ACLs should be) plus +`planner.go` and `applier.go` (diff and execute). They differ only in how they gather the +observed state (gateway Stat on shared paths vs. full eos-ns-inspect scan) and which node set +they cover. Level 1 shares none of this; it only reads and marks the DB. + +### Default ACLs and configuration + +`Config` (decoded from the job's config section) holds an ordered list of path-prefix rules: + +``` +[[path_prefix]] + prefix = "/eos/user" + [[path_prefix.default_acl]] + type = "u" # u | egroup | lw (see package acl) + qualifier = "{owner}" # may contain {owner} / {project} + permissions = "rwx" + enforcement = "must" # "may" (allowed anywhere) | "must" (required everywhere) + [[path_prefix.default_acl]] + type = "egroup" + qualifier = "cbackeosro" + permissions = "rx" + enforcement = "may" +``` + +A space is governed by the single rule whose prefix is a path prefix of its root. Prefixes may +not overlap (e.g. `/eos/user` vs `/eos/project`), so at most one rule matches and there is no +space_type or priority to reason about. The default ACL entry is given as explicit `type` / +`qualifier` / `permissions` rather than a single opaque token, so it is unambiguous and +validatable at config load. `default_acls.go` resolves the `{owner}` / `{project}` templates in +the qualifier per space. + +Semantics, matching the spec: +* Global defaults (`cbackeosro`, `cboxexternal`): `enforcement = "may"`. Present is fine, + absent is fine; never added, never removed by the reconciler. +* Personal space owner: `enforcement = "must"`, template resolves to the space owner uid. + Missing => add; the planner never removes a "must" entry. +* Project spaces: the readers/writers/admins egroups are three `must` entries, templated + from the project name. + +`default_acls.go` resolves templates (`{owner}`, `{project}`) against the space. The planner +treats `must` entries as always-expected and `may` entries as never-diffed (neither added +nor flagged), so a `may` entry present on disk is left untouched. + +### dry_run mode + +`dry_run` is a `Config` bool threaded into `applier.go`. When set, the applier logs and +records each intended `Action` (path, grantee, before/after) into the job's result `Params` +and the run status, and skips the CS3 call entirely. Level 3 additionally accepts a +`prefetched_scan` path so a captured `eos-ns-inspect` snapshot can be replayed offline, so we +can dry-run against production data without touching EOS or the DB. + +### Test suite + +Tests live beside each file plus an integration layer in `pkg/reconciliation`. Coverage per +the spec, driven by table tests: +* Every recipient type: CERN user, group (`share_with_is_group`), external/lightweight. + Assert each maps to the correct CS3 `Grantee` and, for lightweight, that the driver would + target the `sys.reva.lwshare.` xattr. +* Every ACL combination from the hierarchy: all ordered pairs of `{R, RW, Deny}` for + parent/child on nested paths, plus the re-apply and delete cases already covered by + `sharehierarchy` tests, now asserted end to end as `Plan` actions. +* Default-ACL rules: `may` present/absent (untouched), `must` present/absent (added), and + wrong-perms `must` (updated), for personal and project spaces. +* Real `eos-ns-inspect` output: commit captured JSON under + `pkg/storage/fs/eos/testdata/nsinspect` (personal and project space, files and folders, + sys entries, lightweight xattrs). Assert the binary scanner's parser (EOS-driver test) and, + feeding the scanner output into the engine, that the level-3 planner produces the expected + `Plan` (reconciliation test). Reuse the `prefetchedData` path so no QDB or MGM is needed in + CI. (If a native QDB scanner is ever added, it would get its own recorded-response tests and + a cross-check asserting it yields the identical node set and ACLs as the binary scanner.) +* Orphan detection: deleted resource, recycled resource, missing recipient, missing space. +* dry_run: assert no mutation is issued and the recorded actions match what a live run would + have applied (run planner once, apply in both modes, compare). + + +## Work breakdown + +We build the simplest thing that works first, then deepen. The strategy above describes the +eventual full system; the phases below are the build order. Each phase is a self-contained, +reviewable unit that compiles and has its own tests, and each is useful on its own. A later +phase never blocks an earlier one. + +Progress marker: `[x]` done, `[ ]` todo. + +**Phase 1: orphan job.** `[x]` done. +`orphan.go`: a periodic job that scans the share DB and marks a share orphaned +when its resource or its recipient no longer exists. It lists non-orphan shares via +`ListModelShares(nil, nil, hideOrphans=true)`; for each it checks the resource +(`gateway.Stat` on `{Instance, Inode}`) and the recipient (`GetUserByClaim` for users, +`GetGroupByClaim` for groups), then marks via `MarkAsOrphaned`. A lookup error is never +treated as absence: the share is skipped, never orphaned on uncertainty. `dry_run` reports +what would be marked without mutating. Runs `ScopeLeader` because it mutates shared DB state. +Consumer-defined `ShareStore` and `ExistenceChecker` interfaces keep the logic unit-testable: +`*sql.ShareMgr` satisfies the first; the concrete CS3 gateway-backed `ExistenceChecker` is +built at service-startup wiring time (with the gateway address from config), not in this +package, so phase 1 carries no dead wiring. +Missing-space is folded into the resource check for now (if the space is gone the resource +Stat fails); a dedicated space check can come later. +Tests: resource missing, user recipient missing, group recipient missing, all present, +dry_run marks nothing, lookup error skips (no false orphan), already-orphan shares excluded, +mixed batch, share-reference by id. + +**Phase 2: shallow check (DB only).** `[x]` done, except for the default ACLs. +`shallow.go`: reconcile the ACLs implied by the share DB against what is actually set on +each shared path, without a full-namespace scan. For each non-orphan share it resolves the +path (`gateway.GetPath`) and the recipient (`GetUserByClaim`, so the grantee carries the user +type the storage keys the lightweight xattr off), collapses the shares of one recipient in +one space and runs `sharehierarchy.CheckGrantConsistency` over each of them, the same check +that runs at share creation, so an entry is only written where it escalates beyond every share +above it. It then reads the grants on the node and adds or corrects the entry through the +storage provider grant API. Those calls are deliberately not on the gateway API, since a client +that wants to change access goes through CreateShare; the job asks the gateway which provider +hosts the storage and calls that provider directly, the way the gateway does internally. +It never removes an entry: telling a stray entry from a default ACL needs the whole namespace. +It never writes an entry weaker than one above it either, so a nested share can never lose +access to this job; a share that contradicts the hierarchy is reported, not enforced. +A lookup failure skips the share rather than writing an entry built on a guess. `dry_run`. +Targeted and per-share, so cost scales with the number of shares, not the size of the +namespace. +Still to do: the default ACLs are not applied to the visited paths. Computing them needs the +`Space` (root, type, owner, project) a share belongs to, and nothing enumerates spaces yet. +That enumeration is shared with phase 3 and lands with it, so the default-ACL config and the +per-space computation land with it too. + +**Phase 3: deep FS check (eos-ns-inspect).** `[ ]` +The whole-namespace sweep. Enumerate every node of a space directly from QuarkDB via +`eos-ns-inspect` (which reads the namespace, not the MGM), compare each node's actual +`sys.acl` against what the DB says it should be, and correct drift, including stray entries +that no share justifies. Behind a `NamespaceScanner` interface with the EOS binary scanner as +the first implementation; a native QDB reader could come later. `dry_run`. From 7076a1a2f56ad83b0bb8b25cb2ccdce2573dc765 Mon Sep 17 00:00:00 2001 From: Jesse Geens Date: Wed, 26 Aug 2026 22:54:21 +0200 Subject: [PATCH 08/10] WIP --- pkg/reconciliation/deep.go | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/pkg/reconciliation/deep.go b/pkg/reconciliation/deep.go index cae89bdaf3..0f15d6a469 100644 --- a/pkg/reconciliation/deep.go +++ b/pkg/reconciliation/deep.go @@ -31,6 +31,7 @@ import ( "github.com/cs3org/reva/v3/pkg/reconciliation/nsdump" "github.com/cs3org/reva/v3/pkg/spaces" "github.com/cs3org/reva/v3/pkg/storage/fs/eos/acl" + "github.com/pkg/errors" ) // TODO(jgeens): @@ -52,8 +53,10 @@ type Change struct { } type RunParameters struct { - SpaceID string - SpaceType spaces.SpaceType + SpaceID string + SpaceType spaces.SpaceType + OptionalACLs []*acl.Entry + MandatoryACLs []*acl.Entry } type ShareWithPath struct { @@ -92,6 +95,20 @@ func (j *DeepJob) Run(ctx context.Context, p RunParameters) error { return err } + // Now, we check things based on the type of space: + switch p.SpaceType { + case spaces.SpaceTypeHome: + p.MandatoryACLs = append(p.MandatoryACLs, &acl.Entry{ + Type: "user", + Qualifier: statRes.Info.Owner.OpaqueId, + Permissions: "rwx", + }) + case spaces.SpaceTypeProject: + + case spaces.SpaceTypePublic: + return errors.New("deep reconciliation is not supported for public spaces") + } + _, err = j.runAnalysis(ctx, p.SpaceID, namespaceDumper) return err } From 664f01eda5938fcbef01eba14a41c98831ea365c Mon Sep 17 00:00:00 2001 From: Jesse Geens Date: Fri, 4 Sep 2026 14:55:15 +0200 Subject: [PATCH 09/10] move to new cs3apis with space roles --- go.mod | 2 +- go.sum | 2 + .../services/spacesregistry/spacesregistry.go | 36 ++++++++++++- pkg/projects/manager/sql/sql.go | 48 +++++++++++++++-- pkg/projects/manager/sql/sql_test.go | 53 +++++++++++++++++-- 5 files changed, 129 insertions(+), 12 deletions(-) diff --git a/go.mod b/go.mod index 2ae30e91c8..8c559d1d01 100644 --- a/go.mod +++ b/go.mod @@ -14,7 +14,7 @@ require ( github.com/coreos/go-oidc/v3 v3.20.0 github.com/creasty/defaults v1.8.0 github.com/cs3org/cato v0.0.0-20200828125504-e418fc54dd5e - github.com/cs3org/go-cs3apis v0.0.0-20260812114726-73172748ac6b + github.com/cs3org/go-cs3apis v0.0.0-20260903141024-c92f9566c2e1 github.com/dgraph-io/ristretto v0.2.0 github.com/dolthub/go-mysql-server v0.14.0 github.com/glpatcern/go-mime v0.0.0-20221026162842-2a8d71ad17a9 diff --git a/go.sum b/go.sum index 47400fe9af..9532cf8be8 100644 --- a/go.sum +++ b/go.sum @@ -896,6 +896,8 @@ github.com/cs3org/cato v0.0.0-20200828125504-e418fc54dd5e h1:tqSPWQeueWTKnJVMJff github.com/cs3org/cato v0.0.0-20200828125504-e418fc54dd5e/go.mod h1:XJEZ3/EQuI3BXTp/6DUzFr850vlxq11I6satRtz0YQ4= github.com/cs3org/go-cs3apis v0.0.0-20260812114726-73172748ac6b h1:dxDXmPpNjyEFYktWjpblcSz64xqUt+twXGth7Ke8qV0= github.com/cs3org/go-cs3apis v0.0.0-20260812114726-73172748ac6b/go.mod h1:DedpcqXl193qF/08Y04IO0PpxyyMu8+GrkD6kWK2MEQ= +github.com/cs3org/go-cs3apis v0.0.0-20260903141024-c92f9566c2e1 h1:GEbTSrtJC2wqoUQfL1Up55RztD2a6G3X7aK4LTu3VmA= +github.com/cs3org/go-cs3apis v0.0.0-20260903141024-c92f9566c2e1/go.mod h1:DedpcqXl193qF/08Y04IO0PpxyyMu8+GrkD6kWK2MEQ= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/denisenkom/go-mssqldb v0.10.0 h1:QykgLZBorFE95+gO3u9esLd0BmbvpWp0/waNNZfHBM8= diff --git a/internal/grpc/services/spacesregistry/spacesregistry.go b/internal/grpc/services/spacesregistry/spacesregistry.go index 0f6fa8d1a0..942a86ff17 100644 --- a/internal/grpc/services/spacesregistry/spacesregistry.go +++ b/internal/grpc/services/spacesregistry/spacesregistry.go @@ -433,7 +433,21 @@ func (s *service) userSpace(ctx context.Context, user *userpb.User) (*provider.S QuotaMaxBytes: quota.TotalBytes, RemainingBytes: quota.TotalBytes - quota.UsedBytes, }, - PermissionSet: permissions.NewManagerRole().CS3ResourcePermissions(), + Roles: []*provider.SpaceRole{ + { + RoleName: "owner", + PermissionSet: permissions.NewManagerRole().CS3ResourcePermissions(), + Recipients: []*provider.Grantee{ + { + Type: provider.GranteeType_GRANTEE_TYPE_USER, + Id: &provider.Grantee_UserId{ + UserId: user.Id, + }, + }, + }, + }, + }, + //PermissionSet: permissions.NewManagerRole().CS3ResourcePermissions(), }, nil } @@ -445,6 +459,11 @@ func (s *service) getAllPublicSpaces(ctx context.Context) ([]*provider.StorageSp return nil, err } + user, ok := appctx.ContextGetUser(ctx) + if !ok { + return nil, errtypes.UserRequired("Must have a user to list public spaces") + } + publicSpaces := make([]*provider.StorageSpace, 0) for spaceName, content := range s.c.PublicSpaces { path, ok := content["path"] @@ -488,7 +507,20 @@ func (s *service) getAllPublicSpaces(ctx context.Context) ([]*provider.StorageSp QuotaMaxBytes: uint64(math.Pow10(18)), RemainingBytes: uint64(math.Pow10(18)) - resourceInfo.Size, }, - PermissionSet: resourceInfo.PermissionSet, + Roles: []*provider.SpaceRole{ + { + RoleName: "user", + PermissionSet: resourceInfo.PermissionSet, + Recipients: []*provider.Grantee{ + { + Type: provider.GranteeType_GRANTEE_TYPE_USER, + Id: &provider.Grantee_UserId{ + UserId: user.Id, + }, + }, + }, + }, + }, } if description, ok := content["description"]; ok { diff --git a/pkg/projects/manager/sql/sql.go b/pkg/projects/manager/sql/sql.go index 1c75542c1c..303014932e 100644 --- a/pkg/projects/manager/sql/sql.go +++ b/pkg/projects/manager/sql/sql.go @@ -26,6 +26,7 @@ import ( "time" "github.com/ReneKroon/ttlcache/v2" + groupv1beta1 "github.com/cs3org/go-cs3apis/cs3/identity/group/v1beta1" userpb "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1" rpcv1beta1 "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1" provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" @@ -387,10 +388,49 @@ func projectToStorageSpace(p *Project, perms *provider.ResourcePermissions) *pro Path: p.Path, PermissionSet: perms, }, - Description: p.Description, - ThumbnailId: p.ThumbnailPath, - ReadmeId: p.ReadmePath, - PermissionSet: perms, + Description: p.Description, + ThumbnailId: p.ThumbnailPath, + ReadmeId: p.ReadmePath, + Roles: []*provider.SpaceRole{{ + RoleName: "reader", + PermissionSet: permissions.NewViewerRole().CS3ResourcePermissions(), + Recipients: []*provider.Grantee{ + { + Type: provider.GranteeType_GRANTEE_TYPE_GROUP, + Id: &provider.Grantee_GroupId{ + GroupId: &groupv1beta1.GroupId{ + OpaqueId: p.Readers, + }, + }, + }, + }, + }, { + RoleName: "writer", + PermissionSet: permissions.NewEditorRole().CS3ResourcePermissions(), + Recipients: []*provider.Grantee{ + { + Type: provider.GranteeType_GRANTEE_TYPE_GROUP, + Id: &provider.Grantee_GroupId{ + GroupId: &groupv1beta1.GroupId{ + OpaqueId: p.Writers, + }, + }, + }, + }, + }, { + RoleName: "admin", + PermissionSet: permissions.NewManagerRole().CS3ResourcePermissions(), + Recipients: []*provider.Grantee{ + { + Type: provider.GranteeType_GRANTEE_TYPE_GROUP, + Id: &provider.Grantee_GroupId{ + GroupId: &groupv1beta1.GroupId{ + OpaqueId: p.Admins, + }, + }, + }, + }, + }}, } } diff --git a/pkg/projects/manager/sql/sql_test.go b/pkg/projects/manager/sql/sql_test.go index 58c7a26bb9..9df648e97c 100644 --- a/pkg/projects/manager/sql/sql_test.go +++ b/pkg/projects/manager/sql/sql_test.go @@ -25,6 +25,7 @@ import ( "reflect" "testing" + groupv1beta1 "github.com/cs3org/go-cs3apis/cs3/identity/group/v1beta1" userpb "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1" provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" "github.com/cs3org/reva/v3/pkg/appctx" @@ -57,6 +58,48 @@ func TestListProjects(t *testing.T) { spaceID := spaces.EncodeSpaceID("/path/to/project") + // Every project in the test cases below uses these e-groups + roles := []*provider.SpaceRole{{ + RoleName: "reader", + PermissionSet: permissions.NewViewerRole().CS3ResourcePermissions(), + Recipients: []*provider.Grantee{ + { + Type: provider.GranteeType_GRANTEE_TYPE_GROUP, + Id: &provider.Grantee_GroupId{ + GroupId: &groupv1beta1.GroupId{ + OpaqueId: "project-readers", + }, + }, + }, + }, + }, { + RoleName: "writer", + PermissionSet: permissions.NewEditorRole().CS3ResourcePermissions(), + Recipients: []*provider.Grantee{ + { + Type: provider.GranteeType_GRANTEE_TYPE_GROUP, + Id: &provider.Grantee_GroupId{ + GroupId: &groupv1beta1.GroupId{ + OpaqueId: "project-writers", + }, + }, + }, + }, + }, { + RoleName: "admin", + PermissionSet: permissions.NewManagerRole().CS3ResourcePermissions(), + Recipients: []*provider.Grantee{ + { + Type: provider.GranteeType_GRANTEE_TYPE_GROUP, + Id: &provider.Grantee_GroupId{ + GroupId: &groupv1beta1.GroupId{ + OpaqueId: "project-admins", + }, + }, + }, + }, + }} + tests := []struct { description string projects []*Project @@ -100,7 +143,7 @@ func TestListProjects(t *testing.T) { Path: "/path/to/project", PermissionSet: permissions.NewManagerRole().CS3ResourcePermissions(), }, - PermissionSet: permissions.NewManagerRole().CS3ResourcePermissions(), + Roles: roles, }, }, }, @@ -135,7 +178,7 @@ func TestListProjects(t *testing.T) { Path: "/path/to/project", PermissionSet: permissions.NewViewerRole().CS3ResourcePermissions(), }, - PermissionSet: permissions.NewViewerRole().CS3ResourcePermissions(), + Roles: roles, }, }, }, @@ -170,7 +213,7 @@ func TestListProjects(t *testing.T) { Path: "/path/to/project", PermissionSet: permissions.NewEditorRole().CS3ResourcePermissions(), }, - PermissionSet: permissions.NewEditorRole().CS3ResourcePermissions(), + Roles: roles, }, }, }, @@ -205,7 +248,7 @@ func TestListProjects(t *testing.T) { Path: "/path/to/project", PermissionSet: permissions.NewManagerRole().CS3ResourcePermissions(), }, - PermissionSet: permissions.NewManagerRole().CS3ResourcePermissions(), + Roles: roles, }, }, }, @@ -240,7 +283,7 @@ func TestListProjects(t *testing.T) { Path: "/path/to/project", PermissionSet: permissions.NewManagerRole().CS3ResourcePermissions(), }, - PermissionSet: permissions.NewManagerRole().CS3ResourcePermissions(), + Roles: roles, }, }, }, From 051b5a96059869870e5097c65e549bf1909701e7 Mon Sep 17 00:00:00 2001 From: Jesse Geens Date: Mon, 7 Sep 2026 17:27:51 +0200 Subject: [PATCH 10/10] add mandatory permissions for projects --- pkg/reconciliation/deep.go | 56 +++++++++++++++++++++++++++++++++----- 1 file changed, 49 insertions(+), 7 deletions(-) diff --git a/pkg/reconciliation/deep.go b/pkg/reconciliation/deep.go index 0f15d6a469..179449de12 100644 --- a/pkg/reconciliation/deep.go +++ b/pkg/reconciliation/deep.go @@ -27,6 +27,7 @@ import ( rpcv1beta1 "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1" collaborationv1beta1 "github.com/cs3org/go-cs3apis/cs3/sharing/collaboration/v1beta1" provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" + "github.com/cs3org/reva/v3/pkg/errtypes" "github.com/cs3org/reva/v3/pkg/permissions" "github.com/cs3org/reva/v3/pkg/reconciliation/nsdump" "github.com/cs3org/reva/v3/pkg/spaces" @@ -104,7 +105,25 @@ func (j *DeepJob) Run(ctx context.Context, p RunParameters) error { Permissions: "rwx", }) case spaces.SpaceTypeProject: - + // First we query the project from the db + filters := spaces.ListStorageSpaceFilter{}.ByID(&provider.StorageSpaceId{OpaqueId: p.SpaceID}) + res, err := j.gw.ListStorageSpaces(ctx, &provider.ListStorageSpacesRequest{ + Filters: filters.List(), + }) + if err != nil { + return err + } + if res.Status.Code != rpcv1beta1.Code_CODE_OK { + return errors.Errorf("failed to list space %s: %s", p.SpaceID, res.Status.Message) + } + if len(res.StorageSpaces) != 1 { + return errtypes.NotFound(p.SpaceID) + } + space := res.StorageSpaces[0] + for _, role := range space.Roles { + acls := roleToAcls(role) + p.MandatoryACLs = append(p.MandatoryACLs, acls...) + } case spaces.SpaceTypePublic: return errors.New("deep reconciliation is not supported for public spaces") } @@ -188,6 +207,24 @@ func (j *DeepJob) getPath(ctx context.Context, rid *provider.ResourceId) (string return statRes.Info.Path, true } +func roleToAcls(role *provider.SpaceRole) []*acl.Entry { + entries := []*acl.Entry{} + for _, r := range role.Recipients { + e := &acl.Entry{} + e.Permissions = permissionsToACLPerms(role.PermissionSet) + switch r.Type { + case provider.GranteeType_GRANTEE_TYPE_GROUP: + e.Type = "egroup" + e.Qualifier = r.GetGroupId().OpaqueId + case provider.GranteeType_GRANTEE_TYPE_USER: + e.Type = "user" + e.Qualifier = r.GetUserId().OpaqueId + } + entries = append(entries, e) + } + return entries +} + func compare(tree *ACLTree, ns *nsdump.NamespaceDump) ChangeSet { changeSet := ChangeSet{} for _, e := range ns.Entries { @@ -264,17 +301,22 @@ func shareToACL(s *collaborationv1beta1.Share) *acl.Entry { return &e } + e.Permissions = permissionsToACLPerms(s.Permissions.Permissions) + return &e +} + +func permissionsToACLPerms(p *provider.ResourcePermissions) string { // TODO(jgeens): we should define named constants for these int values // TODO(jgeens): we should define named constants for the EOS perms - ocs := permissions.OCSFromCS3Permission(s.Permissions.Permissions) + // TODO(jgeens): we should have some better error handling here + ocs := permissions.OCSFromCS3Permission(p) switch ocs { case 0: - e.Permissions = "!r!w!x" + return "!r!w!x" case 1: - e.Permissions = "rx" + return "rx" case 15: - e.Permissions = "rwx" + return "rwx" } - - return &e + return "" }