mirror of
https://github.com/token2/snapd.git
synced 2026-03-13 11:15:47 -07:00
144 lines
4.2 KiB
Go
144 lines
4.2 KiB
Go
// -*- 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)
|
|
}
|