github.com/stefanmcshane/helm@v0.0.0-20221213002717-88a4a2c6e77d/internal/third_party/dep/fs/fs.go (about) 1 /* 2 Copyright (c) for portions of fs.go are held by The Go Authors, 2016 and are provided under 3 the BSD license. 4 5 Redistribution and use in source and binary forms, with or without 6 modification, are permitted provided that the following conditions are 7 met: 8 9 * Redistributions of source code must retain the above copyright 10 notice, this list of conditions and the following disclaimer. 11 * Redistributions in binary form must reproduce the above 12 copyright notice, this list of conditions and the following disclaimer 13 in the documentation and/or other materials provided with the 14 distribution. 15 * Neither the name of Google Inc. nor the names of its 16 contributors may be used to endorse or promote products derived from 17 this software without specific prior written permission. 18 19 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 20 "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 21 LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 22 A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 23 OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 24 SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 25 LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 26 DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 27 THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 28 (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 */ 31 32 package fs 33 34 import ( 35 "io" 36 "os" 37 "path/filepath" 38 "runtime" 39 "syscall" 40 41 "github.com/pkg/errors" 42 ) 43 44 // fs contains a copy of a few functions from dep tool code to avoid a dependency on golang/dep. 45 // This code is copied from https://github.com/golang/dep/blob/37d6c560cdf407be7b6cd035b23dba89df9275cf/internal/fs/fs.go 46 // No changes to the code were made other than removing some unused functions 47 48 // RenameWithFallback attempts to rename a file or directory, but falls back to 49 // copying in the event of a cross-device link error. If the fallback copy 50 // succeeds, src is still removed, emulating normal rename behavior. 51 func RenameWithFallback(src, dst string) error { 52 _, err := os.Stat(src) 53 if err != nil { 54 return errors.Wrapf(err, "cannot stat %s", src) 55 } 56 57 err = os.Rename(src, dst) 58 if err == nil { 59 return nil 60 } 61 62 return renameFallback(err, src, dst) 63 } 64 65 // renameByCopy attempts to rename a file or directory by copying it to the 66 // destination and then removing the src thus emulating the rename behavior. 67 func renameByCopy(src, dst string) error { 68 var cerr error 69 if dir, _ := IsDir(src); dir { 70 cerr = CopyDir(src, dst) 71 if cerr != nil { 72 cerr = errors.Wrap(cerr, "copying directory failed") 73 } 74 } else { 75 cerr = copyFile(src, dst) 76 if cerr != nil { 77 cerr = errors.Wrap(cerr, "copying file failed") 78 } 79 } 80 81 if cerr != nil { 82 return errors.Wrapf(cerr, "rename fallback failed: cannot rename %s to %s", src, dst) 83 } 84 85 return errors.Wrapf(os.RemoveAll(src), "cannot delete %s", src) 86 } 87 88 var ( 89 errSrcNotDir = errors.New("source is not a directory") 90 errDstExist = errors.New("destination already exists") 91 ) 92 93 // CopyDir recursively copies a directory tree, attempting to preserve permissions. 94 // Source directory must exist, destination directory must *not* exist. 95 func CopyDir(src, dst string) error { 96 src = filepath.Clean(src) 97 dst = filepath.Clean(dst) 98 99 // We use os.Lstat() here to ensure we don't fall in a loop where a symlink 100 // actually links to a one of its parent directories. 101 fi, err := os.Lstat(src) 102 if err != nil { 103 return err 104 } 105 if !fi.IsDir() { 106 return errSrcNotDir 107 } 108 109 _, err = os.Stat(dst) 110 if err != nil && !os.IsNotExist(err) { 111 return err 112 } 113 if err == nil { 114 return errDstExist 115 } 116 117 if err = os.MkdirAll(dst, fi.Mode()); err != nil { 118 return errors.Wrapf(err, "cannot mkdir %s", dst) 119 } 120 121 entries, err := os.ReadDir(src) 122 if err != nil { 123 return errors.Wrapf(err, "cannot read directory %s", dst) 124 } 125 126 for _, entry := range entries { 127 srcPath := filepath.Join(src, entry.Name()) 128 dstPath := filepath.Join(dst, entry.Name()) 129 130 if entry.IsDir() { 131 if err = CopyDir(srcPath, dstPath); err != nil { 132 return errors.Wrap(err, "copying directory failed") 133 } 134 } else { 135 // This will include symlinks, which is what we want when 136 // copying things. 137 if err = copyFile(srcPath, dstPath); err != nil { 138 return errors.Wrap(err, "copying file failed") 139 } 140 } 141 } 142 143 return nil 144 } 145 146 // copyFile copies the contents of the file named src to the file named 147 // by dst. The file will be created if it does not already exist. If the 148 // destination file exists, all its contents will be replaced by the contents 149 // of the source file. The file mode will be copied from the source. 150 func copyFile(src, dst string) (err error) { 151 if sym, err := IsSymlink(src); err != nil { 152 return errors.Wrap(err, "symlink check failed") 153 } else if sym { 154 if err := cloneSymlink(src, dst); err != nil { 155 if runtime.GOOS == "windows" { 156 // If cloning the symlink fails on Windows because the user 157 // does not have the required privileges, ignore the error and 158 // fall back to copying the file contents. 159 // 160 // ERROR_PRIVILEGE_NOT_HELD is 1314 (0x522): 161 // https://msdn.microsoft.com/en-us/library/windows/desktop/ms681385(v=vs.85).aspx 162 if lerr, ok := err.(*os.LinkError); ok && lerr.Err != syscall.Errno(1314) { 163 return err 164 } 165 } else { 166 return err 167 } 168 } else { 169 return nil 170 } 171 } 172 173 in, err := os.Open(src) 174 if err != nil { 175 return 176 } 177 defer in.Close() 178 179 out, err := os.Create(dst) 180 if err != nil { 181 return 182 } 183 184 if _, err = io.Copy(out, in); err != nil { 185 out.Close() 186 return 187 } 188 189 // Check for write errors on Close 190 if err = out.Close(); err != nil { 191 return 192 } 193 194 si, err := os.Stat(src) 195 if err != nil { 196 return 197 } 198 199 // Temporary fix for Go < 1.9 200 // 201 // See: https://github.com/golang/dep/issues/774 202 // and https://github.com/golang/go/issues/20829 203 if runtime.GOOS == "windows" { 204 dst = fixLongPath(dst) 205 } 206 err = os.Chmod(dst, si.Mode()) 207 208 return 209 } 210 211 // cloneSymlink will create a new symlink that points to the resolved path of sl. 212 // If sl is a relative symlink, dst will also be a relative symlink. 213 func cloneSymlink(sl, dst string) error { 214 resolved, err := os.Readlink(sl) 215 if err != nil { 216 return err 217 } 218 219 return os.Symlink(resolved, dst) 220 } 221 222 // IsDir determines is the path given is a directory or not. 223 func IsDir(name string) (bool, error) { 224 fi, err := os.Stat(name) 225 if err != nil { 226 return false, err 227 } 228 if !fi.IsDir() { 229 return false, errors.Errorf("%q is not a directory", name) 230 } 231 return true, nil 232 } 233 234 // IsSymlink determines if the given path is a symbolic link. 235 func IsSymlink(path string) (bool, error) { 236 l, err := os.Lstat(path) 237 if err != nil { 238 return false, err 239 } 240 241 return l.Mode()&os.ModeSymlink == os.ModeSymlink, nil 242 } 243 244 // fixLongPath returns the extended-length (\\?\-prefixed) form of 245 // path when needed, in order to avoid the default 260 character file 246 // path limit imposed by Windows. If path is not easily converted to 247 // the extended-length form (for example, if path is a relative path 248 // or contains .. elements), or is short enough, fixLongPath returns 249 // path unmodified. 250 // 251 // See https://msdn.microsoft.com/en-us/library/windows/desktop/aa365247(v=vs.85).aspx#maxpath 252 func fixLongPath(path string) string { 253 // Do nothing (and don't allocate) if the path is "short". 254 // Empirically (at least on the Windows Server 2013 builder), 255 // the kernel is arbitrarily okay with < 248 bytes. That 256 // matches what the docs above say: 257 // "When using an API to create a directory, the specified 258 // path cannot be so long that you cannot append an 8.3 file 259 // name (that is, the directory name cannot exceed MAX_PATH 260 // minus 12)." Since MAX_PATH is 260, 260 - 12 = 248. 261 // 262 // The MSDN docs appear to say that a normal path that is 248 bytes long 263 // will work; empirically the path must be less then 248 bytes long. 264 if len(path) < 248 { 265 // Don't fix. (This is how Go 1.7 and earlier worked, 266 // not automatically generating the \\?\ form) 267 return path 268 } 269 270 // The extended form begins with \\?\, as in 271 // \\?\c:\windows\foo.txt or \\?\UNC\server\share\foo.txt. 272 // The extended form disables evaluation of . and .. path 273 // elements and disables the interpretation of / as equivalent 274 // to \. The conversion here rewrites / to \ and elides 275 // . elements as well as trailing or duplicate separators. For 276 // simplicity it avoids the conversion entirely for relative 277 // paths or paths containing .. elements. For now, 278 // \\server\share paths are not converted to 279 // \\?\UNC\server\share paths because the rules for doing so 280 // are less well-specified. 281 if len(path) >= 2 && path[:2] == `\\` { 282 // Don't canonicalize UNC paths. 283 return path 284 } 285 if !isAbs(path) { 286 // Relative path 287 return path 288 } 289 290 const prefix = `\\?` 291 292 pathbuf := make([]byte, len(prefix)+len(path)+len(`\`)) 293 copy(pathbuf, prefix) 294 n := len(path) 295 r, w := 0, len(prefix) 296 for r < n { 297 switch { 298 case os.IsPathSeparator(path[r]): 299 // empty block 300 r++ 301 case path[r] == '.' && (r+1 == n || os.IsPathSeparator(path[r+1])): 302 // /./ 303 r++ 304 case r+1 < n && path[r] == '.' && path[r+1] == '.' && (r+2 == n || os.IsPathSeparator(path[r+2])): 305 // /../ is currently unhandled 306 return path 307 default: 308 pathbuf[w] = '\\' 309 w++ 310 for ; r < n && !os.IsPathSeparator(path[r]); r++ { 311 pathbuf[w] = path[r] 312 w++ 313 } 314 } 315 } 316 // A drive's root directory needs a trailing \ 317 if w == len(`\\?\c:`) { 318 pathbuf[w] = '\\' 319 w++ 320 } 321 return string(pathbuf[:w]) 322 } 323 324 func isAbs(path string) (b bool) { 325 v := volumeName(path) 326 if v == "" { 327 return false 328 } 329 path = path[len(v):] 330 if path == "" { 331 return false 332 } 333 return os.IsPathSeparator(path[0]) 334 } 335 336 func volumeName(path string) (v string) { 337 if len(path) < 2 { 338 return "" 339 } 340 // with drive letter 341 c := path[0] 342 if path[1] == ':' && 343 ('0' <= c && c <= '9' || 'a' <= c && c <= 'z' || 344 'A' <= c && c <= 'Z') { 345 return path[:2] 346 } 347 // is it UNC 348 if l := len(path); l >= 5 && os.IsPathSeparator(path[0]) && os.IsPathSeparator(path[1]) && 349 !os.IsPathSeparator(path[2]) && path[2] != '.' { 350 // first, leading `\\` and next shouldn't be `\`. its server name. 351 for n := 3; n < l-1; n++ { 352 // second, next '\' shouldn't be repeated. 353 if os.IsPathSeparator(path[n]) { 354 n++ 355 // third, following something characters. its share name. 356 if !os.IsPathSeparator(path[n]) { 357 if path[n] == '.' { 358 break 359 } 360 for ; n < l; n++ { 361 if os.IsPathSeparator(path[n]) { 362 break 363 } 364 } 365 return path[:n] 366 } 367 break 368 } 369 } 370 } 371 return "" 372 }