File: resolve_path.go

package info (click to toggle)
snapd 2.72-1
  • links: PTS, VCS
  • area: main
  • in suites: sid
  • size: 80,412 kB
  • sloc: sh: 16,506; ansic: 16,211; python: 11,213; makefile: 1,919; exp: 190; awk: 58; xml: 22
file content (143 lines) | stat: -rw-r--r-- 4,266 bytes parent folder | download | duplicates (3)
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
// -*- Mode: Go; indent-tabs-mode: t -*-

/*
 * Copyright (C) 2023 Canonical Ltd
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 3 as
 * published by the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 */

package osutil

import (
	"fmt"
	"os"
	"path/filepath"
	"strings"
)

func dumbJoin(a, b string) string {
	if strings.HasSuffix(a, "/") {
		return a + b
	} else {
		return a + "/" + b
	}
}

func resolvePathInSysrootRec(sysroot, path string, errorOnEscape bool, symlinkRecursion int) (string, error) {
	if path == "" || path == "/" {
		// Relative paths are taken from sysroot
		return "/", nil
	}

	if strings.HasSuffix(path, "/") {
		path = path[:len(path)-1]
	}

	dir, file := filepath.Split(path)
	resolvedDir, err := resolvePathInSysrootRec(sysroot, dir, errorOnEscape, symlinkRecursion)
	if err != nil {
		return "", err
	}
	if file == "" {
		return resolvedDir, nil
	}
	if file == "." {
		return resolvedDir, nil
	}
	if file == ".." {
		if errorOnEscape && (resolvedDir == "/") {
			return "", fmt.Errorf("invalid escaping path")
		}
		upperDir, _ := filepath.Split(resolvedDir)
		return upperDir, nil
	}

	fileInResolvedDir := dumbJoin(resolvedDir, file)

	realPath := dumbJoin(sysroot, fileInResolvedDir)
	st, err := os.Lstat(realPath)
	if err != nil {
		return "", err
	}

	if st.Mode()&os.ModeSymlink != 0 {
		if symlinkRecursion < 0 {
			return "", fmt.Errorf("maximum recursion reached when reading symlinks")
		}
		target, err := os.Readlink(realPath)
		if err != nil {
			return "", err
		}
		if filepath.IsAbs(target) {
			if errorOnEscape {
				return "", fmt.Errorf("invalid absolute symlink")
			}
			return resolvePathInSysrootRec(sysroot, target, errorOnEscape, symlinkRecursion-1)
		} else {
			return resolvePathInSysrootRec(sysroot, dumbJoin(resolvedDir, target), errorOnEscape, symlinkRecursion-1)
		}
	}

	return fileInResolvedDir, nil
}

// ResolvePathInSysroot resolves a path within a sysroot
//
// In a sysroot, abolute symlinks should be relative to the sysroot
// rather than `/`. Also paths with multiple `..` that would escape
// the sysroot should not do so.
//
// The path must point to a file that exists.
//
// Example 1:
//   - /sysroot/path1/a is a symlink pointing to /path2/b
//   - /sysroot/path2/b is a symlink pointing to /path3/c
//   - /sysroot/path3/c is a file
//     ResolvePathInSysroot("/sysroot", "/path1/a") will return "/path3/c"
//
// Example 2:
//   - /sysroot/path1/a  is a symlink pointing to ../../../path2/b
//   - /sysroot/path2/b  is a symlink pointing to ../../../path3/c
//   - /sysroot/path3/c  is a file
//     ResolvePathInSysroot("/sysroot", "../../../path1/a") will return "/path3/c"
//
// Example 3:
//   - /sysroot/path1/a is a symlink pointing to /path2/b
//   - /sysroot/path2/b does not exist
//     ResolvePathInSysroot("/sysroot", "/path1/a") will fail (IsNotExist)
//
// Example 4:
//   - /sysroot/foo is a file or a directory
//   - ResolvePathInSysroot("/sysroot", "/../../../../foo") will return "/foo"
//
// The return path is the path within the sysroot. filepath.Join() has
// to be used to get the path in the sysroot.
func ResolvePathInSysroot(sysroot, path string) (string, error) {
	return resolvePathInSysrootRec(sysroot, path, false, 255)
}

// ResolvePathNoEscape resolves a path within a pseudo sysroot
//
// Like ResolvePathInSysroot(), it resolves path as if it was a sysroot.
// However, any escaping relative path, or absolute symlink generates
// an error.
//
// The input path can however be absolute, and will be treated as
// relative.
//
// This is useful when a path is expected to be relative only and sees
// any "attempt" to escape the sysroot as a malformed path.
func ResolvePathNoEscape(sysroot, path string) (string, error) {
	return resolvePathInSysrootRec(sysroot, path, true, 255)
}