diff --git a/cmd/radFS/arttest/main.go b/cmd/radFS/arttest/main.go new file mode 100644 index 0000000..dded631 --- /dev/null +++ b/cmd/radFS/arttest/main.go @@ -0,0 +1,86 @@ +package main + +import ( + "fmt" + + "github.com/acmpesuecc/radFS/internal/art" +) + +func main() { + t := art.New() + + fmt.Println("====== PHASE 1: STRESS TESTING GROWTH (4 -> 16 -> 48 -> 256) ======") + + // We use keys with a single byte difference to ensure they all go into the SAME internal node + for i := 0; i < 256; i++ { + key := []byte{byte(i)} + t.Insert(key, fmt.Sprintf("%d", i)) + + switch i + 1 { + + case 5: + fmt.Printf("[Check] Inserted 5 keys. (Current: %d children)\n", i+1) + fmt.Printf("NodeType: %s\n", art.GetNodeTypeName(t.Root())) + case 17: + fmt.Printf("[Check] Inserted 17 keys. (Current: %d children)\n", i+1) + fmt.Printf("NodeType: %s\n", art.GetNodeTypeName(t.Root())) + case 49: + fmt.Printf("[Check] Inserted 49 keys. (Current: %d children)\n", i+1) + fmt.Printf("NodeType: %s\n", art.GetNodeTypeName(t.Root())) + } + } + + fmt.Printf("Final Growth State: %d children in root.\n", 256) + + fmt.Println("\n===== PHASE 2: SEARCH VERIFICATION =====") + // Verify we didn't lose data during the messy pointer copying in grow/shrink + testKeys := []int{0, 15, 47, 100, 255} + for _, tk := range testKeys { + val, ok := t.Search([]byte{byte(tk)}) + fmt.Printf("Searching for key %d: Found=%v, Value=%v\n", tk, ok, val) + } + + fmt.Println("\n===== PHASE 3: STRESS TESTING SHRINK (256 -> 48 -> 16 -> 4) =====") + + for i := 255; i >= 37; i-- { + t.Delete([]byte{byte(i)}) + } + fmt.Println("[Check] Deleted down to 37 keys") + fmt.Printf("NodeType: %s\n", art.GetNodeTypeName(t.Root())) + + for i := 36; i >= 12; i-- { + t.Delete([]byte{byte(i)}) + } + fmt.Println("[Check] Deleted down to 12 keys. ") + fmt.Printf("NodeType: %s\n", art.GetNodeTypeName(t.Root())) + + for i := 11; i >= 3; i-- { + t.Delete([]byte{byte(i)}) + } + fmt.Println("[Check] Deleted down to 3 keys. ") + fmt.Printf("NodeType: %s\n", art.GetNodeTypeName(t.Root())) + + fmt.Println("\n===== PHASE 4: FULL COLLAPSE (Node4 -> Leaf -> Nil) =====") + + // Delete until only 1 key remains (should collapse Node4 into a Leaf) + for i := 2; i >= 1; i-- { + t.Delete([]byte{byte(i)}) + } + fmt.Println("[Check] Deleted down to 1 key. Tree should be a single Leaf (no Internal Node).") + art.PrintTree(t.Root(), 0, 0) + + // Delete the very last key + t.Delete([]byte{byte(0)}) + fmt.Println("[Check] Deleted last key. Root should be nil.") + + if t.Root() == nil { + fmt.Println("SUCCESS: Tree is fully empty (Root is nil).") + } else { + fmt.Println("WARNING: Root is not nil. Check your Node4 -> Leaf collapse logic.") + art.PrintTree(t.Root(), 0, 0) + } + + fmt.Println("\n===== FINAL TREE STRUCTURE =====") + fmt.Printf("NodeType: %s\n", art.GetNodeTypeName(t.Root())) + +} diff --git a/docs/weekly/angelo/week_4.md b/docs/weekly/angelo/week_4.md new file mode 100644 index 0000000..db281f3 --- /dev/null +++ b/docs/weekly/angelo/week_4.md @@ -0,0 +1,8 @@ +## Angelo's Progress +* implemented the insert function +* implemented update function in addchild + + +## What's next? +* implement grow to transition to larger nodes +* fix the bugs in insert diff --git a/internal/art/art.go b/internal/art/art.go new file mode 100644 index 0000000..93bfd91 --- /dev/null +++ b/internal/art/art.go @@ -0,0 +1,58 @@ +package art + +// TODO: Public API (Tree struct, Insert, Search, Delete) + +type Tree struct { + root *Node +} + +func (t *Tree) Root() *Node { + return t.root +} + +func (t *Tree) Insert(key []byte, value interface{}) { + t.root = insert(t.root, value, key, 0) +} + +func (t *Tree) Search(key []byte) (interface{}, bool) { + leaf := search(t.root, key, 0) // start from root and depth 0 + if leaf != nil && isleaf(leaf) { + return leaf.leaf.values, true //Node->innerleaf->values + } + return "", false +} +func (t *Tree) Delete(key []byte) bool { + if t.root == nil { + return false + } + + newRoot, deleted := deletekey(t.root, key, 0) + + if deleted { + t.root = newRoot + return true + } + + return false +} + +func GetNodeTypeName(n *Node) string { + if n == nil { + return "Nil" + } + if isleaf(n) { + return "Leaf" + } + + types := []string{"Node4", "Node16", "Node48", "Node256"} + return types[n.innerNode.nodeType] +} +func (t *Tree) ForEach(fn func([]byte, interface{})) { + traverse(t.root, 0, fn) +} +func New() *Tree { + return &Tree{} +} +func (t *Tree) Empty() bool { + return t.root == nil +} diff --git a/internal/art/delete.go b/internal/art/delete.go new file mode 100644 index 0000000..dbd24d2 --- /dev/null +++ b/internal/art/delete.go @@ -0,0 +1,49 @@ +package art + +func deletekey(n *Node, key []byte, depth int) (*Node, bool) { + if n == nil { + return nil, false + } + + if isleaf(n) { + if string(n.leaf.key) == string(key) { + return nil, true + } + return n, false + } + + p := checkprefix(n, key, depth) + if p != n.innerNode.meta.prefixlen { + return n, false + } + depth += n.innerNode.meta.prefixlen + + // 3. KEY EXHAUSTION (The Fix) + // If the key ends here, the value is in the inner node's leaf field + if depth == len(key) { + if n.innerNode.leaf != nil { + n.innerNode.leaf = nil // Remove the value + return n, true + } + return n, false + } + + k := key[depth] + child, pos := findchild(k, n) + if child == nil { + return n, false + } + + newChild, deleted := deletekey(child, key, depth+1) + if !deleted { + return n, false + } + + if newChild == nil { + n = removechild(n, k) + } else { + n.innerNode.children[pos] = newChild + } + + return n, true +} diff --git a/internal/art/insert.go b/internal/art/insert.go new file mode 100644 index 0000000..c879f57 --- /dev/null +++ b/internal/art/insert.go @@ -0,0 +1,104 @@ +package art + +func insert(n *Node, value interface{}, key []byte, depth int) *Node { + + if n == nil { + return newleaf(value, key) + } + if isleaf(n) { + new_node := newNode4() + oldkey := n.leaf.key + i := depth + + for i < len(oldkey) && i < len(key) && oldkey[i] == key[i] { + prefix_index := i - depth + if prefix_index < maxprefixlen { + new_node.innerNode.meta.prefix[prefix_index] = key[i] // stores only the till max prefix + + } + + i++ + } + + new_node.innerNode.meta.prefixlen = i - depth // stores full prefix len even after maxprefixlen + depth = i + if depth == len(key) { + new_node.innerNode.leaf = newleaf(value, key) + + } else { + new_node = addchild(new_node, key[depth], newleaf(value, key)) + + } + if depth == len(oldkey) { + new_node.innerNode.leaf = n + + } else { + new_node = addchild(new_node, oldkey[depth], n) + + } + + return new_node + + } + p := checkprefix(n, key, depth) + + if p != n.innerNode.meta.prefixlen { + + new_node := newNode4() + if p+depth == len(key) { + new_node.innerNode.leaf = newleaf(value, key) + + } else { + new_node = addchild(new_node, key[depth+p], newleaf(value, key)) + + } + leaf := fetchleaf(n) // either its an actual leaf or innernode leaf + oldkey := leaf.leaf.key + + var oldkeybyte byte + if p < maxprefixlen { + oldkeybyte = n.innerNode.meta.prefix[p] + } else { + oldkeybyte = oldkey[depth+p] + } + + new_node = addchild(new_node, oldkeybyte, n) + + new_node.innerNode.meta.prefixlen = p + if p < maxprefixlen { + new_node.innerNode.meta.prefix = deepcopy(n.innerNode.meta.prefix[:p]) + + } else { + new_node.innerNode.meta.prefix = deepcopy(n.innerNode.meta.prefix[:maxprefixlen]) + } + + oldprefixlen := n.innerNode.meta.prefixlen + n.innerNode.meta.prefixlen = oldprefixlen - (p + 1) + if oldprefixlen < maxprefixlen { + n.innerNode.meta.prefix = deepcopy(n.innerNode.meta.prefix[p+1 : oldprefixlen]) + + } else { + leaf := fetchleaf(n) + n.innerNode.meta.prefix = deepcopy(leaf.leaf.key[depth+p+1 : depth+p+1+maxprefixlen]) + } + + return new_node + } + + depth += n.innerNode.meta.prefixlen + if depth == len(key) { + n.innerNode.leaf = newleaf(value, key) + return n + } + next, pos := findchild(key[depth], n) + if next != nil { + n.innerNode.children[pos] = insert(next, value, key, depth+1) + return n + + } else { + n = addchild(n, key[depth], newleaf(value, key)) + return n + + } + +} diff --git a/internal/art/leaf.go b/internal/art/leaf.go new file mode 100644 index 0000000..de66ce2 --- /dev/null +++ b/internal/art/leaf.go @@ -0,0 +1,18 @@ +package art + +// TODO: Leaf node structure for storing values + +type leaf struct { + key []byte + values interface{} +} + +func newleaf(value interface{}, key []byte) *Node { + return &Node{ + leaf: &leaf{key: key, values: value}, + } + +} +func isleaf(n *Node) bool { + return n.leaf != nil +} diff --git a/internal/art/node.go b/internal/art/node.go new file mode 100644 index 0000000..b381512 --- /dev/null +++ b/internal/art/node.go @@ -0,0 +1,41 @@ +package art + +// TODO: Interfaces and shared node header (meta) + +type NodeType int + +const ( + Node4 NodeType = iota + Node16 + Node48 + Node256 +) +const ( + Node4max = 4 + Node16Max = 16 + Node48Max = 48 + Node256Max = 256 + + maxprefixlen = 8 +) + +type Node struct { + innerNode *innerNode + leaf *leaf +} + +type innerNode struct { + nodeType NodeType + keys []byte + children []*Node + leaf *Node + + num_children int + + meta meta +} + +type meta struct { + prefix []byte + prefixlen int +} diff --git a/internal/art/node16.go b/internal/art/node16.go new file mode 100644 index 0000000..a931480 --- /dev/null +++ b/internal/art/node16.go @@ -0,0 +1,15 @@ +package art + +// TODO: Node16 implementation +func newNode16() *Node { + in := &innerNode{ + nodeType: Node16, + keys: make([]byte, Node16Max), + children: make([]*Node, Node16Max), + num_children: 0, + meta: meta{ + prefix: make([]byte, maxprefixlen), + }, + } + return &Node{innerNode: in} +} diff --git a/internal/art/node256.go b/internal/art/node256.go new file mode 100644 index 0000000..0a891fc --- /dev/null +++ b/internal/art/node256.go @@ -0,0 +1,16 @@ +package art + +// TODO: Node256 implementation (direct map) + +func newNode256() *Node { + in := &innerNode{ + nodeType: Node256, + children: make([]*Node, Node256Max), + num_children: 0, + + meta: meta{ + prefix: make([]byte, maxprefixlen), + }, + } + return &Node{innerNode: in} +} diff --git a/internal/art/node4.go b/internal/art/node4.go new file mode 100644 index 0000000..aa996b8 --- /dev/null +++ b/internal/art/node4.go @@ -0,0 +1,15 @@ +package art + +func newNode4() *Node { + in := &innerNode{ + nodeType: Node4, + keys: make([]byte, Node4max), + children: make([]*Node, Node4max), + num_children: 0, + meta: meta{ + prefix: make([]byte, maxprefixlen), + }, + } + return &Node{innerNode: in} + +} diff --git a/internal/art/node48.go b/internal/art/node48.go new file mode 100644 index 0000000..f99a5ca --- /dev/null +++ b/internal/art/node48.go @@ -0,0 +1,15 @@ +package art + +// TODO: Node48 implementation (indirection layer) +func newNode48() *Node { + in := &innerNode{ + nodeType: Node48, + keys: make([]byte, Node256Max), + children: make([]*Node, Node48Max), + num_children: 0, + meta: meta{ + prefix: make([]byte, maxprefixlen), + }, + } + return &Node{innerNode: in} +} diff --git a/internal/art/print_tree.go b/internal/art/print_tree.go new file mode 100644 index 0000000..6f78ff3 --- /dev/null +++ b/internal/art/print_tree.go @@ -0,0 +1,73 @@ +package art + +import "fmt" + +func PrintTree(n *Node, level int, depth int) { + if n == nil { + return + } + + indent := "" + for i := 0; i < level; i++ { + indent += " " + } + + if isleaf(n) { + fmt.Println(indent + "Leaf: " + string(n.leaf.key)) + return + } + + in := n.innerNode + + prefixlen := in.meta.prefixlen + prefix := "" + if prefixlen <= maxprefixlen { + prefix = string(in.meta.prefix[:prefixlen]) + + } else { + leaf := fetchleaf(n) + prefix = string(leaf.leaf.key[depth : depth+prefixlen]) + } + + fmt.Println(indent+"Node(prefix=\""+prefix+"\", prefixLen=", prefixlen, ")") + + if in.leaf != nil { + fmt.Printf("%s [Internal Leaf]: %s\n", indent, string(in.leaf.leaf.key)) + } + // Print children + + newDepth := depth + prefixlen + + switch in.nodeType { + case Node4, Node16: + for i := 0; i < in.num_children; i++ { + key := in.keys[i] + child := in.children[i] + + fmt.Printf("%s Edge('%c' | %d):\t", indent, key, key) + PrintTree(child, level+1, newDepth+1) + } + + case Node48: + for b := 0; b < 256; b++ { + idx := in.keys[b] + + if idx != 0 { + + child := in.children[idx-1] + + fmt.Printf("%s Edge('%c' | %d):\t", indent, byte(b), b) + PrintTree(child, level+1, newDepth+1) + } + } + + case Node256: + for b := 0; b < 256; b++ { + child := in.children[b] + if child != nil { + fmt.Printf("%s Edge('%c' | %d):\t", indent, byte(b), b) + PrintTree(child, level+1, newDepth+1) + } + } + } +} diff --git a/internal/art/search.go b/internal/art/search.go new file mode 100644 index 0000000..453a3c2 --- /dev/null +++ b/internal/art/search.go @@ -0,0 +1,40 @@ +package art + +func search(n *Node, key []byte, depth int) *Node { + if n == nil { + return nil + } + + if isleaf(n) { + if string(n.leaf.key) == string(key) { + return n + } + return nil + } + + if n.innerNode.meta.prefixlen > 0 { + p := checkprefix(n, key, depth) + + if p != n.innerNode.meta.prefixlen { + return nil + } + depth += n.innerNode.meta.prefixlen + } + + // KEY EXHAUSTION CHECK (The Fix) + // If we've consumed the entire key, the value must be in this inner node's leaf + if depth == len(key) { + if n.innerNode.leaf != nil { + return n.innerNode.leaf + } + return nil + } + + k := key[depth] + next, _ := findchild(k, n) + if next != nil { + return search(next, key, depth+1) + } + + return nil +} diff --git a/internal/art/traverse.go b/internal/art/traverse.go new file mode 100644 index 0000000..6e0ba46 --- /dev/null +++ b/internal/art/traverse.go @@ -0,0 +1,52 @@ +package art + +func traverse(n *Node, depth int, fn func([]byte, interface{})) { + if n == nil { + return + } + + if isleaf(n) { + fn(n.leaf.key, n.leaf.values) + return + } + + in := n.innerNode + + prefixlen := in.meta.prefixlen + + if in.leaf != nil { + fn(in.leaf.leaf.key, in.leaf.leaf.values) + } + + newDepth := depth + prefixlen + + switch in.nodeType { + case Node4, Node16: + for i := 0; i < in.num_children; i++ { + + child := in.children[i] + + traverse(child, newDepth+1, fn) + } + + case Node48: + for b := 0; b < 256; b++ { + idx := in.keys[b] + + if idx != 0 { + + child := in.children[idx-1] + + traverse(child, newDepth+1, fn) + } + } + + case Node256: + for b := 0; b < 256; b++ { + child := in.children[b] + if child != nil { + traverse(child, newDepth+1, fn) + } + } + } +} diff --git a/internal/art/util.go b/internal/art/util.go new file mode 100644 index 0000000..1e1084e --- /dev/null +++ b/internal/art/util.go @@ -0,0 +1,351 @@ +package art + +// TODO: Helper functions (e.g., prefix matching) +func addchild(n *Node, k byte, child *Node) *Node { + in := n.innerNode + + child1, pos1 := findchild(k, n) // to prevent duplicate insertions + + if child1 != nil { + in.children[pos1] = child + return n + } + switch n.innerNode.nodeType { + case Node16, Node4: + if n.innerNode.num_children == len(in.keys) { + n = grow(n) + + return addchild(n, k, child) + + } + var i int + for i = in.num_children - 1; i >= 0 && k < in.keys[i]; i-- { //shifts until keybyte place is found + in.keys[i+1] = in.keys[i] + in.children[i+1] = in.children[i] + + } + + in.keys[i+1] = k + in.children[i+1] = child + in.num_children++ + + return n + case Node48: + if n.innerNode.num_children == len(in.children) { + n = grow(n) + + return addchild(n, k, child) + + } + if in.keys[k] != 0 { // if key exist then update + key := int(n.innerNode.keys[k]) - 1 // the zero slot is used to check if its an empty key so we start filling the index values in key from 1 + n.innerNode.children[key] = child + return n + + } + + for i := 0; i < len(in.children); i++ { // find the free child + if in.children[i] == nil { + in.children[i] = child + in.keys[k] = byte(i + 1) + in.num_children++ + + break + + } + + } + case Node256: + if in.children[k] != nil { //update key + in.children[k] = child + return n + + } + in.children[k] = child //inserting new key + in.num_children++ + + } + + return n + +} +func checkprefix(n *Node, key []byte, depth int) int { + in := n.innerNode + var i int + maxcmp := min(maxprefixlen, in.meta.prefixlen) + + for i = 0; i < maxcmp && depth+i < len(key); i++ { //checks prefix until mismatch + if in.meta.prefix[i] != key[depth+i] { + return i // case when you find mismatch and the mismatch is less than maxprefixlen + + } + + } + if in.meta.prefixlen > maxprefixlen { + leaf := fetchleaf(n) + leafkey := leaf.leaf.key + for ; i < in.meta.prefixlen && depth+i < len(leafkey) && depth+i < len(key); i++ { + if key[depth+i] != leaf.leaf.key[depth+i] { + return i // case when you find mismatch and the mismatch is more than maxprefixlen + + } + + } + + } + + return i // case when you find mismatch and the mismatch is equal maxprefixlen + +} + +func findchild(k byte, n *Node) (*Node, int) { + in := n.innerNode + switch in.nodeType { + case Node4, Node16: + for i := 0; i < in.num_children; i++ { + if in.keys[i] == k { + return in.children[i], i //finds the node and the position + } + + } + case Node48: + idx := in.keys[k] + if idx > 0 { + realindex := int(idx - 1) + return in.children[realindex], realindex + + } + case Node256: + if in.children[k] != nil { + return in.children[k], int(k) + } + + } + return nil, -1 + +} + +func removechild(n *Node, k byte) *Node { + in := n.innerNode + _, pos := findchild(k, n) + + // If child doesn't exist, return original node (search loop only for node 4 and 16) + if pos == -1 && in.nodeType <= Node16 { + return n + } + + switch in.nodeType { + case Node4, Node16: + + for i := pos; i < in.num_children-1; i++ { + in.keys[i] = in.keys[i+1] + in.children[i] = in.children[i+1] + } + in.keys[in.num_children-1] = 0 + in.children[in.num_children-1] = nil + + case Node48: + + idx := in.keys[k] + if idx > 0 { + in.keys[k] = 0 + in.children[idx-1] = nil + } + + case Node256: + + in.children[k] = nil + } + + in.num_children-- + + if shouldShrink(n) { + return shrink(n) + } + + return n +} + +func shouldShrink(n *Node) bool { + in := n.innerNode + switch in.nodeType { + case Node256: + return in.num_children <= 48 + case Node48: + return in.num_children <= 16 + case Node16: + return in.num_children <= 4 + case Node4: + return in.num_children <= 1 + } + return false +} + +func grow(n *Node) *Node { + switch n.innerNode.nodeType { + case Node4: + n16 := newNode16() + copymeta(n, n16) + index := 0 + for i := 0; i < 4; i++ { + if n.innerNode.children[i] != nil { + n16.innerNode.keys[index] = n.innerNode.keys[i] + n16.innerNode.children[index] = n.innerNode.children[i] + index++ + + } + + } + n16.innerNode.num_children = index + return n16 + case Node16: + n48 := newNode48() + copymeta(n, n48) + index := 0 + for i := 0; i < n.innerNode.num_children; i++ { + idx := n.innerNode.keys[i] + child := n.innerNode.children[i] + + if child != nil { + + n48.innerNode.keys[idx] = byte(index + 1) + n48.innerNode.children[index] = child + index++ + + } + + } + n48.innerNode.num_children = index + return n48 + + case Node48: + n256 := newNode256() + copymeta(n, n256) + count := 0 + for i := 0; i < 256; i++ { + idx := n.innerNode.keys[i] + + if n.innerNode.keys[i] != 0 { + child := n.innerNode.children[int(idx-1)] + n256.innerNode.children[i] = child + count++ + } + + } + n256.innerNode.num_children = count + + return n256 + + } + return nil + +} + +func shrink(n *Node) *Node { + in := n.innerNode + switch in.nodeType { + case Node4: + if in.num_children == 0 { + if in.leaf != nil { + return in.leaf + } + + return nil + } + if in.num_children == 1 && in.leaf == nil { + return in.children[0] + } + + return n + + case Node16: + n4 := newNode4() + copymeta(n, n4) + for i := 0; i < in.num_children; i++ { + n4.innerNode.keys[i] = in.keys[i] + n4.innerNode.children[i] = in.children[i] + } + n4.innerNode.num_children = in.num_children + return n4 + + case Node48: + n16 := newNode16() + copymeta(n, n16) + count := 0 + for i := 0; i < 256; i++ { + idx := in.keys[i] + if idx > 0 { + n16.innerNode.keys[count] = byte(i) + n16.innerNode.children[count] = in.children[idx-1] + count++ + } + } + n16.innerNode.num_children = count + return n16 + + case Node256: + n48 := newNode48() + copymeta(n, n48) + count := 0 + for i := 0; i < 256; i++ { + child := in.children[i] + if child != nil { + n48.innerNode.children[count] = child + n48.innerNode.keys[byte(i)] = byte(count + 1) + count++ + } + } + n48.innerNode.num_children = count + return n48 + } + return n +} + +func copymeta(n *Node, new_node *Node) { + + new_node.innerNode.meta.prefixlen = n.innerNode.meta.prefixlen + new_node.innerNode.meta.prefix = deepcopy(n.innerNode.meta.prefix[:min(n.innerNode.meta.prefixlen, maxprefixlen)]) + new_node.innerNode.leaf = n.innerNode.leaf + +} + +func fetchleaf(n *Node) *Node { + if isleaf(n) { + return n + } + if n.innerNode.leaf != nil { + return n.innerNode.leaf + } + + in := n.innerNode + switch in.nodeType { + case Node4, Node16: + for i := 0; i < in.num_children; i++ { + if in.children[i] != nil { + return fetchleaf(in.children[i]) + } + } + case Node48: + for _, child := range in.children { + if child != nil { + return fetchleaf(child) + } + } + case Node256: + for i := 0; i < len(in.children); i++ { + if in.children[i] != nil { + return fetchleaf(in.children[i]) + } + } + } + return nil + +} + +func deepcopy(source []byte) []byte { + + desarr := make([]byte, maxprefixlen) + copy(desarr, source) + return desarr + +} diff --git a/internal/fs/dir.go b/internal/fs/dir.go index 028e2f5..541f978 100644 --- a/internal/fs/dir.go +++ b/internal/fs/dir.go @@ -9,6 +9,7 @@ import ( "bazil.org/fuse" "bazil.org/fuse/fs" + "github.com/acmpesuecc/radFS/internal/art" ) func (f *FS) DebugPrint(msg string, v ...any) { @@ -54,39 +55,39 @@ func (d *Dir) Setattr(ctx context.Context, req *fuse.SetattrRequest, resp *fuse. func (d *Dir) Lookup(ctx context.Context, name string) (fs.Node, error) { d.fs.DebugPrint("LOOKUP", "fetching", name) - d.mu.Lock() - defer d.mu.Unlock() + d.mu.RLock() + defer d.mu.RUnlock() - node, ok := d.Nodes[name] + v, ok := d.tree.Search([]byte(name)) if !ok { return nil, syscall.ENOENT } - d.atime = time.Now() - return node, nil + return v.(fs.Node), nil + } func (d *Dir) ReadDirAll(ctx context.Context) ([]fuse.Dirent, error) { d.fs.DebugPrint("READDIR", "inode", d.inode) - d.mu.Lock() - defer d.mu.Unlock() + d.mu.RLock() + defer d.mu.RUnlock() var entries []fuse.Dirent - for name, node := range d.Nodes { - var dt fuse.DirentType - - switch node.(type) { + d.tree.ForEach(func(b []byte, i interface{}) { //traverses tree and appends the dirent to entries + name := string(b) + var dtype fuse.DirentType + switch i.(type) { + case *File: + dtype = fuse.DT_File case *Dir: - dt = fuse.DT_Dir - default: - dt = fuse.DT_File - } + dtype = fuse.DT_Dir - entries = append(entries, fuse.Dirent{Name: name, Type: dt}) - } + } + entries = append(entries, fuse.Dirent{Name: name, Type: dtype}) + }) d.atime = time.Now() @@ -106,22 +107,19 @@ func (d *Dir) Mkdir(ctx context.Context, req *fuse.MkdirRequest) (fs.Node, error d.mu.Lock() defer d.mu.Unlock() - if _, exists := d.Nodes[req.Name]; exists { + if _, exists := d.tree.Search([]byte(req.Name)); exists { return nil, syscall.EEXIST } newDir := &Dir{ inode: nextInode(), - Nodes: make(map[string]fs.Node), + tree: art.New(), fs: d.fs, atime: time.Now(), - ctime: time.Now(), mtime: time.Now(), + ctime: time.Now(), } - d.Nodes[req.Name] = newDir - - d.mtime = time.Now() - d.ctime = time.Now() + d.tree.Insert([]byte(req.Name), newDir) return newDir, nil } @@ -140,22 +138,16 @@ func (d *Dir) Create(ctx context.Context, req *fuse.CreateRequest, resp *fuse.Cr d.mu.Lock() defer d.mu.Unlock() - f := &File{ - inode: nextInode(), - data: []byte{}, - mode: uint32(req.Mode), - atime: time.Now(), - ctime: time.Now(), - mtime: time.Now(), - } - - if _, exists := d.Nodes[req.Name]; exists { // checking for dupes + if _, exist := d.tree.Search([]byte(req.Name)); exist { return nil, nil, syscall.EEXIST + } - d.Nodes[req.Name] = f - d.mtime = time.Now() - d.ctime = time.Now() + f := &File{inode: nextInode(), data: []byte{}, mode: uint32(req.Mode), atime: time.Now(), + ctime: time.Now(), + mtime: time.Now()} + + d.tree.Insert([]byte(req.Name), f) return f, f, nil } @@ -173,17 +165,21 @@ func (d *Dir) Remove(ctx context.Context, req *fuse.RemoveRequest) error { d.mu.Lock() defer d.mu.Unlock() - if _, exists := d.Nodes[req.Name]; !exists { + v, exist := d.tree.Search([]byte(req.Name)) + + if !exist { return syscall.ENOENT } - if dir, flag := d.Nodes[req.Name].(*Dir); flag { - if len(dir.Nodes) > 0 { + if dir, ok := v.(*Dir); ok { + dir.mu.RLock() // we are reading another dir with Empty() , multiple processes may read + defer dir.mu.RUnlock() + if !dir.tree.Empty() { return syscall.ENOTEMPTY } } - delete(d.Nodes, req.Name) + d.tree.Delete([]byte(req.Name)) d.mtime = time.Now() d.ctime = time.Now() @@ -220,29 +216,30 @@ func (d *Dir) Rename(ctx context.Context, req *fuse.RenameRequest, newDir fs.Nod } //checks if source exists - node, exists := d.Nodes[req.OldName] + node, exists := d.tree.Search([]byte(req.OldName)) if !exists { return syscall.ENOENT } - - // if destination exists → overwrite -if existing, exists := newParent.Nodes[req.NewName]; exists { - // if it's a directory, check if empty - if dir, ok := existing.(*Dir); ok { - if len(dir.Nodes) > 0 { - return syscall.ENOTEMPTY - } - } - delete(newParent.Nodes, req.NewName) -} + // if destination exists → overwrite + if existing, exists := newParent.tree.Search([]byte(req.NewName)); exists { + // if it's a directory, check if empty + if dir, ok := existing.(*Dir); ok { + dir.mu.RLock() + defer dir.mu.RUnlock() + if !dir.tree.Empty() { + return syscall.ENOTEMPTY + } + } + newParent.tree.Delete([]byte(req.NewName)) + } //removes from old - delete(d.Nodes, req.OldName) + + d.tree.Delete([]byte(req.OldName)) //adds to new - newParent.Nodes[req.NewName] = node + newParent.tree.Insert([]byte(req.NewName), node) return nil } - diff --git a/internal/fs/file.go b/internal/fs/file.go index 8e86778..21d13af 100644 --- a/internal/fs/file.go +++ b/internal/fs/file.go @@ -10,8 +10,8 @@ import ( ) func (f *File) Attr(ctx context.Context, a *fuse.Attr) error { - f.mu.Lock() - defer f.mu.Unlock() + f.mu.RLock() + defer f.mu.RUnlock() a.Inode = f.inode a.Mode = os.FileMode(f.mode) a.Size = uint64(len(f.data)) @@ -56,7 +56,7 @@ func (f *File) Write(ctx context.Context, req *fuse.WriteRequest, resp *fuse.Wri // Grow the buffer if needed if end > int64(len(f.data)) { - newData := make([]byte, end) + newData := make([]byte, int(end)) copy(newData, f.data) f.data = newData } @@ -85,7 +85,7 @@ func (f *File) Setattr(ctx context.Context, req *fuse.SetattrRequest, resp *fuse f.data = f.data[:req.Size] f.ctime = time.Now() // cuz creating file here } else { - newData := make([]byte, req.Size) + newData := make([]byte, int(req.Size)) copy(newData, f.data) f.data = newData } diff --git a/internal/fs/fs.go b/internal/fs/fs.go index 9fb9eac..365b04b 100644 --- a/internal/fs/fs.go +++ b/internal/fs/fs.go @@ -7,6 +7,7 @@ import ( "time" "bazil.org/fuse/fs" + "github.com/acmpesuecc/radFS/internal/art" ) type FS struct { @@ -24,29 +25,34 @@ func nextInode() uint64 { } func (f *FS) Root() (fs.Node, error) { - return &Dir{ + root := &Dir{ inode: 1, - Nodes: map[string]fs.Node{ - "hello.txt": &File{ - inode: nextInode(), - data: []byte("Hello from radFS!\n"), - mode: 0o666, - atime: time.Now(), - mtime: time.Now(), - ctime: time.Now(), - }, - }, - fs: f, + tree: art.New(), + + fs: f, + + atime: time.Now(), + mtime: time.Now(), + ctime: time.Now()} + + hello := &File{ + inode: nextInode(), + data: []byte("Hello from radFS!\n"), + mode: 0o666, atime: time.Now(), mtime: time.Now(), ctime: time.Now(), + uid: uint32(os.Getuid()), //permissions implemnet based on userid + gid: uint32(os.Getgid()), //permissions implement based on groupid } + root.tree.Insert([]byte("hello.txt"), hello) + return root, nil } type File struct { - mu sync.Mutex + mu sync.RWMutex inode uint64 data []byte mode uint32 @@ -58,9 +64,9 @@ type File struct { } type Dir struct { - mu sync.Mutex + mu sync.RWMutex inode uint64 - Nodes map[string]fs.Node + tree *art.Tree fs *FS atime time.Time mtime time.Time diff --git a/internal/fs/fs_test.go b/internal/fs/fs_test.go index 1b0b945..8415536 100644 --- a/internal/fs/fs_test.go +++ b/internal/fs/fs_test.go @@ -35,7 +35,7 @@ func TestRoot_ReturnsDir(t *testing.T) { func TestRoot_HasHelloTxt(t *testing.T) { d := rootDir(t) - if _, ok := d.Nodes["hello.txt"]; !ok { + if _, ok := d.tree.Search([]byte("hello.txt")); !ok { t.Error("root dir missing hello.txt") } } @@ -79,7 +79,7 @@ func TestCreate_NewFile(t *testing.T) { t.Fatal("Create returned nil handle") } - if _, ok := d.Nodes["new.txt"]; !ok { + if _, ok := d.tree.Search([]byte("new.txt")); !ok { t.Error("new.txt not found in dir after Create") } } @@ -107,7 +107,7 @@ func TestMkdir_NewDir(t *testing.T) { if node == nil { t.Fatal("Mkdir returned nil node") } - if _, ok := d.Nodes["subdir"]; !ok { + if _, ok := d.tree.Search([]byte("subdir")); !ok { t.Error("subdir not found in dir after Mkdir") } } @@ -189,7 +189,7 @@ func TestRemove_ExistingFile(t *testing.T) { if err := d.Remove(ctx(), req); err != nil { t.Fatalf("Remove: %v", err) } - if _, ok := d.Nodes["hello.txt"]; ok { + if _, ok := d.tree.Search([]byte("hello.txt")); ok { t.Error("hello.txt still present after Remove") } }