github.com/avfs/avfs@v0.33.1-0.20240303173310-c6ba67c33eb7/vfs/mountfs/mountfs.go (about) 1 // 2 // Copyright 2022 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 mountfs implements a file system composed of different file systems. 18 package mountfs 19 20 import ( 21 "io/fs" 22 "os" 23 "time" 24 25 "github.com/avfs/avfs" 26 ) 27 28 // Abs returns an absolute representation of path. 29 // If the path is not absolute it will be joined with the current 30 // working directory to turn it into an absolute path. The absolute 31 // path name for a given file is not guaranteed to be unique. 32 // Abs calls [Clean] on the result. 33 func (vfs *MountFS) Abs(path string) (string, error) { 34 if vfs.IsAbs(path) { 35 return vfs.Clean(path), nil 36 } 37 38 return vfs.Join(vfs.CurDir(), path), nil 39 } 40 41 // Base returns the last element of path. 42 // Trailing path separators are removed before extracting the last element. 43 // If the path is empty, Base returns ".". 44 // If the path consists entirely of separators, Base returns a single separator. 45 func (vfs *MountFS) Base(path string) string { 46 return avfs.Base(vfs, path) 47 } 48 49 // Chdir changes the current working directory to the named directory. 50 // If there is an error, it will be of type *PathError. 51 func (vfs *MountFS) Chdir(dir string) error { 52 mnt, vfsPath := vfs.pathToMount(dir) 53 54 err := mnt.vfs.Chdir(vfsPath) 55 if err != nil { 56 return mnt.restoreError(err) 57 } 58 59 vfs.curMnt = mnt 60 _ = vfs.SetCurDir(vfsPath) 61 62 return nil 63 } 64 65 // Chmod changes the mode of the named file to mode. 66 // If the file is a symbolic link, it changes the mode of the link's target. 67 // If there is an error, it will be of type *PathError. 68 // 69 // A different subset of the mode bits are used, depending on the 70 // operating system. 71 // 72 // On Unix, the mode's permission bits, ModeSetuid, ModeSetgid, and 73 // ModeSticky are used. 74 // 75 // On Windows, only the 0200 bit (owner writable) of mode is used; it 76 // controls whether the file's read-only attribute is set or cleared. 77 // The other bits are currently unused. For compatibility with Go 1.12 78 // and earlier, use a non-zero mode. Use mode 0400 for a read-only 79 // file and 0600 for a readable+writable file. 80 // 81 // On Plan 9, the mode's permission bits, ModeAppend, ModeExclusive, 82 // and ModeTemporary are used. 83 func (vfs *MountFS) Chmod(name string, mode fs.FileMode) error { 84 mnt, vfsPath := vfs.pathToMount(name) 85 err := mnt.vfs.Chmod(vfsPath, mode) 86 87 return mnt.restoreError(err) 88 } 89 90 // Chown changes the numeric uid and gid of the named file. 91 // If the file is a symbolic link, it changes the uid and gid of the link's target. 92 // A uid or gid of -1 means to not change that value. 93 // If there is an error, it will be of type *PathError. 94 // 95 // On Windows or Plan 9, Chown always returns the syscall.EWINDOWS or 96 // EPLAN9 error, wrapped in *PathError. 97 func (vfs *MountFS) Chown(name string, uid, gid int) error { 98 mnt, vfsPath := vfs.pathToMount(name) 99 err := mnt.vfs.Chown(vfsPath, uid, gid) 100 101 return mnt.restoreError(err) 102 } 103 104 // Chtimes changes the access and modification times of the named 105 // file, similar to the Unix utime() or utimes() functions. 106 // 107 // The underlying file system may truncate or round the values to a 108 // less precise time unit. 109 // If there is an error, it will be of type *PathError. 110 func (vfs *MountFS) Chtimes(name string, atime, mtime time.Time) error { 111 mnt, vfsPath := vfs.pathToMount(name) 112 err := mnt.vfs.Chtimes(vfsPath, atime, mtime) 113 114 return mnt.restoreError(err) 115 } 116 117 // Clean returns the shortest path name equivalent to path 118 // by purely lexical processing. It applies the following rules 119 // iteratively until no further processing can be done: 120 // 121 // 1. Replace multiple Separator elements with a single one. 122 // 2. Eliminate each . path name element (the current directory). 123 // 3. Eliminate each inner .. path name element (the parent directory) 124 // along with the non-.. element that precedes it. 125 // 4. Eliminate .. elements that begin a rooted path: 126 // that is, replace "/.." by "/" at the beginning of a path, 127 // assuming Separator is '/'. 128 // 129 // The returned path ends in a slash only if it represents a root directory, 130 // such as "/" on Unix or `C:\` on Windows. 131 // 132 // Finally, any occurrences of slash are replaced by Separator. 133 // 134 // If the result of this process is an empty string, Clean 135 // returns the string ".". 136 // 137 // See also Rob Pike, “Lexical File Names in Plan 9 or 138 // Getting Dot-Dot Right,” 139 // https://9p.io/sys/doc/lexnames.html 140 func (vfs *MountFS) Clean(path string) string { 141 return avfs.Clean(vfs, path) 142 } 143 144 // Create creates or truncates the named file. If the file already exists, 145 // it is truncated. If the file does not exist, it is created with mode 0666 146 // (before umask). If successful, methods on the returned DummyFile can 147 // be used for I/O; the associated file descriptor has mode O_RDWR. 148 // If there is an error, it will be of type *PathError. 149 func (vfs *MountFS) Create(name string) (avfs.File, error) { 150 return avfs.Create(vfs, name) 151 } 152 153 // CreateTemp creates a new temporary file in the directory dir, 154 // opens the file for reading and writing, and returns the resulting file. 155 // The filename is generated by taking pattern and adding a random string to the end. 156 // If pattern includes a "*", the random string replaces the last "*". 157 // If dir is the empty string, CreateTemp uses the default directory for temporary files, as returned by TempDir. 158 // Multiple programs or goroutines calling CreateTemp simultaneously will not choose the same file. 159 // The caller can use the file's Name method to find the pathname of the file. 160 // It is the caller's responsibility to remove the file when it is no longer needed. 161 func (vfs *MountFS) CreateTemp(dir, pattern string) (avfs.File, error) { 162 return avfs.CreateTemp(vfs, dir, pattern) 163 } 164 165 // Dir returns all but the last element of path, typically the path's directory. 166 // After dropping the final element, Dir calls Clean on the path and trailing 167 // slashes are removed. 168 // If the path is empty, Dir returns ".". 169 // If the path consists entirely of separators, Dir returns a single separator. 170 // The returned path does not end in a separator unless it is the root directory. 171 func (vfs *MountFS) Dir(path string) string { 172 return avfs.Dir(vfs, path) 173 } 174 175 // EvalSymlinks returns the path name after the evaluation of any symbolic 176 // links. 177 // If path is relative the result will be relative to the current directory, 178 // unless one of the components is an absolute symbolic link. 179 // EvalSymlinks calls Clean on the result. 180 func (vfs *MountFS) EvalSymlinks(path string) (string, error) { 181 op := "lstat" 182 err := error(avfs.ErrPermDenied) 183 184 if vfs.OSType() == avfs.OsWindows { 185 op = "CreateFile" 186 err = avfs.ErrWinAccessDenied 187 } 188 189 return "", &fs.PathError{Op: op, Path: path, Err: err} 190 } 191 192 // FromSlash returns the result of replacing each slash ('/') character 193 // in path with a separator character. Multiple slashes are replaced 194 // by multiple separators. 195 func (vfs *MountFS) FromSlash(path string) string { 196 return avfs.FromSlash(vfs, path) 197 } 198 199 // Getwd returns a rooted name link corresponding to the 200 // current directory. If the current directory can be 201 // reached via multiple paths (due to symbolic links), 202 // Getwd may return any one of them. 203 func (vfs *MountFS) Getwd() (dir string, err error) { 204 cd := vfs.curMnt.toAbsPath(vfs.CurDir()) 205 206 return cd, nil 207 } 208 209 // IsAbs reports whether the path is absolute. 210 func (vfs *MountFS) IsAbs(path string) bool { 211 return avfs.IsAbs(vfs, path) 212 } 213 214 // IsPathSeparator reports whether c is a directory separator character. 215 func (vfs *MountFS) IsPathSeparator(c uint8) bool { 216 return avfs.IsPathSeparator(vfs, c) 217 } 218 219 // Join joins any number of path elements into a single path, 220 // separating them with an OS specific Separator. Empty elements 221 // are ignored. The result is Cleaned. However, if the argument 222 // list is empty or all its elements are empty, Join returns 223 // an empty string. 224 // On Windows, the result will only be a UNC path if the first 225 // non-empty element is a UNC path. 226 func (vfs *MountFS) Join(elem ...string) string { 227 return avfs.Join(vfs, elem...) 228 } 229 230 // Glob returns the names of all files matching pattern or nil 231 // if there is no matching file. The syntax of patterns is the same 232 // as in Match. The pattern may describe hierarchical names such as 233 // /usr/*/bin/ed (assuming the Separator is '/'). 234 // 235 // Glob ignores file system errors such as I/O errors reading directories. 236 // The only possible returned error is ErrBadPattern, when pattern 237 // is malformed. 238 func (vfs *MountFS) Glob(pattern string) (matches []string, err error) { 239 matches, err = avfs.Glob(vfs, pattern) 240 241 /* 242 TODO : restore path if possible or disable function 243 for i, m := range matches { 244 matches[i] = vfs.fromBasePath(m) 245 } 246 */ 247 248 return matches, err 249 } 250 251 // Idm returns the identity manager of the file system. 252 // If the file system does not have an identity manager, avfs.DummyIdm is returned. 253 func (vfs *MountFS) Idm() avfs.IdentityMgr { 254 return vfs.rootFS.Idm() 255 } 256 257 // Lchown changes the numeric uid and gid of the named file. 258 // If the file is a symbolic link, it changes the uid and gid of the link itself. 259 // If there is an error, it will be of type *PathError. 260 // 261 // On Windows, it always returns the syscall.EWINDOWS error, wrapped 262 // in *PathError. 263 func (vfs *MountFS) Lchown(name string, uid, gid int) error { 264 mnt, vfsPath := vfs.pathToMount(name) 265 err := mnt.vfs.Lchown(vfsPath, uid, gid) 266 267 return mnt.restoreError(err) 268 } 269 270 // Link creates newname as a hard link to the oldname file. 271 // If there is an error, it will be of type *LinkError. 272 func (vfs *MountFS) Link(oldname, newname string) error { 273 const op = "" 274 275 oldMnt, oldVfsPath := vfs.pathToMount(oldname) 276 newMnt, newVfsPath := vfs.pathToMount(newname) 277 278 if oldMnt != newMnt { 279 return &os.LinkError{Op: op, Old: oldname, New: newname, Err: avfs.ErrCrossDevLink} 280 } 281 282 err := oldMnt.vfs.Link(oldVfsPath, newVfsPath) 283 284 return oldMnt.restoreError(err) 285 } 286 287 // Lstat returns a FileInfo describing the named file. 288 // If the file is a symbolic link, the returned FileInfo 289 // describes the symbolic link. Lstat makes no attempt to follow the link. 290 // If there is an error, it will be of type *PathError. 291 func (vfs *MountFS) Lstat(path string) (fs.FileInfo, error) { 292 mnt, vfsPath := vfs.pathToMount(path) 293 info, err := mnt.vfs.Lstat(vfsPath) 294 295 return info, mnt.restoreError(err) 296 } 297 298 // Match reports whether name matches the shell file name pattern. 299 // The pattern syntax is: 300 // 301 // pattern: 302 // { term } 303 // term: 304 // '*' matches any sequence of non-Separator characters 305 // '?' matches any single non-Separator character 306 // '[' [ '^' ] { character-range } ']' 307 // character class (must be non-empty) 308 // c matches character c (c != '*', '?', '\\', '[') 309 // '\\' c matches character c 310 // 311 // character-range: 312 // c matches character c (c != '\\', '-', ']') 313 // '\\' c matches character c 314 // lo '-' hi matches character c for lo <= c <= hi 315 // 316 // Match requires pattern to match all of name, not just a substring. 317 // The only possible returned error is ErrBadPattern, when pattern 318 // is malformed. 319 // 320 // On Windows, escaping is disabled. Instead, '\\' is treated as 321 // path separator. 322 func (vfs *MountFS) Match(pattern, name string) (matched bool, err error) { 323 return avfs.Match(vfs, pattern, name) 324 } 325 326 // Mkdir creates a new directory with the specified name and permission 327 // bits (before umask). 328 // If there is an error, it will be of type *PathError. 329 func (vfs *MountFS) Mkdir(name string, perm fs.FileMode) error { 330 if name == "" { 331 err := error(avfs.ErrNoSuchFileOrDir) 332 if vfs.OSType() == avfs.OsWindows { 333 err = avfs.ErrWinPathNotFound 334 } 335 336 return &fs.PathError{Op: "mkdir", Path: "", Err: err} 337 } 338 339 mnt, vfsPath := vfs.pathToMount(name) 340 err := mnt.vfs.Mkdir(vfsPath, perm) 341 342 return mnt.restoreError(err) 343 } 344 345 // MkdirAll creates a directory named name, 346 // along with any necessary parents, and returns nil, 347 // or else returns an error. 348 // The permission bits perm (before umask) are used for all 349 // directories that MkdirAll creates. 350 // If name is already a directory, MkdirAll does nothing 351 // and returns nil. 352 func (vfs *MountFS) MkdirAll(path string, perm fs.FileMode) error { 353 absPath, _ := vfs.rootFS.Abs(path) 354 mnt := vfs.rootMnt 355 mntPos := 0 356 357 pi := avfs.NewPathIterator(vfs, absPath) 358 for pi.Next() { 359 m, ok := vfs.mounts[pi.LeftPart()] 360 if ok { 361 vfsPath := absPath[mntPos:pi.End()] 362 363 err := mnt.vfs.MkdirAll(vfsPath, perm) 364 if err != nil { 365 return mnt.restoreError(err) 366 } 367 368 mnt = m 369 mntPos = pi.End() 370 } 371 } 372 373 err := mnt.vfs.MkdirAll(absPath[mntPos:], perm) 374 375 return mnt.restoreError(err) 376 } 377 378 // MkdirTemp creates a new temporary directory in the directory dir 379 // and returns the pathname of the new directory. 380 // The new directory's name is generated by adding a random string to the end of pattern. 381 // If pattern includes a "*", the random string replaces the last "*" instead. 382 // If dir is the empty string, MkdirTemp uses the default directory for temporary files, as returned by TempDir. 383 // Multiple programs or goroutines calling MkdirTemp simultaneously will not choose the same directory. 384 // It is the caller's responsibility to remove the directory when it is no longer needed. 385 func (vfs *MountFS) MkdirTemp(dir, pattern string) (string, error) { 386 return avfs.MkdirTemp(vfs, dir, pattern) 387 } 388 389 // Open opens the named file for reading. If successful, methods on 390 // the returned file can be used for reading; the associated file 391 // descriptor has mode O_RDONLY. 392 // If there is an error, it will be of type *PathError. 393 func (vfs *MountFS) Open(name string) (avfs.File, error) { 394 return vfs.OpenFile(name, os.O_RDONLY, 0) 395 } 396 397 // OpenFile is the generalized open call; most users will use Open 398 // or Create instead. It opens the named file with specified flag 399 // (O_RDONLY etc.). If the file does not exist, and the O_CREATE flag 400 // is passed, it is created with mode perm (before umask). If successful, 401 // methods on the returned File can be used for I/O. 402 // If there is an error, it will be of type *PathError. 403 func (vfs *MountFS) OpenFile(name string, flag int, perm fs.FileMode) (avfs.File, error) { 404 mnt, vfsPath := vfs.pathToMount(name) 405 406 f, err := mnt.vfs.OpenFile(vfsPath, flag, perm) 407 if err != nil { 408 return f, mnt.restoreError(err) 409 } 410 411 mf := &MountFile{ 412 vfs: vfs, 413 mount: mnt, 414 file: f, 415 } 416 417 return mf, nil 418 } 419 420 // ReadDir reads the named directory, 421 // returning all its directory entries sorted by filename. 422 // If an error occurs reading the directory, 423 // ReadDir returns the entries it was able to read before the error, 424 // along with the error. 425 func (vfs *MountFS) ReadDir(name string) ([]fs.DirEntry, error) { 426 return avfs.ReadDir(vfs, name) 427 } 428 429 // ReadFile reads the named file and returns the contents. 430 // A successful call returns err == nil, not err == EOF. 431 // Because ReadFile reads the whole file, it does not treat an EOF from Read 432 // as an error to be reported. 433 func (vfs *MountFS) ReadFile(name string) ([]byte, error) { 434 return avfs.ReadFile(vfs, name) 435 } 436 437 // Readlink returns the destination of the named symbolic link. 438 // If there is an error, it will be of type *PathError. 439 func (vfs *MountFS) Readlink(name string) (string, error) { 440 const op = "readlink" 441 442 if vfs.OSType() == avfs.OsWindows { 443 return "", &fs.PathError{Op: op, Path: name, Err: avfs.ErrWinNotReparsePoint} 444 } 445 446 return "", &fs.PathError{Op: op, Path: name, Err: avfs.ErrPermDenied} 447 } 448 449 // Rel returns a relative path that is lexically equivalent to targpath when 450 // joined to basepath with an intervening separator. That is, 451 // Join(basepath, Rel(basepath, targpath)) is equivalent to targpath itself. 452 // On success, the returned path will always be relative to basepath, 453 // even if basepath and targpath share no elements. 454 // An error is returned if targpath can't be made relative to basepath or if 455 // knowing the current working directory would be necessary to compute it. 456 // Rel calls Clean on the result. 457 func (vfs *MountFS) Rel(basepath, targpath string) (string, error) { 458 return avfs.Rel(vfs, basepath, targpath) 459 } 460 461 // Remove removes the named file or (empty) directory. 462 // If there is an error, it will be of type *PathError. 463 func (vfs *MountFS) Remove(name string) error { 464 mnt, vfsPath := vfs.pathToMount(name) 465 err := mnt.vfs.Remove(vfsPath) 466 467 return mnt.restoreError(err) 468 } 469 470 // RemoveAll removes path and any children it contains. 471 // It removes everything it can but returns the first error 472 // it encounters. If the path does not exist, RemoveAll 473 // returns nil (no error). 474 // If there is an error, it will be of type *PathError. 475 func (vfs *MountFS) RemoveAll(path string) error { 476 if path == "" { 477 // fail silently to retain compatibility with previous behavior of RemoveAll. 478 return nil 479 } 480 481 mnt, vfsPath := vfs.pathToMount(path) 482 err := mnt.vfs.RemoveAll(vfsPath) 483 484 return mnt.restoreError(err) 485 } 486 487 // Rename renames (moves) oldpath to newpath. 488 // If newpath already exists and is not a directory, Rename replaces it. 489 // OS-specific restrictions may apply when oldpath and newpath are in different directories. 490 // If there is an error, it will be of type *LinkError. 491 func (vfs *MountFS) Rename(oldname, newname string) error { 492 const op = "rename" 493 494 oldMnt, oldVfsPath := vfs.pathToMount(oldname) 495 newMnt, newVfsPath := vfs.pathToMount(newname) 496 497 if oldMnt != newMnt { 498 return &os.LinkError{Op: op, Old: oldname, New: newname, Err: avfs.ErrCrossDevLink} 499 } 500 501 err := oldMnt.vfs.Rename(oldVfsPath, newVfsPath) 502 503 return oldMnt.restoreError(err) 504 } 505 506 // SameFile reports whether fi1 and fi2 describe the same file. 507 // For example, on Unix this means that the device and inode fields 508 // of the two underlying structures are identical; on other systems 509 // the decision may be based on the path names. 510 // SameFile only applies to results returned by this package's Stat. 511 // It returns false in other cases. 512 func (vfs *MountFS) SameFile(fi1, fi2 fs.FileInfo) bool { 513 return vfs.rootFS.SameFile(fi1, fi2) 514 } 515 516 // SetUMask sets the file mode creation mask. 517 func (vfs *MountFS) SetUMask(mask fs.FileMode) error { 518 return vfs.rootFS.SetUMask(mask) 519 } 520 521 // SetUserByName sets the current user by name. 522 // If the user is not found, the returned error is of type UnknownUserError. 523 func (vfs *MountFS) SetUserByName(name string) error { 524 return avfs.SetUserByName(vfs, name) 525 } 526 527 // Split splits path immediately following the final Separator, 528 // separating it into a directory and file name component. 529 // If there is no Separator in path, Split returns an empty dir 530 // and file set to path. 531 // The returned values have the property that path = dir+file. 532 func (vfs *MountFS) Split(path string) (dir, file string) { 533 return avfs.Split(vfs, path) 534 } 535 536 // Stat returns a FileInfo describing the named file. 537 // If there is an error, it will be of type *PathError. 538 func (vfs *MountFS) Stat(path string) (fs.FileInfo, error) { 539 mnt, vfsPath := vfs.pathToMount(path) 540 info, err := mnt.vfs.Stat(vfsPath) 541 542 return info, mnt.restoreError(err) 543 } 544 545 // Sub returns an FS corresponding to the subtree rooted at dir. 546 func (vfs *MountFS) Sub(dir string) (avfs.VFS, error) { 547 mnt, vfsPath := vfs.pathToMount(dir) 548 info, err := mnt.vfs.Sub(vfsPath) 549 550 return info, mnt.restoreError(err) 551 } 552 553 // Symlink creates newname as a symbolic link to oldname. 554 // If there is an error, it will be of type *LinkError. 555 func (vfs *MountFS) Symlink(oldname, newname string) error { 556 const op = "symlink" 557 558 if vfs.OSType() == avfs.OsWindows { 559 return &os.LinkError{Op: op, Old: oldname, New: newname, Err: avfs.ErrWinPrivilegeNotHeld} 560 } 561 562 return &os.LinkError{Op: op, Old: oldname, New: newname, Err: avfs.ErrPermDenied} 563 } 564 565 // TempDir returns the default directory to use for temporary files. 566 // 567 // On Unix systems, it returns $TMPDIR if non-empty, else /tmp. 568 // On Windows, it uses GetTempPath, returning the first non-empty 569 // value from %TMP%, %TEMP%, %USERPROFILE%, or the Windows directory. 570 // On Plan 9, it returns /tmp. 571 // 572 // The directory is neither guaranteed to exist nor have accessible 573 // permissions. 574 func (vfs *MountFS) TempDir() string { 575 return avfs.TempDir(vfs) 576 } 577 578 // ToSlash returns the result of replacing each separator character 579 // in path with a slash ('/') character. Multiple separators are 580 // replaced by multiple slashes. 581 func (vfs *MountFS) ToSlash(path string) string { 582 return avfs.ToSlash(vfs, path) 583 } 584 585 // ToSysStat takes a value from fs.FileInfo.Sys() and returns a value that implements interface avfs.SysStater. 586 func (vfs *MountFS) ToSysStat(info fs.FileInfo) avfs.SysStater { 587 return vfs.rootFS.ToSysStat(info) 588 } 589 590 // Truncate changes the size of the named file. 591 // If the file is a symbolic link, it changes the size of the link's target. 592 // If there is an error, it will be of type *PathError. 593 func (vfs *MountFS) Truncate(name string, size int64) error { 594 mnt, vfsPath := vfs.pathToMount(name) 595 err := mnt.vfs.Truncate(vfsPath, size) 596 597 return mnt.restoreError(err) 598 } 599 600 // UMask returns the file mode creation mask. 601 func (vfs *MountFS) UMask() fs.FileMode { 602 return vfs.rootFS.UMask() 603 } 604 605 // User returns the current user. 606 func (vfs *MountFS) User() avfs.UserReader { 607 return vfs.rootFS.User() 608 } 609 610 // WalkDir walks the file tree rooted at root, calling fn for each file or 611 // directory in the tree, including root. 612 // 613 // All errors that arise visiting files and directories are filtered by fn: 614 // see the fs.WalkDirFunc documentation for details. 615 // 616 // The files are walked in lexical order, which makes the output deterministic 617 // but requires WalkDir to read an entire directory into memory before proceeding 618 // to walk that directory. 619 // 620 // WalkDir does not follow symbolic links. 621 func (vfs *MountFS) WalkDir(root string, fn fs.WalkDirFunc) error { 622 err := avfs.WalkDir(vfs, root, fn) 623 624 return vfs.rootMnt.restoreError(err) 625 } 626 627 // WriteFile writes data to a file named by filename. 628 // If the file does not exist, WriteFile creates it with permissions perm; 629 // otherwise WriteFile truncates it before writing. 630 func (vfs *MountFS) WriteFile(filename string, data []byte, perm fs.FileMode) error { 631 err := avfs.WriteFile(vfs, filename, data, perm) 632 633 return vfs.rootMnt.restoreError(err) 634 }