mirror of
https://github.com/safing/portmaster
synced 2025-04-07 12:39:09 +00:00
175 lines
4.3 KiB
Go
175 lines
4.3 KiB
Go
package utils
|
|
|
|
import (
|
|
"fmt"
|
|
"io/fs"
|
|
"path/filepath"
|
|
"strings"
|
|
"sync"
|
|
)
|
|
|
|
type FSPermission uint8
|
|
|
|
const (
|
|
AdminOnlyPermission FSPermission = iota
|
|
PublicReadPermission
|
|
PublicWritePermission
|
|
)
|
|
|
|
// AsUnixDirExecPermission return the corresponding unix permission for a directory or executable.
|
|
func (perm FSPermission) AsUnixDirExecPermission() fs.FileMode {
|
|
switch perm {
|
|
case AdminOnlyPermission:
|
|
return 0o700
|
|
case PublicReadPermission:
|
|
return 0o755
|
|
case PublicWritePermission:
|
|
return 0o777
|
|
}
|
|
|
|
return 0
|
|
}
|
|
|
|
// AsUnixFilePermission return the corresponding unix permission for a regular file.
|
|
func (perm FSPermission) AsUnixFilePermission() fs.FileMode {
|
|
switch perm {
|
|
case AdminOnlyPermission:
|
|
return 0o600
|
|
case PublicReadPermission:
|
|
return 0o644
|
|
case PublicWritePermission:
|
|
return 0o666
|
|
}
|
|
|
|
return 0
|
|
}
|
|
|
|
// DirStructure represents a directory structure with permissions that should be enforced.
|
|
type DirStructure struct {
|
|
sync.Mutex
|
|
|
|
Path string
|
|
Dir string
|
|
Perm FSPermission
|
|
Parent *DirStructure
|
|
Children map[string]*DirStructure
|
|
}
|
|
|
|
// NewDirStructure returns a new DirStructure.
|
|
func NewDirStructure(path string, perm FSPermission) *DirStructure {
|
|
return &DirStructure{
|
|
Path: path,
|
|
Perm: perm,
|
|
Children: make(map[string]*DirStructure),
|
|
}
|
|
}
|
|
|
|
// ChildDir adds a new child DirStructure and returns it. Should the child already exist, the existing child is returned and the permissions are updated.
|
|
func (ds *DirStructure) ChildDir(dirName string, perm FSPermission) (child *DirStructure) {
|
|
ds.Lock()
|
|
defer ds.Unlock()
|
|
|
|
// if exists, update
|
|
child, ok := ds.Children[dirName]
|
|
if ok {
|
|
child.Perm = perm
|
|
return child
|
|
}
|
|
|
|
// create new
|
|
newDir := &DirStructure{
|
|
Path: filepath.Join(ds.Path, dirName),
|
|
Dir: dirName,
|
|
Perm: perm,
|
|
Parent: ds,
|
|
Children: make(map[string]*DirStructure),
|
|
}
|
|
ds.Children[dirName] = newDir
|
|
return newDir
|
|
}
|
|
|
|
// Ensure ensures that the specified directory structure (from the first parent on) exists.
|
|
func (ds *DirStructure) Ensure() error {
|
|
return ds.EnsureAbsPath(ds.Path)
|
|
}
|
|
|
|
// EnsureRelPath ensures that the specified directory structure (from the first parent on) and the given relative path (to the DirStructure) exists.
|
|
func (ds *DirStructure) EnsureRelPath(dirPath string) error {
|
|
return ds.EnsureAbsPath(filepath.Join(ds.Path, dirPath))
|
|
}
|
|
|
|
// EnsureRelDir ensures that the specified directory structure (from the first parent on) and the given relative path (to the DirStructure) exists.
|
|
func (ds *DirStructure) EnsureRelDir(dirNames ...string) error {
|
|
return ds.EnsureAbsPath(filepath.Join(append([]string{ds.Path}, dirNames...)...))
|
|
}
|
|
|
|
// EnsureAbsPath ensures that the specified directory structure (from the first parent on) and the given absolute path exists.
|
|
// If the given path is outside the DirStructure, an error will be returned.
|
|
func (ds *DirStructure) EnsureAbsPath(dirPath string) error {
|
|
// always start at the top
|
|
if ds.Parent != nil {
|
|
return ds.Parent.EnsureAbsPath(dirPath)
|
|
}
|
|
|
|
// check if root
|
|
if dirPath == ds.Path {
|
|
return ds.ensure(nil)
|
|
}
|
|
|
|
// check scope
|
|
slashedPath := ds.Path
|
|
// add slash to end
|
|
if !strings.HasSuffix(slashedPath, string(filepath.Separator)) {
|
|
slashedPath += string(filepath.Separator)
|
|
}
|
|
// check if given path is in scope
|
|
if !strings.HasPrefix(dirPath, slashedPath) {
|
|
return fmt.Errorf(`path "%s" is outside of DirStructure scope`, dirPath)
|
|
}
|
|
|
|
// get relative path
|
|
relPath, err := filepath.Rel(ds.Path, dirPath)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to get relative path: %w", err)
|
|
}
|
|
|
|
// split to path elements
|
|
pathDirs := strings.Split(filepath.ToSlash(relPath), "/")
|
|
|
|
// start checking
|
|
return ds.ensure(pathDirs)
|
|
}
|
|
|
|
func (ds *DirStructure) ensure(pathDirs []string) error {
|
|
ds.Lock()
|
|
defer ds.Unlock()
|
|
|
|
// check current dir
|
|
err := EnsureDirectory(ds.Path, ds.Perm)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if len(pathDirs) == 0 {
|
|
// we reached the end!
|
|
return nil
|
|
}
|
|
|
|
child, ok := ds.Children[pathDirs[0]]
|
|
if !ok {
|
|
// we have reached the end of the defined dir structure
|
|
// ensure all remaining dirs
|
|
dirPath := ds.Path
|
|
for _, dir := range pathDirs {
|
|
dirPath = filepath.Join(dirPath, dir)
|
|
err := EnsureDirectory(dirPath, ds.Perm)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// we got a child, continue
|
|
return child.ensure(pathDirs[1:])
|
|
}
|