diff --git a/weed/filer/posixlock/Makefile b/weed/filer/posixlock/Makefile new file mode 100644 index 000000000..d8d85dcb7 --- /dev/null +++ b/weed/filer/posixlock/Makefile @@ -0,0 +1,6 @@ +all: gen + +.PHONY : gen + +gen: + protoc posix_lock.proto --go_out=. --go-grpc_out=. --go_opt=paths=source_relative --go-grpc_opt=paths=source_relative diff --git a/weed/filer/posixlock/codec.go b/weed/filer/posixlock/codec.go new file mode 100644 index 000000000..5f50d2fba --- /dev/null +++ b/weed/filer/posixlock/codec.go @@ -0,0 +1,58 @@ +package posixlock + +import ( + "fmt" + + "google.golang.org/protobuf/proto" +) + +// The held lock set rides in an inode's entry metadata, so the wire format must +// stay backward compatible across filer versions. It is a protobuf message +// (LockSetProto), so new fields can be added without breaking old readers. + +// Marshal encodes the held locks for storage in the inode's entry metadata. A +// lock-free set encodes to nil, so an inode that holds no locks carries no blob. +func (s *Set) Marshal() ([]byte, error) { + if len(s.locks) == 0 { + return nil, nil + } + pb := &LockSetProto{Locks: make([]*LockProto, len(s.locks))} + for i, l := range s.locks { + pb.Locks[i] = &LockProto{ + Start: l.Start, + End: l.End, + Type: l.Type, + Sid: l.Sid, + Owner: l.Owner, + Pid: l.Pid, + IsFlock: l.IsFlock, + } + } + return proto.Marshal(pb) +} + +// Unmarshal decodes a Set produced by Marshal. Empty input is an empty set, so a +// missing metadata key reads back as "no locks held". A malformed blob is +// rejected rather than silently misread. +func Unmarshal(b []byte) (*Set, error) { + if len(b) == 0 { + return &Set{}, nil + } + pb := &LockSetProto{} + if err := proto.Unmarshal(b, pb); err != nil { + return nil, fmt.Errorf("posixlock: %w", err) + } + locks := make([]Range, len(pb.Locks)) + for i, l := range pb.Locks { + locks[i] = Range{ + Start: l.Start, + End: l.End, + Type: l.Type, + Sid: l.Sid, + Owner: l.Owner, + Pid: l.Pid, + IsFlock: l.IsFlock, + } + } + return &Set{locks: locks}, nil +} diff --git a/weed/filer/posixlock/codec_test.go b/weed/filer/posixlock/codec_test.go new file mode 100644 index 000000000..a3e16b8fd --- /dev/null +++ b/weed/filer/posixlock/codec_test.go @@ -0,0 +1,75 @@ +package posixlock + +import ( + "math" + "reflect" + "testing" +) + +func TestMarshalEmptyIsNil(t *testing.T) { + s := &Set{} + b, err := s.Marshal() + if err != nil { + t.Fatalf("marshal empty: %v", err) + } + if b != nil { + t.Fatalf("empty set should marshal to nil, got %d bytes", len(b)) + } + got, err := Unmarshal(nil) + if err != nil { + t.Fatalf("unmarshal nil: %v", err) + } + if !got.Empty() { + t.Fatal("unmarshal nil should be an empty set") + } +} + +func TestMarshalRoundTrip(t *testing.T) { + s := &Set{} + // A spread of fields: EOF range, both namespaces, two sessions, distinct pids. + mustAcquire(t, s, Range{Start: 0, End: 99, Type: Write, Sid: 1, Owner: 7, Pid: 10}) + mustAcquire(t, s, Range{Start: 200, End: math.MaxUint64, Type: Read, Sid: 2, Owner: 7, Pid: 20}) + mustAcquire(t, s, Range{Start: 0, End: math.MaxUint64, Type: Write, Sid: 2, Owner: 9, Pid: 30, IsFlock: true}) + + b, err := s.Marshal() + if err != nil { + t.Fatalf("marshal: %v", err) + } + got, err := Unmarshal(b) + if err != nil { + t.Fatalf("unmarshal: %v", err) + } + if !reflect.DeepEqual(got.Locks(), s.Locks()) { + t.Fatalf("round-trip mismatch:\n got %+v\nwant %+v", got.Locks(), s.Locks()) + } +} + +func TestUnmarshalRejectsMalformed(t *testing.T) { + // field 1 (locks), wire type 2 (length-delimited) declaring 5 bytes that + // are not there: a corrupt blob must fail loudly, not read back as empty. + if _, err := Unmarshal([]byte{0x0A, 0x05}); err == nil { + t.Fatal("expected an error on malformed input") + } +} + +// A decoded set must behave identically — conflict detection still fires. +func TestRoundTripPreservesConflict(t *testing.T) { + s := &Set{} + mustAcquire(t, s, Range{Start: 0, End: 99, Type: Write, Sid: 1, Owner: 1, Pid: 10}) + + b, err := s.Marshal() + if err != nil { + t.Fatalf("marshal: %v", err) + } + decoded, err := Unmarshal(b) + if err != nil { + t.Fatalf("unmarshal: %v", err) + } + if _, granted := decoded.Acquire(Range{Start: 50, End: 149, Type: Write, Sid: 2, Owner: 1, Pid: 20}); granted { + t.Fatal("decoded set should still report the conflict") + } + // Same owner across the wire is still the same owner. + if _, granted := decoded.Acquire(Range{Start: 0, End: 99, Type: Read, Sid: 1, Owner: 1, Pid: 10}); !granted { + t.Fatal("decoded set should still recognize the same (sid,owner)") + } +} diff --git a/weed/filer/posixlock/posix_lock.pb.go b/weed/filer/posixlock/posix_lock.pb.go new file mode 100644 index 000000000..48876af2e --- /dev/null +++ b/weed/filer/posixlock/posix_lock.pb.go @@ -0,0 +1,230 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.36.6 +// protoc v6.33.4 +// source: posix_lock.proto + +package posixlock + +import ( + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" + unsafe "unsafe" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +// LockSetProto is the serialized set of advisory locks held on one inode. It +// rides in the inode's entry metadata, so the schema must stay backward +// compatible across filer versions. +type LockSetProto struct { + state protoimpl.MessageState `protogen:"open.v1"` + Locks []*LockProto `protobuf:"bytes,1,rep,name=locks,proto3" json:"locks,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *LockSetProto) Reset() { + *x = LockSetProto{} + mi := &file_posix_lock_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *LockSetProto) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*LockSetProto) ProtoMessage() {} + +func (x *LockSetProto) ProtoReflect() protoreflect.Message { + mi := &file_posix_lock_proto_msgTypes[0] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use LockSetProto.ProtoReflect.Descriptor instead. +func (*LockSetProto) Descriptor() ([]byte, []int) { + return file_posix_lock_proto_rawDescGZIP(), []int{0} +} + +func (x *LockSetProto) GetLocks() []*LockProto { + if x != nil { + return x.Locks + } + return nil +} + +// LockProto mirrors Range. Owner identity is the (sid, owner) pair; end is +// inclusive and max uint64 means EOF; is_flock separates the flock and fcntl +// namespaces, which never conflict. +type LockProto struct { + state protoimpl.MessageState `protogen:"open.v1"` + Start uint64 `protobuf:"varint,1,opt,name=start,proto3" json:"start,omitempty"` + End uint64 `protobuf:"varint,2,opt,name=end,proto3" json:"end,omitempty"` + Type uint32 `protobuf:"varint,3,opt,name=type,proto3" json:"type,omitempty"` + Sid uint64 `protobuf:"varint,4,opt,name=sid,proto3" json:"sid,omitempty"` + Owner uint64 `protobuf:"varint,5,opt,name=owner,proto3" json:"owner,omitempty"` + Pid uint32 `protobuf:"varint,6,opt,name=pid,proto3" json:"pid,omitempty"` + IsFlock bool `protobuf:"varint,7,opt,name=is_flock,json=isFlock,proto3" json:"is_flock,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *LockProto) Reset() { + *x = LockProto{} + mi := &file_posix_lock_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *LockProto) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*LockProto) ProtoMessage() {} + +func (x *LockProto) ProtoReflect() protoreflect.Message { + mi := &file_posix_lock_proto_msgTypes[1] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use LockProto.ProtoReflect.Descriptor instead. +func (*LockProto) Descriptor() ([]byte, []int) { + return file_posix_lock_proto_rawDescGZIP(), []int{1} +} + +func (x *LockProto) GetStart() uint64 { + if x != nil { + return x.Start + } + return 0 +} + +func (x *LockProto) GetEnd() uint64 { + if x != nil { + return x.End + } + return 0 +} + +func (x *LockProto) GetType() uint32 { + if x != nil { + return x.Type + } + return 0 +} + +func (x *LockProto) GetSid() uint64 { + if x != nil { + return x.Sid + } + return 0 +} + +func (x *LockProto) GetOwner() uint64 { + if x != nil { + return x.Owner + } + return 0 +} + +func (x *LockProto) GetPid() uint32 { + if x != nil { + return x.Pid + } + return 0 +} + +func (x *LockProto) GetIsFlock() bool { + if x != nil { + return x.IsFlock + } + return false +} + +var File_posix_lock_proto protoreflect.FileDescriptor + +const file_posix_lock_proto_rawDesc = "" + + "\n" + + "\x10posix_lock.proto\x12\tposixlock\":\n" + + "\fLockSetProto\x12*\n" + + "\x05locks\x18\x01 \x03(\v2\x14.posixlock.LockProtoR\x05locks\"\x9c\x01\n" + + "\tLockProto\x12\x14\n" + + "\x05start\x18\x01 \x01(\x04R\x05start\x12\x10\n" + + "\x03end\x18\x02 \x01(\x04R\x03end\x12\x12\n" + + "\x04type\x18\x03 \x01(\rR\x04type\x12\x10\n" + + "\x03sid\x18\x04 \x01(\x04R\x03sid\x12\x14\n" + + "\x05owner\x18\x05 \x01(\x04R\x05owner\x12\x10\n" + + "\x03pid\x18\x06 \x01(\rR\x03pid\x12\x19\n" + + "\bis_flock\x18\a \x01(\bR\aisFlockB5Z3github.com/seaweedfs/seaweedfs/weed/filer/posixlockb\x06proto3" + +var ( + file_posix_lock_proto_rawDescOnce sync.Once + file_posix_lock_proto_rawDescData []byte +) + +func file_posix_lock_proto_rawDescGZIP() []byte { + file_posix_lock_proto_rawDescOnce.Do(func() { + file_posix_lock_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_posix_lock_proto_rawDesc), len(file_posix_lock_proto_rawDesc))) + }) + return file_posix_lock_proto_rawDescData +} + +var file_posix_lock_proto_msgTypes = make([]protoimpl.MessageInfo, 2) +var file_posix_lock_proto_goTypes = []any{ + (*LockSetProto)(nil), // 0: posixlock.LockSetProto + (*LockProto)(nil), // 1: posixlock.LockProto +} +var file_posix_lock_proto_depIdxs = []int32{ + 1, // 0: posixlock.LockSetProto.locks:type_name -> posixlock.LockProto + 1, // [1:1] is the sub-list for method output_type + 1, // [1:1] is the sub-list for method input_type + 1, // [1:1] is the sub-list for extension type_name + 1, // [1:1] is the sub-list for extension extendee + 0, // [0:1] is the sub-list for field type_name +} + +func init() { file_posix_lock_proto_init() } +func file_posix_lock_proto_init() { + if File_posix_lock_proto != nil { + return + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: unsafe.Slice(unsafe.StringData(file_posix_lock_proto_rawDesc), len(file_posix_lock_proto_rawDesc)), + NumEnums: 0, + NumMessages: 2, + NumExtensions: 0, + NumServices: 0, + }, + GoTypes: file_posix_lock_proto_goTypes, + DependencyIndexes: file_posix_lock_proto_depIdxs, + MessageInfos: file_posix_lock_proto_msgTypes, + }.Build() + File_posix_lock_proto = out.File + file_posix_lock_proto_goTypes = nil + file_posix_lock_proto_depIdxs = nil +} diff --git a/weed/filer/posixlock/posix_lock.proto b/weed/filer/posixlock/posix_lock.proto new file mode 100644 index 000000000..924b56042 --- /dev/null +++ b/weed/filer/posixlock/posix_lock.proto @@ -0,0 +1,25 @@ +syntax = "proto3"; + +package posixlock; + +option go_package = "github.com/seaweedfs/seaweedfs/weed/filer/posixlock"; + +// LockSetProto is the serialized set of advisory locks held on one inode. It +// rides in the inode's entry metadata, so the schema must stay backward +// compatible across filer versions. +message LockSetProto { + repeated LockProto locks = 1; +} + +// LockProto mirrors Range. Owner identity is the (sid, owner) pair; end is +// inclusive and max uint64 means EOF; is_flock separates the flock and fcntl +// namespaces, which never conflict. +message LockProto { + uint64 start = 1; + uint64 end = 2; + uint32 type = 3; + uint64 sid = 4; + uint64 owner = 5; + uint32 pid = 6; + bool is_flock = 7; +}