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  }