github.com/avfs/avfs@v0.33.1-0.20240303173310-c6ba67c33eb7/vfs.go (about) 1 // 2 // Copyright 2021 The AVFS authors 3 // 4 // Licensed under the Apache License, Version 2.0 (the "License"); 5 // you may not use this file except in compliance with the License. 6 // You may obtain a copy of the License at 7 // 8 // http://www.apache.org/licenses/LICENSE-2.0 9 // 10 // Unless required by applicable law or agreed to in writing, software 11 // distributed under the License is distributed on an "AS IS" BASIS, 12 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 // See the License for the specific language governing permissions and 14 // limitations under the License. 15 // 16 17 package avfs 18 19 import ( 20 "errors" 21 "io" 22 "io/fs" 23 "os" 24 "path/filepath" 25 "sort" 26 "strings" 27 _ "unsafe" // for go:linkname only. 28 ) 29 30 // cleanGlobPath prepares path for glob matching. 31 func cleanGlobPath[T VFSBase](vfs T, path string) string { 32 pathSeparator := vfs.PathSeparator() 33 34 switch path { 35 case "": 36 return "." 37 case string(pathSeparator): 38 // do nothing to the path 39 return path 40 default: 41 return path[0 : len(path)-1] // chop off trailing separator 42 } 43 } 44 45 // cleanGlobPathWindows is Windows version of cleanGlobPath. 46 func cleanGlobPathWindows[T VFSBase](vfs T, path string) (prefixLen int, cleaned string) { 47 vollen := VolumeNameLen(vfs, path) 48 49 switch { 50 case path == "": 51 return 0, "." 52 case vollen+1 == len(path) && IsPathSeparator(vfs, path[len(path)-1]): // /, \, C:\ and C:/ 53 // do nothing to the path 54 return vollen + 1, path 55 case vollen == len(path) && len(path) == 2: // C: 56 return vollen, path + "." // convert C: into C:. 57 default: 58 if vollen >= len(path) { 59 vollen = len(path) - 1 60 } 61 62 return vollen, path[0 : len(path)-1] // chop off trailing separator 63 } 64 } 65 66 // Create creates or truncates the named file. If the file already exists, 67 // it is truncated. If the file does not exist, it is created with mode 0666 68 // (before umask). If successful, methods on the returned DummyFile can 69 // be used for I/O; the associated file descriptor has mode O_RDWR. 70 // If there is an error, it will be of type *PathError. 71 func Create[T VFSBase](vfs T, name string) (File, error) { 72 return vfs.OpenFile(name, os.O_RDWR|os.O_CREATE|os.O_TRUNC, DefaultFilePerm) 73 } 74 75 // CreateTemp creates a new temporary file in the directory dir, 76 // opens the file for reading and writing, and returns the resulting file. 77 // The filename is generated by taking pattern and adding a random string to the end. 78 // If pattern includes a "*", the random string replaces the last "*". 79 // If dir is the empty string, CreateTemp uses the default directory for temporary files, as returned by TempDir. 80 // Multiple programs or goroutines calling CreateTemp simultaneously will not choose the same file. 81 // The caller can use the file's Name method to find the pathname of the file. 82 // It is the caller's responsibility to remove the file when it is no longer needed. 83 func CreateTemp[T VFSBase](vfs T, dir, pattern string) (File, error) { 84 const op = "createtemp" 85 86 if dir == "" { 87 dir = TempDir(vfs) 88 } 89 90 prefix, suffix, err := prefixAndSuffix(vfs, pattern) 91 if err != nil { 92 return nil, &fs.PathError{Op: op, Path: pattern, Err: err} 93 } 94 95 prefix = joinPath(vfs, dir, prefix) 96 97 try := 0 98 99 for { 100 name := prefix + nextRandom() + suffix 101 102 f, err := vfs.OpenFile(name, os.O_RDWR|os.O_CREATE|os.O_EXCL, 0o600) 103 if IsExist(err) { 104 try++ 105 if try < 10000 { 106 continue 107 } 108 109 return nil, &fs.PathError{Op: op, Path: dir + string(vfs.PathSeparator()) + prefix + "*" + suffix, Err: fs.ErrExist} 110 } 111 112 return f, err 113 } 114 } 115 116 // FromUnixPath returns valid path for Unix or Windows from a unix path. 117 // For Windows systems, absolute paths are prefixed with the default volume 118 // and relative paths are preserved. 119 func FromUnixPath[T VFSBase](vfs T, path string) string { 120 if vfs.OSType() != OsWindows { 121 return path 122 } 123 124 if path[0] != '/' { 125 return FromSlash(vfs, path) 126 } 127 128 return Join(vfs, DefaultVolume, FromSlash(vfs, path)) 129 } 130 131 // Glob returns the names of all files matching pattern or nil 132 // if there is no matching file. The syntax of patterns is the same 133 // as in Match. The pattern may describe hierarchical names such as 134 // /usr/*/bin/ed (assuming the Separator is '/'). 135 // 136 // Glob ignores file system errors such as I/O errors reading directories. 137 // The only possible returned error is ErrBadPattern, when pattern 138 // is malformed. 139 func Glob[T VFSBase](vfs T, pattern string) (matches []string, err error) { 140 // Check pattern is well-formed. 141 if _, err = Match(vfs, pattern, ""); err != nil { 142 return nil, err 143 } 144 145 if !hasMeta(vfs, pattern) { 146 if _, err = vfs.Lstat(pattern); err != nil { 147 return nil, nil 148 } 149 150 return []string{pattern}, nil 151 } 152 153 dir, file := Split(vfs, pattern) 154 volumeLen := 0 155 156 if vfs.OSType() == OsWindows { 157 volumeLen, dir = cleanGlobPathWindows(vfs, dir) 158 } else { 159 dir = cleanGlobPath(vfs, dir) 160 } 161 162 if !hasMeta(vfs, dir[volumeLen:]) { 163 return glob(vfs, dir, file, nil) 164 } 165 166 // Prevent infinite recursion. See issue 15879. 167 if dir == pattern { 168 return nil, filepath.ErrBadPattern 169 } 170 171 var m []string 172 173 m, err = Glob(vfs, dir) 174 if err != nil { 175 return 176 } 177 178 for _, d := range m { 179 matches, err = glob(vfs, d, file, matches) 180 if err != nil { 181 return 182 } 183 } 184 185 return //nolint:nakedret // Adapted from standard library. 186 } 187 188 // glob searches for files matching pattern in the directory dir 189 // and appends them to matches. If the directory cannot be 190 // opened, it returns the existing matches. New matches are 191 // added in lexicographical order. 192 func glob[T VFSBase](vfs T, dir, pattern string, matches []string) (m []string, e error) { 193 m = matches 194 195 fi, err := vfs.Stat(dir) 196 if err != nil { 197 return // ignore I/O error 198 } 199 200 if !fi.IsDir() { 201 return // ignore I/O error 202 } 203 204 d, err := vfs.OpenFile(dir, os.O_RDONLY, 0) 205 if err != nil { 206 return // ignore I/O error 207 } 208 209 defer d.Close() 210 211 names, _ := d.Readdirnames(-1) 212 sort.Strings(names) 213 214 for _, n := range names { 215 matched, err := Match(vfs, pattern, n) 216 if err != nil { 217 return m, err 218 } 219 220 if matched { 221 m = append(m, Join(vfs, dir, n)) 222 } 223 } 224 225 return //nolint:nakedret // Adapted from standard library. 226 } 227 228 // hasMeta reports whether path contains any of the magic characters 229 // recognized by Match. 230 func hasMeta[T VFSBase](vfs T, path string) bool { 231 magicChars := `*?[` 232 233 if vfs.OSType() != OsWindows { 234 magicChars = `*?[\` 235 } 236 237 return strings.ContainsAny(path, magicChars) 238 } 239 240 // HomeDir returns the home directory of the file system. 241 func HomeDir[T VFSBase](vfs T, basePath string) string { 242 switch vfs.OSType() { 243 case OsWindows: 244 return Join(vfs, basePath, `\Users`) 245 default: 246 return Join(vfs, "/home") 247 } 248 } 249 250 // HomeDirUser returns the home directory of the user. 251 // If the file system does not have an identity manager, the root directory is returned. 252 func HomeDirUser[T VFSBase](vfs T, basePath string, u UserReader) string { 253 name := u.Name() 254 if vfs.OSType() == OsWindows { 255 return Join(vfs, HomeDir(vfs, basePath), name) 256 } 257 258 if name == AdminUserName(vfs.OSType()) { 259 return "/root" 260 } 261 262 return Join(vfs, HomeDir(vfs, basePath), name) 263 } 264 265 // HomeDirPerm return the default permission for home directories. 266 func HomeDirPerm() fs.FileMode { 267 return 0o700 268 } 269 270 // IsExist returns a boolean indicating whether the error is known to report 271 // that a file or directory already exists. It is satisfied by ErrExist as 272 // well as some syscall errors. 273 // 274 // This function predates errors.Is. It only supports errors returned by 275 // the os package. New code should use errors.Is(err, fs.ErrExist). 276 func IsExist(err error) bool { 277 return errors.Is(err, fs.ErrExist) 278 } 279 280 // IsNotExist returns a boolean indicating whether the error is known to 281 // report that a file or directory does not exist. It is satisfied by 282 // ErrNotExist as well as some syscall errors. 283 // 284 // This function predates errors.Is. It only supports errors returned by 285 // the os package. New code should use errors.Is(err, fs.ErrNotExist). 286 func IsNotExist(err error) bool { 287 return errors.Is(err, fs.ErrNotExist) 288 } 289 290 func joinPath[T VFSBase](vfs T, dir, name string) string { 291 if dir != "" && IsPathSeparator(vfs, dir[len(dir)-1]) { 292 return dir + name 293 } 294 295 return dir + string(vfs.PathSeparator()) + name 296 } 297 298 // MkdirTemp creates a new temporary directory in the directory dir 299 // and returns the pathname of the new directory. 300 // The new directory's name is generated by adding a random string to the end of pattern. 301 // If pattern includes a "*", the random string replaces the last "*" instead. 302 // If dir is the empty string, MkdirTemp uses the default directory for temporary files, as returned by TempDir. 303 // Multiple programs or goroutines calling MkdirTemp simultaneously will not choose the same directory. 304 // It is the caller's responsibility to remove the directory when it is no longer needed. 305 func MkdirTemp[T VFSBase](vfs T, dir, pattern string) (string, error) { 306 const op = "mkdirtemp" 307 308 if dir == "" { 309 dir = vfs.TempDir() 310 } 311 312 prefix, suffix, err := prefixAndSuffix(vfs, pattern) 313 if err != nil { 314 return "", &fs.PathError{Op: op, Path: pattern, Err: err} 315 } 316 317 prefix = joinPath(vfs, dir, prefix) 318 try := 0 319 320 for { 321 name := prefix + nextRandom() + suffix 322 323 err := vfs.Mkdir(name, 0o700) 324 if err == nil { 325 return name, nil 326 } 327 328 if IsExist(err) { 329 try++ 330 if try < 10000 { 331 continue 332 } 333 334 return "", &fs.PathError{Op: op, Path: dir + string(vfs.PathSeparator()) + prefix + "*" + suffix, Err: fs.ErrExist} 335 } 336 337 if IsNotExist(err) { 338 _, err := vfs.Stat(dir) //nolint:govet // declaration of "err" shadows declaration 339 if IsNotExist(err) { 340 return "", err 341 } 342 } 343 344 return "", err 345 } 346 } 347 348 // MkHomeDir creates and returns the home directory of a user. 349 // If there is an error, it will be of type *PathError. 350 func MkHomeDir[T VFSBase](vfs T, basePath string, u UserReader) (string, error) { 351 userDir := HomeDirUser(vfs, basePath, u) 352 353 err := vfs.Mkdir(userDir, HomeDirPerm()) 354 if err != nil { 355 return "", err 356 } 357 358 switch vfs.OSType() { 359 case OsWindows: 360 err = vfs.MkdirAll(TempDirUser(vfs, basePath, u.Name()), DefaultDirPerm) 361 default: 362 err = vfs.Chown(userDir, u.Uid(), u.Gid()) 363 } 364 365 if err != nil { 366 return "", err 367 } 368 369 return userDir, nil 370 } 371 372 // MkSystemDirs creates the system directories of a file system. 373 func MkSystemDirs[T VFSBase](vfs T, dirs []DirInfo) error { 374 for _, dir := range dirs { 375 err := vfs.MkdirAll(dir.Path, dir.Perm) 376 if err != nil { 377 return err 378 } 379 380 if vfs.OSType() != OsWindows { 381 err = vfs.Chmod(dir.Path, dir.Perm) 382 if err != nil { 383 return err 384 } 385 } 386 } 387 388 return nil 389 } 390 391 // nextRandom is used in Utils.CreateTemp and Utils.MkdirTemp. 392 // 393 //go:linkname nextRandom os.nextRandom 394 func nextRandom() string 395 396 // prefixAndSuffix splits pattern by the last wildcard "*", if applicable, 397 // returning prefix as the part before "*" and suffix as the part after "*". 398 func prefixAndSuffix[T VFSBase](vfs T, pattern string) (prefix, suffix string, err error) { 399 for i := 0; i < len(pattern); i++ { 400 if IsPathSeparator(vfs, pattern[i]) { 401 return "", "", ErrPatternHasSeparator 402 } 403 } 404 405 if pos := strings.LastIndexByte(pattern, '*'); pos != -1 { 406 prefix, suffix = pattern[:pos], pattern[pos+1:] 407 } else { 408 prefix = pattern 409 } 410 411 return prefix, suffix, nil 412 } 413 414 // ReadDir reads the named directory, 415 // returning all its directory entries sorted by filename. 416 // If an error occurs reading the directory, 417 // ReadDir returns the entries it was able to read before the error, 418 // along with the error. 419 func ReadDir[T VFSBase](vfs T, name string) ([]fs.DirEntry, error) { 420 f, err := vfs.OpenFile(name, os.O_RDONLY, 0) 421 if err != nil { 422 return nil, err 423 } 424 425 defer f.Close() 426 427 dirs, err := f.ReadDir(-1) 428 429 sort.Slice(dirs, func(i, j int) bool { return dirs[i].Name() < dirs[j].Name() }) 430 431 return dirs, err 432 } 433 434 // ReadFile reads the named file and returns the contents. 435 // A successful call returns err == nil, not err == EOF. 436 // Because ReadFile reads the whole file, it does not treat an EOF from Read 437 // as an error to be reported. 438 func ReadFile[T VFSBase](vfs T, name string) ([]byte, error) { 439 f, err := vfs.OpenFile(name, os.O_RDONLY, 0) 440 if err != nil { 441 return nil, err 442 } 443 444 defer f.Close() 445 446 var size int 447 448 if info, err := f.Stat(); err == nil { 449 size64 := info.Size() 450 if int64(int(size64)) == size64 { 451 size = int(size64) 452 } 453 } 454 455 size++ // one byte for final read at EOF 456 457 // If a file claims a small size, read at least 512 bytes. 458 // In particular, files in Linux's /proc claim size 0 but 459 // then do not work right if read in small pieces, 460 // so an initial read of 1 byte would not work correctly. 461 if size < 512 { 462 size = 512 463 } 464 465 data := make([]byte, 0, size) 466 467 for { 468 if len(data) >= cap(data) { 469 d := append(data[:cap(data)], 0) //nolint:gocritic // append result not assigned to the same slice 470 data = d[:len(data)] 471 } 472 473 n, err := f.Read(data[len(data):cap(data)]) 474 data = data[:len(data)+n] 475 476 if err != nil { 477 if err == io.EOF { 478 err = nil 479 } 480 481 return data, err 482 } 483 } 484 } 485 486 // SetUserByName sets the current user by name. 487 // If the user is not found, the returned error is of type UnknownUserError. 488 func SetUserByName[T VFSBase](vfs T, name string) error { 489 if !vfs.HasFeature(FeatIdentityMgr) { 490 return ErrPermDenied 491 } 492 493 if vfs.User().Name() == name { 494 return nil 495 } 496 497 u, err := vfs.Idm().LookupUser(name) 498 if err != nil { 499 return err 500 } 501 502 err = vfs.SetUser(u) 503 504 return err 505 } 506 507 // SplitAbs splits an absolute path immediately preceding the final Separator, 508 // separating it into a directory and file name component. 509 // If there is no Separator in path, splitPath returns an empty dir 510 // and file set to path. 511 // The returned values have the property that path = dir + PathSeparator + file. 512 func SplitAbs[T VFSBase](vfs T, path string) (dir, file string) { 513 l := VolumeNameLen(vfs, path) 514 515 i := len(path) - 1 516 for i >= l && !IsPathSeparator(vfs, path[i]) { 517 i-- 518 } 519 520 return path[:i], path[i+1:] 521 } 522 523 // SystemDirs returns an array of system directories always present in the file system. 524 func SystemDirs[T VFSBase](vfs T, basePath string) []DirInfo { 525 switch vfs.OSType() { 526 case OsWindows: 527 if basePath == "" { 528 basePath = DefaultVolume 529 } 530 531 return []DirInfo{ 532 {Path: HomeDir(vfs, basePath), Perm: DefaultDirPerm}, 533 {Path: TempDirUser(vfs, basePath, AdminUserName(vfs.OSType())), Perm: DefaultDirPerm}, 534 {Path: TempDirUser(vfs, basePath, DefaultName), Perm: DefaultDirPerm}, 535 {Path: Join(vfs, basePath, `\Windows`), Perm: DefaultDirPerm}, 536 } 537 default: 538 return []DirInfo{ 539 {Path: HomeDir(vfs, basePath), Perm: HomeDirPerm()}, 540 {Path: Join(vfs, basePath, "/root"), Perm: 0o700}, 541 {Path: Join(vfs, basePath, "/tmp"), Perm: 0o777}, 542 } 543 } 544 } 545 546 // TempDir returns the default directory to use for temporary files. 547 // 548 // On Unix systems, it returns $TMPDIR if non-empty, else /tmp. 549 // On Windows, it uses GetTempPath, returning the first non-empty 550 // value from %TMP%, %TEMP%, %USERPROFILE%, or the Windows directory. 551 // On Plan 9, it returns /tmp. 552 // 553 // The directory is neither guaranteed to exist nor have accessible 554 // permissions. 555 func TempDir[T VFSBase](vfs T) string { 556 return TempDirUser(vfs, "", vfs.User().Name()) 557 } 558 559 // TempDirUser returns the default directory to use for temporary files with for a specific user. 560 func TempDirUser[T VFSBase](vfs T, basePath, username string) string { 561 if vfs.OSType() != OsWindows { 562 return Join(vfs, basePath, "/tmp") 563 } 564 565 if basePath == "" { 566 basePath = DefaultVolume 567 } 568 569 dir := Join(vfs, basePath, `\Users\`, username, `\AppData\Local\Temp`) 570 571 return dir 572 } 573 574 // ToOpenMode returns the open mode from the input flags. 575 func ToOpenMode(flag int) OpenMode { 576 var om OpenMode 577 578 // Mask flags that can be used in read only mode (syscall.O_DIRECT for example) 579 if flag&0xFFF == os.O_RDONLY { 580 return OpenRead 581 } 582 583 if flag&os.O_RDWR != 0 { 584 om = OpenRead | OpenWrite 585 } 586 587 if flag&(os.O_EXCL|os.O_CREATE) == (os.O_EXCL | os.O_CREATE) { 588 om |= OpenCreate | OpenCreateExcl | OpenWrite 589 } 590 591 if flag&os.O_CREATE != 0 { 592 om |= OpenCreate | OpenWrite 593 } 594 595 if flag&os.O_APPEND != 0 { 596 om |= OpenAppend | OpenWrite 597 } 598 599 if flag&os.O_TRUNC != 0 { 600 om |= OpenTruncate | OpenWrite 601 } 602 603 if flag&os.O_WRONLY != 0 { 604 om |= OpenWrite 605 } 606 607 return om 608 } 609 610 // VolumeName returns leading volume name. 611 // Given "C:\foo\bar" it returns "C:" on Windows. 612 // Given "\\host\share\foo" it returns "\\host\share". 613 // On other platforms it returns "". 614 func VolumeName[T VFSBase](vfs T, path string) string { 615 return FromSlash(vfs, path[:VolumeNameLen(vfs, path)]) 616 } 617 618 //go:linkname volumeNameLen path/filepath.volumeNameLen 619 func volumeNameLen(path string) int 620 621 // WalkDir walks the file tree rooted at root, calling fn for each file or 622 // directory in the tree, including root. 623 // 624 // All errors that arise visiting files and directories are filtered by fn: 625 // see the fs.WalkDirFunc documentation for details. 626 // 627 // The files are walked in lexical order, which makes the output deterministic 628 // but requires WalkDir to read an entire directory into memory before proceeding 629 // to walk that directory. 630 // 631 // WalkDir does not follow symbolic links. 632 func WalkDir[T VFSBase](vfs T, root string, fn fs.WalkDirFunc) error { 633 info, err := vfs.Lstat(root) 634 if err != nil { 635 err = fn(root, nil, err) 636 } else { 637 err = walkDir(vfs, root, &statDirEntry{info}, fn) 638 } 639 640 if err == filepath.SkipDir { 641 return nil 642 } 643 644 return err 645 } 646 647 // walkDir recursively descends path, calling walkDirFn. 648 func walkDir[T VFSBase](vfs T, path string, d fs.DirEntry, walkDirFn fs.WalkDirFunc) error { 649 if err := walkDirFn(path, d, nil); err != nil || !d.IsDir() { 650 if err == filepath.SkipDir && d.IsDir() { 651 // Successfully skipped directory. 652 err = nil 653 } 654 655 return err 656 } 657 658 dirs, err := ReadDir(vfs, path) 659 if err != nil { 660 // Second call, to report ReadDir error. 661 err = walkDirFn(path, d, err) 662 if err != nil { 663 return err 664 } 665 } 666 667 for _, d1 := range dirs { 668 path1 := Join(vfs, path, d1.Name()) 669 if err := walkDir(vfs, path1, d1, walkDirFn); err != nil { 670 if err == filepath.SkipDir { 671 break 672 } 673 674 return err 675 } 676 } 677 678 return nil 679 } 680 681 type statDirEntry struct { 682 info fs.FileInfo 683 } 684 685 func (d *statDirEntry) Name() string { return d.info.Name() } 686 func (d *statDirEntry) IsDir() bool { return d.info.IsDir() } 687 func (d *statDirEntry) Type() fs.FileMode { return d.info.Mode().Type() } 688 func (d *statDirEntry) Info() (fs.FileInfo, error) { return d.info, nil } 689 690 // WriteFile writes data to the named file, creating it if necessary. 691 // If the file does not exist, WriteFile creates it with permissions perm (before umask); 692 // otherwise WriteFile truncates it before writing, without changing permissions. 693 func WriteFile[T VFSBase](vfs T, name string, data []byte, perm fs.FileMode) error { 694 f, err := vfs.OpenFile(name, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, perm) 695 if err != nil { 696 return err 697 } 698 699 _, err = f.Write(data) 700 if err1 := f.Close(); err1 != nil && err == nil { 701 err = err1 702 } 703 704 return err 705 }