Skip to content

Commit

Permalink
Merge pull request #92 from willscott/feat/mknod
Browse files Browse the repository at this point in the history
Implement interface for FS implementations for mknod and link
  • Loading branch information
willscott authored Nov 15, 2023
2 parents e45deb3 + 3c09dcd commit a91b8b3
Show file tree
Hide file tree
Showing 4 changed files with 267 additions and 5 deletions.
29 changes: 29 additions & 0 deletions example/osnfs/changeos_unix.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
//go:build darwin || dragonfly || freebsd || linux || nacl || netbsd || openbsd || solaris
// +build darwin dragonfly freebsd linux nacl netbsd openbsd solaris

package main

import (
"golang.org/x/sys/unix"
)

func (fs COS) Mknod(path string, mode uint32, major uint32, minor uint32) error {
dev := unix.Mkdev(major, minor)
return unix.Mknod(fs.Join(fs.Root(), path), mode, int(dev))
}

func (fs COS) Mkfifo(path string, mode uint32) error {
return unix.Mkfifo(fs.Join(fs.Root(), path), mode)
}

func (fs COS) Link(path string, link string) error {
return unix.Link(fs.Join(fs.Root(), path), link)
}

func (fs COS) Socket(path string) error {
fd, err := unix.Socket(unix.AF_UNIX, unix.SOCK_STREAM, 0)
if err != nil {
return err
}
return unix.Bind(fd, &unix.SockaddrUnix{Name: fs.Join(fs.Root(), path)})
}
10 changes: 10 additions & 0 deletions handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ type Handler interface {
Mount(context.Context, net.Conn, MountRequest) (MountStatus, billy.Filesystem, []AuthFlavor)

// Change can return 'nil' if filesystem is read-only
// If the returned value can be cast to `UnixChange`, mknod and link RPCs will be available.
Change(billy.Filesystem) billy.Change

// Optional methods - generic helpers or trivial implementations can be sufficient depending on use case.
Expand All @@ -30,6 +31,15 @@ type Handler interface {
HandleLimit() int
}

// UnixChange extends the billy `Change` interface with support for special files.
type UnixChange interface {
billy.Change
Mknod(path string, mode uint32, major uint32, minor uint32) error
Mkfifo(path string, mode uint32) error
Socket(path string) error
Link(path string, link string) error
}

// CachingHandler represents the optional caching work that a user may wish to over-ride with
// their own implementations, but which can be otherwise provided through defaults.
type CachingHandler interface {
Expand Down
88 changes: 84 additions & 4 deletions nfs_onlink.go
Original file line number Diff line number Diff line change
@@ -1,14 +1,94 @@
package nfs

import (
"bytes"
"context"
"os"
)

var linkErrorBody = [12]byte{}
"github.com/go-git/go-billy/v5"
"github.com/willscott/go-nfs-client/nfs/xdr"
)

// Backing billy.FS doesn't support hard links
func onLink(ctx context.Context, w *response, userHandle Handler) error {
w.errorFmt = errFormatterWithBody(linkErrorBody[:])
return &NFSStatusError{NFSStatusNotSupp, os.ErrPermission}
w.errorFmt = wccDataErrorFormatter
obj := DirOpArg{}
err := xdr.Read(w.req.Body, &obj)
if err != nil {
return &NFSStatusError{NFSStatusInval, err}
}
attrs, err := ReadSetFileAttributes(w.req.Body)
if err != nil {
return &NFSStatusError{NFSStatusInval, err}
}

target, err := xdr.ReadOpaque(w.req.Body)
if err != nil {
return &NFSStatusError{NFSStatusInval, err}
}

fs, path, err := userHandle.FromHandle(obj.Handle)
if err != nil {
return &NFSStatusError{NFSStatusStale, err}
}
if !billy.CapabilityCheck(fs, billy.WriteCapability) {
return &NFSStatusError{NFSStatusROFS, os.ErrPermission}
}

if len(string(obj.Filename)) > PathNameMax {
return &NFSStatusError{NFSStatusNameTooLong, os.ErrInvalid}
}

newFilePath := fs.Join(append(path, string(obj.Filename))...)
if _, err := fs.Stat(newFilePath); err == nil {
return &NFSStatusError{NFSStatusExist, os.ErrExist}
}
if s, err := fs.Stat(fs.Join(path...)); err != nil {
return &NFSStatusError{NFSStatusAccess, err}
} else if !s.IsDir() {
return &NFSStatusError{NFSStatusNotDir, nil}
}

fp := userHandle.ToHandle(fs, append(path, string(obj.Filename)))
changer := userHandle.Change(fs)
if changer == nil {
return &NFSStatusError{NFSStatusAccess, err}
}
cos, ok := changer.(UnixChange)
if !ok {
return &NFSStatusError{NFSStatusAccess, err}
}

err = cos.Link(string(target), newFilePath)
if err != nil {
return &NFSStatusError{NFSStatusAccess, err}
}
if err := attrs.Apply(changer, fs, newFilePath); err != nil {
return &NFSStatusError{NFSStatusIO, err}
}

writer := bytes.NewBuffer([]byte{})
if err := xdr.Write(writer, uint32(NFSStatusOk)); err != nil {
return &NFSStatusError{NFSStatusServerFault, err}
}

// "handle follows"
if err := xdr.Write(writer, uint32(1)); err != nil {
return &NFSStatusError{NFSStatusServerFault, err}
}
if err := xdr.Write(writer, fp); err != nil {
return &NFSStatusError{NFSStatusServerFault, err}
}
if err := WritePostOpAttrs(writer, tryStat(fs, append(path, string(obj.Filename)))); err != nil {
return &NFSStatusError{NFSStatusServerFault, err}
}

if err := WriteWcc(writer, nil, tryStat(fs, path)); err != nil {
return &NFSStatusError{NFSStatusServerFault, err}
}

if err := w.Write(writer.Bytes()); err != nil {
return &NFSStatusError{NFSStatusServerFault, err}
}
return nil
}
145 changes: 144 additions & 1 deletion nfs_onmknod.go
Original file line number Diff line number Diff line change
@@ -1,13 +1,156 @@
package nfs

import (
"bytes"
"context"
"os"

"github.com/go-git/go-billy/v5"
"github.com/willscott/go-nfs-client/nfs/xdr"
)

type nfs_ftype int32

const (
FTYPE_NF3REG nfs_ftype = 1
FTYPE_NF3DIR nfs_ftype = 2
FTYPE_NF3BLK nfs_ftype = 3
FTYPE_NF3CHR nfs_ftype = 4
FTYPE_NF3LNK nfs_ftype = 5
FTYPE_NF3SOCK nfs_ftype = 6
FTYPE_NF3FIFO nfs_ftype = 7
)

// Backing billy.FS doesn't support creation of
// char, block, socket, or fifo pipe nodes
func onMknod(ctx context.Context, w *response, userHandle Handler) error {
w.errorFmt = wccDataErrorFormatter
return &NFSStatusError{NFSStatusNotSupp, os.ErrPermission}
obj := DirOpArg{}
err := xdr.Read(w.req.Body, &obj)
if err != nil {
return &NFSStatusError{NFSStatusInval, err}
}

ftype, err := xdr.ReadUint32(w.req.Body)
if err != nil {
return &NFSStatusError{NFSStatusInval, err}
}

// see if the filesystem supports mknod
fs, path, err := userHandle.FromHandle(obj.Handle)
if err != nil {
return &NFSStatusError{NFSStatusStale, err}
}
if !billy.CapabilityCheck(fs, billy.WriteCapability) {
return &NFSStatusError{NFSStatusROFS, os.ErrPermission}
}
c := userHandle.Change(fs)
if c == nil {
return &NFSStatusError{NFSStatusAccess, os.ErrPermission}
}
cu, ok := c.(UnixChange)
if !ok {
return &NFSStatusError{NFSStatusAccess, os.ErrPermission}
}

if len(string(obj.Filename)) > PathNameMax {
return &NFSStatusError{NFSStatusNameTooLong, os.ErrInvalid}
}

newFilePath := fs.Join(append(path, string(obj.Filename))...)
if _, err := fs.Stat(newFilePath); err == nil {
return &NFSStatusError{NFSStatusExist, os.ErrExist}
}
parent, err := fs.Stat(fs.Join(path...))
if err != nil {
return &NFSStatusError{NFSStatusAccess, err}
} else if !parent.IsDir() {
return &NFSStatusError{NFSStatusNotDir, nil}
}
fp := userHandle.ToHandle(fs, append(path, string(obj.Filename)))

switch nfs_ftype(ftype) {
case FTYPE_NF3CHR:
case FTYPE_NF3BLK:
// read devicedata3 = {sattr3, specdata3}
attrs, err := ReadSetFileAttributes(w.req.Body)
if err != nil {
return &NFSStatusError{NFSStatusInval, err}
}
specData1, err := xdr.ReadUint32(w.req.Body)
if err != nil {
return &NFSStatusError{NFSStatusInval, err}
}
specData2, err := xdr.ReadUint32(w.req.Body)
if err != nil {
return &NFSStatusError{NFSStatusInval, err}
}

err = cu.Mknod(newFilePath, uint32(attrs.Mode(parent.Mode())), specData1, specData2)
if err != nil {
return &NFSStatusError{NFSStatusAccess, err}
}
if err = attrs.Apply(cu, fs, newFilePath); err != nil {
return &NFSStatusError{NFSStatusServerFault, err}
}

case FTYPE_NF3SOCK:
// read sattr3
attrs, err := ReadSetFileAttributes(w.req.Body)
if err != nil {
return &NFSStatusError{NFSStatusInval, err}
}
if err := cu.Socket(newFilePath); err != nil {
return &NFSStatusError{NFSStatusAccess, err}
}
if err = attrs.Apply(cu, fs, newFilePath); err != nil {
return &NFSStatusError{NFSStatusServerFault, err}
}

case FTYPE_NF3FIFO:
// read sattr3
attrs, err := ReadSetFileAttributes(w.req.Body)
if err != nil {
return &NFSStatusError{NFSStatusInval, err}
}
err = cu.Mkfifo(newFilePath, uint32(attrs.Mode(parent.Mode())))
if err != nil {
return &NFSStatusError{NFSStatusAccess, err}
}
if err = attrs.Apply(cu, fs, newFilePath); err != nil {
return &NFSStatusError{NFSStatusServerFault, err}
}

default:
return &NFSStatusError{NFSStatusBadType, os.ErrInvalid}
// end of input.
}

writer := bytes.NewBuffer([]byte{})
if err := xdr.Write(writer, uint32(NFSStatusOk)); err != nil {
return &NFSStatusError{NFSStatusServerFault, err}
}

// "handle follows"
if err := xdr.Write(writer, uint32(1)); err != nil {
return &NFSStatusError{NFSStatusServerFault, err}
}
// fh3
if err := xdr.Write(writer, fp); err != nil {
return &NFSStatusError{NFSStatusServerFault, err}
}
// attr
if err := WritePostOpAttrs(writer, tryStat(fs, append(path, string(obj.Filename)))); err != nil {
return &NFSStatusError{NFSStatusServerFault, err}
}
// wcc
if err := WriteWcc(writer, nil, tryStat(fs, path)); err != nil {
return &NFSStatusError{NFSStatusServerFault, err}
}

if err := w.Write(writer.Bytes()); err != nil {
return &NFSStatusError{NFSStatusServerFault, err}
}

return nil
}

0 comments on commit a91b8b3

Please sign in to comment.