From a831e251d240f338c19425ac5f776ab12d30d0f5 Mon Sep 17 00:00:00 2001 From: Kenneth Stuart Date: Mon, 6 Sep 2021 16:40:09 +0100 Subject: [PATCH 1/7] initial support for file mode and modification time Provides the ability to add/update file mode and modification time on an MFS File, and update the modification time when the MFS File is written to or truncated. --- file.go | 92 +++++++++++++++++++++++++++++++ mfs_test.go | 153 ++++++++++++++++++++++++++++++++++++++++++++++++++++ ops.go | 29 ++++++++++ 3 files changed, 274 insertions(+) diff --git a/file.go b/file.go index 2a2789a..78b091e 100644 --- a/file.go +++ b/file.go @@ -3,7 +3,9 @@ package mfs import ( "context" "fmt" + "os" "sync" + "time" dag "github.com/ipfs/go-merkledag" ft "github.com/ipfs/go-unixfs" @@ -177,3 +179,93 @@ func (fi *File) Sync() error { func (fi *File) Type() NodeType { return TFile } + +func (fi *File) Mode() (os.FileMode, error) { + fi.nodeLock.RLock() + defer fi.nodeLock.RUnlock() + + nd, err := fi.GetNode() + if err == nil { + fsn, err := ft.ExtractFSNode(nd) + if err == nil { + return fsn.Mode() & 0xFFF, nil + } + } + + return 0, err +} + +func (fi *File) SetMode(mode os.FileMode) error { + nd, err := fi.GetNode() + if err != nil { + return err + } + + fsn, err := ft.ExtractFSNode(nd) + if err != nil { + return err + } + + mode = (fsn.Mode() & 0xFFFFF000) | (mode & 0xFFF) + fsn.SetMode(mode) + + data, err := fsn.GetBytes() + if err != nil { + return err + } + + return fi.setNodeData(data) +} + +// ModTime returns the files' last modification time +func (fi *File) ModTime() (time.Time, error) { + fi.nodeLock.RLock() + defer fi.nodeLock.RUnlock() + + nd, err := fi.GetNode() + if err == nil { + fsn, err := ft.ExtractFSNode(nd) + if err == nil { + return fsn.ModTime(), nil + } + } + + return time.Time{}, err +} + +// SetModTime sets the files' last modification time +func (fi *File) SetModTime(ts time.Time) error { + nd, err := fi.GetNode() + if err != nil { + return err + } + + fsn, err := ft.ExtractFSNode(nd) + if err != nil { + return err + } + + fsn.SetModTime(ts) + data, err := fsn.GetBytes() + if err != nil { + return err + } + + return fi.setNodeData(data) +} + +func (fi *File) setNodeData(data []byte) error { + nd := dag.NodeWithData(data) + err := fi.inode.dagService.Add(context.TODO(), nd) + if err != nil { + return err + } + + fi.nodeLock.Lock() + fi.node = nd + parent := fi.inode.parent + name := fi.inode.name + fi.nodeLock.Unlock() + + return parent.updateChildEntry(child{name, fi.node}) +} diff --git a/mfs_test.go b/mfs_test.go index 1ea90ef..89c0360 100644 --- a/mfs_test.go +++ b/mfs_test.go @@ -512,6 +512,18 @@ func TestMfsFile(t *testing.T) { t.Fatal("some is seriously wrong here") } + if m, err := fi.Mode(); err != nil { + t.Fatal("failed to get file mode: ", err) + } else if m != 0 { + t.Fatal("mode should not be set on a new file") + } + + if ts, err := fi.ModTime(); err != nil { + t.Fatal("failed to get file mtime: ", err) + } else if !ts.IsZero() { + t.Fatal("modification time should not be set on a new file") + } + wfd, err := fi.Open(Flags{Read: true, Write: true, Sync: true}) if err != nil { t.Fatal(err) @@ -613,6 +625,12 @@ func TestMfsFile(t *testing.T) { t.Fatal(err) } + if ts, err := fi.ModTime(); err != nil { + t.Fatal("failed to get file mtime: ", err) + } else if !ts.IsZero() { + t.Fatal("file with unset modification time should not update modification time") + } + // make sure we can get node. TODO: verify it later _, err = fi.GetNode() if err != nil { @@ -620,6 +638,141 @@ func TestMfsFile(t *testing.T) { } } +func TestMfsModeAndModTime(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + ds, rt := setupRoot(ctx, t) + rootdir := rt.GetDirectory() + nd := getRandFile(t, ds, 1000) + + err := rootdir.AddChild("file", nd) + if err != nil { + t.Fatal(err) + } + + fsn, err := rootdir.Child("file") + if err != nil { + t.Fatal(err) + } + + fi := fsn.(*File) + + if fi.Type() != TFile { + t.Fatal("some is seriously wrong here") + } + + var mode os.FileMode + ts, ts2 := time.Now(), time.Time{} + + // can set mode + if err = fi.SetMode(644); err == nil { + if mode, err = fi.Mode(); mode != 644 { + t.Fatal("failed to get correct mode of file") + } + } + if err != nil { + t.Fatal("failed to check file mode: ", err) + } + + // can set last modification time + if err = fi.SetModTime(ts); err == nil { + if ts2, err = fi.ModTime(); !ts2.Equal(ts) { + t.Fatal("failed to get correct modification time of file") + } + } + if err != nil { + t.Fatal("failed to check file modification time: ", err) + } + + // test modification time update after write (on closing file) + wfd, err := fi.Open(Flags{Read: false, Write: true, Sync: true}) + if err != nil { + t.Fatal(err) + } + _, err = wfd.Write([]byte("test")) + if err != nil { + t.Fatal(err) + } + err = wfd.Close() + if err != nil { + t.Fatal(err) + } + ts2, err = fi.ModTime() + if err != nil { + t.Fatal(err) + } + if !ts2.After(ts) { + t.Fatal("modification time should be updated after file write") + } + + // writeAt + ts = ts2 + wfd, err = fi.Open(Flags{Read: false, Write: true, Sync: true}) + if err != nil { + t.Fatal(err) + } + _, err = wfd.WriteAt([]byte("test"), 42) + if err != nil { + t.Fatal(err) + } + err = wfd.Close() + if err != nil { + t.Fatal(err) + } + ts2, err = fi.ModTime() + if err != nil { + t.Fatal(err) + } + if !ts2.After(ts) { + t.Fatal("modification time should be updated after file writeAt") + } + + // truncate (shrink) + ts = ts2 + wfd, err = fi.Open(Flags{Read: false, Write: true, Sync: true}) + if err != nil { + t.Fatal(err) + } + err = wfd.Truncate(100) + if err != nil { + t.Fatal(err) + } + err = wfd.Close() + if err != nil { + t.Fatal(err) + } + ts2, err = fi.ModTime() + if err != nil { + t.Fatal(err) + } + if !ts2.After(ts) { + t.Fatal("modification time should be updated after file truncate (shrink)") + } + + // truncate (expand) + ts = ts2 + wfd, err = fi.Open(Flags{Read: false, Write: true, Sync: true}) + if err != nil { + t.Fatal(err) + } + err = wfd.Truncate(1500) + if err != nil { + t.Fatal(err) + } + err = wfd.Close() + if err != nil { + t.Fatal(err) + } + ts2, err = fi.ModTime() + if err != nil { + t.Fatal(err) + } + if !ts2.After(ts) { + t.Fatal("modification time should be updated after file truncate (expand)") + } +} + func TestMfsDirListNames(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() diff --git a/ops.go b/ops.go index 2b29072..d4dbaab 100644 --- a/ops.go +++ b/ops.go @@ -6,6 +6,7 @@ import ( "os" gopath "path" "strings" + "time" path "github.com/ipfs/go-path" @@ -244,3 +245,31 @@ func FlushPath(ctx context.Context, rt *Root, pth string) (ipld.Node, error) { rt.repub.WaitPub(ctx) return nd.GetNode() } + +func Chmod(rt *Root, pth string, mode os.FileMode) error { + nd, err := Lookup(rt, pth) + if err != nil { + return err + } + + switch n := nd.(type) { + case *File: + return n.SetMode(mode) + default: + return fmt.Errorf("unsupported UnixFs node type") + } +} + +func Touch(rt *Root, pth string, ts time.Time) error { + nd, err := Lookup(rt, pth) + if err != nil { + return err + } + + switch n := nd.(type) { + case *File: + return n.SetModTime(ts) + default: + return fmt.Errorf("unsupported UnixFs node type") + } +} From 73610cbfb0082bee0befd53a9a7cd2725e507dda Mon Sep 17 00:00:00 2001 From: Kenneth Stuart Date: Wed, 22 Jun 2022 14:36:07 +0100 Subject: [PATCH 2/7] [file] don't mask mode before calling SetMode --- file.go | 2 -- mfs_test.go | 4 ++-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/file.go b/file.go index 78b091e..15f12f6 100644 --- a/file.go +++ b/file.go @@ -206,9 +206,7 @@ func (fi *File) SetMode(mode os.FileMode) error { return err } - mode = (fsn.Mode() & 0xFFFFF000) | (mode & 0xFFF) fsn.SetMode(mode) - data, err := fsn.GetBytes() if err != nil { return err diff --git a/mfs_test.go b/mfs_test.go index 89c0360..3dec671 100644 --- a/mfs_test.go +++ b/mfs_test.go @@ -666,8 +666,8 @@ func TestMfsModeAndModTime(t *testing.T) { ts, ts2 := time.Now(), time.Time{} // can set mode - if err = fi.SetMode(644); err == nil { - if mode, err = fi.Mode(); mode != 644 { + if err = fi.SetMode(0644); err == nil { + if mode, err = fi.Mode(); mode != 0644 { t.Fatal("failed to get correct mode of file") } } From 83ee2184def5a23f2fbf8398c30019c773074033 Mon Sep 17 00:00:00 2001 From: Kenneth Stuart Date: Wed, 22 Jun 2022 14:42:40 +0100 Subject: [PATCH 3/7] [mkdir] support setting mode and mtime --- dir.go | 21 ++++++++------------- ops.go | 4 +++- 2 files changed, 11 insertions(+), 14 deletions(-) diff --git a/dir.go b/dir.go index 52b1b04..5295941 100644 --- a/dir.go +++ b/dir.go @@ -4,14 +4,12 @@ import ( "context" "errors" "fmt" - "os" - "path" - "sync" - "time" - dag "github.com/ipfs/go-merkledag" ft "github.com/ipfs/go-unixfs" uio "github.com/ipfs/go-unixfs/io" + "os" + "path" + "sync" cid "github.com/ipfs/go-cid" ipld "github.com/ipfs/go-ipld-format" @@ -39,8 +37,6 @@ type Directory struct { // UnixFS directory implementation used for creating, // reading and editing directories. unixfsDir uio.Directory - - modTime time.Time } // NewDirectory constructs a new MFS directory. @@ -62,7 +58,6 @@ func NewDirectory(ctx context.Context, name string, node ipld.Node, parent paren ctx: ctx, unixfsDir: db, entriesCache: make(map[string]FSNode), - modTime: time.Now(), }, nil } @@ -132,9 +127,6 @@ func (d *Directory) updateChild(c child) error { if err != nil { return err } - - d.modTime = time.Now() - return nil } @@ -290,6 +282,10 @@ func (d *Directory) ForEachEntry(ctx context.Context, f func(NodeListing) error) } func (d *Directory) Mkdir(name string) (*Directory, error) { + return d.MkdirWithOpts(name, MkdirOpts{}) +} + +func (d *Directory) MkdirWithOpts(name string, opts MkdirOpts) (*Directory, error) { d.lock.Lock() defer d.lock.Unlock() @@ -305,7 +301,7 @@ func (d *Directory) Mkdir(name string) (*Directory, error) { } } - ndir := ft.EmptyDirNode() + ndir := ft.EmptyDirNodeWithStat(opts.Mode, opts.ModTime) ndir.SetCidBuilder(d.GetCidBuilder()) err = d.dagService.Add(d.ctx, ndir) @@ -365,7 +361,6 @@ func (d *Directory) AddChild(name string, nd ipld.Node) error { return err } - d.modTime = time.Now() return nil } diff --git a/ops.go b/ops.go index d4dbaab..0080fc6 100644 --- a/ops.go +++ b/ops.go @@ -124,6 +124,8 @@ type MkdirOpts struct { Mkparents bool Flush bool CidBuilder cid.Builder + Mode os.FileMode + ModTime time.Time } // Mkdir creates a directory at 'path' under the directory 'd', creating @@ -173,7 +175,7 @@ func Mkdir(r *Root, pth string, opts MkdirOpts) error { cur = next } - final, err := cur.Mkdir(parts[len(parts)-1]) + final, err := cur.MkdirWithOpts(parts[len(parts)-1], opts) if err != nil { if !opts.Mkparents || err != os.ErrExist || final == nil { return err From 82787b723d181f519df790d4927cd5b29c729cc2 Mon Sep 17 00:00:00 2001 From: Kenneth Stuart Date: Sun, 26 Jun 2022 17:21:43 +0100 Subject: [PATCH 4/7] correct typo --- mfs_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mfs_test.go b/mfs_test.go index 3dec671..d90e666 100644 --- a/mfs_test.go +++ b/mfs_test.go @@ -659,7 +659,7 @@ func TestMfsModeAndModTime(t *testing.T) { fi := fsn.(*File) if fi.Type() != TFile { - t.Fatal("some is seriously wrong here") + t.Fatal("something is seriously wrong here") } var mode os.FileMode From fe8cfdd8d46277434597c83554cba72e88eecde4 Mon Sep 17 00:00:00 2001 From: Kenneth Stuart Date: Tue, 25 Oct 2022 17:00:21 +0100 Subject: [PATCH 5/7] defer node unlock --- file.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/file.go b/file.go index 15f12f6..c3ea9a5 100644 --- a/file.go +++ b/file.go @@ -260,10 +260,10 @@ func (fi *File) setNodeData(data []byte) error { } fi.nodeLock.Lock() + defer fi.nodeLock.Unlock() fi.node = nd parent := fi.inode.parent name := fi.inode.name - fi.nodeLock.Unlock() return parent.updateChildEntry(child{name, fi.node}) } From b6b9c8bde007bfea3019ba7d30036ef2675cf4f8 Mon Sep 17 00:00:00 2001 From: Kenneth Stuart Date: Tue, 25 Oct 2022 17:02:43 +0100 Subject: [PATCH 6/7] abstract setting mode and modtime --- ops.go | 14 ++------------ root.go | 3 +++ 2 files changed, 5 insertions(+), 12 deletions(-) diff --git a/ops.go b/ops.go index 0080fc6..60b8579 100644 --- a/ops.go +++ b/ops.go @@ -254,12 +254,7 @@ func Chmod(rt *Root, pth string, mode os.FileMode) error { return err } - switch n := nd.(type) { - case *File: - return n.SetMode(mode) - default: - return fmt.Errorf("unsupported UnixFs node type") - } + return nd.SetMode(mode) } func Touch(rt *Root, pth string, ts time.Time) error { @@ -268,10 +263,5 @@ func Touch(rt *Root, pth string, ts time.Time) error { return err } - switch n := nd.(type) { - case *File: - return n.SetModTime(ts) - default: - return fmt.Errorf("unsupported UnixFs node type") - } + return nd.SetModTime(ts) } diff --git a/root.go b/root.go index 026a320..79d10c4 100644 --- a/root.go +++ b/root.go @@ -7,6 +7,7 @@ import ( "context" "errors" "fmt" + "os" "time" dag "github.com/ipfs/go-merkledag" @@ -71,6 +72,8 @@ type FSNode interface { Flush() error Type() NodeType + SetModTime(ts time.Time) error + SetMode(mode os.FileMode) error } // IsDir checks whether the FSNode is dir type From e9330ff61f0702b53ca920f29ca7431ea73d052b Mon Sep 17 00:00:00 2001 From: Kenneth Stuart Date: Tue, 25 Oct 2022 17:03:53 +0100 Subject: [PATCH 7/7] support setting mode and modtime on directories --- dir.go | 66 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 66 insertions(+) diff --git a/dir.go b/dir.go index 5295941..df99db4 100644 --- a/dir.go +++ b/dir.go @@ -10,6 +10,7 @@ import ( "os" "path" "sync" + "time" cid "github.com/ipfs/go-cid" ipld "github.com/ipfs/go-ipld-format" @@ -420,3 +421,68 @@ func (d *Directory) GetNode() (ipld.Node, error) { return nd.Copy(), err } + +func (d *Directory) SetMode(mode os.FileMode) error { + nd, err := d.GetNode() + if err != nil { + return err + } + + fsn, err := ft.ExtractFSNode(nd) + if err != nil { + return err + } + + fsn.SetMode(mode) + data, err := fsn.GetBytes() + if err != nil { + return err + } + + return d.setNodeData(data, nd.Links()) +} + +func (d *Directory) SetModTime(ts time.Time) error { + nd, err := d.GetNode() + if err != nil { + return err + } + + fsn, err := ft.ExtractFSNode(nd) + if err != nil { + return err + } + + fsn.SetModTime(ts) + data, err := fsn.GetBytes() + if err != nil { + return err + } + + return d.setNodeData(data, nd.Links()) +} + +func (d *Directory) setNodeData(data []byte, links []*ipld.Link) error { + nd := dag.NodeWithData(data) + nd.SetLinks(links) + + err := d.dagService.Add(d.ctx, nd) + if err != nil { + return err + } + + err = d.parent.updateChildEntry(child{d.name, nd}) + if err != nil { + return err + } + + d.lock.Lock() + defer d.lock.Unlock() + db, err := uio.NewDirectoryFromNode(d.dagService, nd) + if err != nil { + return err + } + d.unixfsDir = db + + return nil +}