Browse Source

package amntfs

ZRY 1 month ago
parent
commit
6c61beb171
10 changed files with 478 additions and 0 deletions
  1. 5 0
      amntfs/README.md
  2. 161 0
      amntfs/afs_impl.go
  3. 97 0
      amntfs/error.go
  4. 14 0
      amntfs/go.mod
  5. 10 0
      amntfs/go.sum
  6. 76 0
      amntfs/mnt_fs.go
  7. 46 0
      amntfs/mnt_node.go
  8. 64 0
      go.work.sum
  9. 1 0
      mountree/payload_intf.go
  10. 4 0
      testing/mountree_test/unix_mountree_test.go

+ 5 - 0
amntfs/README.md

@@ -0,0 +1,5 @@
+# amntfs
+
+Based on `github.com/spf13/afero` `afero.Fs`.
+
+A mountable filesystem that can mount many `afero.Fs` to different mount points.

+ 161 - 0
amntfs/afs_impl.go

@@ -0,0 +1,161 @@
+package amntfs
+
+import (
+	"errors"
+	"fmt"
+	"github.com/spf13/afero"
+	"os"
+	"time"
+)
+
+func (fs *AMNTFS) Create(name string) (afero.File, error) {
+	f, r := fs.getMountEntity(name)
+	if f == nil {
+		return nil, NewAMNTFSError(
+			ErrNoAvailableMountPointForThisPath,
+			fmt.Errorf("path '%' not belonging to any mounted fs", name),
+		)
+	}
+	file, err := f.GetFs().Create(r)
+	if err != nil {
+		return nil, NewAMNTFSChildFsError(f, err)
+	}
+	return file, nil
+}
+
+func (fs *AMNTFS) Mkdir(name string, perm os.FileMode) error {
+	f, r := fs.getMountEntity(name)
+	if f == nil {
+		return NewAMNTFSError(
+			ErrNoAvailableMountPointForThisPath,
+			fmt.Errorf("path '%' not belonging to any mounted fs", name),
+		)
+	}
+	return f.GetFs().Mkdir(r, perm)
+}
+
+func (fs *AMNTFS) MkdirAll(path string, perm os.FileMode) error {
+	f, r := fs.getMountEntity(path)
+	if f == nil {
+		return NewAMNTFSError(
+			ErrNoAvailableMountPointForThisPath,
+			fmt.Errorf("path '%' not belonging to any mounted fs", path),
+		)
+	}
+	return f.GetFs().MkdirAll(r, perm)
+}
+
+func (fs *AMNTFS) Open(name string) (afero.File, error) {
+	f, r := fs.getMountEntity(name)
+	if f == nil {
+		return nil, NewAMNTFSError(
+			ErrNoAvailableMountPointForThisPath,
+			fmt.Errorf("path '%' not belonging to any mounted fs", name),
+		)
+	}
+	file, err := f.GetFs().Open(r)
+	if err != nil {
+		return nil, NewAMNTFSChildFsError(f, err)
+	}
+	return file, nil
+}
+
+func (fs *AMNTFS) OpenFile(name string, flag int, perm os.FileMode) (afero.File, error) {
+	f, r := fs.getMountEntity(name)
+	if f == nil {
+		return nil, NewAMNTFSError(
+			ErrNoAvailableMountPointForThisPath,
+			fmt.Errorf("path '%' not belonging to any mounted fs", name),
+		)
+	}
+	file, err := f.GetFs().OpenFile(r, flag, perm)
+	if err != nil {
+		return nil, NewAMNTFSChildFsError(f, err)
+	}
+	return file, nil
+}
+
+func (fs *AMNTFS) Remove(name string) error {
+	f, r := fs.getMountEntity(name)
+	if f == nil {
+		return NewAMNTFSError(
+			ErrNoAvailableMountPointForThisPath,
+			fmt.Errorf("path '%' not belonging to any mounted fs", name),
+		)
+	}
+	return f.GetFs().Remove(r)
+}
+
+func (fs *AMNTFS) RemoveAll(path string) error {
+	f, r := fs.getMountEntity(path)
+	if f == nil {
+		return NewAMNTFSError(
+			ErrNoAvailableMountPointForThisPath,
+			fmt.Errorf("path '%' not belonging to any mounted fs", path),
+		)
+	}
+	return f.GetFs().RemoveAll(r)
+}
+
+func (fs *AMNTFS) Rename(oldname, newname string) error {
+	oldF, oldR := fs.getMountEntity(oldname)
+	newF, newR := fs.getMountEntity(newname)
+	if oldF == nil || newF == nil {
+		return NewAMNTFSError(
+			ErrNoAvailableMountPointForThisPath,
+			fmt.Errorf("path '%' or '%' not belonging to any mounted fs", oldname, newname),
+		)
+	}
+	if oldF != newF {
+		return errors.New("rename across different file systems is not supported")
+	}
+	return oldF.GetFs().Rename(oldR, newR)
+}
+
+func (fs *AMNTFS) Stat(name string) (os.FileInfo, error) {
+	f, r := fs.getMountEntity(name)
+	if f == nil {
+		return nil, NewAMNTFSError(
+			ErrNoAvailableMountPointForThisPath,
+			fmt.Errorf("path '%' not belonging to any mounted fs", name),
+		)
+	}
+	return f.GetFs().Stat(r)
+}
+
+func (fs *AMNTFS) Name() string {
+	return "AMNTFS"
+}
+
+func (fs *AMNTFS) Chmod(name string, mode os.FileMode) error {
+	f, r := fs.getMountEntity(name)
+	if f == nil {
+		return NewAMNTFSError(
+			ErrNoAvailableMountPointForThisPath,
+			fmt.Errorf("path '%' not belonging to any mounted fs", name),
+		)
+	}
+	return f.GetFs().Chmod(r, mode)
+}
+
+func (fs *AMNTFS) Chown(name string, uid, gid int) error {
+	f, r := fs.getMountEntity(name)
+	if f == nil {
+		return NewAMNTFSError(
+			ErrNoAvailableMountPointForThisPath,
+			fmt.Errorf("path '%' not belonging to any mounted fs", name),
+		)
+	}
+	return f.GetFs().Chown(r, uid, gid)
+}
+
+func (fs *AMNTFS) Chtimes(name string, atime time.Time, mtime time.Time) error {
+	f, r := fs.getMountEntity(name)
+	if f == nil {
+		return NewAMNTFSError(
+			ErrNoAvailableMountPointForThisPath,
+			fmt.Errorf("path '%' not belonging to any mounted fs", name),
+		)
+	}
+	return f.GetFs().Chtimes(r, atime, mtime)
+}

