diff --git a/bitcask-intro.pdf b/bitcask-intro.pdf new file mode 100644 index 0000000..43e1284 Binary files /dev/null and b/bitcask-intro.pdf differ diff --git a/entity/entry_test.go b/entity/entry_test.go new file mode 100644 index 0000000..bf35d25 --- /dev/null +++ b/entity/entry_test.go @@ -0,0 +1,66 @@ +package entity + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestVerifyRecordCRC(t *testing.T) { + tests := []struct { + name string + mutate func([]byte) + wantOK bool + }{ + { + name: "valid_roundtrip", + mutate: func([]byte) {}, + wantOK: true, + }, + { + name: "flip_payload_byte", + mutate: func(b []byte) { + if len(b) > MetaSize { + b[len(b)-1] ^= 0xFF + } + }, + wantOK: false, + }, + { + name: "flip_crc_byte", + mutate: func(b []byte) { + b[0] ^= 0xFF + }, + wantOK: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + e := NewEntryWithData([]byte("k"), []byte("v")) + buf := e.Encode() + tt.mutate(buf) + assert.Equal(t, tt.wantOK, VerifyRecordCRC(buf)) + }) + } +} + +func TestNewTombstoneEntry_RoundTrip(t *testing.T) { + tests := []struct { + name string + key []byte + }{ + {name: "short", key: []byte("k")}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + e := NewTombstoneEntry(tt.key) + require.Equal(t, uint8(DeleteFlag), e.Meta.Flag) + require.Equal(t, uint32(len(tt.key)), e.Meta.KeySize) + require.Equal(t, uint32(0), e.Meta.ValueSize) + buf := e.Encode() + require.True(t, VerifyRecordCRC(buf)) + require.Equal(t, int64(len(buf)), e.Size()) + }) + } +} diff --git a/lock_other.go b/lock_other.go new file mode 100644 index 0000000..a2db75e --- /dev/null +++ b/lock_other.go @@ -0,0 +1,10 @@ +//go:build !unix + +package tiny_bitcask + +import "os" + +// acquireDBLock is a no-op on non-Unix platforms; ExclusiveLock does not enforce single-writer access. +func acquireDBLock(_ string, _, _ bool) (*os.File, error) { + return nil, nil +} diff --git a/lock_unix.go b/lock_unix.go new file mode 100644 index 0000000..ad4599d --- /dev/null +++ b/lock_unix.go @@ -0,0 +1,30 @@ +//go:build unix + +package tiny_bitcask + +import ( + "fmt" + "os" + "path/filepath" + "syscall" +) + +func acquireDBLock(dir string, readOnly, exclusive bool) (*os.File, error) { + if !exclusive { + return nil, nil + } + path := filepath.Join(dir, ".tiny-bitcask.lock") + f, err := os.OpenFile(path, os.O_CREATE|os.O_RDWR, 0o600) + if err != nil { + return nil, err + } + how := syscall.LOCK_EX + if readOnly { + how = syscall.LOCK_SH + } + if err := syscall.Flock(int(f.Fd()), how|syscall.LOCK_NB); err != nil { + _ = f.Close() + return nil, fmt.Errorf("tiny-bitcask: database lock: %w", err) + } + return f, nil +}