mirror of
https://github.com/xiaoqidun/symfs.git
synced 2026-07-04 19:59:52 +08:00
1015 lines
29 KiB
Go
1015 lines
29 KiB
Go
// Copyright 2026 肖其顿 (XIAO QI DUN)
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
package fs
|
|
|
|
import (
|
|
"errors"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"syscall"
|
|
"time"
|
|
"unsafe"
|
|
|
|
"github.com/winfsp/cgofuse/fuse"
|
|
"golang.org/x/sys/windows"
|
|
)
|
|
|
|
// FileNotifyInformation 文件通知信息结构体
|
|
type FileNotifyInformation struct {
|
|
NextEntryOffset uint32
|
|
Action uint32
|
|
FileNameLength uint32
|
|
FileName [1]uint16
|
|
}
|
|
|
|
const invalidFileHandle = ^uint64(0)
|
|
|
|
// winErrToFuse Windows 错误码到 FUSE POSIX errno 的映射表
|
|
var winErrToFuse = map[syscall.Errno]int{
|
|
windows.ERROR_INVALID_FUNCTION: int(fuse.EINVAL),
|
|
windows.ERROR_FILE_NOT_FOUND: int(fuse.ENOENT),
|
|
windows.ERROR_PATH_NOT_FOUND: int(fuse.ENOENT),
|
|
windows.ERROR_ACCESS_DENIED: int(fuse.EACCES),
|
|
windows.ERROR_INVALID_HANDLE: int(fuse.EBADF),
|
|
windows.ERROR_TOO_MANY_OPEN_FILES: int(fuse.EMFILE),
|
|
windows.ERROR_NOT_ENOUGH_MEMORY: int(fuse.ENOMEM),
|
|
windows.ERROR_OUTOFMEMORY: int(fuse.ENOMEM),
|
|
windows.ERROR_INVALID_DRIVE: int(fuse.ENOENT),
|
|
windows.ERROR_NO_MORE_FILES: int(fuse.ENOENT),
|
|
windows.ERROR_WRITE_PROTECT: int(fuse.EROFS),
|
|
windows.ERROR_NOT_READY: int(fuse.EIO),
|
|
windows.ERROR_SHARING_VIOLATION: int(fuse.EBUSY),
|
|
windows.ERROR_LOCK_VIOLATION: int(fuse.EBUSY),
|
|
windows.ERROR_HANDLE_EOF: int(fuse.EIO),
|
|
windows.ERROR_HANDLE_DISK_FULL: int(fuse.ENOSPC),
|
|
windows.ERROR_NOT_SUPPORTED: int(fuse.ENOSYS),
|
|
windows.ERROR_INVALID_PARAMETER: int(fuse.EINVAL),
|
|
windows.ERROR_INSUFFICIENT_BUFFER: int(fuse.EINVAL),
|
|
windows.ERROR_INVALID_NAME: int(fuse.ENOENT),
|
|
windows.ERROR_DIR_NOT_EMPTY: int(fuse.ENOTEMPTY),
|
|
windows.ERROR_ALREADY_EXISTS: int(fuse.EEXIST),
|
|
windows.ERROR_FILE_EXISTS: int(fuse.EEXIST),
|
|
windows.ERROR_BROKEN_PIPE: int(fuse.EPIPE),
|
|
windows.ERROR_DISK_FULL: int(fuse.ENOSPC),
|
|
windows.ERROR_CALL_NOT_IMPLEMENTED: int(fuse.ENOSYS),
|
|
windows.ERROR_NEGATIVE_SEEK: int(fuse.EINVAL),
|
|
windows.ERROR_SEEK_ON_DEVICE: int(fuse.ESPIPE),
|
|
windows.ERROR_DIRECTORY: int(fuse.ENOTDIR),
|
|
windows.ERROR_NOT_EMPTY: int(fuse.ENOTEMPTY),
|
|
windows.ERROR_NOT_SAME_DEVICE: int(fuse.EXDEV),
|
|
windows.ERROR_CURRENT_DIRECTORY: int(fuse.EBUSY),
|
|
windows.ERROR_BAD_NETPATH: int(fuse.ENOENT),
|
|
windows.ERROR_NETWORK_ACCESS_DENIED: int(fuse.EACCES),
|
|
windows.ERROR_CANNOT_MAKE: int(fuse.EACCES),
|
|
windows.ERROR_BAD_PATHNAME: int(fuse.ENOENT),
|
|
windows.ERROR_FILENAME_EXCED_RANGE: int(fuse.ENAMETOOLONG),
|
|
windows.ERROR_FILE_TOO_LARGE: int(fuse.EFBIG),
|
|
windows.ERROR_OPERATION_ABORTED: int(fuse.ECANCELED),
|
|
windows.ERROR_PRIVILEGE_NOT_HELD: int(fuse.EPERM),
|
|
}
|
|
|
|
// errno 转换错误码
|
|
// 入参: err 错误对象
|
|
// 返回: int 错误码
|
|
func errno(err error) int {
|
|
if err == nil {
|
|
return 0
|
|
}
|
|
var sysErr syscall.Errno
|
|
if errors.As(err, &sysErr) {
|
|
if fuseErr, ok := winErrToFuse[sysErr]; ok {
|
|
return -fuseErr
|
|
}
|
|
}
|
|
return -int(fuse.EIO)
|
|
}
|
|
|
|
// shareMode 获取通用共享模式
|
|
// 返回: uint32 Windows 共享模式
|
|
func shareMode() uint32 {
|
|
return windows.FILE_SHARE_READ | windows.FILE_SHARE_WRITE | windows.FILE_SHARE_DELETE
|
|
}
|
|
|
|
// openHandle 打开 Windows 句柄
|
|
// 入参: path 路径, access 访问权限, disposition 创建模式, attrs 文件属性
|
|
// 返回: windows.Handle 句柄, error 错误对象
|
|
func openHandle(path string, access uint32, disposition uint32, attrs uint32) (windows.Handle, error) {
|
|
pathPtr, err := windows.UTF16PtrFromString(path)
|
|
if err != nil {
|
|
return windows.InvalidHandle, err
|
|
}
|
|
return windows.CreateFile(pathPtr, access, shareMode(), nil, disposition, attrs, 0)
|
|
}
|
|
|
|
// closeHandle 关闭 Windows 句柄
|
|
// 入参: fh 文件句柄
|
|
// 返回: int 错误码
|
|
func closeHandle(fh uint64) int {
|
|
if fh == invalidFileHandle {
|
|
return 0
|
|
}
|
|
return errno(windows.CloseHandle(windows.Handle(fh)))
|
|
}
|
|
|
|
// flushHandle 刷新 Windows 句柄
|
|
// 入参: fh 文件句柄
|
|
// 返回: int 错误码
|
|
func flushHandle(fh uint64) int {
|
|
if fh == invalidFileHandle {
|
|
return 0
|
|
}
|
|
err := windows.FlushFileBuffers(windows.Handle(fh))
|
|
if err != nil {
|
|
if sysErr, ok := err.(syscall.Errno); ok && sysErr == windows.ERROR_ACCESS_DENIED {
|
|
return 0
|
|
}
|
|
return errno(err)
|
|
}
|
|
return 0
|
|
}
|
|
|
|
// timespecToFiletime 转换 FUSE 时间到 Windows 时间
|
|
// 入参: tmsp FUSE 时间戳
|
|
// 返回: windows.Filetime Windows 时间戳
|
|
func timespecToFiletime(tmsp fuse.Timespec) windows.Filetime {
|
|
return windows.NsecToFiletime(time.Unix(tmsp.Sec, tmsp.Nsec).UnixNano())
|
|
}
|
|
|
|
// newOverlapped 创建带偏移的重叠 IO 结构
|
|
// 入参: ofst 偏移量
|
|
// 返回: windows.Overlapped 重叠 IO 结构, windows.Handle 事件句柄, error 错误对象
|
|
func newOverlapped(ofst int64) (windows.Overlapped, windows.Handle, error) {
|
|
event, err := windows.CreateEvent(nil, 1, 0, nil)
|
|
if err != nil {
|
|
return windows.Overlapped{}, 0, err
|
|
}
|
|
overlapped := windows.Overlapped{HEvent: event}
|
|
overlapped.Offset = uint32(ofst)
|
|
overlapped.OffsetHigh = uint32(ofst >> 32)
|
|
return overlapped, event, nil
|
|
}
|
|
|
|
// finishOverlapped 等待重叠 IO 完成
|
|
// 入参: h 文件句柄, overlapped 重叠 IO 结构, n 字节数指针, err IO 错误
|
|
// 返回: error 错误对象
|
|
func finishOverlapped(h windows.Handle, overlapped *windows.Overlapped, n *uint32, err error) error {
|
|
if err == windows.ERROR_IO_PENDING {
|
|
return windows.GetOverlappedResult(h, overlapped, n, true)
|
|
}
|
|
return err
|
|
}
|
|
|
|
// Init 初始化文件系统
|
|
func (s *SymFS) Init() {
|
|
s.done = make(chan struct{})
|
|
go s.watch()
|
|
}
|
|
|
|
// Destroy 销毁文件系统
|
|
func (s *SymFS) Destroy() {
|
|
if s.done != nil {
|
|
close(s.done)
|
|
s.done = nil
|
|
}
|
|
}
|
|
|
|
// Statfs 获取文件系统统计信息
|
|
// 入参: path 路径, stat 统计信息结构体指针
|
|
// 返回: int 错误码
|
|
func (s *SymFS) Statfs(path string, stat *fuse.Statfs_t) int {
|
|
path = s.realPath(path)
|
|
pathPtr, err := windows.UTF16PtrFromString(path)
|
|
if err != nil {
|
|
return errno(err)
|
|
}
|
|
var free, total, avail uint64
|
|
err = windows.GetDiskFreeSpaceEx(pathPtr, &avail, &total, &free)
|
|
if err != nil {
|
|
return errno(err)
|
|
}
|
|
const blockSize = 4096
|
|
stat.Bsize = blockSize
|
|
stat.Frsize = blockSize
|
|
stat.Blocks = total / blockSize
|
|
stat.Bfree = free / blockSize
|
|
stat.Bavail = avail / blockSize
|
|
stat.Namemax = 255
|
|
return 0
|
|
}
|
|
|
|
// Mknod 创建文件节点
|
|
// 入参: path 路径, mode 模式, dev 设备号
|
|
// 返回: int 错误码
|
|
func (s *SymFS) Mknod(path string, mode uint32, dev uint64) int {
|
|
errc, fh := s.open(path, os.O_CREATE|os.O_EXCL|os.O_WRONLY, mode)
|
|
if errc != 0 {
|
|
return errc
|
|
}
|
|
return closeHandle(fh)
|
|
}
|
|
|
|
// Mkdir 创建目录
|
|
// 入参: path 路径, mode 模式
|
|
// 返回: int 错误码
|
|
func (s *SymFS) Mkdir(path string, mode uint32) int {
|
|
return errno(os.Mkdir(s.realPath(path), os.FileMode(mode)))
|
|
}
|
|
|
|
// Unlink 删除文件
|
|
// 入参: path 路径
|
|
// 返回: int 错误码
|
|
func (s *SymFS) Unlink(path string) int {
|
|
return errno(os.Remove(s.realPath(path)))
|
|
}
|
|
|
|
// Rmdir 删除目录
|
|
// 入参: path 路径
|
|
// 返回: int 错误码
|
|
func (s *SymFS) Rmdir(path string) int {
|
|
return errno(os.Remove(s.realPath(path)))
|
|
}
|
|
|
|
// Link 创建硬链接
|
|
// 入参: oldpath 旧路径, newpath 新路径
|
|
// 返回: int 错误码
|
|
func (s *SymFS) Link(oldpath string, newpath string) int {
|
|
return errno(os.Link(s.realPath(oldpath), s.realPath(newpath)))
|
|
}
|
|
|
|
// Symlink 创建符号链接
|
|
// 入参: target 目标路径, newpath 新路径
|
|
// 返回: int 错误码
|
|
func (s *SymFS) Symlink(target string, newpath string) int {
|
|
return errno(os.Symlink(target, s.realPath(newpath)))
|
|
}
|
|
|
|
// Readlink 读取符号链接
|
|
// 入参: path 路径
|
|
// 返回: int 错误码, string 目标路径
|
|
func (s *SymFS) Readlink(path string) (int, string) {
|
|
target, err := os.Readlink(s.realPath(path))
|
|
if err != nil {
|
|
return errno(err), ""
|
|
}
|
|
return 0, target
|
|
}
|
|
|
|
// Rename 重命名文件
|
|
// 入参: oldpath 旧路径, newpath 新路径
|
|
// 返回: int 错误码
|
|
func (s *SymFS) Rename(oldpath string, newpath string) int {
|
|
return errno(windows.Rename(s.realPath(oldpath), s.realPath(newpath)))
|
|
}
|
|
|
|
// Rename3 重命名文件
|
|
// 入参: oldpath 旧路径, newpath 新路径, flags 标志位
|
|
// 返回: int 错误码
|
|
func (s *SymFS) Rename3(oldpath string, newpath string, flags uint32) int {
|
|
if flags&^uint32(fuse.RENAME_NOREPLACE) != 0 {
|
|
return -int(fuse.EINVAL)
|
|
}
|
|
if flags&uint32(fuse.RENAME_NOREPLACE) != 0 {
|
|
_, err := os.Lstat(s.realPath(newpath))
|
|
if err == nil {
|
|
return -int(fuse.EEXIST)
|
|
}
|
|
if !os.IsNotExist(err) {
|
|
return errno(err)
|
|
}
|
|
}
|
|
return s.Rename(oldpath, newpath)
|
|
}
|
|
|
|
// Chmod 修改文件权限
|
|
// 入参: path 路径, mode 模式
|
|
// 返回: int 错误码
|
|
func (s *SymFS) Chmod(path string, mode uint32) int {
|
|
return errno(windows.Chmod(s.realPath(path), mode))
|
|
}
|
|
|
|
// Chown 修改文件所有者
|
|
// 入参: path 路径, uid 用户ID, gid 组ID
|
|
// 返回: int 错误码
|
|
func (s *SymFS) Chown(path string, uid uint32, gid uint32) int {
|
|
return -int(fuse.ENOSYS)
|
|
}
|
|
|
|
// Utimens 修改文件时间
|
|
// 入参: path 路径, tmsp 时间戳数组
|
|
// 返回: int 错误码
|
|
func (s *SymFS) Utimens(path string, tmsp []fuse.Timespec) int {
|
|
return s.Utimens3(path, tmsp, invalidFileHandle)
|
|
}
|
|
|
|
// Utimens3 修改文件时间
|
|
// 入参: path 路径, tmsp 时间戳数组, fh 文件句柄
|
|
// 返回: int 错误码
|
|
func (s *SymFS) Utimens3(path string, tmsp []fuse.Timespec, fh uint64) int {
|
|
if len(tmsp) < 2 {
|
|
return -int(fuse.EINVAL)
|
|
}
|
|
path = s.realPath(path)
|
|
h := windows.Handle(fh)
|
|
closeAfter := false
|
|
if fh == invalidFileHandle {
|
|
var err error
|
|
h, err = openHandle(path,
|
|
windows.FILE_READ_ATTRIBUTES|windows.FILE_WRITE_ATTRIBUTES,
|
|
windows.OPEN_EXISTING,
|
|
windows.FILE_FLAG_BACKUP_SEMANTICS)
|
|
if err != nil {
|
|
return errno(err)
|
|
}
|
|
closeAfter = true
|
|
}
|
|
if closeAfter {
|
|
defer windows.CloseHandle(h)
|
|
}
|
|
now := fuse.Now()
|
|
var atime *windows.Filetime
|
|
var mtime *windows.Filetime
|
|
var atimeValue windows.Filetime
|
|
var mtimeValue windows.Filetime
|
|
switch tmsp[0].Nsec {
|
|
case fuse.UTIME_OMIT:
|
|
case fuse.UTIME_NOW:
|
|
atimeValue = timespecToFiletime(now)
|
|
atime = &atimeValue
|
|
default:
|
|
atimeValue = timespecToFiletime(tmsp[0])
|
|
atime = &atimeValue
|
|
}
|
|
switch tmsp[1].Nsec {
|
|
case fuse.UTIME_OMIT:
|
|
case fuse.UTIME_NOW:
|
|
mtimeValue = timespecToFiletime(now)
|
|
mtime = &mtimeValue
|
|
default:
|
|
mtimeValue = timespecToFiletime(tmsp[1])
|
|
mtime = &mtimeValue
|
|
}
|
|
return errno(windows.SetFileTime(h, nil, atime, mtime))
|
|
}
|
|
|
|
// Access 检查文件访问权限
|
|
// 入参: path 路径, mask 掩码
|
|
// 返回: int 错误码
|
|
func (s *SymFS) Access(path string, mask uint32) int {
|
|
path = s.realPath(path)
|
|
if mask == fuse.F_OK {
|
|
_, err := os.Lstat(path)
|
|
return errno(err)
|
|
}
|
|
access := uint32(windows.FILE_READ_ATTRIBUTES)
|
|
if mask&fuse.R_OK != 0 {
|
|
access |= windows.FILE_READ_DATA
|
|
}
|
|
if mask&fuse.W_OK != 0 {
|
|
access |= windows.FILE_WRITE_DATA | windows.FILE_APPEND_DATA | windows.FILE_WRITE_ATTRIBUTES
|
|
}
|
|
if mask&fuse.X_OK != 0 {
|
|
access |= windows.FILE_EXECUTE
|
|
}
|
|
if mask&fuse.DELETE_OK != 0 {
|
|
access |= windows.DELETE
|
|
}
|
|
h, err := openHandle(path, access, windows.OPEN_EXISTING, windows.FILE_FLAG_BACKUP_SEMANTICS)
|
|
if err != nil {
|
|
return errno(err)
|
|
}
|
|
return errno(windows.CloseHandle(h))
|
|
}
|
|
|
|
// Create 创建并打开文件
|
|
// 入参: path 路径, flags 标志位, mode 模式
|
|
// 返回: int 错误码, uint64 文件句柄
|
|
func (s *SymFS) Create(path string, flags int, mode uint32) (int, uint64) {
|
|
return s.open(path, flags|os.O_CREATE, mode)
|
|
}
|
|
|
|
// Open 打开文件
|
|
// 入参: path 路径, flags 标志位
|
|
// 返回: int 错误码, uint64 文件句柄
|
|
func (s *SymFS) Open(path string, flags int) (int, uint64) {
|
|
return s.open(path, flags, 0)
|
|
}
|
|
|
|
// Getattr 获取文件属性
|
|
// 入参: path 路径, stat 属性结构体指针, fh 文件句柄
|
|
// 返回: int 错误码
|
|
func (s *SymFS) Getattr(path string, stat *fuse.Stat_t, fh uint64) int {
|
|
path = s.realPath(path)
|
|
if fh != invalidFileHandle {
|
|
var info windows.ByHandleFileInformation
|
|
if err := windows.GetFileInformationByHandle(windows.Handle(fh), &info); err != nil {
|
|
return errno(err)
|
|
}
|
|
s.fillStatFromHandle(stat, &info)
|
|
return 0
|
|
}
|
|
fi, err := os.Lstat(path)
|
|
if err != nil {
|
|
return errno(err)
|
|
}
|
|
s.fillStat(stat, fi)
|
|
h, err := openHandle(path,
|
|
windows.FILE_READ_ATTRIBUTES,
|
|
windows.OPEN_EXISTING,
|
|
windows.FILE_FLAG_BACKUP_SEMANTICS|windows.FILE_FLAG_OPEN_REPARSE_POINT)
|
|
if err == nil {
|
|
var info windows.ByHandleFileInformation
|
|
if windows.GetFileInformationByHandle(h, &info) == nil {
|
|
s.applyHandleStat(stat, &info)
|
|
}
|
|
windows.CloseHandle(h)
|
|
}
|
|
return 0
|
|
}
|
|
|
|
// Truncate 截断文件
|
|
// 入参: path 路径, size 大小, fh 文件句柄
|
|
// 返回: int 错误码
|
|
func (s *SymFS) Truncate(path string, size int64, fh uint64) int {
|
|
if fh != invalidFileHandle {
|
|
return errno(windows.Ftruncate(windows.Handle(fh), size))
|
|
}
|
|
h, err := openHandle(s.realPath(path),
|
|
windows.FILE_WRITE_DATA|windows.FILE_WRITE_ATTRIBUTES,
|
|
windows.OPEN_EXISTING,
|
|
windows.FILE_ATTRIBUTE_NORMAL)
|
|
if err != nil {
|
|
return errno(err)
|
|
}
|
|
defer windows.CloseHandle(h)
|
|
return errno(windows.Ftruncate(h, size))
|
|
}
|
|
|
|
// Read 读取文件内容
|
|
// 入参: path 路径, buff 缓冲区, ofst 偏移量, fh 文件句柄
|
|
// 返回: int 读取字节数
|
|
func (s *SymFS) Read(path string, buff []byte, ofst int64, fh uint64) int {
|
|
h := windows.Handle(fh)
|
|
overlapped, event, err := newOverlapped(ofst)
|
|
if err != nil {
|
|
return errno(err)
|
|
}
|
|
defer windows.CloseHandle(event)
|
|
var n uint32
|
|
err = windows.ReadFile(h, buff, &n, &overlapped)
|
|
err = finishOverlapped(h, &overlapped, &n, err)
|
|
if err != nil && err != windows.ERROR_HANDLE_EOF {
|
|
return errno(err)
|
|
}
|
|
return int(n)
|
|
}
|
|
|
|
// Write 写入文件内容
|
|
// 入参: path 路径, buff 缓冲区, ofst 偏移量, fh 文件句柄
|
|
// 返回: int 写入字节数
|
|
func (s *SymFS) Write(path string, buff []byte, ofst int64, fh uint64) int {
|
|
h := windows.Handle(fh)
|
|
overlapped, event, err := newOverlapped(ofst)
|
|
if err != nil {
|
|
return errno(err)
|
|
}
|
|
defer windows.CloseHandle(event)
|
|
var n uint32
|
|
err = windows.WriteFile(h, buff, &n, &overlapped)
|
|
err = finishOverlapped(h, &overlapped, &n, err)
|
|
if err != nil {
|
|
return errno(err)
|
|
}
|
|
return int(n)
|
|
}
|
|
|
|
// Flush 刷新文件缓冲
|
|
// 入参: path 路径, fh 文件句柄
|
|
// 返回: int 错误码
|
|
func (s *SymFS) Flush(path string, fh uint64) int {
|
|
return flushHandle(fh)
|
|
}
|
|
|
|
// Release 释放文件句柄
|
|
// 入参: path 路径, fh 文件句柄
|
|
// 返回: int 错误码
|
|
func (s *SymFS) Release(path string, fh uint64) int {
|
|
return closeHandle(fh)
|
|
}
|
|
|
|
// Fsync 同步文件内容
|
|
// 入参: path 路径, datasync 是否仅同步数据, fh 文件句柄
|
|
// 返回: int 错误码
|
|
func (s *SymFS) Fsync(path string, datasync bool, fh uint64) int {
|
|
return flushHandle(fh)
|
|
}
|
|
|
|
// Opendir 打开目录
|
|
// 入参: path 路径
|
|
// 返回: int 错误码, uint64 目录句柄
|
|
func (s *SymFS) Opendir(path string) (int, uint64) {
|
|
path = s.realPath(path)
|
|
fi, err := os.Lstat(path)
|
|
if err != nil {
|
|
return errno(err), invalidFileHandle
|
|
}
|
|
if !fi.IsDir() {
|
|
return -int(fuse.ENOTDIR), invalidFileHandle
|
|
}
|
|
h, err := openHandle(path,
|
|
windows.FILE_LIST_DIRECTORY|windows.FILE_READ_ATTRIBUTES,
|
|
windows.OPEN_EXISTING,
|
|
windows.FILE_FLAG_BACKUP_SEMANTICS)
|
|
if err != nil {
|
|
return errno(err), invalidFileHandle
|
|
}
|
|
return 0, uint64(h)
|
|
}
|
|
|
|
// Readdir 读取目录内容
|
|
// 入参: path 路径, fill 填充函数, ofst 偏移量, fh 目录句柄
|
|
// 返回: int 错误码
|
|
func (s *SymFS) Readdir(path string, fill func(name string, stat *fuse.Stat_t, ofst int64) bool, ofst int64, fh uint64) int {
|
|
realPath := s.realPath(path)
|
|
f, err := os.Open(realPath)
|
|
if err != nil {
|
|
return errno(err)
|
|
}
|
|
defer f.Close()
|
|
entries, err := f.Readdir(-1)
|
|
if err != nil {
|
|
return errno(err)
|
|
}
|
|
var dot fuse.Stat_t
|
|
if s.Getattr(path, &dot, invalidFileHandle) == 0 {
|
|
fill(".", &dot, 0)
|
|
} else {
|
|
fill(".", nil, 0)
|
|
}
|
|
fill("..", nil, 0)
|
|
for _, entry := range entries {
|
|
var stat fuse.Stat_t
|
|
s.fillStat(&stat, entry)
|
|
if !fill(entry.Name(), &stat, 0) {
|
|
break
|
|
}
|
|
}
|
|
return 0
|
|
}
|
|
|
|
// Releasedir 释放目录句柄
|
|
// 入参: path 路径, fh 目录句柄
|
|
// 返回: int 错误码
|
|
func (s *SymFS) Releasedir(path string, fh uint64) int {
|
|
return closeHandle(fh)
|
|
}
|
|
|
|
// Fsyncdir 同步目录内容
|
|
// 入参: path 路径, datasync 是否仅同步数据, fh 目录句柄
|
|
// 返回: int 错误码
|
|
func (s *SymFS) Fsyncdir(path string, datasync bool, fh uint64) int {
|
|
return flushHandle(fh)
|
|
}
|
|
|
|
// Chflags 修改文件属性
|
|
// 入参: path 路径, flags BSD 标志位
|
|
// 返回: int 错误码
|
|
func (s *SymFS) Chflags(path string, flags uint32) int {
|
|
path = s.realPath(path)
|
|
pathPtr, err := windows.UTF16PtrFromString(path)
|
|
if err != nil {
|
|
return errno(err)
|
|
}
|
|
attrs, err := windows.GetFileAttributes(pathPtr)
|
|
if err != nil {
|
|
return errno(err)
|
|
}
|
|
return errno(windows.SetFileAttributes(pathPtr, flagsToAttributes(attrs, flags)))
|
|
}
|
|
|
|
// Setcrtime 修改文件创建时间
|
|
// 入参: path 路径, tmsp 时间戳
|
|
// 返回: int 错误码
|
|
func (s *SymFS) Setcrtime(path string, tmsp fuse.Timespec) int {
|
|
if tmsp.Nsec == fuse.UTIME_OMIT {
|
|
return 0
|
|
}
|
|
if tmsp.Nsec == fuse.UTIME_NOW {
|
|
tmsp = fuse.Now()
|
|
}
|
|
h, err := openHandle(s.realPath(path),
|
|
windows.FILE_WRITE_ATTRIBUTES,
|
|
windows.OPEN_EXISTING,
|
|
windows.FILE_FLAG_BACKUP_SEMANTICS)
|
|
if err != nil {
|
|
return errno(err)
|
|
}
|
|
defer windows.CloseHandle(h)
|
|
ctime := timespecToFiletime(tmsp)
|
|
return errno(windows.SetFileTime(h, &ctime, nil, nil))
|
|
}
|
|
|
|
// Setchgtime 修改文件变更时间
|
|
// 入参: path 路径, tmsp 时间戳
|
|
// 返回: int 错误码
|
|
func (s *SymFS) Setchgtime(path string, tmsp fuse.Timespec) int {
|
|
return 0
|
|
}
|
|
|
|
// Getpath 获取真实大小写路径
|
|
// 入参: path 路径, fh 文件句柄
|
|
// 返回: int 错误码, string 真实路径
|
|
func (s *SymFS) Getpath(path string, fh uint64) (int, string) {
|
|
actual, err := s.casePath(path)
|
|
if err != nil {
|
|
return errno(err), ""
|
|
}
|
|
return 0, actual
|
|
}
|
|
|
|
// Setxattr 设置扩展属性
|
|
// 入参: path 路径, name 属性名, value 属性值, flags 标志位
|
|
// 返回: int 错误码
|
|
func (s *SymFS) Setxattr(path string, name string, value []byte, flags int) int {
|
|
return -int(fuse.ENOSYS)
|
|
}
|
|
|
|
// Getxattr 获取扩展属性
|
|
// 入参: path 路径, name 属性名
|
|
// 返回: int 错误码, []byte 属性值
|
|
func (s *SymFS) Getxattr(path string, name string) (int, []byte) {
|
|
return -int(fuse.ENOSYS), nil
|
|
}
|
|
|
|
// Removexattr 删除扩展属性
|
|
// 入参: path 路径, name 属性名
|
|
// 返回: int 错误码
|
|
func (s *SymFS) Removexattr(path string, name string) int {
|
|
return -int(fuse.ENOSYS)
|
|
}
|
|
|
|
// Listxattr 列出扩展属性
|
|
// 入参: path 路径, fill 填充函数
|
|
// 返回: int 错误码
|
|
func (s *SymFS) Listxattr(path string, fill func(name string) bool) int {
|
|
return -int(fuse.ENOSYS)
|
|
}
|
|
|
|
// open 打开文件辅助函数
|
|
// 入参: path 路径, flags 标志位, mode 模式
|
|
// 返回: int 错误码, uint64 文件句柄
|
|
func (s *SymFS) open(path string, flags int, mode uint32) (int, uint64) {
|
|
path = s.realPath(path)
|
|
access := uint32(windows.FILE_READ_ATTRIBUTES | windows.FILE_WRITE_ATTRIBUTES)
|
|
switch flags & (os.O_RDONLY | os.O_WRONLY | os.O_RDWR) {
|
|
case os.O_RDONLY:
|
|
access |= windows.FILE_GENERIC_READ
|
|
case os.O_WRONLY:
|
|
access |= windows.FILE_GENERIC_WRITE
|
|
case os.O_RDWR:
|
|
access |= windows.FILE_GENERIC_READ | windows.FILE_GENERIC_WRITE
|
|
}
|
|
if flags&os.O_CREATE != 0 {
|
|
access |= windows.FILE_GENERIC_WRITE
|
|
}
|
|
if flags&os.O_TRUNC != 0 {
|
|
access |= windows.FILE_WRITE_DATA
|
|
}
|
|
if flags&os.O_APPEND != 0 {
|
|
access &^= windows.FILE_WRITE_DATA
|
|
access |= windows.FILE_APPEND_DATA
|
|
}
|
|
var createDisposition uint32
|
|
switch {
|
|
case flags&(os.O_CREATE|os.O_EXCL) == (os.O_CREATE | os.O_EXCL):
|
|
createDisposition = windows.CREATE_NEW
|
|
case flags&(os.O_CREATE|os.O_TRUNC) == (os.O_CREATE | os.O_TRUNC):
|
|
createDisposition = windows.CREATE_ALWAYS
|
|
case flags&os.O_CREATE == os.O_CREATE:
|
|
createDisposition = windows.OPEN_ALWAYS
|
|
case flags&os.O_TRUNC == os.O_TRUNC:
|
|
createDisposition = windows.TRUNCATE_EXISTING
|
|
default:
|
|
createDisposition = windows.OPEN_EXISTING
|
|
}
|
|
attrs := createAttributes(mode) | windows.FILE_FLAG_OVERLAPPED
|
|
h, err := openHandle(path, access, createDisposition, attrs)
|
|
if err != nil {
|
|
return errno(err), invalidFileHandle
|
|
}
|
|
return 0, uint64(h)
|
|
}
|
|
|
|
// watch 监控目录变更
|
|
func (s *SymFS) watch() {
|
|
pathPtr, err := windows.UTF16PtrFromString(s.root)
|
|
if err != nil {
|
|
return
|
|
}
|
|
h, err := windows.CreateFile(
|
|
pathPtr,
|
|
windows.FILE_LIST_DIRECTORY,
|
|
windows.FILE_SHARE_READ|windows.FILE_SHARE_WRITE|windows.FILE_SHARE_DELETE,
|
|
nil,
|
|
windows.OPEN_EXISTING,
|
|
windows.FILE_FLAG_BACKUP_SEMANTICS|windows.FILE_FLAG_OVERLAPPED,
|
|
0,
|
|
)
|
|
if err != nil {
|
|
return
|
|
}
|
|
defer windows.CloseHandle(h)
|
|
event, err := windows.CreateEvent(nil, 0, 0, nil)
|
|
if err != nil {
|
|
return
|
|
}
|
|
defer windows.CloseHandle(event)
|
|
buf := make([]byte, 16384)
|
|
for {
|
|
select {
|
|
case <-s.done:
|
|
return
|
|
default:
|
|
}
|
|
var overlapped windows.Overlapped
|
|
overlapped.HEvent = event
|
|
err = windows.ReadDirectoryChanges(
|
|
h,
|
|
&buf[0],
|
|
uint32(len(buf)),
|
|
true,
|
|
windows.FILE_NOTIFY_CHANGE_FILE_NAME|
|
|
windows.FILE_NOTIFY_CHANGE_DIR_NAME|
|
|
windows.FILE_NOTIFY_CHANGE_ATTRIBUTES|
|
|
windows.FILE_NOTIFY_CHANGE_SIZE|
|
|
windows.FILE_NOTIFY_CHANGE_LAST_WRITE|
|
|
windows.FILE_NOTIFY_CHANGE_CREATION|
|
|
windows.FILE_NOTIFY_CHANGE_SECURITY,
|
|
nil,
|
|
(*windows.Overlapped)(unsafe.Pointer(&overlapped)),
|
|
0,
|
|
)
|
|
if err != nil && err != windows.ERROR_IO_PENDING {
|
|
return
|
|
}
|
|
for {
|
|
result, _ := windows.WaitForSingleObject(event, 100)
|
|
if result == uint32(windows.WAIT_OBJECT_0) {
|
|
break
|
|
}
|
|
select {
|
|
case <-s.done:
|
|
windows.CancelIo(h)
|
|
return
|
|
default:
|
|
}
|
|
}
|
|
var bytesReturned uint32
|
|
err = windows.GetOverlappedResult(h, (*windows.Overlapped)(unsafe.Pointer(&overlapped)), &bytesReturned, false)
|
|
if err != nil {
|
|
return
|
|
}
|
|
headerSize := uint32(unsafe.Offsetof(FileNotifyInformation{}.FileName))
|
|
var offset uint32
|
|
for {
|
|
if offset+headerSize > bytesReturned {
|
|
break
|
|
}
|
|
info := (*FileNotifyInformation)(unsafe.Pointer(&buf[offset]))
|
|
if offset+headerSize+info.FileNameLength > bytesReturned {
|
|
break
|
|
}
|
|
length := info.FileNameLength / 2
|
|
nameSlice := (*[1 << 16]uint16)(unsafe.Pointer(&info.FileName[0]))[:length:length]
|
|
fileName := syscall.UTF16ToString(nameSlice)
|
|
fileName = strings.ReplaceAll(fileName, "\\", "/")
|
|
fullPath := "/" + fileName
|
|
var fuseAction uint32
|
|
switch info.Action {
|
|
case windows.FILE_ACTION_ADDED, windows.FILE_ACTION_RENAMED_NEW_NAME:
|
|
fuseAction = fuse.NOTIFY_CREATE | fuse.NOTIFY_MKDIR
|
|
case windows.FILE_ACTION_REMOVED, windows.FILE_ACTION_RENAMED_OLD_NAME:
|
|
fuseAction = fuse.NOTIFY_UNLINK | fuse.NOTIFY_RMDIR
|
|
case windows.FILE_ACTION_MODIFIED:
|
|
fuseAction = fuse.NOTIFY_CHMOD | fuse.NOTIFY_CHOWN | fuse.NOTIFY_UTIME | fuse.NOTIFY_TRUNCATE
|
|
default:
|
|
fuseAction = fuse.NOTIFY_CREATE | fuse.NOTIFY_UNLINK | fuse.NOTIFY_TRUNCATE
|
|
}
|
|
if info.FileNameLength > 0 {
|
|
s.host.Notify(fullPath, fuseAction)
|
|
}
|
|
if info.NextEntryOffset == 0 {
|
|
break
|
|
}
|
|
offset += info.NextEntryOffset
|
|
}
|
|
}
|
|
}
|
|
|
|
// createAttributes 获取创建文件属性
|
|
// 入参: mode 文件模式
|
|
// 返回: uint32 Windows 文件属性
|
|
func createAttributes(mode uint32) uint32 {
|
|
if mode&0222 == 0 {
|
|
return windows.FILE_ATTRIBUTE_READONLY
|
|
}
|
|
return windows.FILE_ATTRIBUTE_NORMAL
|
|
}
|
|
|
|
// attributesToFlags 转换 Windows 属性到 BSD 标志位
|
|
// 入参: attrs Windows 文件属性
|
|
// 返回: uint32 BSD 标志位
|
|
func attributesToFlags(attrs uint32) uint32 {
|
|
var flags uint32
|
|
if attrs&windows.FILE_ATTRIBUTE_HIDDEN != 0 {
|
|
flags |= fuse.UF_HIDDEN
|
|
}
|
|
if attrs&windows.FILE_ATTRIBUTE_READONLY != 0 {
|
|
flags |= fuse.UF_READONLY
|
|
}
|
|
if attrs&windows.FILE_ATTRIBUTE_SYSTEM != 0 {
|
|
flags |= fuse.UF_SYSTEM
|
|
}
|
|
if attrs&windows.FILE_ATTRIBUTE_ARCHIVE != 0 {
|
|
flags |= fuse.UF_ARCHIVE
|
|
}
|
|
return flags
|
|
}
|
|
|
|
// flagsToAttributes 转换 BSD 标志位到 Windows 属性
|
|
// 入参: attrs 原 Windows 文件属性, flags BSD 标志位
|
|
// 返回: uint32 新 Windows 文件属性
|
|
func flagsToAttributes(attrs uint32, flags uint32) uint32 {
|
|
attrs &^= windows.FILE_ATTRIBUTE_NORMAL |
|
|
windows.FILE_ATTRIBUTE_HIDDEN |
|
|
windows.FILE_ATTRIBUTE_READONLY |
|
|
windows.FILE_ATTRIBUTE_SYSTEM |
|
|
windows.FILE_ATTRIBUTE_ARCHIVE
|
|
if flags&fuse.UF_HIDDEN != 0 {
|
|
attrs |= windows.FILE_ATTRIBUTE_HIDDEN
|
|
}
|
|
if flags&fuse.UF_READONLY != 0 {
|
|
attrs |= windows.FILE_ATTRIBUTE_READONLY
|
|
}
|
|
if flags&fuse.UF_SYSTEM != 0 {
|
|
attrs |= windows.FILE_ATTRIBUTE_SYSTEM
|
|
}
|
|
if flags&fuse.UF_ARCHIVE != 0 {
|
|
attrs |= windows.FILE_ATTRIBUTE_ARCHIVE
|
|
}
|
|
if attrs == 0 {
|
|
attrs |= windows.FILE_ATTRIBUTE_NORMAL
|
|
}
|
|
return attrs
|
|
}
|
|
|
|
// applyReadonlyMode 应用只读属性到模式
|
|
// 入参: stat 统计信息结构体指针, attrs Windows 文件属性
|
|
func applyReadonlyMode(stat *fuse.Stat_t, attrs uint32) {
|
|
if attrs&windows.FILE_ATTRIBUTE_READONLY != 0 {
|
|
stat.Mode &^= fuse.S_IWUSR | fuse.S_IWGRP | fuse.S_IWOTH
|
|
return
|
|
}
|
|
if stat.Mode&fuse.S_IFMT != fuse.S_IFLNK {
|
|
stat.Mode |= fuse.S_IWUSR | fuse.S_IWGRP | fuse.S_IWOTH
|
|
}
|
|
}
|
|
|
|
// filetimeToTimespec 转换 Windows 时间到 FUSE 时间
|
|
// 入参: ft Windows 时间戳
|
|
// 返回: fuse.Timespec FUSE 时间戳
|
|
func filetimeToTimespec(ft windows.Filetime) fuse.Timespec {
|
|
return fuse.NewTimespec(time.Unix(0, ft.Nanoseconds()))
|
|
}
|
|
|
|
// setBlocks 填充块信息
|
|
// 入参: stat 统计信息结构体指针
|
|
func setBlocks(stat *fuse.Stat_t) {
|
|
const blockSize = 4096
|
|
stat.Blksize = blockSize
|
|
stat.Blocks = (stat.Size + 511) / 512
|
|
}
|
|
|
|
// fillStatFromHandle 通过句柄信息填充统计信息
|
|
// 入参: stat 统计信息结构体指针, info Windows 句柄信息
|
|
func (s *SymFS) fillStatFromHandle(stat *fuse.Stat_t, info *windows.ByHandleFileInformation) {
|
|
*stat = fuse.Stat_t{}
|
|
mode := uint32(0666)
|
|
if info.FileAttributes&windows.FILE_ATTRIBUTE_DIRECTORY != 0 {
|
|
mode = 0777 | fuse.S_IFDIR
|
|
} else {
|
|
mode |= fuse.S_IFREG
|
|
}
|
|
stat.Mode = mode
|
|
s.applyHandleStat(stat, info)
|
|
}
|
|
|
|
// applyHandleStat 应用 Windows 句柄统计信息
|
|
// 入参: stat 统计信息结构体指针, info Windows 句柄信息
|
|
func (s *SymFS) applyHandleStat(stat *fuse.Stat_t, info *windows.ByHandleFileInformation) {
|
|
size := int64(uint64(info.FileSizeHigh)<<32 | uint64(info.FileSizeLow))
|
|
if stat.Mode&fuse.S_IFMT != fuse.S_IFLNK {
|
|
stat.Size = size
|
|
}
|
|
stat.Atim = filetimeToTimespec(info.LastAccessTime)
|
|
stat.Mtim = filetimeToTimespec(info.LastWriteTime)
|
|
stat.Ctim = stat.Mtim
|
|
stat.Birthtim = filetimeToTimespec(info.CreationTime)
|
|
stat.Dev = uint64(info.VolumeSerialNumber)
|
|
stat.Ino = uint64(info.FileIndexHigh)<<32 | uint64(info.FileIndexLow)
|
|
stat.Nlink = info.NumberOfLinks
|
|
if stat.Nlink == 0 {
|
|
stat.Nlink = 1
|
|
}
|
|
stat.Uid = 0
|
|
stat.Gid = 0
|
|
stat.Flags = attributesToFlags(info.FileAttributes)
|
|
applyReadonlyMode(stat, info.FileAttributes)
|
|
setBlocks(stat)
|
|
}
|
|
|
|
// casePath 获取真实大小写路径
|
|
// 入参: path 路径
|
|
// 返回: string 真实路径, error 错误对象
|
|
func (s *SymFS) casePath(path string) (string, error) {
|
|
path = strings.ReplaceAll(path, "\\", "/")
|
|
path = strings.Trim(path, "/")
|
|
if path == "" {
|
|
return "/", nil
|
|
}
|
|
dir := s.root
|
|
parts := strings.Split(path, "/")
|
|
actual := make([]string, 0, len(parts))
|
|
for _, part := range parts {
|
|
if part == "" || part == "." {
|
|
continue
|
|
}
|
|
if part == ".." {
|
|
return "", windows.ERROR_INVALID_NAME
|
|
}
|
|
entries, err := os.ReadDir(dir)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
name := ""
|
|
for _, entry := range entries {
|
|
if strings.EqualFold(entry.Name(), part) {
|
|
name = entry.Name()
|
|
break
|
|
}
|
|
}
|
|
if name == "" {
|
|
return "", windows.ERROR_FILE_NOT_FOUND
|
|
}
|
|
actual = append(actual, name)
|
|
dir = filepath.Join(dir, name)
|
|
}
|
|
return "/" + strings.Join(actual, "/"), nil
|
|
}
|
|
|
|
// fillStat 填充统计信息
|
|
// 入参: stat 统计信息结构体指针, fi 文件信息接口
|
|
func (s *SymFS) fillStat(stat *fuse.Stat_t, fi os.FileInfo) {
|
|
*stat = fuse.Stat_t{}
|
|
stat.Size = fi.Size()
|
|
stat.Mtim = fuse.NewTimespec(fi.ModTime())
|
|
if sys, ok := fi.Sys().(*syscall.Win32FileAttributeData); ok {
|
|
stat.Atim = fuse.NewTimespec(time.Unix(0, sys.LastAccessTime.Nanoseconds()))
|
|
stat.Birthtim = fuse.NewTimespec(time.Unix(0, sys.CreationTime.Nanoseconds()))
|
|
stat.Ctim = stat.Mtim
|
|
stat.Flags = attributesToFlags(sys.FileAttributes)
|
|
} else {
|
|
stat.Atim = stat.Mtim
|
|
stat.Ctim = stat.Mtim
|
|
stat.Birthtim = stat.Mtim
|
|
}
|
|
mode := uint32(fi.Mode() & os.ModePerm)
|
|
if fi.IsDir() {
|
|
mode |= fuse.S_IFDIR
|
|
} else if fi.Mode()&os.ModeSymlink != 0 {
|
|
mode |= fuse.S_IFLNK
|
|
} else {
|
|
mode |= fuse.S_IFREG
|
|
}
|
|
stat.Mode = mode
|
|
stat.Nlink = 1
|
|
stat.Uid = 0
|
|
stat.Gid = 0
|
|
if sys, ok := fi.Sys().(*syscall.Win32FileAttributeData); ok {
|
|
applyReadonlyMode(stat, sys.FileAttributes)
|
|
}
|
|
setBlocks(stat)
|
|
}
|