github.com/zntrio/harp/v2@v2.0.9/pkg/bundle/fs/root.go (about) 1 // Licensed to Elasticsearch B.V. under one or more contributor 2 // license agreements. See the NOTICE file distributed with 3 // this work for additional information regarding copyright 4 // ownership. Elasticsearch B.V. licenses this file to you under 5 // the Apache License, Version 2.0 (the "License"); you may 6 // not use this file except in compliance with the License. 7 // You may obtain a copy of the License at 8 // 9 // http://www.apache.org/licenses/LICENSE-2.0 10 // 11 // Unless required by applicable law or agreed to in writing, 12 // software distributed under the License is distributed on an 13 // "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 14 // KIND, either express or implied. See the License for the 15 // specific language governing permissions and limitations 16 // under the License. 17 18 //go:build go1.16 19 // +build go1.16 20 21 package fs 22 23 import ( 24 "errors" 25 "fmt" 26 "io" 27 "io/fs" 28 "os" 29 "path/filepath" 30 "strings" 31 32 "github.com/awnumar/memguard" 33 "google.golang.org/protobuf/proto" 34 35 bundlev1 "github.com/zntrio/harp/v2/api/gen/go/harp/bundle/v1" 36 ) 37 38 const ( 39 directoryAccess = 0o555 40 fileAccess = 0o444 41 ) 42 43 type bundleFs struct { 44 root *directory 45 } 46 47 // ----------------------------------------------------------------------------- 48 49 // FromBundle initializes an fs.FS object from the given bundle. 50 func FromBundle(b *bundlev1.Bundle) (BundleFS, error) { 51 // Check arguments 52 if b == nil { 53 return nil, errors.New("unable to create a filesytem from a nil bundle") 54 } 55 56 // Prepare vfs root 57 bfs := &bundleFs{ 58 root: &directory{ 59 children: map[string]interface{}{}, 60 }, 61 } 62 63 // Prepare filesystem 64 for _, p := range b.Packages { 65 if p == nil { 66 // ignore nil package 67 continue 68 } 69 70 // Serialize package 71 body, err := proto.Marshal(p) 72 if err != nil { 73 return nil, fmt.Errorf("unable to serialize package %q: %w", p.Name, err) 74 } 75 76 // Write content 77 if errWrite := bfs.WriteFile(p.Name, body, fileAccess); errWrite != nil { 78 return nil, fmt.Errorf("unable to write package %q in filesystem: %w", p.Name, err) 79 } 80 } 81 82 // Return bundle filesystem 83 return bfs, nil 84 } 85 86 // ----------------------------------------------------------------------------- 87 88 func (bfs *bundleFs) Open(name string) (fs.File, error) { 89 // Return root as default 90 if name == "" { 91 return bfs.root, nil 92 } 93 94 // Validate input path 95 if !fs.ValidPath(name) { 96 return nil, &fs.PathError{ 97 Op: "open", 98 Path: name, 99 Err: fs.ErrInvalid, 100 } 101 } 102 103 // Create directory tree 104 dirPath, name := filepath.Split(name) 105 dirNames := strings.Split(dirPath, "/") 106 107 // Browse directory tree 108 currentDirectory := bfs.root 109 for _, dirName := range dirNames { 110 // Skip empty directory name 111 if dirName == "" { 112 continue 113 } 114 115 it, ok := currentDirectory.children[dirName] 116 if !ok { 117 return nil, fmt.Errorf("directory %q not found: %w", dirName, fs.ErrNotExist) 118 } 119 currentDirectory, ok = it.(*directory) 120 if !ok { 121 return nil, errors.New("invalid directory iterator value") 122 } 123 } 124 125 // Get child 126 h, ok := currentDirectory.children[name] 127 if !ok { 128 return nil, fmt.Errorf("item %q not found in directory %q: %w", name, currentDirectory.name, fs.ErrNotExist) 129 } 130 131 switch it := h.(type) { 132 case *directory: 133 // Return directory 134 return it, nil 135 case *file: 136 // Open enclave 137 body, err := it.content.Open() 138 if err != nil { 139 return nil, fmt.Errorf("file %q could not be opened: %w", name, err) 140 } 141 142 // Assign body reader 143 it.bodyReader = body.Reader() 144 145 // Return file 146 return it, nil 147 } 148 149 return nil, fmt.Errorf("unexpected file type in filesystem %s: %w", name, fs.ErrInvalid) 150 } 151 152 func (bfs *bundleFs) ReadDir(name string) ([]fs.DirEntry, error) { 153 // Try to open directory 154 h, err := bfs.Open(name) 155 if err != nil { 156 return nil, fmt.Errorf("unable to open directory: %w", err) 157 } 158 159 // Retrieve directory info 160 fi, err := h.Stat() 161 if err != nil { 162 return nil, fmt.Errorf("unable to retrieve directory info: %w", err) 163 } 164 165 // Confirm it's a directory 166 if !fi.IsDir() { 167 return nil, fmt.Errorf("path %q point to a file", name) 168 } 169 170 // Convert handle to directory reader 171 dir, ok := h.(fs.ReadDirFile) 172 if !ok { 173 return nil, fmt.Errorf("path %q point to a directory but could not be listed", name) 174 } 175 176 // Delegate to directory list 177 return dir.ReadDir(0) 178 } 179 180 func (bfs *bundleFs) ReadFile(name string) ([]byte, error) { 181 // Try to open file 182 h, err := bfs.Open(name) 183 if err != nil { 184 return nil, fmt.Errorf("unable to open file: %w", err) 185 } 186 187 // Delete to file 188 return io.ReadAll(h) 189 } 190 191 func (bfs *bundleFs) WriteFile(name string, data []byte, perm os.FileMode) error { 192 // Create directory tree 193 dirPath, fname := filepath.Split(name) 194 dirNames := strings.Split(dirPath, "/") 195 196 // MkDirAll 197 currentDirectory := bfs.root 198 for _, dirName := range dirNames { 199 // Skip empty directory name 200 if dirName == "" { 201 continue 202 } 203 204 currentDirectory.RLock() 205 it, ok := currentDirectory.children[dirName] 206 currentDirectory.RUnlock() 207 if !ok { 208 it = &directory{ 209 name: dirName, 210 perm: directoryAccess, 211 children: map[string]interface{}{}, 212 } 213 214 currentDirectory.Lock() 215 currentDirectory.children[dirName] = it 216 currentDirectory.Unlock() 217 } 218 currentDirectory, ok = it.(*directory) 219 if !ok { 220 return errors.New("invalid directory iterator value") 221 } 222 } 223 224 // Create file entry 225 currentDirectory.Lock() 226 currentDirectory.children[fname] = &file{ 227 name: fname, 228 mode: fileAccess, 229 size: int64(len(data)), 230 content: memguard.NewEnclave(data), 231 } 232 currentDirectory.Unlock() 233 234 // No error 235 return nil 236 } 237 238 func (bfs *bundleFs) Stat(name string) (fs.FileInfo, error) { 239 // Try to open file 240 h, err := bfs.Open(name) 241 if err != nil { 242 return nil, fmt.Errorf("unable to open file: %w", err) 243 } 244 245 // Delegate to file 246 return h.Stat() 247 }