Skip to content

Commit

Permalink
Merge pull request #213 from sonroyaalmerol/server-nfs
Browse files Browse the repository at this point in the history
implement statx for fs stat
  • Loading branch information
sonroyaalmerol authored Feb 25, 2025
2 parents a7d205a + cae294c commit 7f68014
Show file tree
Hide file tree
Showing 5 changed files with 118 additions and 18 deletions.
37 changes: 19 additions & 18 deletions internal/agent/vssfs/vssfs.go
Original file line number Diff line number Diff line change
Expand Up @@ -71,11 +71,20 @@ func (s *VSSFSServer) Close() {
}

func (s *VSSFSServer) respondError(method, drive string, err error) arpc.Response {
syslog.L.Errorf("%s (%s): %v", method, drive, err)
if syslog.L != nil {
syslog.L.Errorf("%s (%s): %v", method, drive, err)
}
return arpc.Response{Status: 500, Message: fmt.Sprintf("%s (%s): %v", method, drive, err)}
}

func (s *VSSFSServer) handleFsStat(_ arpc.Request) (arpc.Response, error) {
func (s *VSSFSServer) invalidRequest(method, drive string, err error) arpc.Response {
if syslog.L != nil {
syslog.L.Errorf("%s (%s): %v", method, drive, err)
}
return arpc.Response{Status: 400, Message: "invalid request"}
}

func (s *VSSFSServer) handleFsStat(req arpc.Request) (arpc.Response, error) {
var totalBytes uint64
err := windows.GetDiskFreeSpaceEx(
windows.StringToUTF16Ptr(s.rootDir),
Expand All @@ -84,8 +93,7 @@ func (s *VSSFSServer) handleFsStat(_ arpc.Request) (arpc.Response, error) {
nil,
)
if err != nil {
syslog.L.Error(err.Error())
return arpc.Response{Status: 500, Message: err.Error()}, nil
return s.respondError(req.Method, s.drive, err), nil
}

fsStat := &utils.FSStat{
Expand All @@ -107,8 +115,7 @@ func (s *VSSFSServer) handleOpenFile(req arpc.Request) (arpc.Response, error) {
Perm int `json:"perm"`
}
if err := json.Unmarshal(req.Payload, &params); err != nil {
syslog.L.Error(err.Error())
return arpc.Response{Status: 400, Message: "invalid request"}, nil
return s.invalidRequest(req.Method, s.drive, err), nil
}

// Verify read-only access
Expand Down Expand Up @@ -169,8 +176,7 @@ func (s *VSSFSServer) handleStat(req arpc.Request) (arpc.Response, error) {
Path string `json:"path"`
}
if err := json.Unmarshal(req.Payload, &params); err != nil {
syslog.L.Error(err.Error())
return arpc.Response{Status: 400, Message: "invalid request"}, nil
return s.invalidRequest(req.Method, s.drive, err), nil
}

fullPath := filepath.Join(s.rootDir, filepath.Clean(params.Path))
Expand All @@ -197,8 +203,7 @@ func (s *VSSFSServer) handleReadDir(req arpc.Request) (arpc.Response, error) {
Path string `json:"path"`
}
if err := json.Unmarshal(req.Payload, &params); err != nil {
syslog.L.Error(err.Error())
return arpc.Response{Status: 400, Message: "invalid request"}, nil
return s.invalidRequest(req.Method, s.drive, err), nil
}

fullPath := filepath.Join(s.rootDir, filepath.Clean(params.Path))
Expand Down Expand Up @@ -248,8 +253,7 @@ func (s *VSSFSServer) handleRead(req arpc.Request) (arpc.Response, error) {
Length int `json:"length"`
}
if err := json.Unmarshal(req.Payload, &params); err != nil {
syslog.L.Error(err.Error())
return arpc.Response{Status: 400, Message: "invalid request"}, nil
return s.invalidRequest(req.Method, s.drive, err), nil
}

s.mu.RLock()
Expand Down Expand Up @@ -293,8 +297,7 @@ func (s *VSSFSServer) handleReadAt(req arpc.Request) (arpc.Response, error) {
Length int `json:"length"`
}
if err := json.Unmarshal(req.Payload, &params); err != nil {
syslog.L.Error(err.Error())
return arpc.Response{Status: 400, Message: "invalid request"}, nil
return s.invalidRequest(req.Method, s.drive, err), nil
}

s.mu.RLock()
Expand Down Expand Up @@ -340,8 +343,7 @@ func (s *VSSFSServer) handleClose(req arpc.Request) (arpc.Response, error) {
HandleID uint64 `json:"handleID"`
}
if err := json.Unmarshal(req.Payload, &params); err != nil {
syslog.L.Error(err.Error())
return arpc.Response{Status: 400, Message: "invalid request"}, nil
return s.invalidRequest(req.Method, s.drive, err), nil
}

s.mu.Lock()
Expand All @@ -368,8 +370,7 @@ func (s *VSSFSServer) handleFstat(req arpc.Request) (arpc.Response, error) {
HandleID uint64 `json:"handleID"`
}
if err := json.Unmarshal(req.Payload, &params); err != nil {
syslog.L.Error(err.Error())
return arpc.Response{Status: 400, Message: "invalid request"}, nil
return s.invalidRequest(req.Method, s.drive, err), nil
}

s.mu.RLock()
Expand Down
29 changes: 29 additions & 0 deletions internal/backend/arpc/fs.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (
"github.com/go-git/go-billy/v5"
"github.com/sonroyaalmerol/pbs-plus/internal/arpc"
"github.com/sonroyaalmerol/pbs-plus/internal/backend/arpc/fuse"
"github.com/sonroyaalmerol/pbs-plus/internal/backend/arpc/types"
"github.com/sonroyaalmerol/pbs-plus/internal/syslog"
"github.com/sonroyaalmerol/pbs-plus/internal/utils"
)
Expand Down Expand Up @@ -122,6 +123,34 @@ func (fs *ARPCFS) Stat(filename string) (os.FileInfo, error) {
}, nil
}

func (fs *ARPCFS) StatFS() (types.StatFS, error) {
if fs.session == nil {
syslog.L.Error("RPC failed: aRPC session is nil")
return types.StatFS{}, fmt.Errorf("RPC failed: aRPC session is nil")
}

var fsStat utils.FSStat

ctx, cancel := TimeoutCtx()
defer cancel()

err := fs.session.CallJSON(ctx, fs.drive+"/FSstat", struct{}{}, &fsStat)
if err != nil {
syslog.L.Errorf("StatFS RPC failed: %v", err)
return types.StatFS{}, fmt.Errorf("StatFS RPC failed: %w", err)
}

return types.StatFS{
Bsize: uint64(4096), // Standard block size
Blocks: uint64(fsStat.TotalSize / 4096),
Bfree: uint64(fsStat.FreeSize / 4096),
Bavail: uint64(fsStat.AvailableSize / 4096),
Files: uint64(fsStat.TotalFiles),
Ffree: uint64(fsStat.FreeFiles),
NameLen: 255, // Windows typically supports long filenames
}, nil
}

func (fs *ARPCFS) ReadDir(path string) ([]os.FileInfo, error) {
var resp ReadDirResponse
if fs.session == nil {
Expand Down
48 changes: 48 additions & 0 deletions internal/backend/arpc/fuse/fuse.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,14 @@ import (
"github.com/go-git/go-billy/v5"
"github.com/hanwen/go-fuse/v2/fs"
"github.com/hanwen/go-fuse/v2/fuse"
"github.com/sonroyaalmerol/pbs-plus/internal/backend/arpc/types"
"github.com/sonroyaalmerol/pbs-plus/internal/syslog"
)

type StatFSer interface {
StatFS() (types.StatFS, error)
}

// CallHook is the callback called before every FUSE operation
type CallHook func(ctx context.Context) error

Expand Down Expand Up @@ -175,6 +181,42 @@ func (r *BillyRoot) Open(ctx context.Context, flags uint32) (fs.FileHandle, uint
}, 0, 0
}

func (r *BillyRoot) Statfs(ctx context.Context, out *fuse.StatfsOut) syscall.Errno {
if err := r.callHook(ctx); err != nil {
return fs.ToErrno(err)
}

// Try to use StatFSer interface if available
if statfser, ok := r.underlying.(StatFSer); ok {
stats, err := statfser.StatFS()
if err == nil {
out.Blocks = stats.Blocks
out.Bfree = stats.Bfree
out.Bavail = stats.Bavail
out.Files = stats.Files
out.Ffree = stats.Ffree
out.Bsize = uint32(stats.Bsize)
out.NameLen = uint32(stats.NameLen)
out.Frsize = uint32(stats.Bsize)
return 0
}
// Fall through to defaults if error occurs
syslog.L.Warnf("Failed to get StatFS info: %v", err)
}

// Fallback to reasonable defaults for a read-only filesystem
out.Blocks = 1000000 // Just a reasonable number
out.Bfree = 0 // No free blocks (read-only)
out.Bavail = 0 // No available blocks (read-only)
out.Files = 1000 // Reasonable number of inodes
out.Ffree = 0 // No free inodes (read-only)
out.Bsize = 4096 // Standard block size
out.NameLen = 255 // Standard name length
out.Frsize = 4096 // Fragment size

return 0
}

// BillyNode represents a file or directory in the filesystem
type BillyNode struct {
fs.Inode
Expand All @@ -187,6 +229,8 @@ var _ = (fs.NodeLookuper)((*BillyNode)(nil))
var _ = (fs.NodeReaddirer)((*BillyNode)(nil))
var _ = (fs.NodeOpener)((*BillyNode)(nil))
var _ = (fs.NodeReadlinker)((*BillyNode)(nil))
var _ = (fs.NodeStatfser)((*BillyRoot)(nil))
var _ = (fs.NodeStatfser)((*BillyNode)(nil))

// Getattr implements NodeGetattrer
func (n *BillyNode) Getattr(ctx context.Context, fh fs.FileHandle, out *fuse.AttrOut) syscall.Errno {
Expand Down Expand Up @@ -332,6 +376,10 @@ func (n *BillyNode) Readlink(ctx context.Context) ([]byte, syscall.Errno) {
return nil, syscall.ENOSYS
}

func (n *BillyNode) Statfs(ctx context.Context, out *fuse.StatfsOut) syscall.Errno {
return n.root.Statfs(ctx, out)
}

// BillyFileHandle handles file operations
type BillyFileHandle struct {
root *BillyRoot
Expand Down
10 changes: 10 additions & 0 deletions internal/backend/arpc/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,16 @@ type ARPCFile struct {
drive string
}

type StatFS struct {
Bsize uint64 // Block size
Blocks uint64 // Total blocks
Bfree uint64 // Free blocks
Bavail uint64 // Available blocks
Files uint64 // Total files/inodes
Ffree uint64 // Free files/inodes
NameLen uint64 // Maximum name length
}

// FileInfoResponse represents server's file info response
type FileInfoResponse struct {
Name string `json:"name"`
Expand Down
12 changes: 12 additions & 0 deletions internal/backend/arpc/types/types.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package types

type StatFS struct {
Bsize uint64 // Block size
Blocks uint64 // Total blocks
Bfree uint64 // Free blocks
Bavail uint64 // Available blocks
Files uint64 // Total files/inodes
Ffree uint64 // Free files/inodes
NameLen uint64 // Maximum name length
}

0 comments on commit 7f68014

Please sign in to comment.