1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151
|
package imagebuildah
import (
"flag"
"fmt"
"os"
"path/filepath"
"strings"
"github.com/containers/storage/pkg/reexec"
"github.com/pkg/errors"
"golang.org/x/sys/unix"
)
const (
symlinkChrootedCommand = "chrootsymlinks-resolve"
maxSymlinksResolved = 40
)
func init() {
reexec.Register(symlinkChrootedCommand, resolveChrootedSymlinks)
}
// resolveSymlink uses a child subprocess to resolve any symlinks in filename
// in the context of rootdir.
func resolveSymlink(rootdir, filename string) (string, error) {
// The child process expects a chroot and one path that
// will be consulted relative to the chroot directory and evaluated
// for any symbolic links present.
cmd := reexec.Command(symlinkChrootedCommand, rootdir, filename)
output, err := cmd.CombinedOutput()
if err != nil {
return "", errors.Wrapf(err, string(output))
}
// Hand back the resolved symlink, will be filename if a symlink is not found
return string(output), nil
}
// main() for resolveSymlink()'s subprocess.
func resolveChrootedSymlinks() {
status := 0
flag.Parse()
if len(flag.Args()) < 2 {
fmt.Fprintf(os.Stderr, "%s needs two arguments\n", symlinkChrootedCommand)
os.Exit(1)
}
// Our first parameter is the directory to chroot into.
if err := unix.Chdir(flag.Arg(0)); err != nil {
fmt.Fprintf(os.Stderr, "chdir(): %v\n", err)
os.Exit(1)
}
if err := unix.Chroot(flag.Arg(0)); err != nil {
fmt.Fprintf(os.Stderr, "chroot(): %v\n", err)
os.Exit(1)
}
// Our second parameter is the path name to evaluate for symbolic links
symLink, err := getSymbolicLink(flag.Arg(1))
if err != nil {
fmt.Fprintf(os.Stderr, "error getting symbolic links: %v\n", err)
os.Exit(1)
}
if _, err := os.Stdout.WriteString(symLink); err != nil {
fmt.Fprintf(os.Stderr, "error writing string to stdout: %v\n", err)
os.Exit(1)
}
os.Exit(status)
}
// getSymbolic link goes through each part of the path and continues resolving symlinks as they appear.
// Returns what the whole target path for what "path" resolves to.
func getSymbolicLink(path string) (string, error) {
var (
symPath string
symLinksResolved int
)
// Splitting path as we need to resolve each part of the path at a time
splitPath := strings.Split(path, "/")
if splitPath[0] == "" {
splitPath = splitPath[1:]
symPath = "/"
}
for _, p := range splitPath {
// If we have resolved 40 symlinks, that means something is terribly wrong
// will return an error and exit
if symLinksResolved >= maxSymlinksResolved {
return "", errors.Errorf("have resolved %q symlinks, something is terribly wrong!", maxSymlinksResolved)
}
symPath = filepath.Join(symPath, p)
isSymlink, resolvedPath, err := hasSymlink(symPath)
if err != nil {
return "", err
}
// if isSymlink is true, check if resolvedPath is potentially another symlink
// keep doing this till resolvedPath is not a symlink and isSymlink is false
for isSymlink {
// Need to keep track of number of symlinks resolved
// Will also return an error if the symlink points to itself as that will exceed maxSymlinksResolved
if symLinksResolved >= maxSymlinksResolved {
return "", errors.Errorf("have resolved %q symlinks, something is terribly wrong!", maxSymlinksResolved)
}
isSymlink, resolvedPath, err = hasSymlink(resolvedPath)
if err != nil {
return "", err
}
symLinksResolved++
}
// Assign resolvedPath to symPath. The next part of the loop will append the next part of the original path
// and continue resolving
symPath = resolvedPath
symLinksResolved++
}
return symPath, nil
}
// hasSymlink returns true and the target if path is symlink
// otherwise it returns false and path
func hasSymlink(path string) (bool, string, error) {
info, err := os.Lstat(path)
if err != nil {
if os.IsNotExist(err) {
if err = os.MkdirAll(path, 0755); err != nil {
return false, "", err
}
info, err = os.Lstat(path)
if err != nil {
return false, "", err
}
} else {
return false, path, err
}
}
// Return false and path as path if not a symlink
if info.Mode()&os.ModeSymlink != os.ModeSymlink {
return false, path, nil
}
// Read the symlink to get what it points to
targetDir, err := os.Readlink(path)
if err != nil {
return false, "", err
}
// if the symlink points to a relative path, prepend the path till now to the resolved path
if !filepath.IsAbs(targetDir) {
targetDir = filepath.Join(filepath.Dir(path), targetDir)
}
// run filepath.Clean to remove the ".." from relative paths
return true, filepath.Clean(targetDir), nil
}
|