github.com/lineaje-labs/syft@v0.98.1-0.20231227153149-9e393f60ff1b/syft/internal/fileresolver/chroot_context.go (about)

     1  package fileresolver
     2  
     3  import (
     4  	"fmt"
     5  	"os"
     6  	"path"
     7  	"path/filepath"
     8  	"strings"
     9  
    10  	"github.com/lineaje-labs/syft/syft/internal/windows"
    11  )
    12  
    13  // ChrootContext helps to modify path from a real filesystem to a chroot-like filesystem, taking into account
    14  // the user given root, the base path (if any) to consider as the root, and the current working directory.
    15  // Note: this only works on a real filesystem, not on a virtual filesystem (such as a stereoscope filetree).
    16  type ChrootContext struct {
    17  	root              string
    18  	base              string
    19  	cwd               string
    20  	cwdRelativeToRoot string
    21  }
    22  
    23  func NewChrootContextFromCWD(root, base string) (*ChrootContext, error) {
    24  	currentWD, err := os.Getwd()
    25  	if err != nil {
    26  		return nil, fmt.Errorf("could not get current working directory: %w", err)
    27  	}
    28  
    29  	return NewChrootContext(root, base, currentWD)
    30  }
    31  
    32  func NewChrootContext(root, base, cwd string) (*ChrootContext, error) {
    33  	cleanRoot, err := NormalizeRootDirectory(root)
    34  	if err != nil {
    35  		return nil, err
    36  	}
    37  
    38  	cleanBase, err := NormalizeBaseDirectory(base)
    39  	if err != nil {
    40  		return nil, err
    41  	}
    42  
    43  	chroot := &ChrootContext{
    44  		root: cleanRoot,
    45  		base: cleanBase,
    46  		cwd:  cwd,
    47  	}
    48  
    49  	return chroot, chroot.ChangeDirectory(cwd)
    50  }
    51  
    52  func NormalizeRootDirectory(root string) (string, error) {
    53  	cleanRoot, err := filepath.EvalSymlinks(root)
    54  	if err != nil {
    55  		return "", fmt.Errorf("could not evaluate root=%q symlinks: %w", root, err)
    56  	}
    57  	return cleanRoot, nil
    58  }
    59  
    60  func NormalizeBaseDirectory(base string) (string, error) {
    61  	if base == "" {
    62  		return "", nil
    63  	}
    64  
    65  	cleanBase, err := filepath.EvalSymlinks(base)
    66  	if err != nil {
    67  		return "", fmt.Errorf("could not evaluate base=%q symlinks: %w", base, err)
    68  	}
    69  
    70  	return filepath.Abs(cleanBase)
    71  }
    72  
    73  // Root returns the root path with all symlinks evaluated.
    74  func (r ChrootContext) Root() string {
    75  	return r.root
    76  }
    77  
    78  // Base returns the absolute base path with all symlinks evaluated.
    79  func (r ChrootContext) Base() string {
    80  	return r.base
    81  }
    82  
    83  // ChangeRoot swaps the path for the chroot.
    84  func (r *ChrootContext) ChangeRoot(dir string) error {
    85  	newR, err := NewChrootContext(dir, r.base, r.cwd)
    86  	if err != nil {
    87  		return fmt.Errorf("could not change root: %w", err)
    88  	}
    89  
    90  	*r = *newR
    91  
    92  	return nil
    93  }
    94  
    95  // ChangeDirectory changes the current working directory so that any relative paths passed
    96  // into ToNativePath() and ToChrootPath() honor the new CWD. If the process changes the CWD in-flight, this should be
    97  // called again to ensure correct functionality of ToNativePath() and ToChrootPath().
    98  func (r *ChrootContext) ChangeDirectory(dir string) error {
    99  	var (
   100  		cwdRelativeToRoot string
   101  		err               error
   102  	)
   103  
   104  	dir, err = filepath.Abs(dir)
   105  	if err != nil {
   106  		return fmt.Errorf("could not determine absolute path to CWD: %w", err)
   107  	}
   108  
   109  	if path.IsAbs(r.root) {
   110  		cwdRelativeToRoot, err = filepath.Rel(dir, r.root)
   111  		if err != nil {
   112  			return fmt.Errorf("could not determine given root path to CWD: %w", err)
   113  		}
   114  	} else {
   115  		cwdRelativeToRoot = filepath.Clean(r.root)
   116  	}
   117  
   118  	r.cwd = dir
   119  	r.cwdRelativeToRoot = cwdRelativeToRoot
   120  	return nil
   121  }
   122  
   123  // ToNativePath takes a path in the context of the chroot-like filesystem and converts it to a path in the underlying fs domain.
   124  func (r ChrootContext) ToNativePath(chrootPath string) (string, error) {
   125  	responsePath := chrootPath
   126  
   127  	if filepath.IsAbs(responsePath) {
   128  		// don't allow input to potentially hop above root path
   129  		responsePath = path.Join(r.root, responsePath)
   130  	} else {
   131  		// ensure we take into account any relative difference between the root path and the CWD for relative requests
   132  		responsePath = path.Join(r.cwdRelativeToRoot, responsePath)
   133  	}
   134  
   135  	var err error
   136  	responsePath, err = filepath.Abs(responsePath)
   137  	if err != nil {
   138  		return "", err
   139  	}
   140  	return responsePath, nil
   141  }
   142  
   143  // ToChrootPath takes a path from the underlying fs domain and converts it to a path that is relative to the current root context.
   144  func (r ChrootContext) ToChrootPath(nativePath string) string {
   145  	responsePath := nativePath
   146  	// check to see if we need to encode back to Windows from posix
   147  	if windows.HostRunningOnWindows() {
   148  		responsePath = windows.FromPosix(responsePath)
   149  	}
   150  
   151  	// clean references to the request path (either the root, or the base if set)
   152  	if filepath.IsAbs(responsePath) {
   153  		var prefix string
   154  		if r.base != "" {
   155  			prefix = r.base
   156  		} else {
   157  			// we need to account for the cwd relative to the running process and the given root for the directory resolver
   158  			prefix = filepath.Clean(filepath.Join(r.cwd, r.cwdRelativeToRoot))
   159  			prefix += string(filepath.Separator)
   160  		}
   161  		responsePath = strings.TrimPrefix(responsePath, prefix)
   162  	}
   163  
   164  	return responsePath
   165  }