+ 97 - 0
amntfs/error.go

@@ -0,0 +1,97 @@
+package amntfs
+
+import "fmt"
+
+type AMNTFSErrNo uint8
+
+const (
+	ErrChildFsError                     AMNTFSErrNo = iota
+	ErrUnknownInternalError             AMNTFSErrNo = iota
+	ErrMountPointAlreadyMounted         AMNTFSErrNo = iota
+	ErrNoAvailableMountPointForThisPath AMNTFSErrNo = iota
+)
+
+func (e AMNTFSErrNo) Message() string {
+	switch e {
+	case ErrMountPointAlreadyMounted:
+		return "mount point already mounted"
+	case ErrUnknownInternalError:
+		return "unknown internal error"
+	case ErrNoAvailableMountPointForThisPath:
+		return "no available mount point for this path"
+	case ErrChildFsError:
+		return "child filesystem error"
+	default:
+		return "unknown error"
+	}
+}
+
+var _ error = (*AMNTFSError)(nil)
+
+type AMNTFSError struct {
+	etype   AMNTFSErrNo
+	fstype  string
+	mountid int64
+	rawErr  error
+}
+
+func (e AMNTFSError) Error() string {
+	if e.etype == ErrChildFsError {
+		return fmt.Sprintf("%s [mid=0x%08X]: %s", e.fstype, e.mountid, e.rawErr.Error())
+	}
+	if e.rawErr == nil {
+		return fmt.Sprintf("amntfs: %s", e.etype.Message())
+	} else {
+		return fmt.Sprintf("amntfs: %s: %s", e.etype.Message(), e.rawErr.Error())
+	}
+}
+
+func (e AMNTFSError) GetRawError() error {
+	return e.rawErr
+}
+
+func (e AMNTFSError) GetErrNo() AMNTFSErrNo {
+	return e.etype
+}
+
+func (e AMNTFSError) GetFsType() string {
+	return e.fstype
+}
+
+func (e AMNTFSError) GetMountId() int64 {
+	return e.mountid
+}
+
+func (e AMNTFSError) GetFsDescription() string {
+	return fmt.Sprintf("%s (mid=0x%08X)", e.fstype, e.mountid)
+}
+
+func NewAMNTFSError(emsg AMNTFSErrNo, rawErr error) *AMNTFSError {
+	return &AMNTFSError{
+		etype:   emsg,
+		rawErr:  rawErr,
+		fstype:  "",
+		mountid: 0,
+	}
+}
+
+func NewAMNTFSChildFsError(fsent *MountNode, rawErr error) *AMNTFSError {
+	return &AMNTFSError{
+		etype:   ErrChildFsError,
+		rawErr:  rawErr,
+		fstype:  fsent.FsType(),
+		mountid: fsent.MountId(),
+	}
+}
+
+func CheckErrorType(err error, errNo AMNTFSErrNo) bool {
+	if err == nil {
+		return false
+	}
+	switch err.(type) {
+	case AMNTFSError:
+		return err.(AMNTFSError).GetErrNo() == errNo
+	default:
+		return false
+	}
+}

