summaryrefslogtreecommitdiff
path: root/vendor/github.com/projectatomic/buildah/imagebuildah/chroot_symlink.go
blob: b2452b61c73f149da46103164642b6f59e98758a (plain)
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
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)
}

func resolveChrootedSymlinks() {
	status := 0
	flag.Parse()
	if len(flag.Args()) < 1 {
		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 paramter is the path name to evaluate for symbolic links
	symLink, err := getSymbolicLink(flag.Arg(0), 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)
}

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 "" if a symlink is not found
	return string(output), nil
}

// 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(rootdir, path string) (string, error) {
	var (
		symPath          string
		symLinksResolved int
	)

	// Splitting path as we need to resolve each parth 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 "", errors.Wrapf(err, "error checking symlink for %q", symPath)
		}
		// 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 == true {
			// 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 "", errors.Wrapf(err, "error checking symlink for %q", resolvedPath)
			}
			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 os.IsNotExist(err) {
		if err = os.MkdirAll(path, 0755); err != nil {
			return false, "", errors.Wrapf(err, "error ensuring volume path %q exists", path)
		}
		info, err = os.Lstat(path)
		if err != nil {
			return false, "", errors.Wrapf(err, "error running lstat on %q", path)
		}
	}
	// Return false and path as path is 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, "", errors.Wrapf(err, "error reading link %q", path)
	}
	// if the symlink points to a relative path, prepend the path till now to the resolved path
	if !filepath.IsAbs(targetDir) {
		targetDir = filepath.Join(path, targetDir)
	}
	// run filepath.Clean to remove the ".." from relative paths
	return true, filepath.Clean(targetDir), nil
}