github.com/kastenhq/syft@v0.0.0-20230821225854-0710af25cdbe/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/kastenhq/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 }