github.com/SagerNet/gvisor@v0.0.0-20210707092255-7731c139d75c/pkg/sentry/fs/path.go (about)

     1  // Copyright 2018 The gVisor Authors.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package fs
    16  
    17  import (
    18  	"path/filepath"
    19  	"strings"
    20  )
    21  
    22  // TrimTrailingSlashes trims any trailing slashes.
    23  //
    24  // The returned boolean indicates whether any changes were made.
    25  //
    26  //go:nosplit
    27  func TrimTrailingSlashes(dir string) (trimmed string, changed bool) {
    28  	// Trim the trailing slash, except for root.
    29  	for len(dir) > 1 && dir[len(dir)-1] == '/' {
    30  		dir = dir[:len(dir)-1]
    31  		changed = true
    32  	}
    33  	return dir, changed
    34  }
    35  
    36  // SplitLast splits the given path into a directory and a file.
    37  //
    38  // The "absoluteness" of the path is preserved, but dir is always stripped of
    39  // trailing slashes.
    40  //
    41  //go:nosplit
    42  func SplitLast(path string) (dir, file string) {
    43  	path, _ = TrimTrailingSlashes(path)
    44  	if path == "" {
    45  		return ".", "."
    46  	} else if path == "/" {
    47  		return "/", "."
    48  	}
    49  
    50  	var slash int // Last location of slash in path.
    51  	for slash = len(path) - 1; slash >= 0 && path[slash] != '/'; slash-- {
    52  	}
    53  	switch {
    54  	case slash < 0:
    55  		return ".", path
    56  	case slash == 0:
    57  		// Directory of the form "/foo", or just "/". We need to
    58  		// preserve the first slash here, since it indicates an
    59  		// absolute path.
    60  		return "/", path[1:]
    61  	default:
    62  		// Drop the trailing slash.
    63  		dir, _ = TrimTrailingSlashes(path[:slash])
    64  		return dir, path[slash+1:]
    65  	}
    66  }
    67  
    68  // SplitFirst splits the given path into a first directory and the remainder.
    69  //
    70  // If remainder is empty, then the path is a single element.
    71  //
    72  //go:nosplit
    73  func SplitFirst(path string) (current, remainder string) {
    74  	path, _ = TrimTrailingSlashes(path)
    75  	if path == "" {
    76  		return ".", ""
    77  	}
    78  
    79  	var slash int // First location of slash in path.
    80  	for slash = 0; slash < len(path) && path[slash] != '/'; slash++ {
    81  	}
    82  	switch {
    83  	case slash >= len(path):
    84  		return path, ""
    85  	case slash == 0:
    86  		// See above.
    87  		return "/", path[1:]
    88  	default:
    89  		current = path[:slash]
    90  		remainder = path[slash+1:]
    91  		// Strip redundant slashes.
    92  		for len(remainder) > 0 && remainder[0] == '/' {
    93  			remainder = remainder[1:]
    94  		}
    95  		return current, remainder
    96  	}
    97  }
    98  
    99  // IsSubpath checks whether the first path is a (strict) descendent of the
   100  // second. If it is a subpath, then true is returned along with a clean
   101  // relative path from the second path to the first. Otherwise false is
   102  // returned.
   103  func IsSubpath(subpath, path string) (string, bool) {
   104  	cleanPath := filepath.Clean(path)
   105  	cleanSubpath := filepath.Clean(subpath)
   106  
   107  	// Add a trailing slash to the path if it does not already have one.
   108  	if len(cleanPath) == 0 || cleanPath[len(cleanPath)-1] != '/' {
   109  		cleanPath += "/"
   110  	}
   111  	if cleanPath == cleanSubpath {
   112  		// Paths are equal, thus not a strict subpath.
   113  		return "", false
   114  	}
   115  	if strings.HasPrefix(cleanSubpath, cleanPath) {
   116  		return strings.TrimPrefix(cleanSubpath, cleanPath), true
   117  	}
   118  	return "", false
   119  }