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 }