126 lines
3.4 KiB
Go
126 lines
3.4 KiB
Go
package proot
|
|
|
|
import (
|
|
"errors"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
|
|
prootext "sirherobrine23.com.br/go-bds/exec/v2/proot/extensions/extensions"
|
|
)
|
|
|
|
type pathMapper struct {
|
|
root string
|
|
res []prootext.PathResolver
|
|
}
|
|
|
|
func newPathMapper(root string, resolvers []prootext.PathResolver) (*pathMapper, error) {
|
|
rootAbs, err := filepath.Abs(root)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
rootAbs = filepath.Clean(rootAbs)
|
|
if st, err := os.Stat(rootAbs); err != nil {
|
|
return nil, err
|
|
} else if !st.IsDir() {
|
|
return nil, errors.New("proot: Rootfs is not a directory")
|
|
}
|
|
|
|
return &pathMapper{root: rootAbs, res: append([]prootext.PathResolver(nil), resolvers...)}, nil
|
|
}
|
|
|
|
func cleanGuestPath(p string) string {
|
|
if p == "" {
|
|
return "/"
|
|
}
|
|
if !strings.HasPrefix(p, "/") {
|
|
p = "/" + p
|
|
}
|
|
p = filepath.Clean(p)
|
|
if p == "." {
|
|
return "/"
|
|
}
|
|
return p
|
|
}
|
|
|
|
func joinGuest(cwd, p string) string {
|
|
if p == "" {
|
|
return cleanGuestPath(cwd)
|
|
}
|
|
if strings.HasPrefix(p, "/") {
|
|
return cleanGuestPath(p)
|
|
}
|
|
return cleanGuestPath(filepath.Join(cleanGuestPath(cwd), p))
|
|
}
|
|
|
|
func hasHostPrefix(path, prefix string) bool {
|
|
path = filepath.Clean(path)
|
|
prefix = filepath.Clean(prefix)
|
|
return path == prefix || strings.HasPrefix(path, strings.TrimRight(prefix, string(os.PathSeparator))+string(os.PathSeparator))
|
|
}
|
|
|
|
func suffixAfterHostPrefix(path, prefix string) string {
|
|
if filepath.Clean(path) == filepath.Clean(prefix) {
|
|
return ""
|
|
}
|
|
return strings.TrimPrefix(filepath.Clean(path), strings.TrimRight(filepath.Clean(prefix), string(os.PathSeparator)))
|
|
}
|
|
|
|
func (pm *pathMapper) GuestToHost(guest string) string {
|
|
return pm.GuestToHostMode(guest, prootext.PathRead)
|
|
}
|
|
|
|
func (pm *pathMapper) GuestToHostMode(guest string, mode prootext.PathMode) string {
|
|
guest = cleanGuestPath(guest)
|
|
for i := len(pm.res) - 1; i >= 0; i-- {
|
|
resolver := pm.res[i]
|
|
resolution, err := resolver.ResolvePath(prootext.PathRequest{GuestPath: guest, Mode: mode})
|
|
if err == nil && resolution.Handled && resolution.HostPath != "" {
|
|
return filepath.Clean(resolution.HostPath)
|
|
}
|
|
}
|
|
|
|
return filepath.Clean(filepath.Join(pm.root, strings.TrimPrefix(guest, "/")))
|
|
}
|
|
|
|
func (pm *pathMapper) HostToGuest(host string) string {
|
|
if guest, ok := pm.hostToGuestAny(host); ok {
|
|
return guest
|
|
}
|
|
return filepath.Clean(host)
|
|
}
|
|
|
|
func (pm *pathMapper) hostToGuestAny(host string) (string, bool) {
|
|
for i := len(pm.res) - 1; i >= 0; i-- {
|
|
resolver := pm.res[i]
|
|
if guest, ok := resolver.HostToGuest(host); ok {
|
|
return guest, true
|
|
}
|
|
}
|
|
host = filepath.Clean(host)
|
|
if hasHostPrefix(host, pm.root) {
|
|
return cleanGuestPath(filepath.Join("/", suffixAfterHostPrefix(host, pm.root))), true
|
|
}
|
|
return "", false
|
|
}
|
|
|
|
func (pm *pathMapper) Translate(cwd, path string) (guest, host string) {
|
|
return pm.TranslateMode(cwd, path, prootext.PathRead)
|
|
}
|
|
|
|
func (pm *pathMapper) TranslateMode(cwd, path string, mode prootext.PathMode) (guest, host string) {
|
|
// A few paths are necessarily injected as host paths before the tracee is
|
|
// fully under ptrace control. The dynamic linker is the most visible case:
|
|
// when the kernel starts /rootfs/bin/ls directly it may later reopen that
|
|
// same absolute host path. Treat already-mapped host paths as canonical
|
|
// host paths instead of prefixing the rootfs a second time.
|
|
if filepath.IsAbs(path) {
|
|
if mappedGuest, ok := pm.hostToGuestAny(path); ok {
|
|
return mappedGuest, filepath.Clean(path)
|
|
}
|
|
}
|
|
guest = joinGuest(cwd, path)
|
|
host = pm.GuestToHostMode(guest, mode)
|
|
return guest, host
|
|
}
|