github.com/safing/portbase@v0.19.5/utils/structure.go (about) 1 package utils 2 3 import ( 4 "fmt" 5 "os" 6 "path/filepath" 7 "strings" 8 "sync" 9 ) 10 11 // DirStructure represents a directory structure with permissions that should be enforced. 12 type DirStructure struct { 13 sync.Mutex 14 15 Path string 16 Dir string 17 Perm os.FileMode 18 Parent *DirStructure 19 Children map[string]*DirStructure 20 } 21 22 // NewDirStructure returns a new DirStructure. 23 func NewDirStructure(path string, perm os.FileMode) *DirStructure { 24 return &DirStructure{ 25 Path: path, 26 Perm: perm, 27 Children: make(map[string]*DirStructure), 28 } 29 } 30 31 // ChildDir adds a new child DirStructure and returns it. Should the child already exist, the existing child is returned and the permissions are updated. 32 func (ds *DirStructure) ChildDir(dirName string, perm os.FileMode) (child *DirStructure) { 33 ds.Lock() 34 defer ds.Unlock() 35 36 // if exists, update 37 child, ok := ds.Children[dirName] 38 if ok { 39 child.Perm = perm 40 return child 41 } 42 43 // create new 44 newDir := &DirStructure{ 45 Path: filepath.Join(ds.Path, dirName), 46 Dir: dirName, 47 Perm: perm, 48 Parent: ds, 49 Children: make(map[string]*DirStructure), 50 } 51 ds.Children[dirName] = newDir 52 return newDir 53 } 54 55 // Ensure ensures that the specified directory structure (from the first parent on) exists. 56 func (ds *DirStructure) Ensure() error { 57 return ds.EnsureAbsPath(ds.Path) 58 } 59 60 // EnsureRelPath ensures that the specified directory structure (from the first parent on) and the given relative path (to the DirStructure) exists. 61 func (ds *DirStructure) EnsureRelPath(dirPath string) error { 62 return ds.EnsureAbsPath(filepath.Join(ds.Path, dirPath)) 63 } 64 65 // EnsureRelDir ensures that the specified directory structure (from the first parent on) and the given relative path (to the DirStructure) exists. 66 func (ds *DirStructure) EnsureRelDir(dirNames ...string) error { 67 return ds.EnsureAbsPath(filepath.Join(append([]string{ds.Path}, dirNames...)...)) 68 } 69 70 // EnsureAbsPath ensures that the specified directory structure (from the first parent on) and the given absolute path exists. 71 // If the given path is outside the DirStructure, an error will be returned. 72 func (ds *DirStructure) EnsureAbsPath(dirPath string) error { 73 // always start at the top 74 if ds.Parent != nil { 75 return ds.Parent.EnsureAbsPath(dirPath) 76 } 77 78 // check if root 79 if dirPath == ds.Path { 80 return ds.ensure(nil) 81 } 82 83 // check scope 84 slashedPath := ds.Path 85 // add slash to end 86 if !strings.HasSuffix(slashedPath, string(filepath.Separator)) { 87 slashedPath += string(filepath.Separator) 88 } 89 // check if given path is in scope 90 if !strings.HasPrefix(dirPath, slashedPath) { 91 return fmt.Errorf(`path "%s" is outside of DirStructure scope`, dirPath) 92 } 93 94 // get relative path 95 relPath, err := filepath.Rel(ds.Path, dirPath) 96 if err != nil { 97 return fmt.Errorf("failed to get relative path: %w", err) 98 } 99 100 // split to path elements 101 pathDirs := strings.Split(filepath.ToSlash(relPath), "/") 102 103 // start checking 104 return ds.ensure(pathDirs) 105 } 106 107 func (ds *DirStructure) ensure(pathDirs []string) error { 108 ds.Lock() 109 defer ds.Unlock() 110 111 // check current dir 112 err := EnsureDirectory(ds.Path, ds.Perm) 113 if err != nil { 114 return err 115 } 116 117 if len(pathDirs) == 0 { 118 // we reached the end! 119 return nil 120 } 121 122 child, ok := ds.Children[pathDirs[0]] 123 if !ok { 124 // we have reached the end of the defined dir structure 125 // ensure all remaining dirs 126 dirPath := ds.Path 127 for _, dir := range pathDirs { 128 dirPath = filepath.Join(dirPath, dir) 129 err := EnsureDirectory(dirPath, ds.Perm) 130 if err != nil { 131 return err 132 } 133 } 134 return nil 135 } 136 137 // we got a child, continue 138 return child.ensure(pathDirs[1:]) 139 }