+ 14 - 0
amntfs/go.mod

@@ -0,0 +1,14 @@
+module git.swzry.com/ProjectNagae/FsUtils/amntfs
+
+go 1.21
+
+require (
+	git.swzry.com/ProjectNagae/FsUtils/mountree v0.0.0-20240222142738-b0d888c10cdc
+	github.com/GUAIK-ORG/go-snowflake v0.0.0-20200116064823-220c4260e85f
+	github.com/spf13/afero v1.11.0
+)
+
+require (
+	github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b // indirect
+	golang.org/x/text v0.14.0 // indirect
+)

+ 10 - 0
amntfs/go.sum

@@ -0,0 +1,10 @@
+git.swzry.com/ProjectNagae/FsUtils/mountree v0.0.0-20240222142738-b0d888c10cdc h1:mbAAFQ6zsEUkjDfl4urAi1YsjWJiez8m8gulQ5Ksfc4=
+git.swzry.com/ProjectNagae/FsUtils/mountree v0.0.0-20240222142738-b0d888c10cdc/go.mod h1:k7qCoLuuMmOdJIuW7qAqu3j65CtKmPKga88WZ0EC8rc=
+github.com/GUAIK-ORG/go-snowflake v0.0.0-20200116064823-220c4260e85f h1:RDkg3pyE1qGbBpRWmvSN9RNZC5nUrOaEPiEpEb8y2f0=
+github.com/GUAIK-ORG/go-snowflake v0.0.0-20200116064823-220c4260e85f/go.mod h1:zA7AF9RTfpluCfz0omI4t5KCMaWHUMicsZoMccnaT44=
+github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58=
+github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
+github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8=
+github.com/spf13/afero v1.11.0/go.mod h1:GH9Y3pIexgf1MTIWtNGyogA5MwRIDXGUr+hbWNoBjkY=
+golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
+golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=

+ 76 - 0
amntfs/mnt_fs.go

