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  }