github.com/rclone/rclone@v1.66.1-0.20240517100346-7b89735ae726/cmd/cmount/fs.go (about) 1 //go:build cmount && ((linux && cgo) || (darwin && cgo) || (freebsd && cgo) || windows) 2 3 package cmount 4 5 import ( 6 "io" 7 "os" 8 "path" 9 "strings" 10 "sync" 11 "sync/atomic" 12 "time" 13 14 "github.com/rclone/rclone/cmd/mountlib" 15 "github.com/rclone/rclone/fs" 16 "github.com/rclone/rclone/fs/fserrors" 17 "github.com/rclone/rclone/fs/log" 18 "github.com/rclone/rclone/vfs" 19 "github.com/winfsp/cgofuse/fuse" 20 ) 21 22 const fhUnset = ^uint64(0) 23 24 // FS represents the top level filing system 25 type FS struct { 26 VFS *vfs.VFS 27 f fs.Fs 28 opt *mountlib.Options 29 ready chan (struct{}) 30 mu sync.Mutex // to protect the below 31 handles []vfs.Handle 32 destroyed atomic.Int32 33 } 34 35 // NewFS makes a new FS 36 func NewFS(VFS *vfs.VFS, opt *mountlib.Options) *FS { 37 fsys := &FS{ 38 VFS: VFS, 39 f: VFS.Fs(), 40 opt: opt, 41 ready: make(chan (struct{})), 42 } 43 return fsys 44 } 45 46 // Open a handle returning an integer file handle 47 func (fsys *FS) openHandle(handle vfs.Handle) (fh uint64) { 48 fsys.mu.Lock() 49 defer fsys.mu.Unlock() 50 var i int 51 var oldHandle vfs.Handle 52 for i, oldHandle = range fsys.handles { 53 if oldHandle == nil { 54 fsys.handles[i] = handle 55 goto found 56 } 57 } 58 fsys.handles = append(fsys.handles, handle) 59 i = len(fsys.handles) - 1 60 found: 61 return uint64(i) 62 } 63 64 // get the handle for fh, call with the lock held 65 func (fsys *FS) _getHandle(fh uint64) (i int, handle vfs.Handle, errc int) { 66 if fh > uint64(len(fsys.handles)) { 67 fs.Debugf(nil, "Bad file handle: too big: 0x%X", fh) 68 return i, nil, -fuse.EBADF 69 } 70 i = int(fh) 71 handle = fsys.handles[i] 72 if handle == nil { 73 fs.Debugf(nil, "Bad file handle: nil handle: 0x%X", fh) 74 return i, nil, -fuse.EBADF 75 } 76 return i, handle, 0 77 } 78 79 // Get the handle for the file handle 80 func (fsys *FS) getHandle(fh uint64) (handle vfs.Handle, errc int) { 81 fsys.mu.Lock() 82 _, handle, errc = fsys._getHandle(fh) 83 fsys.mu.Unlock() 84 return 85 } 86 87 // Close the handle 88 func (fsys *FS) closeHandle(fh uint64) (errc int) { 89 fsys.mu.Lock() 90 i, _, errc := fsys._getHandle(fh) 91 if errc == 0 { 92 fsys.handles[i] = nil 93 } 94 fsys.mu.Unlock() 95 return 96 } 97 98 // lookup a Node given a path 99 func (fsys *FS) lookupNode(path string) (node vfs.Node, errc int) { 100 node, err := fsys.VFS.Stat(path) 101 return node, translateError(err) 102 } 103 104 // lookup a Dir given a path 105 func (fsys *FS) lookupDir(path string) (dir *vfs.Dir, errc int) { 106 node, errc := fsys.lookupNode(path) 107 if errc != 0 { 108 return nil, errc 109 } 110 dir, ok := node.(*vfs.Dir) 111 if !ok { 112 return nil, -fuse.ENOTDIR 113 } 114 return dir, 0 115 } 116 117 // lookup a parent Dir given a path returning the dir and the leaf 118 func (fsys *FS) lookupParentDir(filePath string) (leaf string, dir *vfs.Dir, errc int) { 119 parentDir, leaf := path.Split(filePath) 120 dir, errc = fsys.lookupDir(parentDir) 121 return leaf, dir, errc 122 } 123 124 // lookup a File given a path 125 func (fsys *FS) lookupFile(path string) (file *vfs.File, errc int) { 126 node, errc := fsys.lookupNode(path) 127 if errc != 0 { 128 return nil, errc 129 } 130 file, ok := node.(*vfs.File) 131 if !ok { 132 return nil, -fuse.EISDIR 133 } 134 return file, 0 135 } 136 137 // get a node and handle from the path or from the fh if not fhUnset 138 // 139 // handle may be nil 140 func (fsys *FS) getNode(path string, fh uint64) (node vfs.Node, handle vfs.Handle, errc int) { 141 if fh == fhUnset { 142 node, errc = fsys.lookupNode(path) 143 } else { 144 handle, errc = fsys.getHandle(fh) 145 if errc == 0 { 146 node = handle.Node() 147 } 148 } 149 return 150 } 151 152 // stat fills up the stat block for Node 153 func (fsys *FS) stat(node vfs.Node, stat *fuse.Stat_t) (errc int) { 154 Size := uint64(node.Size()) 155 Blocks := (Size + 511) / 512 156 modTime := node.ModTime() 157 Mode := node.Mode().Perm() 158 if node.IsDir() { 159 Mode |= fuse.S_IFDIR 160 } else { 161 Mode |= fuse.S_IFREG 162 } 163 //stat.Dev = 1 164 stat.Ino = node.Inode() // FIXME do we need to set the inode number? 165 stat.Mode = uint32(Mode) 166 stat.Nlink = 1 167 stat.Uid = fsys.VFS.Opt.UID 168 stat.Gid = fsys.VFS.Opt.GID 169 //stat.Rdev 170 stat.Size = int64(Size) 171 t := fuse.NewTimespec(modTime) 172 stat.Atim = t 173 stat.Mtim = t 174 stat.Ctim = t 175 stat.Blksize = 512 176 stat.Blocks = int64(Blocks) 177 stat.Birthtim = t 178 // fs.Debugf(nil, "stat = %+v", *stat) 179 return 0 180 } 181 182 // Init is called after the filesystem is ready 183 func (fsys *FS) Init() { 184 defer log.Trace(fsys.f, "")("") 185 close(fsys.ready) 186 } 187 188 // Destroy is called when it is unmounted (note that depending on how 189 // the file system is terminated the file system may not receive the 190 // Destroy call). 191 func (fsys *FS) Destroy() { 192 defer log.Trace(fsys.f, "")("") 193 fsys.destroyed.Store(1) 194 } 195 196 // Getattr reads the attributes for path 197 func (fsys *FS) Getattr(path string, stat *fuse.Stat_t, fh uint64) (errc int) { 198 defer log.Trace(path, "fh=0x%X", fh)("errc=%v", &errc) 199 node, _, errc := fsys.getNode(path, fh) 200 if errc == 0 { 201 errc = fsys.stat(node, stat) 202 } 203 return 204 } 205 206 // Opendir opens path as a directory 207 func (fsys *FS) Opendir(path string) (errc int, fh uint64) { 208 defer log.Trace(path, "")("errc=%d, fh=0x%X", &errc, &fh) 209 handle, err := fsys.VFS.OpenFile(path, os.O_RDONLY, 0777) 210 if err != nil { 211 return translateError(err), fhUnset 212 } 213 return 0, fsys.openHandle(handle) 214 } 215 216 // Readdir reads the directory at dirPath 217 func (fsys *FS) Readdir(dirPath string, 218 fill func(name string, stat *fuse.Stat_t, ofst int64) bool, 219 ofst int64, 220 fh uint64) (errc int) { 221 itemsRead := -1 222 defer log.Trace(dirPath, "ofst=%d, fh=0x%X", ofst, fh)("items=%d, errc=%d", &itemsRead, &errc) 223 224 dir, errc := fsys.lookupDir(dirPath) 225 if errc != 0 { 226 return errc 227 } 228 229 // We can't seek in directories and FUSE should know that so 230 // return an error if ofst is ever set. 231 if ofst > 0 { 232 return -fuse.ESPIPE 233 } 234 235 nodes, err := dir.ReadDirAll() 236 if err != nil { 237 return translateError(err) 238 } 239 240 // Optionally, create a struct stat that describes the file as 241 // for getattr (but FUSE only looks at st_ino and the 242 // file-type bits of st_mode). 243 // 244 // We have called host.SetCapReaddirPlus() so WinFsp will 245 // use the full stat information - a Useful optimization on 246 // Windows. 247 // 248 // NB we are using the first mode for readdir: The readdir 249 // implementation ignores the offset parameter, and passes 250 // zero to the filler function's offset. The filler function 251 // will not return '1' (unless an error happens), so the whole 252 // directory is read in a single readdir operation. 253 fill(".", nil, 0) 254 fill("..", nil, 0) 255 for _, node := range nodes { 256 name := node.Name() 257 if len(name) > mountlib.MaxLeafSize { 258 fs.Errorf(dirPath, "Name too long (%d bytes) for FUSE, skipping: %s", len(name), name) 259 continue 260 } 261 // We have called host.SetCapReaddirPlus() so supply the stat information 262 // It is very cheap at this point so supply it regardless of OS capabilities 263 var stat fuse.Stat_t 264 _ = fsys.stat(node, &stat) // not capable of returning an error 265 fill(name, &stat, 0) 266 } 267 itemsRead = len(nodes) 268 return 0 269 } 270 271 // Releasedir finished reading the directory 272 func (fsys *FS) Releasedir(path string, fh uint64) (errc int) { 273 defer log.Trace(path, "fh=0x%X", fh)("errc=%d", &errc) 274 return fsys.closeHandle(fh) 275 } 276 277 // Statfs reads overall stats on the filesystem 278 func (fsys *FS) Statfs(path string, stat *fuse.Statfs_t) (errc int) { 279 defer log.Trace(path, "")("stat=%+v, errc=%d", stat, &errc) 280 const blockSize = 4096 281 total, _, free := fsys.VFS.Statfs() 282 stat.Blocks = uint64(total) / blockSize // Total data blocks in file system. 283 stat.Bfree = uint64(free) / blockSize // Free blocks in file system. 284 stat.Bavail = stat.Bfree // Free blocks in file system if you're not root. 285 stat.Files = 1e9 // Total files in file system. 286 stat.Ffree = 1e9 // Free files in file system. 287 stat.Bsize = blockSize // Block size 288 stat.Namemax = 255 // Maximum file name length? 289 stat.Frsize = blockSize // Fragment size, smallest addressable data size in the file system. 290 mountlib.ClipBlocks(&stat.Blocks) 291 mountlib.ClipBlocks(&stat.Bfree) 292 mountlib.ClipBlocks(&stat.Bavail) 293 return 0 294 } 295 296 // OpenEx opens a file 297 func (fsys *FS) OpenEx(path string, fi *fuse.FileInfo_t) (errc int) { 298 defer log.Trace(path, "flags=0x%X", fi.Flags)("errc=%d, fh=0x%X", &errc, &fi.Fh) 299 fi.Fh = fhUnset 300 301 // translate the fuse flags to os flags 302 flags := translateOpenFlags(fi.Flags) 303 handle, err := fsys.VFS.OpenFile(path, flags, 0777) 304 if err != nil { 305 return translateError(err) 306 } 307 308 // If size unknown then use direct io to read 309 if entry := handle.Node().DirEntry(); entry != nil && entry.Size() < 0 { 310 fi.DirectIo = true 311 } 312 if fsys.opt.DirectIO { 313 fi.DirectIo = true 314 } 315 316 fi.Fh = fsys.openHandle(handle) 317 return 0 318 } 319 320 // Open opens a file 321 func (fsys *FS) Open(path string, flags int) (errc int, fh uint64) { 322 var fi = fuse.FileInfo_t{ 323 Flags: flags, 324 } 325 errc = fsys.OpenEx(path, &fi) 326 return errc, fi.Fh 327 } 328 329 // CreateEx creates and opens a file. 330 func (fsys *FS) CreateEx(filePath string, mode uint32, fi *fuse.FileInfo_t) (errc int) { 331 defer log.Trace(filePath, "flags=0x%X, mode=0%o", fi.Flags, mode)("errc=%d, fh=0x%X", &errc, &fi.Fh) 332 fi.Fh = fhUnset 333 leaf, parentDir, errc := fsys.lookupParentDir(filePath) 334 if errc != 0 { 335 return errc 336 } 337 file, err := parentDir.Create(leaf, fi.Flags) 338 if err != nil { 339 return translateError(err) 340 } 341 // translate the fuse flags to os flags 342 flags := translateOpenFlags(fi.Flags) | os.O_CREATE 343 handle, err := file.Open(flags) 344 if err != nil { 345 return translateError(err) 346 } 347 fi.Fh = fsys.openHandle(handle) 348 return 0 349 } 350 351 // Create creates and opens a file. 352 func (fsys *FS) Create(filePath string, flags int, mode uint32) (errc int, fh uint64) { 353 var fi = fuse.FileInfo_t{ 354 Flags: flags, 355 } 356 errc = fsys.CreateEx(filePath, mode, &fi) 357 return errc, fi.Fh 358 } 359 360 // Truncate truncates a file to size 361 func (fsys *FS) Truncate(path string, size int64, fh uint64) (errc int) { 362 defer log.Trace(path, "size=%d, fh=0x%X", size, fh)("errc=%d", &errc) 363 node, handle, errc := fsys.getNode(path, fh) 364 if errc != 0 { 365 return errc 366 } 367 var err error 368 if handle != nil { 369 err = handle.Truncate(size) 370 } else { 371 err = node.Truncate(size) 372 } 373 if err != nil { 374 return translateError(err) 375 } 376 return 0 377 } 378 379 // Read data from file handle 380 func (fsys *FS) Read(path string, buff []byte, ofst int64, fh uint64) (n int) { 381 defer log.Trace(path, "ofst=%d, fh=0x%X", ofst, fh)("n=%d", &n) 382 handle, errc := fsys.getHandle(fh) 383 if errc != 0 { 384 return errc 385 } 386 n, err := handle.ReadAt(buff, ofst) 387 if err == io.EOF { 388 } else if err != nil { 389 return translateError(err) 390 } 391 return n 392 } 393 394 // Write data to file handle 395 func (fsys *FS) Write(path string, buff []byte, ofst int64, fh uint64) (n int) { 396 defer log.Trace(path, "ofst=%d, fh=0x%X", ofst, fh)("n=%d", &n) 397 handle, errc := fsys.getHandle(fh) 398 if errc != 0 { 399 return errc 400 } 401 n, err := handle.WriteAt(buff, ofst) 402 if err != nil { 403 return translateError(err) 404 } 405 return n 406 } 407 408 // Flush flushes an open file descriptor or path 409 func (fsys *FS) Flush(path string, fh uint64) (errc int) { 410 defer log.Trace(path, "fh=0x%X", fh)("errc=%d", &errc) 411 handle, errc := fsys.getHandle(fh) 412 if errc != 0 { 413 return errc 414 } 415 return translateError(handle.Flush()) 416 } 417 418 // Release closes the file if still open 419 func (fsys *FS) Release(path string, fh uint64) (errc int) { 420 defer log.Trace(path, "fh=0x%X", fh)("errc=%d", &errc) 421 handle, errc := fsys.getHandle(fh) 422 if errc != 0 { 423 return errc 424 } 425 _ = fsys.closeHandle(fh) 426 return translateError(handle.Release()) 427 } 428 429 // Unlink removes a file. 430 func (fsys *FS) Unlink(filePath string) (errc int) { 431 defer log.Trace(filePath, "")("errc=%d", &errc) 432 leaf, parentDir, errc := fsys.lookupParentDir(filePath) 433 if errc != 0 { 434 return errc 435 } 436 return translateError(parentDir.RemoveName(leaf)) 437 } 438 439 // Mkdir creates a directory. 440 func (fsys *FS) Mkdir(dirPath string, mode uint32) (errc int) { 441 defer log.Trace(dirPath, "mode=0%o", mode)("errc=%d", &errc) 442 leaf, parentDir, errc := fsys.lookupParentDir(dirPath) 443 if errc != 0 { 444 return errc 445 } 446 _, err := parentDir.Mkdir(leaf) 447 return translateError(err) 448 } 449 450 // Rmdir removes a directory 451 func (fsys *FS) Rmdir(dirPath string) (errc int) { 452 defer log.Trace(dirPath, "")("errc=%d", &errc) 453 leaf, parentDir, errc := fsys.lookupParentDir(dirPath) 454 if errc != 0 { 455 return errc 456 } 457 return translateError(parentDir.RemoveName(leaf)) 458 } 459 460 // Rename renames a file. 461 func (fsys *FS) Rename(oldPath string, newPath string) (errc int) { 462 defer log.Trace(oldPath, "newPath=%q", newPath)("errc=%d", &errc) 463 return translateError(fsys.VFS.Rename(oldPath, newPath)) 464 } 465 466 // Windows sometimes seems to send times that are the epoch which is 467 // 1601-01-01 +/- timezone so filter out times that are earlier than 468 // this. 469 var invalidDateCutoff = time.Date(1601, 1, 2, 0, 0, 0, 0, time.UTC) 470 471 // Utimens changes the access and modification times of a file. 472 func (fsys *FS) Utimens(path string, tmsp []fuse.Timespec) (errc int) { 473 defer log.Trace(path, "tmsp=%+v", tmsp)("errc=%d", &errc) 474 node, errc := fsys.lookupNode(path) 475 if errc != 0 { 476 return errc 477 } 478 if tmsp == nil || len(tmsp) < 2 { 479 fs.Debugf(path, "Utimens: Not setting time as timespec isn't complete: %v", tmsp) 480 return 0 481 } 482 t := tmsp[1].Time() 483 if t.Before(invalidDateCutoff) { 484 fs.Debugf(path, "Utimens: Not setting out of range time: %v", t) 485 return 0 486 } 487 fs.Debugf(path, "Utimens: SetModTime: %v", t) 488 return translateError(node.SetModTime(t)) 489 } 490 491 // Mknod creates a file node. 492 func (fsys *FS) Mknod(path string, mode uint32, dev uint64) (errc int) { 493 defer log.Trace(path, "mode=0x%X, dev=0x%X", mode, dev)("errc=%d", &errc) 494 return -fuse.ENOSYS 495 } 496 497 // Fsync synchronizes file contents. 498 func (fsys *FS) Fsync(path string, datasync bool, fh uint64) (errc int) { 499 defer log.Trace(path, "datasync=%v, fh=0x%X", datasync, fh)("errc=%d", &errc) 500 // This is a no-op for rclone 501 return 0 502 } 503 504 // Link creates a hard link to a file. 505 func (fsys *FS) Link(oldpath string, newpath string) (errc int) { 506 defer log.Trace(oldpath, "newpath=%q", newpath)("errc=%d", &errc) 507 return -fuse.ENOSYS 508 } 509 510 // Symlink creates a symbolic link. 511 func (fsys *FS) Symlink(target string, newpath string) (errc int) { 512 defer log.Trace(target, "newpath=%q", newpath)("errc=%d", &errc) 513 return -fuse.ENOSYS 514 } 515 516 // Readlink reads the target of a symbolic link. 517 func (fsys *FS) Readlink(path string) (errc int, linkPath string) { 518 defer log.Trace(path, "")("linkPath=%q, errc=%d", &linkPath, &errc) 519 return -fuse.ENOSYS, "" 520 } 521 522 // Chmod changes the permission bits of a file. 523 func (fsys *FS) Chmod(path string, mode uint32) (errc int) { 524 defer log.Trace(path, "mode=0%o", mode)("errc=%d", &errc) 525 // This is a no-op for rclone 526 return 0 527 } 528 529 // Chown changes the owner and group of a file. 530 func (fsys *FS) Chown(path string, uid uint32, gid uint32) (errc int) { 531 defer log.Trace(path, "uid=%d, gid=%d", uid, gid)("errc=%d", &errc) 532 // This is a no-op for rclone 533 return 0 534 } 535 536 // Access checks file access permissions. 537 func (fsys *FS) Access(path string, mask uint32) (errc int) { 538 defer log.Trace(path, "mask=0%o", mask)("errc=%d", &errc) 539 // This is a no-op for rclone 540 return 0 541 } 542 543 // Fsyncdir synchronizes directory contents. 544 func (fsys *FS) Fsyncdir(path string, datasync bool, fh uint64) (errc int) { 545 defer log.Trace(path, "datasync=%v, fh=0x%X", datasync, fh)("errc=%d", &errc) 546 // This is a no-op for rclone 547 return 0 548 } 549 550 // Setxattr sets extended attributes. 551 func (fsys *FS) Setxattr(path string, name string, value []byte, flags int) (errc int) { 552 defer log.Trace(path, "name=%q, value=%q, flags=%d", name, value, flags)("errc=%d", &errc) 553 return -fuse.ENOSYS 554 } 555 556 // Getxattr gets extended attributes. 557 func (fsys *FS) Getxattr(path string, name string) (errc int, value []byte) { 558 defer log.Trace(path, "name=%q", name)("errc=%d, value=%q", &errc, &value) 559 return -fuse.ENOSYS, nil 560 } 561 562 // Removexattr removes extended attributes. 563 func (fsys *FS) Removexattr(path string, name string) (errc int) { 564 defer log.Trace(path, "name=%q", name)("errc=%d", &errc) 565 return -fuse.ENOSYS 566 } 567 568 // Listxattr lists extended attributes. 569 func (fsys *FS) Listxattr(path string, fill func(name string) bool) (errc int) { 570 defer log.Trace(path, "fill=%p", fill)("errc=%d", &errc) 571 return -fuse.ENOSYS 572 } 573 574 // Getpath allows a case-insensitive file system to report the correct case of 575 // a file path. 576 func (fsys *FS) Getpath(path string, fh uint64) (errc int, normalisedPath string) { 577 defer log.Trace(path, "Getpath fh=%d", fh)("errc=%d, normalisedPath=%q", &errc, &normalisedPath) 578 node, _, errc := fsys.getNode(path, fh) 579 if errc != 0 { 580 return errc, "" 581 } 582 normalisedPath = node.Path() 583 if !strings.HasPrefix("/", normalisedPath) { 584 normalisedPath = "/" + normalisedPath 585 } 586 return 0, normalisedPath 587 } 588 589 // Translate errors from mountlib 590 func translateError(err error) (errc int) { 591 if err == nil { 592 return 0 593 } 594 _, uErr := fserrors.Cause(err) 595 switch uErr { 596 case vfs.OK: 597 return 0 598 case vfs.ENOENT, fs.ErrorDirNotFound, fs.ErrorObjectNotFound: 599 return -fuse.ENOENT 600 case vfs.EEXIST, fs.ErrorDirExists: 601 return -fuse.EEXIST 602 case vfs.EPERM, fs.ErrorPermissionDenied: 603 return -fuse.EPERM 604 case vfs.ECLOSED: 605 return -fuse.EBADF 606 case vfs.ENOTEMPTY: 607 return -fuse.ENOTEMPTY 608 case vfs.ESPIPE: 609 return -fuse.ESPIPE 610 case vfs.EBADF: 611 return -fuse.EBADF 612 case vfs.EROFS: 613 return -fuse.EROFS 614 case vfs.ENOSYS, fs.ErrorNotImplemented: 615 return -fuse.ENOSYS 616 case vfs.EINVAL: 617 return -fuse.EINVAL 618 } 619 fs.Errorf(nil, "IO error: %v", err) 620 return -fuse.EIO 621 } 622 623 // Translate Open Flags from FUSE to os (as used in the vfs layer) 624 func translateOpenFlags(inFlags int) (outFlags int) { 625 switch inFlags & fuse.O_ACCMODE { 626 case fuse.O_RDONLY: 627 outFlags = os.O_RDONLY 628 case fuse.O_WRONLY: 629 outFlags = os.O_WRONLY 630 case fuse.O_RDWR: 631 outFlags = os.O_RDWR 632 } 633 if inFlags&fuse.O_APPEND != 0 { 634 outFlags |= os.O_APPEND 635 } 636 if inFlags&fuse.O_CREAT != 0 { 637 outFlags |= os.O_CREATE 638 } 639 if inFlags&fuse.O_EXCL != 0 { 640 outFlags |= os.O_EXCL 641 } 642 if inFlags&fuse.O_TRUNC != 0 { 643 outFlags |= os.O_TRUNC 644 } 645 // NB O_SYNC isn't defined by fuse 646 return outFlags 647 } 648 649 // Make sure interfaces are satisfied 650 var ( 651 _ fuse.FileSystemInterface = (*FS)(nil) 652 _ fuse.FileSystemOpenEx = (*FS)(nil) 653 _ fuse.FileSystemGetpath = (*FS)(nil) 654 //_ fuse.FileSystemChflags = (*FS)(nil) 655 //_ fuse.FileSystemSetcrtime = (*FS)(nil) 656 //_ fuse.FileSystemSetchgtime = (*FS)(nil) 657 )