@@ -0,0 +1,76 @@
+package amntfs
+
+import (
+	"git.swzry.com/ProjectNagae/FsUtils/mountree"
+	"github.com/GUAIK-ORG/go-snowflake/snowflake"
+	"github.com/spf13/afero"
+)
+
+var _ afero.Fs = (*AMNTFS)(nil)
+
+type AMNTFS struct {
+	mountTree  *mountree.SimpleUnixLikePathTree[*MountNode]
+	snowflaker *snowflake.Snowflake
+}
+
+func NewAMNTFS() *AMNTFS {
+	sn, err := snowflake.NewSnowflake(0, 0)
+	if err != nil {
+		panic(err)
+	}
+	fs := &AMNTFS{
+		mountTree:  mountree.NewSimpleUnixLikePathTree[*MountNode](),
+		snowflaker: sn,
+	}
+	return fs
+}
+
+func (fs *AMNTFS) Mount(path string, childFs afero.Fs, forceRemountExists bool) error {
+	node := &MountNode{
+		mountId:        fs.snowflaker.NextVal(),
+		mountPoint:     path,
+		filesystemType: childFs.Name(),
+		filesystem:     childFs,
+	}
+	err := fs.mountTree.Mount(path, node, forceRemountExists)
+	if err != nil {
+		if mountree.CheckErrorType(err, mountree.ErrMountPointAlreadyExists) {
+			return NewAMNTFSError(ErrMountPointAlreadyMounted, nil)
+		} else {
+			return NewAMNTFSError(ErrUnknownInternalError, err)
+		}
+	}
+	return nil
+}
+
+func (fs *AMNTFS) Umount(path string) error {
+	err := fs.mountTree.Umount(path)
+	if err != nil {
+		if mountree.CheckErrorType(err, mountree.ErrMountPointNotExists) {
+			return NewAMNTFSError(ErrMountPointAlreadyMounted, nil)
+		} else {
+			return NewAMNTFSError(ErrUnknownInternalError, err)
+		}
+	}
+	return nil
+}
+
+func (fs *AMNTFS) ListAllMount() []MountInfoEntity {
+	mil := make([]MountInfoEntity, 0)
+	fs.mountTree.ListAllMount(func(path string, payload *MountNode) {
+		mil = append(mil, MountInfoEntity{
+			MountId:    payload.MountId(),
+			MountPoint: payload.MountPoint(),
+			FsType:     payload.FsType(),
+		})
+	})
+	return mil
+}
+
+func (fs *AMNTFS) getMountEntity(path string) (*MountNode, string) {
+	p, remain, err := fs.mountTree.GetPayload(path)
+	if err != nil {
+		return nil, ""
+	}
+	return *p, remain
+}

+ 46 - 0
amntfs/mnt_node.go

@@ -0,0 +1,46 @@
+package amntfs
+
+import (
+	"fmt"
+	"git.swzry.com/ProjectNagae/FsUtils/mountree"
+	"github.com/spf13/afero"
+)
+
+var _ mountree.PayloadType = (*MountNode)(nil)
+
+type MountNode struct {
+	mountId        int64
+	mountPoint     string
+	filesystemType string
+	filesystem     afero.Fs
+}
+
+func (m MountNode) MountId() int64 {
+	return m.mountId
+}
+
+func (m MountNode) MountPoint() string {
+	return m.mountPoint
+}
+
+func (m MountNode) FsType() string {
+	return m.filesystemType
+}
+
+func (m MountNode) Name() string {
+	return fmt.Sprintf("%08X", m.mountId)
+}
+
+func (m MountNode) Description() string {
+	return fmt.Sprintf("%s[MID=%08X]@'%s'", m.filesystemType, m.mountId, m.mountPoint)
+}
+
+func (m MountNode) GetFs() afero.Fs {
+	return m.filesystem
+}
+
+type MountInfoEntity struct {
+	MountId    int64
+	MountPoint string
+	FsType     string
+}

+ 64 - 0
go.work.sum

