github.com/yandex-cloud/geesefs@v0.40.9/internal/goofys_windows.go (about) 1 // Copyright 2021 Yandex LLC 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 internal 16 17 import ( 18 "context" 19 "fmt" 20 "os" 21 "runtime" 22 "sync/atomic" 23 "syscall" 24 "time" 25 26 "github.com/winfsp/cgofuse/fuse" 27 "github.com/jacobsa/fuse/fuseops" 28 "github.com/sirupsen/logrus" 29 30 "github.com/yandex-cloud/geesefs/internal/cfg" 31 ) 32 33 // winfsp/cgofuse interface to the file system 34 35 func init() { 36 cfg.FuseOptions = `WinFSP options: 37 -o umask=MASK set file permissions (octal) 38 -o FileSecurity=SDDL set file DACL (SDDL format) 39 -o create_umask=MASK set newly created file permissions (octal) 40 -o create_file_umask=MASK for files only 41 -o create_dir_umask=MASK for directories only 42 -o uid=N set file owner (default is mounting user id) 43 -o gid=N set file group (default is mounting user group) 44 -o rellinks interpret absolute symlinks as volume relative 45 -o dothidden dot files have the Windows hidden file attrib 46 -o volname=NAME set volume label 47 -o VolumePrefix=UNC set UNC prefix (/Server/Share) 48 -o FileSystemName=NAME set file system name (use NTFS to run executables) 49 -o debug enable debug output 50 -o DebugLog=FILE debug log file (requires -o debug) 51 WinFSP advanced options: 52 -o FileInfoTimeout=N metadata timeout (millis, -1 for data caching) 53 -o DirInfoTimeout=N directory info timeout (millis) 54 -o EaTimeout=N extended attribute timeout (millis) 55 -o VolumeInfoTimeout=N volume info timeout (millis) 56 -o KeepFileCache do not discard cache when files are closed 57 -o LegacyUnlinkRename do not support new POSIX unlink/rename 58 -o ThreadCount number of file system dispatcher threads 59 -o uidmap=UID:SID[;...] explicit UID <-> SID map (max 8 entries) 60 `; 61 } 62 63 type GoofysWin struct { 64 fuse.FileSystemBase 65 *Goofys 66 host *fuse.FileSystemHost 67 initialized bool 68 initCh chan int 69 } 70 71 func NewGoofysWin(fs *Goofys) *GoofysWin { 72 fsint := &GoofysWin{ 73 Goofys: fs, 74 } 75 fsint.initCh = make(chan int, 3) 76 fs.NotifyCallback = func(notifications []interface{}) { 77 go fsint.Notify(notifications) 78 } 79 return fsint 80 } 81 82 // Init is called when the file system is created. 83 func (fs *GoofysWin) Init() { 84 fs.initialized = true 85 fs.initCh <- 1 86 } 87 88 // Destroy is called when the file system is destroyed. 89 func (fs *GoofysWin) Destroy() { 90 fs.initialized = false 91 fs.initCh <- 2 92 } 93 94 // Statfs gets file system statistics. 95 func (fs *GoofysWin) Statfs(path string, stat *fuse.Statfs_t) int { 96 atomic.AddInt64(&fs.stats.metadataReads, 1) 97 fuseLog.Debugf("<--> Statfs %v", path) 98 const BLOCK_SIZE = 4096 99 const TOTAL_SPACE = 1 * 1024 * 1024 * 1024 * 1024 * 1024 // 1PB 100 const TOTAL_BLOCKS = TOTAL_SPACE / BLOCK_SIZE 101 const INODES = 1 * 1000 * 1000 * 1000 // 1 billion 102 stat.Bsize = BLOCK_SIZE 103 stat.Frsize = BLOCK_SIZE 104 stat.Blocks = TOTAL_BLOCKS 105 stat.Bfree = TOTAL_BLOCKS 106 stat.Bavail = TOTAL_BLOCKS 107 stat.Files = INODES 108 stat.Ffree = INODES 109 stat.Favail = INODES 110 stat.Namemax = 255 111 return 0 112 } 113 114 func mapWinError(err error) int { 115 if err == nil { 116 return 0 117 } 118 if fuseLog.Level == logrus.DebugLevel { 119 pc, _, _, _ := runtime.Caller(1) 120 details := runtime.FuncForPC(pc) 121 fuseLog.Debugf("%v: error %v", details, err) 122 } 123 err = mapAwsError(err) 124 switch err { 125 case syscall.EINVAL: 126 return -fuse.EINVAL 127 case syscall.EACCES: 128 return -fuse.EACCES 129 case syscall.ENOENT: 130 return -fuse.ENOENT 131 case syscall.ENOTSUP: 132 return -fuse.ENOTSUP 133 case syscall.EINTR: 134 return -fuse.EINTR 135 case syscall.ERANGE: 136 return -fuse.ERANGE 137 case syscall.EAGAIN: 138 return -fuse.EAGAIN 139 case syscall.ESTALE: 140 return -fuse.EINVAL 141 case syscall.EOPNOTSUPP: 142 return -fuse.EOPNOTSUPP 143 default: 144 return -fuse.EIO 145 } 146 } 147 148 // Mknod creates a file node. 149 func (fs *GoofysWin) Mknod(path string, mode uint32, dev uint64) (ret int) { 150 if fuseLog.Level == logrus.DebugLevel { 151 fuseLog.Debugf("-> Mknod %v %v %v", path, mode, dev) 152 defer func() { 153 fuseLog.Debugf("<- Mknod %v %v %v = %v", path, mode, dev, ret) 154 }() 155 } 156 157 atomic.AddInt64(&fs.stats.metadataWrites, 1) 158 159 if (mode & fuse.S_IFMT) != fuse.S_IFDIR && 160 (mode & fuse.S_IFMT) != 0 && 161 !fs.flags.EnableSpecials { 162 return -fuse.ENOTSUP 163 } 164 165 parent, child, err := fs.LookupParent(path) 166 if err != nil { 167 return mapWinError(err) 168 } 169 170 var inode *Inode 171 if (mode & fuse.S_IFDIR) != 0 { 172 inode, err = parent.MkDir(child) 173 if err != nil { 174 return mapWinError(err) 175 } 176 } else { 177 var fh *FileHandle 178 inode, fh, err = parent.Create(child) 179 if err != nil { 180 return mapWinError(err) 181 } 182 fh.Release() 183 } 184 inode.Attributes.Rdev = uint32(dev) 185 inode.setFileMode(fuseops.ConvertFileMode(mode)) 186 187 if fs.flags.FsyncOnClose { 188 err = inode.SyncFile() 189 if err != nil { 190 return mapWinError(err) 191 } 192 } 193 194 return 0 195 } 196 197 // Mkdir creates a directory. 198 func (fs *GoofysWin) Mkdir(path string, mode uint32) (ret int) { 199 if fuseLog.Level == logrus.DebugLevel { 200 fuseLog.Debugf("-> Mkdir %v %v", path, mode) 201 defer func() { 202 fuseLog.Debugf("<- Mkdir %v %v = %v", path, mode, ret) 203 }() 204 } 205 206 atomic.AddInt64(&fs.stats.metadataWrites, 1) 207 208 parent, child, err := fs.LookupParent(path) 209 if err != nil { 210 return mapWinError(err) 211 } 212 213 inode, err := parent.MkDir(child) 214 if err != nil { 215 return mapWinError(err) 216 } 217 if fs.flags.EnablePerms { 218 inode.Attributes.Mode = os.ModeDir | fuseops.ConvertFileMode(mode) & os.ModePerm 219 } else { 220 inode.Attributes.Mode = os.ModeDir | fs.flags.DirMode 221 } 222 223 return 0 224 } 225 226 // Unlink removes a file. 227 func (fs *GoofysWin) Unlink(path string) (ret int) { 228 if fuseLog.Level == logrus.DebugLevel { 229 fuseLog.Debugf("-> Unlink %v", path) 230 defer func() { 231 fuseLog.Debugf("<- Unlink %v = %v", path, ret) 232 }() 233 } 234 235 atomic.AddInt64(&fs.stats.metadataWrites, 1) 236 237 parent, child, err := fs.LookupParent(path) 238 if err != nil { 239 return mapWinError(err) 240 } 241 242 err = parent.Unlink(child) 243 return mapWinError(err) 244 } 245 246 // Rmdir removes a directory. 247 func (fs *GoofysWin) Rmdir(path string) (ret int) { 248 if fuseLog.Level == logrus.DebugLevel { 249 fuseLog.Debugf("-> Rmdir %v", path) 250 defer func() { 251 fuseLog.Debugf("<- Rmdir %v = %v", path, ret) 252 }() 253 } 254 255 atomic.AddInt64(&fs.stats.metadataWrites, 1) 256 257 parent, child, err := fs.LookupParent(path) 258 if err != nil { 259 return mapWinError(err) 260 } 261 262 err = parent.RmDir(child) 263 return mapWinError(err) 264 } 265 266 // Symlink creates a symbolic link. 267 func (fs *GoofysWin) Symlink(target string, newpath string) (ret int) { 268 if fuseLog.Level == logrus.DebugLevel { 269 fuseLog.Debugf("-> Symlink %v %v", target, newpath) 270 defer func() { 271 fuseLog.Debugf("<- Symlink %v %v = %v", target, newpath, ret) 272 }() 273 } 274 275 atomic.AddInt64(&fs.stats.metadataWrites, 1) 276 277 parent, child, err := fs.LookupParent(newpath) 278 if err != nil { 279 return mapWinError(err) 280 } 281 282 parent.CreateSymlink(child, target) 283 return 0 284 } 285 286 // Readlink reads the target of a symbolic link. 287 func (fs *GoofysWin) Readlink(path string) (ret int, target string) { 288 if fuseLog.Level == logrus.DebugLevel { 289 fuseLog.Debugf("-> Readlink %v", path) 290 defer func() { 291 fuseLog.Debugf("<- Readlink %v = %v %v", path, ret, target) 292 }() 293 } 294 295 atomic.AddInt64(&fs.stats.metadataReads, 1) 296 297 inode, err := fs.LookupPath(path) 298 if err != nil { 299 return mapWinError(err), "" 300 } 301 302 target, err = inode.ReadSymlink() 303 if err != nil { 304 return mapWinError(err), "" 305 } 306 307 return 0, target 308 } 309 310 // Rename renames a file. 311 func (fs *GoofysWin) Rename(oldpath string, newpath string) (ret int) { 312 if fuseLog.Level == logrus.DebugLevel { 313 fuseLog.Debugf("-> Rename %v %v", oldpath, newpath) 314 defer func() { 315 fuseLog.Debugf("<- Rename %v %v = %v", oldpath, newpath, ret) 316 }() 317 } 318 319 atomic.AddInt64(&fs.stats.metadataWrites, 1) 320 321 parent, oldName, err := fs.LookupParent(oldpath) 322 if err != nil { 323 return mapWinError(err) 324 } 325 newParent, newName, err := fs.LookupParent(newpath) 326 if err != nil { 327 return mapWinError(err) 328 } 329 330 err = parent.Rename(oldName, newParent, newName) 331 332 return mapWinError(err) 333 } 334 335 // Chmod changes the permission bits of a file. 336 func (fs *GoofysWin) Chmod(path string, mode uint32) (ret int) { 337 if fuseLog.Level == logrus.DebugLevel { 338 fuseLog.Debugf("-> Chmod %v %v", path, mode) 339 defer func() { 340 fuseLog.Debugf("<- Chmod %v %v = %v", path, mode, ret) 341 }() 342 } 343 344 atomic.AddInt64(&fs.stats.metadataWrites, 1) 345 346 inode, err := fs.LookupPath(path) 347 if err != nil { 348 return mapWinError(err) 349 } 350 351 goMode := fuseops.ConvertFileMode(mode) 352 353 return mapWinError(mapAwsError(inode.SetAttributes(nil, &goMode, nil, nil, nil))) 354 } 355 356 // Chown changes the owner and group of a file. 357 func (fs *GoofysWin) Chown(path string, uid uint32, gid uint32) (ret int) { 358 if fuseLog.Level == logrus.DebugLevel { 359 fuseLog.Debugf("-> Chown %v %v %v", path, uid, gid) 360 defer func() { 361 fuseLog.Debugf("<- Chown %v %v %v = %v", path, uid, gid, ret) 362 }() 363 } 364 365 atomic.AddInt64(&fs.stats.metadataWrites, 1) 366 367 inode, err := fs.LookupPath(path) 368 if err != nil { 369 return mapWinError(err) 370 } 371 372 return mapWinError(mapAwsError(inode.SetAttributes(nil, nil, nil, &uid, &gid))) 373 } 374 375 // Utimens changes the access and modification times of a file. 376 func (fs *GoofysWin) Utimens(path string, tmsp []fuse.Timespec) (ret int) { 377 if fuseLog.Level == logrus.DebugLevel { 378 fuseLog.Debugf("-> Utimens %v %v", path, tmsp) 379 defer func() { 380 fuseLog.Debugf("<- Utimens %v %v = %v", path, tmsp, ret) 381 }() 382 } 383 384 atomic.AddInt64(&fs.stats.metadataWrites, 1) 385 386 inode, err := fs.LookupPath(path) 387 if err != nil { 388 return mapWinError(err) 389 } 390 391 // only mtime, atime is ignored 392 tm := time.Unix(tmsp[1].Sec, tmsp[1].Nsec) 393 394 return mapWinError(mapAwsError(inode.SetAttributes(nil, nil, &tm, nil, nil))) 395 } 396 397 // Access is only used by winfsp with FSP_FUSE_DELETE_OK. Ignore it 398 func (fs *GoofysWin) Access(path string, mask uint32) int { 399 if fuseLog.Level == logrus.DebugLevel { 400 fuseLog.Debugf("<--> Access %v %v = 0", path, mask) 401 } 402 atomic.AddInt64(&fs.stats.noops, 1) 403 return 0 404 } 405 406 // Create creates and opens a file. 407 // The flags are a combination of the fuse.O_* constants. 408 func (fs *GoofysWin) Create(path string, flags int, mode uint32) (ret int, fhId uint64) { 409 if fuseLog.Level == logrus.DebugLevel { 410 fuseLog.Debugf("-> Create %v %v %v", path, flags, mode) 411 defer func() { 412 fuseLog.Debugf("<- Create %v %v %v = %v %v", path, flags, mode, ret, fhId) 413 }() 414 } 415 416 atomic.AddInt64(&fs.stats.metadataWrites, 1) 417 418 parent, child, err := fs.LookupParent(path) 419 if err != nil { 420 return mapWinError(err), 0 421 } 422 423 if fs.flags.FlushFilename != "" && child == fs.flags.FlushFilename { 424 err = fs.SyncFS(parent) 425 if err == nil { 426 err = syscall.ENOENT 427 } 428 return mapWinError(err), 0 429 } 430 431 if fs.flags.RefreshFilename != "" && child == fs.flags.RefreshFilename { 432 err = fs.RefreshInodeCache(parent) 433 if err == nil { 434 err = syscall.ENOENT 435 } 436 return mapWinError(err), 0 437 } 438 439 inode, fh, err := parent.CreateOrOpen(child, true) 440 if err != nil { 441 return mapWinError(err), 0 442 } 443 444 inode.setFileMode(fuseops.ConvertFileMode(mode)) 445 446 handleID := fs.AddFileHandle(fh) 447 448 return 0, uint64(handleID) 449 } 450 451 func endsWith(path, part string) bool { 452 ld := len(path)-len(part) 453 return len(part) > 0 && ld >= 0 && (ld == 0 || path[ld-1] == '/') && path[ld:] == part 454 } 455 456 // Open opens a file. 457 // The flags are a combination of the fuse.O_* constants. 458 func (fs *GoofysWin) Open(path string, flags int) (ret int, fhId uint64) { 459 if fuseLog.Level == logrus.DebugLevel { 460 fuseLog.Debugf("-> Open %v %v", path, flags) 461 defer func() { 462 fuseLog.Debugf("<- Open %v %v = %v %v", path, flags, ret, fhId) 463 }() 464 } 465 466 atomic.AddInt64(&fs.stats.noops, 1) 467 468 inode, err := fs.LookupPath(path) 469 if err != nil { 470 if endsWith(path, fs.flags.FlushFilename) { 471 parent, _, err := fs.LookupParent(path) 472 if err != nil { 473 return mapWinError(err), 0 474 } 475 if err == nil { 476 err = fs.SyncFS(parent) 477 } 478 if err == nil { 479 err = syscall.ENOENT 480 } 481 } 482 if endsWith(path, fs.flags.RefreshFilename) { 483 parent, _, err := fs.LookupParent(path) 484 if err != nil { 485 return mapWinError(err), 0 486 } 487 if err == nil { 488 err = fs.RefreshInodeCache(parent) 489 } 490 if err == nil { 491 err = syscall.ENOENT 492 } 493 } 494 return mapWinError(err), 0 495 } 496 497 fh, err := inode.OpenFile() 498 if err != nil { 499 return mapWinError(err), 0 500 } 501 502 handleID := fs.AddFileHandle(fh) 503 504 return 0, uint64(handleID) 505 } 506 507 // Getattr gets file attributes. 508 func (fs *GoofysWin) Getattr(path string, stat *fuse.Stat_t, fh uint64) (ret int) { 509 if fuseLog.Level == logrus.DebugLevel { 510 fuseLog.Debugf("-> Getattr %v %v", path, fh) 511 defer func() { 512 fuseLog.Debugf("<- Getattr %v %v = %v %v", path, fh, ret, *stat) 513 }() 514 } 515 516 atomic.AddInt64(&fs.stats.metadataReads, 1) 517 518 inode, err := fs.LookupPath(path) 519 if err != nil { 520 return mapWinError(err) 521 } 522 523 makeFuseAttributes(inode.GetAttributes(), stat) 524 525 return 0 526 } 527 528 func makeFuseAttributes(attr *fuseops.InodeAttributes, stat *fuse.Stat_t) { 529 stat.Mode = fuseops.ConvertGolangMode(attr.Mode) 530 stat.Nlink = 1 531 stat.Uid = attr.Uid 532 stat.Gid = attr.Gid 533 stat.Rdev = uint64(attr.Rdev) 534 stat.Size = int64(attr.Size) 535 stat.Atim.Sec = attr.Atime.Unix() 536 stat.Atim.Nsec = int64(attr.Atime.Nanosecond()) 537 stat.Mtim.Sec = attr.Mtime.Unix() 538 stat.Mtim.Nsec = int64(attr.Mtime.Nanosecond()) 539 stat.Ctim.Sec = attr.Ctime.Unix() 540 stat.Ctim.Nsec = int64(attr.Ctime.Nanosecond()) 541 stat.Blksize = 4096 542 stat.Blocks = int64(attr.Size) / stat.Blksize 543 stat.Birthtim.Sec = attr.Mtime.Unix() 544 stat.Birthtim.Nsec = int64(attr.Mtime.Nanosecond()) 545 } 546 547 // Truncate changes the size of a file. 548 func (fs *GoofysWin) Truncate(path string, size int64, fh uint64) (ret int) { 549 if fuseLog.Level == logrus.DebugLevel { 550 fuseLog.Debugf("-> Truncate %v %v %v", path, size, fh) 551 defer func() { 552 fuseLog.Debugf("<- Truncate %v %v %v = %v", path, size, fh, ret) 553 }() 554 } 555 556 atomic.AddInt64(&fs.stats.metadataWrites, 1) 557 558 inode, err := fs.LookupPath(path) 559 if err != nil { 560 return mapWinError(err) 561 } 562 563 usize := uint64(size) 564 565 return mapWinError(mapAwsError(inode.SetAttributes(&usize, nil, nil, nil, nil))) 566 } 567 568 // Read reads data from a file. 569 func (fs *GoofysWin) Read(path string, buff []byte, ofst int64, fhId uint64) (ret int) { 570 if fuseLog.Level == logrus.DebugLevel { 571 fuseLog.Debugf("-> Read %v %v %v %v", path, len(buff), ofst, fhId) 572 defer func() { 573 fuseLog.Debugf("<- Read %v %v %v %v = %v", path, len(buff), ofst, fhId, ret) 574 }() 575 } 576 577 atomic.AddInt64(&fs.stats.reads, 1) 578 579 fs.mu.RLock() 580 fh := fs.fileHandles[fuseops.HandleID(fhId)] 581 fs.mu.RUnlock() 582 if fh == nil { 583 return -fuse.EINVAL 584 } 585 586 data, bytesRead, err := fh.ReadFile(ofst, int64(len(buff))) 587 if err != nil { 588 return mapWinError(err) 589 } 590 done := 0 591 for i := 0; i < len(data); i++ { 592 copy(buff[done:], data[i]) 593 done += len(data[i]) 594 } 595 596 return bytesRead 597 } 598 599 // Write writes data to a file. 600 func (fs *GoofysWin) Write(path string, buff []byte, ofst int64, fhId uint64) (ret int) { 601 if fuseLog.Level == logrus.DebugLevel { 602 fuseLog.Debugf("-> Write %v %v %v %v", path, len(buff), ofst, fhId) 603 defer func() { 604 fuseLog.Debugf("<- Write %v %v %v %v = %v", path, len(buff), ofst, fhId, ret) 605 }() 606 } 607 608 atomic.AddInt64(&fs.stats.writes, 1) 609 610 fs.mu.RLock() 611 fh := fs.fileHandles[fuseops.HandleID(fhId)] 612 fs.mu.RUnlock() 613 if fh == nil { 614 return -fuse.EINVAL 615 } 616 617 err := fh.WriteFile(ofst, buff, true) 618 if err != nil { 619 return mapWinError(err) 620 } 621 622 return len(buff) 623 } 624 625 // Flush flushes cached file data. Ignore it. 626 func (fs *GoofysWin) Flush(path string, fhId uint64) (ret int) { 627 if fuseLog.Level == logrus.DebugLevel { 628 fuseLog.Debugf("<--> Flush %v %v = 0", path, fhId) 629 } 630 atomic.AddInt64(&fs.stats.noops, 1) 631 return 0 632 } 633 634 // Release closes an open file. 635 func (fs *GoofysWin) Release(path string, fhId uint64) (ret int) { 636 if fuseLog.Level == logrus.DebugLevel { 637 fuseLog.Debugf("-> Release %v %v", path, fhId) 638 defer func() { 639 fuseLog.Debugf("<- Release %v %v = %v", path, fhId, ret) 640 }() 641 } 642 643 atomic.AddInt64(&fs.stats.noops, 1) 644 645 fs.mu.RLock() 646 fh := fs.fileHandles[fuseops.HandleID(fhId)] 647 fs.mu.RUnlock() 648 if fh == nil { 649 return -fuse.EINVAL 650 } 651 fh.Release() 652 653 fs.mu.Lock() 654 delete(fs.fileHandles, fuseops.HandleID(fhId)) 655 fs.mu.Unlock() 656 657 if fs.flags.FsyncOnClose { 658 err := fh.inode.SyncFile() 659 if err != nil { 660 return mapWinError(err) 661 } 662 } 663 664 return 0 665 } 666 667 // Fsync synchronizes file contents. 668 func (fs *GoofysWin) Fsync(path string, datasync bool, fhId uint64) (ret int) { 669 if fuseLog.Level == logrus.DebugLevel { 670 fuseLog.Debugf("-> Fsync %v %v %v", path, datasync, fhId) 671 defer func() { 672 fuseLog.Debugf("<- Fsync %v %v %v = %v", path, datasync, fhId, ret) 673 }() 674 } 675 676 atomic.AddInt64(&fs.stats.metadataWrites, 1) 677 678 if !fs.flags.IgnoreFsync { 679 var inode *Inode 680 var err error 681 if fhId != 0 { 682 fs.mu.RLock() 683 fh := fs.fileHandles[fuseops.HandleID(fhId)] 684 fs.mu.RUnlock() 685 if fh == nil { 686 return -fuse.EINVAL 687 } 688 inode = fh.inode 689 } else { 690 inode, err = fs.LookupPath(path) 691 if err != nil { 692 return mapWinError(err) 693 } 694 } 695 if inode.Id == fuseops.RootInodeID { 696 err = fs.SyncFS(nil) 697 } else if inode.isDir() { 698 err = fs.SyncFS(inode) 699 } else { 700 err = inode.SyncFile() 701 } 702 return mapWinError(err) 703 } 704 705 return 0 706 } 707 708 // Opendir opens a directory. 709 func (fs *GoofysWin) Opendir(path string) (ret int, dhId uint64) { 710 if fuseLog.Level == logrus.DebugLevel { 711 fuseLog.Debugf("-> Opendir %v", path) 712 defer func() { 713 fuseLog.Debugf("<- Opendir %v = %v %v", path, ret, dhId) 714 }() 715 } 716 717 atomic.AddInt64(&fs.stats.noops, 1) 718 719 inode, err := fs.LookupPath(path) 720 if err != nil { 721 return mapWinError(err), 0 722 } 723 724 dh := inode.OpenDir() 725 handleID := fs.AddDirHandle(dh) 726 727 return 0, uint64(handleID) 728 } 729 730 // Readdir reads a directory. 731 func (fs *GoofysWin) Readdir(path string, 732 fill func(name string, stat *fuse.Stat_t, ofst int64) bool, 733 ofst int64, dhId uint64) (ret int) { 734 735 if fuseLog.Level == logrus.DebugLevel { 736 fuseLog.Debugf("-> Readdir %v %v %v", path, ofst, dhId) 737 defer func() { 738 fuseLog.Debugf("<- Readdir %v %v %v = %v", path, ofst, dhId, ret) 739 }() 740 } 741 742 atomic.AddInt64(&fs.stats.metadataReads, 1) 743 744 // Find the handle. 745 fs.mu.RLock() 746 dh := fs.dirHandles[fuseops.HandleID(dhId)] 747 fs.mu.RUnlock() 748 749 if dh == nil { 750 return -fuse.EINVAL 751 } 752 753 dh.inode.logFuse("ReadDir", ofst) 754 755 dh.mu.Lock() 756 defer dh.mu.Unlock() 757 758 dh.Seek(fuseops.DirOffset(ofst)) 759 760 for { 761 inode, err := dh.ReadDir() 762 if err != nil { 763 return mapWinError(err) 764 } 765 if inode == nil { 766 break 767 } 768 st := &fuse.Stat_t{} 769 inode.mu.Lock() 770 name := inode.Name 771 attr := inode.InflateAttributes() 772 makeFuseAttributes(&attr, st) 773 inode.mu.Unlock() 774 if dh.lastExternalOffset == 0 { 775 name = "." 776 } else if dh.lastExternalOffset == 1 { 777 name = ".." 778 } 779 if !fill(name, st, int64(dh.lastExternalOffset)) { 780 break 781 } 782 fuseLog.Debugf("<- Readdir %v %v %v = %v %v", path, ofst, dhId, name, dh.lastExternalOffset) 783 // We have to modify it here because fill() MAY not send the entry 784 dh.Next(name) 785 } 786 787 return 0 788 } 789 790 // Releasedir closes an open directory. 791 func (fs *GoofysWin) Releasedir(path string, dhId uint64) (ret int) { 792 if fuseLog.Level == logrus.DebugLevel { 793 fuseLog.Debugf("-> Releasedir %v %v", path, dhId) 794 defer func() { 795 fuseLog.Debugf("<- Releasedir %v %v = %v", path, dhId, ret) 796 }() 797 } 798 799 atomic.AddInt64(&fs.stats.noops, 1) 800 801 fs.mu.RLock() 802 dh := fs.dirHandles[fuseops.HandleID(dhId)] 803 fs.mu.RUnlock() 804 805 dh.CloseDir() 806 807 fs.mu.Lock() 808 delete(fs.dirHandles, fuseops.HandleID(dhId)) 809 fs.mu.Unlock() 810 811 return 0 812 } 813 814 // Fsyncdir synchronizes directory contents. 815 func (fs *GoofysWin) Fsyncdir(path string, datasync bool, fhId uint64) (ret int) { 816 return fs.Fsync(path, datasync, fhId) 817 } 818 819 // Setxattr sets extended attributes. 820 func (fs *GoofysWin) Setxattr(path string, name string, value []byte, flags int) (ret int) { 821 if fs.flags.DisableXattr { 822 return -fuse.EOPNOTSUPP 823 } 824 825 if fuseLog.Level == logrus.DebugLevel { 826 fuseLog.Debugf("-> Setxattr %v %v %v %v", path, name, value, flags) 827 defer func() { 828 fuseLog.Debugf("<- Setxattr %v %v %v %v = %v", path, name, value, flags, ret) 829 }() 830 } 831 832 atomic.AddInt64(&fs.stats.metadataWrites, 1) 833 834 inode, err := fs.LookupPath(path) 835 if err != nil { 836 return mapWinError(err) 837 } 838 839 if name == fs.flags.RefreshAttr { 840 // Setting xattr with special name (.invalidate) refreshes the inode's cache 841 return mapWinError(fs.RefreshInodeCache(inode)) 842 } 843 844 err = inode.SetXattr(name, value, uint32(flags)) 845 return mapWinError(err) 846 } 847 848 // Getxattr gets extended attributes. 849 func (fs *GoofysWin) Getxattr(path string, name string) (ret int, data []byte) { 850 if fs.flags.DisableXattr { 851 return -fuse.EOPNOTSUPP, nil 852 } 853 854 if fuseLog.Level == logrus.DebugLevel { 855 fuseLog.Debugf("-> Getxattr %v %v", path, name) 856 defer func() { 857 fuseLog.Debugf("<- Getxattr %v %v = %v %v", path, name, ret, data) 858 }() 859 } 860 861 atomic.AddInt64(&fs.stats.metadataReads, 1) 862 863 inode, err := fs.LookupPath(path) 864 if err != nil { 865 return mapWinError(err), nil 866 } 867 868 value, err := inode.GetXattr(name) 869 if err != nil { 870 return mapWinError(err), nil 871 } 872 873 return 0, value 874 } 875 876 // Removexattr removes extended attributes. 877 func (fs *GoofysWin) Removexattr(path string, name string) (ret int) { 878 if fs.flags.DisableXattr { 879 return -fuse.EOPNOTSUPP 880 } 881 882 if fuseLog.Level == logrus.DebugLevel { 883 fuseLog.Debugf("-> Removexattr %v %v", path, name) 884 defer func() { 885 fuseLog.Debugf("<- Removexattr %v %v = %v", path, name, ret) 886 }() 887 } 888 889 atomic.AddInt64(&fs.stats.metadataWrites, 1) 890 891 inode, err := fs.LookupPath(path) 892 if err != nil { 893 return mapWinError(err) 894 } 895 896 err = inode.RemoveXattr(name) 897 return mapWinError(err) 898 } 899 900 // Listxattr lists extended attributes. 901 func (fs *GoofysWin) Listxattr(path string, fill func(name string) bool) (ret int) { 902 if fs.flags.DisableXattr { 903 return -fuse.EOPNOTSUPP 904 } 905 906 if fuseLog.Level == logrus.DebugLevel { 907 fuseLog.Debugf("-> Listxattr %v", path) 908 defer func() { 909 fuseLog.Debugf("<- Listxattr %v = %v", path, ret) 910 }() 911 } 912 913 atomic.AddInt64(&fs.stats.metadataReads, 1) 914 915 inode, err := fs.LookupPath(path) 916 if err != nil { 917 return mapWinError(err) 918 } 919 920 xattrs, err := inode.ListXattr() 921 if err != nil { 922 return mapWinError(err) 923 } 924 925 for _, name := range xattrs { 926 if !fill(name) { 927 return -fuse.ERANGE 928 } 929 fuseLog.Debugf("<- Listxattr %v = %v", path, name) 930 } 931 932 return 0 933 } 934 935 // Notify sends file invalidation/deletion notifications to the kernel 936 func (fs *GoofysWin) Notify(notifications []interface{}) { 937 if fs.host == nil { 938 return 939 } 940 var parent fuseops.InodeID 941 var child string 942 var op uint32 943 for _, n := range notifications { 944 switch v := n.(type) { 945 case *fuseops.NotifyDelete: 946 parent = v.Parent 947 child = v.Name 948 op = fuse.NOTIFY_UNLINK 949 case *fuseops.NotifyInvalEntry: 950 parent = v.Parent 951 child = v.Name 952 op = fuse.NOTIFY_CHMOD | fuse.NOTIFY_CHOWN | fuse.NOTIFY_UTIME | fuse.NOTIFY_CHFLAGS | fuse.NOTIFY_TRUNCATE 953 default: 954 panic("Unexpected notification") 955 } 956 fs.mu.RLock() 957 in := fs.inodes[parent] 958 fs.mu.RUnlock() 959 if in != nil { 960 in.mu.Lock() 961 p := in.FullName() 962 in.mu.Unlock() 963 fs.host.Notify(p+"/"+child, op) 964 } 965 } 966 } 967 968 // Mount the file system based on the supplied arguments, returning a 969 // MountedFS that can be joined to wait for unmounting. 970 func MountWin( 971 ctx context.Context, 972 bucketName string, 973 flags *cfg.FlagStorage) (fs *Goofys, mfs MountedFS, err error) { 974 fs, err = NewGoofys(ctx, bucketName, flags) 975 if fs == nil { 976 if err == nil { 977 err = fmt.Errorf("GeeseFS initialization failed") 978 } 979 return 980 } 981 mfs, err = mountFuseFS(fs) 982 return 983 } 984 985 func mountFuseFS(fs *Goofys) (mfs MountedFS, err error) { 986 var mountOpt []string 987 if fs.flags.DebugFuse { 988 mountOpt = append(mountOpt, "-o", "debug") 989 } 990 mountOpt = append(mountOpt, "-o", fmt.Sprintf("uid=%v", int32(fs.flags.Uid))) 991 mountOpt = append(mountOpt, "-o", fmt.Sprintf("gid=%v", int32(fs.flags.Gid))) 992 for _, s := range fs.flags.MountOptions { 993 mountOpt = append(mountOpt, "-o", s) 994 } 995 fuseLog.Debugf("Starting WinFSP with options: %v", mountOpt) 996 997 fsint := NewGoofysWin(fs) 998 fuse.RecoverFromPanic = false 999 host := fuse.NewFileSystemHost(fsint) 1000 fsint.host = host 1001 host.SetCapReaddirPlus(true) 1002 go func() { 1003 ok := host.Mount(fs.flags.MountPoint, mountOpt) 1004 if !ok { 1005 fsint.initCh <- 0 1006 } 1007 }() 1008 v := <-fsint.initCh 1009 if v == 0 { 1010 return nil, fmt.Errorf("WinFSP initialization failed") 1011 } 1012 1013 return fsint, nil 1014 } 1015 1016 // Join is a part of MountedFS interface 1017 func (fs *GoofysWin) Join(ctx context.Context) error { 1018 <-fs.initCh 1019 return nil 1020 } 1021 1022 // Unmount is also a part of MountedFS interface 1023 func (fs *GoofysWin) Unmount() error { 1024 if !fs.initialized { 1025 return fmt.Errorf("not mounted") 1026 } 1027 r := fs.host.Unmount() 1028 if !r { 1029 return fmt.Errorf("unmounting failed") 1030 } 1031 fs.Shutdown() 1032 return nil 1033 }