diff --git a/go.mod b/go.mod index f3c36f65aa6f..9750ee86c405 100644 --- a/go.mod +++ b/go.mod @@ -233,7 +233,7 @@ require ( replace github.com/minio/minio v0.0.0-20210206053228-97fe57bba92c => github.com/juicedata/minio v0.0.0-20220613143934-cee0571a1b03 -replace github.com/hanwen/go-fuse/v2 v2.1.1-0.20210611132105-24a1dfe6b4f8 => github.com/juicedata/go-fuse/v2 v2.1.1-0.20220720062817-183cb227b353 +replace github.com/hanwen/go-fuse/v2 v2.1.1-0.20210611132105-24a1dfe6b4f8 => github.com/juicedata/go-fuse/v2 v2.1.1-0.20220822092405-ece777deb343 replace github.com/dgrijalva/jwt-go v3.2.0+incompatible => github.com/golang-jwt/jwt v3.2.1+incompatible diff --git a/go.sum b/go.sum index 6180d4c8aa1a..aa49fa072e23 100644 --- a/go.sum +++ b/go.sum @@ -598,8 +598,8 @@ github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1 github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= -github.com/juicedata/go-fuse/v2 v2.1.1-0.20220720062817-183cb227b353 h1:cIwY+HZQiRQAYoCItVNCkpFDyPahgZzJeNM3rMgZSOE= -github.com/juicedata/go-fuse/v2 v2.1.1-0.20220720062817-183cb227b353/go.mod h1:oRyA5eK+pvJyv5otpO/DgccS8y/RvYMaO00GgRLGryc= +github.com/juicedata/go-fuse/v2 v2.1.1-0.20220822092405-ece777deb343 h1:fbMz7gSlX5eYwuWNB3Z1+dK2q2Ekg5tw6uSYYTOdZrw= +github.com/juicedata/go-fuse/v2 v2.1.1-0.20220822092405-ece777deb343/go.mod h1:oRyA5eK+pvJyv5otpO/DgccS8y/RvYMaO00GgRLGryc= github.com/juicedata/godaemon v0.0.0-20210629045518-3da5144a127d h1:kpQMvNZJKGY3PTt7OSoahYc4nM0HY67SvK0YyS0GLwA= github.com/juicedata/godaemon v0.0.0-20210629045518-3da5144a127d/go.mod h1:dlxKkLh3qAIPtgr2U/RVzsZJDuXA1ffg+Njikfmhvgw= github.com/juicedata/minio v0.0.0-20220613143934-cee0571a1b03 h1:+0jiEkr6qE5Zm2+LilAv5pV/+V+V1XZ5KUv0pmXZHhQ= diff --git a/pkg/fuse/fuse.go b/pkg/fuse/fuse.go index b3ce3fc654fc..2f0336cc1bd3 100644 --- a/pkg/fuse/fuse.go +++ b/pkg/fuse/fuse.go @@ -421,6 +421,13 @@ func (fs *fileSystem) StatFs(cancel <-chan struct{}, in *fuse.InHeader, out *fus return 0 } +func (fs *fileSystem) Ioctl(cancel <-chan struct{}, in *fuse.IoctlIn, out *fuse.IoctlOut, bufIn, bufOut []byte) (status fuse.Status) { + ctx := newContext(cancel, &in.InHeader) + defer releaseContext(ctx) + out.Result = int32(fs.v.Ioctl(ctx, Ino(in.NodeId), in.Cmd, in.Arg, bufIn, bufOut)) + return 0 +} + // Serve starts a server to serve requests from FUSE. func Serve(v *vfs.VFS, options string, xattrs bool) error { if err := syscall.Setpriority(syscall.PRIO_PROCESS, os.Getpid(), -19); err != nil { diff --git a/pkg/meta/base.go b/pkg/meta/base.go index 85439cc5e1d0..5d89d835595f 100644 --- a/pkg/meta/base.go +++ b/pkg/meta/base.go @@ -834,6 +834,19 @@ func (m *baseMeta) Open(ctx Context, inode Ino, flags uint32, attr *Attr) syscal if attr != nil && !attr.Full { err = m.GetAttr(ctx, inode, attr) } + if attr.Flags&FlagImmutable != 0 { + if flags&(syscall.O_WRONLY|syscall.O_RDWR) != 0 { + return syscall.EPERM + } + } + if attr.Flags&FlagAppend != 0 { + if (flags&(syscall.O_WRONLY|syscall.O_RDWR)) != 0 && (flags&syscall.O_APPEND) == 0 { + return syscall.EPERM + } + if flags&syscall.O_TRUNC != 0 { + return syscall.EPERM + } + } if err == 0 { m.of.Open(inode, attr) } diff --git a/pkg/meta/base_test.go b/pkg/meta/base_test.go index 43ae398e4107..0c4055b1132b 100644 --- a/pkg/meta/base_test.go +++ b/pkg/meta/base_test.go @@ -69,6 +69,7 @@ func testMeta(t *testing.T, m Meta) { testCopyFileRange(t, m) testCloseSession(t, m) testConcurrentDir(t, m) + testAttrFlags(t, m) base := m.getBase() base.conf.OpenCache = time.Second base.of.expire = time.Second @@ -1426,3 +1427,160 @@ func testConcurrentDir(t *testing.T, m Meta) { } g.Wait() } + +func testAttrFlags(t *testing.T, m Meta) { + ctx := Background + var attr = &Attr{} + var inode Ino + if st := m.Create(ctx, 1, "f", 0644, 022, 0, &inode, nil); st != 0 { + t.Fatalf("create f: %s", st) + } + attr.Flags = FlagAppend + if st := m.SetAttr(ctx, inode, SetAttrFlag, 0, attr); st != 0 { + t.Fatalf("setattr f: %s", st) + } + if st := m.Open(ctx, inode, syscall.O_WRONLY, attr); st != syscall.EPERM { + t.Fatalf("open f: %s", st) + } + if st := m.Open(ctx, inode, syscall.O_WRONLY|syscall.O_APPEND, attr); st != 0 { + t.Fatalf("open f: %s", st) + } + attr.Flags = FlagAppend | FlagImmutable + if st := m.SetAttr(ctx, inode, SetAttrFlag, 0, attr); st != 0 { + t.Fatalf("setattr f: %s", st) + } + if st := m.Open(ctx, inode, syscall.O_WRONLY, attr); st != syscall.EPERM { + t.Fatalf("open f: %s", st) + } + if st := m.Open(ctx, inode, syscall.O_WRONLY|syscall.O_APPEND, attr); st != syscall.EPERM { + t.Fatalf("open f: %s", st) + } + + var d Ino + if st := m.Mkdir(ctx, 1, "d", 0640, 022, 0, &d, attr); st != 0 { + t.Fatalf("mkdir d: %s", st) + } + attr.Flags = FlagAppend + if st := m.SetAttr(ctx, d, SetAttrFlag, 0, attr); st != 0 { + t.Fatalf("setattr d: %s", st) + } + if st := m.Create(ctx, d, "f", 0644, 022, 0, &inode, nil); st != 0 { + t.Fatalf("create f: %s", st) + } + if st := m.Unlink(ctx, d, "f"); st != syscall.EPERM { + t.Fatalf("unlink f: %s", st) + } + attr.Flags = FlagAppend | FlagImmutable + if st := m.SetAttr(ctx, d, SetAttrFlag, 0, attr); st != 0 { + t.Fatalf("setattr d: %s", st) + } + if st := m.Create(ctx, d, "f2", 0644, 022, 0, &inode, nil); st != syscall.EPERM { + t.Fatalf("create f2: %s", st) + } + + var Immutable Ino + if st := m.Mkdir(ctx, 1, "ImmutFile", 0640, 022, 0, &Immutable, attr); st != 0 { + t.Fatalf("mkdir d: %s", st) + } + attr.Flags = FlagImmutable + if st := m.SetAttr(ctx, Immutable, SetAttrFlag, 0, attr); st != 0 { + t.Fatalf("setattr d: %s", st) + } + if st := m.Create(ctx, Immutable, "f2", 0644, 022, 0, &inode, nil); st != syscall.EPERM { + t.Fatalf("create f2: %s", st) + } + + var src1, dst1, mfile Ino + attr.Flags = 0 + if st := m.Mkdir(ctx, 1, "src1", 0640, 022, 0, &src1, attr); st != 0 { + t.Fatalf("mkdir src1: %s", st) + } + if st := m.Create(ctx, src1, "mfile", 0644, 022, 0, &mfile, nil); st != 0 { + t.Fatalf("create mfile: %s", st) + } + if st := m.Mkdir(ctx, 1, "dst1", 0640, 022, 0, &dst1, attr); st != 0 { + t.Fatalf("mkdir dst1: %s", st) + } + + attr.Flags = FlagAppend + if st := m.SetAttr(ctx, src1, SetAttrFlag, 0, attr); st != 0 { + t.Fatalf("setattr d: %s", st) + } + if st := m.Rename(ctx, src1, "mfile", dst1, "mfile", 0, &mfile, attr); st != syscall.EPERM { + t.Fatalf("rename d: %s", st) + } + + attr.Flags = FlagImmutable + if st := m.SetAttr(ctx, src1, SetAttrFlag, 0, attr); st != 0 { + t.Fatalf("setattr d: %s", st) + } + if st := m.Rename(ctx, src1, "mfile", dst1, "mfile", 0, &mfile, attr); st != syscall.EPERM { + t.Fatalf("rename d: %s", st) + } + + if st := m.SetAttr(ctx, dst1, SetAttrFlag, 0, attr); st != 0 { + t.Fatalf("setattr d: %s", st) + } + if st := m.Rename(ctx, src1, "mfile", dst1, "mfile", 0, &mfile, attr); st != syscall.EPERM { + t.Fatalf("rename d: %s", st) + } + + var delFile Ino + if st := m.Create(ctx, 1, "delfile", 0644, 022, 0, &delFile, nil); st != 0 { + t.Fatalf("create f: %s", st) + } + attr.Flags = FlagImmutable | FlagAppend + if st := m.SetAttr(ctx, delFile, SetAttrFlag, 0, attr); st != 0 { + t.Fatalf("setattr d: %s", st) + } + if st := m.Unlink(ctx, 1, "delfile"); st != syscall.EPERM { + t.Fatalf("unlink f: %s", st) + } + + var fallocFile Ino + if st := m.Create(ctx, 1, "fallocfile", 0644, 022, 0, &fallocFile, nil); st != 0 { + t.Fatalf("create f: %s", st) + } + attr.Flags = FlagAppend + if st := m.SetAttr(ctx, fallocFile, SetAttrFlag, 0, attr); st != 0 { + t.Fatalf("setattr f: %s", st) + } + if st := m.Fallocate(ctx, fallocFile, fallocKeepSize, 0, 1024); st != 0 { + t.Fatalf("fallocate f: %s", st) + } + if st := m.Fallocate(ctx, fallocFile, fallocKeepSize|fallocZeroRange, 0, 1024); st != syscall.EPERM { + t.Fatalf("fallocate f: %s", st) + } + attr.Flags = FlagImmutable + if st := m.SetAttr(ctx, fallocFile, SetAttrFlag, 0, attr); st != 0 { + t.Fatalf("setattr f: %s", st) + } + if st := m.Fallocate(ctx, fallocFile, fallocKeepSize, 0, 1024); st != syscall.EPERM { + t.Fatalf("fallocate f: %s", st) + } + + var copysrcFile, copydstFile Ino + if st := m.Create(ctx, 1, "copysrcfile", 0644, 022, 0, ©srcFile, nil); st != 0 { + t.Fatalf("create f: %s", st) + } + if st := m.Create(ctx, 1, "copydstfile", 0644, 022, 0, ©dstFile, nil); st != 0 { + t.Fatalf("create f: %s", st) + } + if st := m.Fallocate(ctx, copysrcFile, 0, 0, 1024); st != 0 { + t.Fatalf("fallocate f: %s", st) + } + attr.Flags = FlagAppend + if st := m.SetAttr(ctx, copydstFile, SetAttrFlag, 0, attr); st != 0 { + t.Fatalf("setattr f: %s", st) + } + if st := m.CopyFileRange(ctx, copysrcFile, 0, copydstFile, 0, 1024, 0, nil); st != syscall.EPERM { + t.Fatalf("copy_file_range f: %s", st) + } + attr.Flags = FlagImmutable + if st := m.SetAttr(ctx, copydstFile, SetAttrFlag, 0, attr); st != 0 { + t.Fatalf("setattr f: %s", st) + } + if st := m.CopyFileRange(ctx, copysrcFile, 0, copydstFile, 0, 1024, 0, nil); st != syscall.EPERM { + t.Fatalf("copy_file_range f: %s", st) + } +} diff --git a/pkg/meta/interface.go b/pkg/meta/interface.go index 3cbea3a35c2e..e67e17b1f894 100644 --- a/pkg/meta/interface.go +++ b/pkg/meta/interface.go @@ -75,6 +75,12 @@ const ( SetAttrCtime SetAttrAtimeNow SetAttrMtimeNow + SetAttrFlag = 1 << 15 +) + +const ( + FlagImmutable = 1 << iota + FlagAppend ) const MaxName = 255 @@ -99,7 +105,7 @@ type MsgCallback func(...interface{}) error // Attr represents attributes of a node. type Attr struct { - Flags uint8 // reserved flags + Flags uint8 // flags Typ uint8 // type of a node Mode uint16 // permission mode Uid uint32 // owner id diff --git a/pkg/meta/redis.go b/pkg/meta/redis.go index 99114455e9eb..8aa1640a3423 100644 --- a/pkg/meta/redis.go +++ b/pkg/meta/redis.go @@ -930,6 +930,12 @@ func (m *redisMeta) Fallocate(ctx Context, inode Ino, mode uint8, off uint64, si if t.Typ != TypeFile { return syscall.EPERM } + if (t.Flags & FlagImmutable) != 0 { + return syscall.EPERM + } + if (t.Flags&FlagAppend) != 0 && (mode&^fallocKeepSize) != 0 { + return syscall.EPERM + } length := t.Length if off+size > t.Length { if mode&fallocKeepSize == 0 { @@ -1037,6 +1043,10 @@ func (m *redisMeta) SetAttr(ctx Context, inode Ino, set uint16, sugidclearmode u cur.Mtimensec = uint32(now.Nanosecond()) changed = true } + if set&SetAttrFlag != 0 { + cur.Flags = attr.Flags + changed = true + } if !changed { *attr = cur return nil @@ -1106,6 +1116,9 @@ func (m *redisMeta) doMknod(ctx Context, parent Ino, name string, _type uint8, m if pattr.Typ != TypeDirectory { return syscall.ENOTDIR } + if (pattr.Flags & FlagImmutable) != 0 { + return syscall.EPERM + } buf, err := tx.HGet(ctx, m.entryKey(parent), name).Bytes() if err != nil && err != redis.Nil { @@ -1229,6 +1242,9 @@ func (m *redisMeta) doUnlink(ctx Context, parent Ino, name string) syscall.Errno if pattr.Typ != TypeDirectory { return syscall.ENOTDIR } + if (pattr.Flags&FlagAppend) != 0 || (pattr.Flags&FlagImmutable) != 0 { + return syscall.EPERM + } var updateParent bool now := time.Now() if !isTrash(parent) && now.Sub(time.Unix(pattr.Mtime, int64(pattr.Mtimensec))) >= minUpdateTime { @@ -1243,6 +1259,9 @@ func (m *redisMeta) doUnlink(ctx Context, parent Ino, name string) syscall.Errno if ctx.Uid() != 0 && pattr.Mode&01000 != 0 && ctx.Uid() != pattr.Uid && ctx.Uid() != attr.Uid { return syscall.EACCES } + if (attr.Flags&FlagAppend) != 0 || (attr.Flags&FlagImmutable) != 0 { + return syscall.EPERM + } attr.Ctime = now.Unix() attr.Ctimensec = uint32(now.Nanosecond()) if trash == 0 { @@ -1349,6 +1368,9 @@ func (m *redisMeta) doRmdir(ctx Context, parent Ino, name string) syscall.Errno if pattr.Typ != TypeDirectory { return syscall.ENOTDIR } + if (pattr.Flags&FlagAppend) != 0 || (pattr.Flags&FlagImmutable) != 0 { + return syscall.EPERM + } now := time.Now() pattr.Nlink-- pattr.Mtime = now.Unix() @@ -1482,6 +1504,9 @@ func (m *redisMeta) doRename(ctx Context, parentSrc Ino, nameSrc string, parentD return syscall.ENOTDIR } m.parseAttr([]byte(rs[2].(string)), &iattr) + if (sattr.Flags&FlagAppend) != 0 || (sattr.Flags&FlagImmutable) != 0 || (dattr.Flags&FlagImmutable) != 0 || (iattr.Flags&FlagAppend) != 0 || (iattr.Flags&FlagImmutable) != 0 { + return syscall.EPERM + } var supdate, dupdate bool now := time.Now() @@ -1491,6 +1516,9 @@ func (m *redisMeta) doRename(ctx Context, parentSrc Ino, nameSrc string, parentD trash = 0 } m.parseAttr([]byte(rs[3].(string)), &tattr) + if (tattr.Flags&FlagAppend) != 0 || (tattr.Flags&FlagImmutable) != 0 { + return syscall.EPERM + } tattr.Ctime = now.Unix() tattr.Ctimensec = uint32(now.Nanosecond()) if exchange { @@ -1666,6 +1694,9 @@ func (m *redisMeta) doLink(ctx Context, inode, parent Ino, name string, attr *At if pattr.Typ != TypeDirectory { return syscall.ENOTDIR } + if pattr.Flags&FlagImmutable != 0 { + return syscall.EPERM + } var updateParent bool now := time.Now() if now.Sub(time.Unix(pattr.Mtime, int64(pattr.Mtimensec))) >= minUpdateTime { @@ -1679,6 +1710,9 @@ func (m *redisMeta) doLink(ctx Context, inode, parent Ino, name string, attr *At if iattr.Typ == TypeDirectory { return syscall.EPERM } + if (iattr.Flags&FlagAppend) != 0 || (iattr.Flags&FlagImmutable) != 0 { + return syscall.EPERM + } oldParent := iattr.Parent iattr.Parent = 0 iattr.Ctime = now.Unix() @@ -2053,6 +2087,9 @@ func (m *redisMeta) CopyFileRange(ctx Context, fin Ino, offIn uint64, fout Ino, if attr.Typ != TypeFile { return syscall.EINVAL } + if (attr.Flags&FlagImmutable) != 0 || (attr.Flags&FlagAppend) != 0 { + return syscall.EPERM + } newleng := offOut + size if newleng > attr.Length { diff --git a/pkg/meta/sql.go b/pkg/meta/sql.go index bd25d6b4933d..a41f28ae0f55 100644 --- a/pkg/meta/sql.go +++ b/pkg/meta/sql.go @@ -867,12 +867,16 @@ func (m *dbMeta) SetAttr(ctx Context, inode Ino, set uint16, sugidclearmode uint cur.Mtime = now changed = true } + if set&SetAttrFlag != 0 { + cur.Flags = attr.Flags + changed = true + } m.parseAttr(&cur, attr) if !changed { return nil } cur.Ctime = now - _, err = s.Cols("mode", "uid", "gid", "atime", "mtime", "ctime").Update(&cur, &node{Inode: inode}) + _, err = s.Cols("flags", "mode", "uid", "gid", "atime", "mtime", "ctime").Update(&cur, &node{Inode: inode}) if err == nil { m.parseAttr(&cur, attr) } @@ -1013,6 +1017,12 @@ func (m *dbMeta) Fallocate(ctx Context, inode Ino, mode uint8, off uint64, size if n.Type != TypeFile { return syscall.EPERM } + if (n.Flags & FlagImmutable) != 0 { + return syscall.EPERM + } + if (n.Flags&FlagAppend) != 0 && (mode&^fallocKeepSize) != 0 { + return syscall.EPERM + } length := n.Length if off+size > n.Length { if mode&fallocKeepSize == 0 { @@ -1120,6 +1130,9 @@ func (m *dbMeta) doMknod(ctx Context, parent Ino, name string, _type uint8, mode if pn.Type != TypeDirectory { return syscall.ENOTDIR } + if (pn.Flags & FlagImmutable) != 0 { + return syscall.EPERM + } var e = edge{Parent: parent, Name: []byte(name)} ok, err = s.ForUpdate().Get(&e) if err != nil { @@ -1219,6 +1232,9 @@ func (m *dbMeta) doUnlink(ctx Context, parent Ino, name string) syscall.Errno { if pn.Type != TypeDirectory { return syscall.ENOTDIR } + if (pn.Flags&FlagAppend) != 0 || (pn.Flags&FlagImmutable) != 0 { + return syscall.EPERM + } var e = edge{Parent: parent, Name: []byte(name)} ok, err = s.ForUpdate().Get(&e) if err != nil { @@ -1249,6 +1265,9 @@ func (m *dbMeta) doUnlink(ctx Context, parent Ino, name string) syscall.Errno { if ctx.Uid() != 0 && pn.Mode&01000 != 0 && ctx.Uid() != pn.Uid && ctx.Uid() != n.Uid { return syscall.EACCES } + if (n.Flags&FlagAppend) != 0 || (n.Flags&FlagImmutable) != 0 { + return syscall.EPERM + } n.Ctime = now if trash == 0 { n.Nlink-- @@ -1350,6 +1369,9 @@ func (m *dbMeta) doRmdir(ctx Context, parent Ino, name string) syscall.Errno { if pn.Type != TypeDirectory { return syscall.ENOTDIR } + if pn.Flags&FlagImmutable != 0 || pn.Flags&FlagAppend != 0 { + return syscall.EPERM + } var e = edge{Parent: parent, Name: []byte(name)} ok, err = s.ForUpdate().Get(&e) if err != nil { @@ -1465,6 +1487,9 @@ func (m *dbMeta) doRename(ctx Context, parentSrc Ino, nameSrc string, parentDst if spn.Type != TypeDirectory || dpn.Type != TypeDirectory { return syscall.ENOTDIR } + if (spn.Flags&FlagAppend) != 0 || (spn.Flags&FlagImmutable) != 0 || (dpn.Flags&FlagImmutable) != 0 { + return syscall.EPERM + } var se = edge{Parent: parentSrc, Name: []byte(nameSrc)} ok, err := s.ForUpdate().Get(&se) if err != nil { @@ -1495,6 +1520,9 @@ func (m *dbMeta) doRename(ctx Context, parentSrc Ino, nameSrc string, parentDst if !ok { return syscall.ENOENT } + if (sn.Flags&FlagAppend) != 0 || (sn.Flags&FlagImmutable) != 0 { + return syscall.EPERM + } var de = edge{Parent: parentDst, Name: []byte(nameDst)} ok, err = s.ForUpdate().Get(&de) @@ -1525,6 +1553,9 @@ func (m *dbMeta) doRename(ctx Context, parentSrc Ino, nameSrc string, parentDst logger.Warnf("no attribute for inode %d (%d, %s)", dino, parentDst, de.Name) trash = 0 } + if (dn.Flags&FlagAppend) != 0 || (dn.Flags&FlagImmutable) != 0 { + return syscall.EPERM + } dn.Ctime = now if exchange { if parentSrc != parentDst { @@ -1708,6 +1739,9 @@ func (m *dbMeta) doLink(ctx Context, inode, parent Ino, name string, attr *Attr) if pn.Type != TypeDirectory { return syscall.ENOTDIR } + if pn.Flags&FlagImmutable != 0 { + return syscall.EPERM + } var e = edge{Parent: parent, Name: []byte(name)} ok, err = s.ForUpdate().Get(&e) if err != nil { @@ -1728,6 +1762,9 @@ func (m *dbMeta) doLink(ctx Context, inode, parent Ino, name string, attr *Attr) if n.Type == TypeDirectory { return syscall.EPERM } + if (n.Flags&FlagAppend) != 0 || (n.Flags&FlagImmutable) != 0 { + return syscall.EPERM + } var updateParent bool now := time.Now().UnixNano() / 1e3 @@ -2059,6 +2096,9 @@ func (m *dbMeta) CopyFileRange(ctx Context, fin Ino, offIn uint64, fout Ino, off if nout.Type != TypeFile { return syscall.EINVAL } + if (nout.Flags&FlagImmutable) != 0 || (nout.Flags&FlagAppend) != 0 { + return syscall.EPERM + } newleng := offOut + size if newleng > nout.Length { diff --git a/pkg/meta/tkv.go b/pkg/meta/tkv.go index 25b43ef53308..f3827cef89b9 100644 --- a/pkg/meta/tkv.go +++ b/pkg/meta/tkv.go @@ -794,6 +794,10 @@ func (m *kvMeta) SetAttr(ctx Context, inode Ino, set uint16, sugidclearmode uint cur.Mtimensec = uint32(now.Nanosecond()) changed = true } + if set&SetAttrFlag != 0 { + cur.Flags = attr.Flags + changed = true + } if !changed { *attr = cur return nil @@ -911,6 +915,12 @@ func (m *kvMeta) Fallocate(ctx Context, inode Ino, mode uint8, off uint64, size if t.Typ != TypeFile { return syscall.EPERM } + if (t.Flags & FlagImmutable) != 0 { + return syscall.EPERM + } + if (t.Flags&FlagAppend) != 0 && (mode&^fallocKeepSize) != 0 { + return syscall.EPERM + } length := t.Length if off+size > t.Length { if mode&fallocKeepSize == 0 { @@ -1007,6 +1017,9 @@ func (m *kvMeta) doMknod(ctx Context, parent Ino, name string, _type uint8, mode if pattr.Typ != TypeDirectory { return syscall.ENOTDIR } + if (pattr.Flags & FlagImmutable) != 0 { + return syscall.EPERM + } buf := tx.get(m.entryKey(parent, name)) var foundIno Ino @@ -1114,12 +1127,20 @@ func (m *kvMeta) doUnlink(ctx Context, parent Ino, name string) syscall.Errno { if pattr.Typ != TypeDirectory { return syscall.ENOTDIR } + if (pattr.Flags&FlagAppend) != 0 || (pattr.Flags&FlagImmutable) != 0 { + return syscall.EPERM + } + attr = Attr{} + opened = false now := time.Now() if rs[1] != nil { m.parseAttr(rs[1], &attr) if ctx.Uid() != 0 && pattr.Mode&01000 != 0 && ctx.Uid() != pattr.Uid && ctx.Uid() != attr.Uid { return syscall.EACCES } + if (attr.Flags&FlagAppend) != 0 || (attr.Flags&FlagImmutable) != 0 { + return syscall.EPERM + } attr.Ctime = now.Unix() attr.Ctimensec = uint32(now.Nanosecond()) if trash == 0 { @@ -1223,6 +1244,9 @@ func (m *kvMeta) doRmdir(ctx Context, parent Ino, name string) syscall.Errno { if pattr.Typ != TypeDirectory { return syscall.ENOTDIR } + if (pattr.Flags&FlagAppend) != 0 || (pattr.Flags&FlagImmutable) != 0 { + return syscall.EPERM + } if tx.exist(m.entryKey(inode, "")) { return syscall.ENOTEMPTY } @@ -1314,6 +1338,9 @@ func (m *kvMeta) doRename(ctx Context, parentSrc Ino, nameSrc string, parentDst return syscall.ENOTDIR } m.parseAttr(rs[2], &iattr) + if (sattr.Flags&FlagAppend) != 0 || (sattr.Flags&FlagImmutable) != 0 || (dattr.Flags&FlagImmutable) != 0 || (iattr.Flags&FlagAppend) != 0 || (iattr.Flags&FlagImmutable) != 0 { + return syscall.EPERM + } dbuf := tx.get(m.entryKey(parentDst, nameDst)) if dbuf == nil && m.conf.CaseInsensi { @@ -1335,6 +1362,9 @@ func (m *kvMeta) doRename(ctx Context, parentSrc Ino, nameSrc string, parentDst trash = 0 } m.parseAttr(a, &tattr) + if (tattr.Flags&FlagAppend) != 0 || (tattr.Flags&FlagImmutable) != 0 { + return syscall.EPERM + } tattr.Ctime = now.Unix() tattr.Ctimensec = uint32(now.Nanosecond()) if exchange { @@ -1497,10 +1527,16 @@ func (m *kvMeta) doLink(ctx Context, inode, parent Ino, name string, attr *Attr) if pattr.Typ != TypeDirectory { return syscall.ENOTDIR } + if pattr.Flags&FlagImmutable != 0 { + return syscall.EPERM + } m.parseAttr(rs[1], &iattr) if iattr.Typ == TypeDirectory { return syscall.EPERM } + if (iattr.Flags&FlagAppend) != 0 || (iattr.Flags&FlagImmutable) != 0 { + return syscall.EPERM + } buf := tx.get(m.entryKey(parent, name)) if buf != nil || buf == nil && m.conf.CaseInsensi && m.resolveCase(ctx, parent, name) != nil { return syscall.EEXIST @@ -1744,6 +1780,9 @@ func (m *kvMeta) CopyFileRange(ctx Context, fin Ino, offIn uint64, fout Ino, off if attr.Typ != TypeFile { return syscall.EINVAL } + if (attr.Flags&FlagImmutable) != 0 || (attr.Flags&FlagAppend) != 0 { + return syscall.EPERM + } newleng := offOut + size if newleng > attr.Length { diff --git a/pkg/utils/buffer.go b/pkg/utils/buffer.go index 4cc7e79d2c63..46986adecc00 100644 --- a/pkg/utils/buffer.go +++ b/pkg/utils/buffer.go @@ -145,11 +145,11 @@ func (b *Buffer) Bytes() []byte { return b.buf } -var nativeEndian binary.ByteOrder +var NativeEndian binary.ByteOrder // NewNativeBuffer utility to create *Buffer of given size with nativeEndian func NewNativeBuffer(buf []byte) *Buffer { - return &Buffer{nativeEndian, 0, buf} + return &Buffer{NativeEndian, 0, buf} } func init() { @@ -158,9 +158,9 @@ func init() { switch buf { case [2]byte{0xCD, 0xAB}: - nativeEndian = binary.LittleEndian + NativeEndian = binary.LittleEndian case [2]byte{0xAB, 0xCD}: - nativeEndian = binary.BigEndian + NativeEndian = binary.BigEndian default: panic("Could not determine native endianness.") } diff --git a/pkg/vfs/vfs_unix.go b/pkg/vfs/vfs_unix.go index 62d6a6fb40a6..291a90ce1e8e 100644 --- a/pkg/vfs/vfs_unix.go +++ b/pkg/vfs/vfs_unix.go @@ -26,6 +26,7 @@ import ( "syscall" "github.com/juicedata/juicefs/pkg/meta" + "github.com/juicedata/juicefs/pkg/utils" "golang.org/x/sys/unix" ) @@ -290,3 +291,64 @@ func (v *VFS) Flock(ctx Context, ino Ino, fh uint64, owner uint64, typ uint32, b } return } + +func (v *VFS) Ioctl(ctx Context, ino Ino, cmd uint32, arg uint64, bufIn, bufOut []byte) (err syscall.Errno) { + const ( + FS_IOC_GETFLAGS = 0x80086601 + FS_IOC_SETFLAGS = 0x40086602 + FS_IOC_GETFLAGS_32 = 0x80046601 + FS_IOC_SETFLAGS_32 = 0x40046602 + ) + const ( + FS_IMMUTABLE_FL = 0x00000010 + FS_APPEND_FL = 0x00000020 + ) + defer func() { logit(ctx, "ioctl (%d,0x%X,0x%X,%v,%v): %s", ino, cmd, arg, bufIn, bufOut, strerr(err)) }() + switch cmd { + default: + err = syscall.ENOTTY + return + case FS_IOC_SETFLAGS, FS_IOC_GETFLAGS, FS_IOC_SETFLAGS_32, FS_IOC_GETFLAGS_32: + } + if IsSpecialNode(ino) { + err = syscall.EPERM + return + } + var attr = &Attr{} + if cmd>>30 == 1 { // set + var iflag uint64 + if len(bufIn) == 8 { + iflag = utils.NativeEndian.Uint64(bufIn) + } else if len(bufIn) == 4 { + iflag = uint64(utils.NativeEndian.Uint32(bufIn)) + } else { + err = syscall.EINVAL + return + } + if (iflag & FS_IMMUTABLE_FL) != 0 { + attr.Flags |= meta.FlagImmutable + } + if (iflag & FS_APPEND_FL) != 0 { + attr.Flags |= meta.FlagAppend + } + err = v.Meta.SetAttr(ctx, ino, meta.SetAttrFlag, 0, attr) + } else { + err = v.Meta.GetAttr(ctx, ino, attr) + var iflag uint64 + if (attr.Flags & meta.FlagImmutable) != 0 { + iflag |= FS_IMMUTABLE_FL + } + if (attr.Flags & meta.FlagAppend) != 0 { + iflag |= FS_APPEND_FL + } + if len(bufOut) == 8 { + utils.NativeEndian.PutUint64(bufOut, iflag) + } else if len(bufOut) == 4 { + utils.NativeEndian.PutUint32(bufOut, uint32(iflag)) + } else { + err = syscall.EINVAL + return + } + } + return +}