@@ -0,0 +1,64 @@
+cloud.google.com/go v0.110.10 h1:LXy9GEO+timppncPIAZoOj3l58LIU9k+kn48AN7IO3Y=
+cloud.google.com/go v0.110.10/go.mod h1:v1OoFqYxiBkUrruItNM3eT4lLByNjxmJSV/xDKJNnic=
+cloud.google.com/go/compute v1.23.3 h1:6sVlXXBmbd7jNX0Ipq0trII3e4n1/MsADLK6a+aiVlk=
+cloud.google.com/go/compute v1.23.3/go.mod h1:VCgBUoMnIVIR0CscqQiPJLAG25E3ZRZMzcFZeQ+h8CI=
+cloud.google.com/go/compute/metadata v0.2.3 h1:mg4jlk7mCAj6xXp9UJ4fjI9VUI5rubuGBW5aJ7UnBMY=
+cloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA=
+cloud.google.com/go/iam v1.1.5 h1:1jTsCu4bcsNsE4iiqNT5SHwrDRCfRmIaaaVFhRveTJI=
+cloud.google.com/go/iam v1.1.5/go.mod h1:rB6P/Ic3mykPbFio+vo7403drjlgvoWfYpJhMXEbzv8=
+cloud.google.com/go/storage v1.35.1 h1:B59ahL//eDfx2IIKFBeT5Atm9wnNmj3+8xG/W4WB//w=
+cloud.google.com/go/storage v1.35.1/go.mod h1:M6M/3V/D3KpzMTJyPOR/HU6n2Si5QdaXYEsng2xgOs8=
+github.com/golang-infrastructure/go-if-expression v0.0.3-0.20221205164245-e3b4693b143e/go.mod h1:U4ZtU29/RtZ/cM0tAdBzFaFxENFShSFj+jCRFRE/cv4=
+github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE=
+github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
+github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
+github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
+github.com/google/s2a-go v0.1.7 h1:60BLSyTrOV4/haCDW4zb1guZItoSq8foHCXrAnjBo/o=
+github.com/google/s2a-go v0.1.7/go.mod h1:50CgR4k1jNlWBu4UfS4AcfhVe1r6pdZPygJ3R8F0Qdw=
+github.com/google/uuid v1.4.0 h1:MtMxsa51/r9yyhkyLsVeVt0B+BGQZzpQiTQ4eHZ8bc4=
+github.com/google/uuid v1.4.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
+github.com/googleapis/enterprise-certificate-proxy v0.3.2 h1:Vie5ybvEvT75RniqhfFxPRy3Bf7vr3h0cechB90XaQs=
+github.com/googleapis/enterprise-certificate-proxy v0.3.2/go.mod h1:VLSiSSBs/ksPL8kq3OBOQ6WRI2QnaFynd1DCjZ62+V0=
+github.com/googleapis/gax-go/v2 v2.12.0 h1:A+gCJKdRfqXkr+BIRGtZLibNXf0m1f9E4HG56etFpas=
+github.com/googleapis/gax-go/v2 v2.12.0/go.mod h1:y+aIqrI5eb1YGMVJfuV3185Ts/D7qKpsEkdD5+I6QGU=
+github.com/googleapis/google-cloud-go-testing v0.0.0-20210719221736-1c9a4c676720 h1:zC34cGQu69FG7qzJ3WiKW244WfhDC3xxYMeNOX2gtUQ=
+github.com/googleapis/google-cloud-go-testing v0.0.0-20210719221736-1c9a4c676720/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g=
+github.com/kr/fs v0.1.0 h1:Jskdu9ieNAYnjxsi0LbQp1ulIKZV1LAFgK1tWhpZgl8=
+github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=
+github.com/pkg/sftp v1.13.6 h1:JFZT4XbOU7l77xGSpOdW+pwIMqP044IyjXX6FGyEKFo=
+github.com/pkg/sftp v1.13.6/go.mod h1:tz1ryNURKu77RL+GuCzmoJYxQczL3wLNNpPWagdg4Qk=
+github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
+go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0=
+go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo=
+golang.org/x/crypto v0.16.0 h1:mMMrFzRSCF0GvB7Ne27XVtVAaXLrPmgPC7/v0tkwHaY=
+golang.org/x/crypto v0.16.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4=
+golang.org/x/mod v0.8.0 h1:LUYupSeNrTNCGzR/hVBk2NHZO4hXcVaW1k4Qx7rjPx8=
+golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
+golang.org/x/net v0.19.0 h1:zTwKpTd2XuCqf8huc7Fo2iSy+4RHPd10s4KzeTnVr1c=
+golang.org/x/net v0.19.0/go.mod h1:CfAk/cbD4CthTvqiEl8NpboMuiuOYsAr/7NOjZJtv1U=
+golang.org/x/oauth2 v0.15.0 h1:s8pnnxNVzjWyrvYdFUQq5llS1PX2zhPXmccZv99h7uQ=
+golang.org/x/oauth2 v0.15.0/go.mod h1:q48ptWNTY5XWf+JNten23lcvHpLJ0ZSxF5ttTHKVCAM=
+golang.org/x/sync v0.5.0 h1:60k92dhOjHxJkrqnwsfl8KuaHbn/5dl0lUPUklKo3qE=
+golang.org/x/sync v0.5.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
+golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc=
+golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
+golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
+golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
+golang.org/x/tools v0.6.0 h1:BOw41kyTf3PuCW1pVQf8+Cyg8pMlkYB1oo9iJ6D/lKM=
+golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
+golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 h1:H2TDz8ibqkAF6YGhCdN3jS9O0/s90v0rJh3X/OLHEUk=
+golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8=
+google.golang.org/api v0.152.0 h1:t0r1vPnfMc260S2Ci+en7kfCZaLOPs5KI0sVV/6jZrY=
+google.golang.org/api v0.152.0/go.mod h1:3qNJX5eOmhiWYc67jRA/3GsDw97UFb5ivv7Y2PrriAY=
+google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c=
+google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
+google.golang.org/genproto v0.0.0-20231106174013-bbf56f31fb17 h1:wpZ8pe2x1Q3f2KyT5f8oP/fa9rHAKgFPr/HZdNuS+PQ=
+google.golang.org/genproto v0.0.0-20231106174013-bbf56f31fb17/go.mod h1:J7XzRzVy1+IPwWHZUzoD0IccYZIrXILAQpc+Qy9CMhY=
+google.golang.org/genproto/googleapis/api v0.0.0-20231106174013-bbf56f31fb17 h1:JpwMPBpFN3uKhdaekDpiNlImDdkUAyiJ6ez/uxGaUSo=
+google.golang.org/genproto/googleapis/api v0.0.0-20231106174013-bbf56f31fb17/go.mod h1:0xJLfVdJqpAPl8tDg1ujOCGzx6LFLttXT5NhllGOXY4=
+google.golang.org/genproto/googleapis/rpc v0.0.0-20231120223509-83a465c0220f h1:ultW7fxlIvee4HYrtnaRPon9HpEgFk5zYpmfMgtKB5I=
+google.golang.org/genproto/googleapis/rpc v0.0.0-20231120223509-83a465c0220f/go.mod h1:L9KNLi232K1/xB6f7AlSX692koaRnKaWSR0stBki0Yc=
+google.golang.org/grpc v1.59.0 h1:Z5Iec2pjwb+LEOqzpB2MR12/eKFhDPhuqW91O+4bwUk=
+google.golang.org/grpc v1.59.0/go.mod h1:aUPDwccQo6OTjy7Hct4AfBPD1GptF4fyUjIkQ9YtF98=
+google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8=
+google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=

+ 1 - 0
mountree/payload_intf.go

@@ -2,5 +2,6 @@ package mountree
 
 type PayloadType interface {
 	Name() string
+	FsType() string
 	Description() string
 }

+ 4 - 0
testing/mountree_test/unix_mountree_test.go

@@ -24,6 +24,10 @@ func (p *Payload) Name() string {
 	return p.FsName
 }
 
+func (p *Payload) FsType() string {
+	return p.FsName
+}
+
 func (p *Payload) Description() string {
 	return p.FsName
 }