Skip to content

Commit 897f12f

Browse files
authored
fix : read only user should be able to open db on ReadOnly mode (#2268)
**Description** Fixes #2234 Help appreciated 😄 Step to reproduce : - Open a db and fill it with some data - run `chmod -w ./db/*` - Open it again in `WithReadOnly` mode **Checklist** - [x] Code compiles correctly and linting passes locally - [x] Tests added for new functionality, or regression tests for bug fixes added as applicable
1 parent ca9e10b commit 897f12f

4 files changed

Lines changed: 61 additions & 13 deletions

File tree

db.go

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -378,7 +378,7 @@ func Open(opt Options) (*DB, error) {
378378
db.closers.writes = z.NewCloser(1)
379379
go db.doWrites(db.closers.writes)
380380

381-
if !db.opt.InMemory {
381+
if !db.opt.InMemory && !db.opt.ReadOnly {
382382
db.closers.valueGC = z.NewCloser(1)
383383
go db.vlog.waitOnGC(db.closers.valueGC)
384384
}
@@ -538,7 +538,7 @@ func (db *DB) close() (err error) {
538538
db.blockWrites.Store(1)
539539
db.isClosed.Store(1)
540540

541-
if !db.opt.InMemory {
541+
if db.closers.valueGC != nil {
542542
// Stop value GC first.
543543
db.closers.valueGC.SignalAndWait()
544544
}
@@ -671,8 +671,8 @@ const (
671671
// Sync syncs database content to disk. This function provides
672672
// more control to user to sync data whenever required.
673673
func (db *DB) Sync() error {
674-
if db.opt.InMemory {
675-
// InMemory mode does not use WAL/vlog files, so Sync is a no-op.
674+
if db.opt.InMemory || db.opt.ReadOnly {
675+
// InMemory and read-only modes do not use WAL/vlog files, so Sync is a no-op.
676676
return nil
677677
}
678678

@@ -1235,6 +1235,9 @@ func (db *DB) RunValueLogGC(discardRatio float64) error {
12351235
if db.opt.InMemory {
12361236
return ErrGCInMemoryMode
12371237
}
1238+
if db.opt.ReadOnly {
1239+
return ErrGCInReadOnlyMode
1240+
}
12381241
if discardRatio >= 1.0 || discardRatio <= 0.0 {
12391242
return ErrInvalidRequest
12401243
}
@@ -1355,6 +1358,9 @@ func (db *DB) GetSequence(key []byte, bandwidth uint64) (*Sequence, error) {
13551358
if db.opt.managedTxns {
13561359
panic("Cannot use GetSequence with managedDB=true.")
13571360
}
1361+
if db.opt.ReadOnly {
1362+
panic("Cannot use GetSequence in read-only mode.")
1363+
}
13581364

13591365
switch {
13601366
case len(key) == 0:
@@ -1563,6 +1569,9 @@ func (db *DB) startMemoryFlush() {
15631569
// stopped. Ideally, no writes are going on during Flatten. Otherwise, it would create competition
15641570
// between flattening the tree and new tables being created at level zero.
15651571
func (db *DB) Flatten(workers int) error {
1572+
if db.opt.ReadOnly {
1573+
panic("Cannot flatten in read-only mode.")
1574+
}
15661575

15671576
db.stopCompactions()
15681577
defer db.startCompactions()
@@ -1851,6 +1860,9 @@ func (db *DB) BanNamespace(ns uint64) error {
18511860
if db.opt.NamespaceOffset < 0 {
18521861
return ErrNamespaceMode
18531862
}
1863+
if db.opt.ReadOnly {
1864+
panic("Cannot ban namespace in read-only mode.")
1865+
}
18541866
db.opt.Infof("Banning namespace: %d", ns)
18551867
// First set the banned namespaces in DB and then update the in-memory structure.
18561868
key := y.KeyWithTs(append(bannedNsKey, y.U64ToBytes(ns)...), 1)

db_test.go

Lines changed: 31 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import (
1111
"encoding/binary"
1212
"flag"
1313
"fmt"
14+
"io/fs"
1415
"math"
1516
"math/rand"
1617
"os"
@@ -1742,8 +1743,6 @@ func TestTestSequence2(t *testing.T) {
17421743
}
17431744

17441745
func TestReadOnly(t *testing.T) {
1745-
t.Skipf("TODO: ReadOnly needs truncation, so this fails")
1746-
17471746
dir, err := os.MkdirTemp("", "badger-test")
17481747
require.NoError(t, err)
17491748
defer removeDir(dir)
@@ -1771,12 +1770,10 @@ func TestReadOnly(t *testing.T) {
17711770
opts.ReadOnly = true
17721771
kv1, err := Open(opts)
17731772
require.NoError(t, err)
1774-
defer kv1.Close()
17751773

17761774
// Open another read-only
17771775
kv2, err := Open(opts)
17781776
require.NoError(t, err)
1779-
defer kv2.Close()
17801777

17811778
// Attempt a read-write open while it's open for read-only
17821779
opts.ReadOnly = false
@@ -1793,6 +1790,10 @@ func TestReadOnly(t *testing.T) {
17931790
require.Equal(t, b1, []byte("value1"))
17941791
err = txn1.Commit()
17951792
require.NoError(t, err)
1793+
err = kv1.RunValueLogGC(0.5)
1794+
require.Error(t, err, ErrGCInReadOnlyMode)
1795+
err = kv1.Sync()
1796+
require.NoError(t, err)
17961797

17971798
// Get a thing from the DB via the other connection
17981799
txn2 := kv2.NewTransaction(true)
@@ -1811,6 +1812,32 @@ func TestReadOnly(t *testing.T) {
18111812
require.Contains(t, err.Error(), "No sets or deletes are allowed in a read-only transaction")
18121813
err = txn.Commit()
18131814
require.NoError(t, err)
1815+
1816+
// Close
1817+
require.NoError(t, kv1.Close())
1818+
require.NoError(t, kv2.Close())
1819+
1820+
// Test os permission read-only open
1821+
err = filepath.WalkDir(dir, func(path string, d fs.DirEntry, err error) error {
1822+
require.NoError(t, err)
1823+
if path == dir {
1824+
return os.Chmod(path, 0o500)
1825+
}
1826+
return os.Chmod(path, 0o400)
1827+
})
1828+
require.NoError(t, err)
1829+
1830+
opts.ReadOnly = true
1831+
kv3, err := Open(opts)
1832+
require.NoError(t, err)
1833+
txn3 := kv3.NewTransaction(true)
1834+
_, err = txn3.Get([]byte("key1"))
1835+
require.NoError(t, err)
1836+
require.NoError(t, txn3.Commit())
1837+
require.NoError(t, kv3.Close())
1838+
1839+
// Restore permissions for cleanup
1840+
require.NoError(t, os.Chmod(dir, 0o700))
18141841
}
18151842

18161843
func TestLSMOnly(t *testing.T) {
@@ -1845,7 +1872,6 @@ func TestLSMOnly(t *testing.T) {
18451872
txnSet(t, db, []byte(fmt.Sprintf("key%d", i)), value, 0x00)
18461873
}
18471874
require.NoError(t, db.Close())
1848-
18491875
}
18501876

18511877
// This test function is doing some intricate sorcery.

errors.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,9 @@ var (
109109
// ErrGCInMemoryMode is returned when db.RunValueLogGC is called in in-memory mode.
110110
ErrGCInMemoryMode = stderrors.New("Cannot run value log GC when DB is opened in InMemory mode")
111111

112+
// ErrGCInReadOnlyMode is returned when db.RunValueLogGC is called in read-only mode.
113+
ErrGCInReadOnlyMode = stderrors.New("Cannot run value log GC when DB is opened in ReadOnly mode")
114+
112115
// ErrDBClosed is returned when a get operation is performed after closing the DB.
113116
ErrDBClosed = stderrors.New("DB Closed")
114117
)

value.go

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -544,6 +544,10 @@ func (vlog *valueLog) init(db *DB) {
544544
}
545545
vlog.dirPath = vlog.opt.ValueDir
546546

547+
if vlog.opt.ReadOnly {
548+
return
549+
}
550+
547551
vlog.garbageCh = make(chan struct{}, 1) // Only allow one GC at a time.
548552
lf, err := InitDiscardStats(vlog.opt)
549553
y.Check(err)
@@ -575,14 +579,17 @@ func (vlog *valueLog) open(db *DB) error {
575579
lf, ok := vlog.filesMap[fid]
576580
y.AssertTrue(ok)
577581

578-
// Just open in RDWR mode. This should not create a new log file.
579582
lf.opt = vlog.opt
580-
if err := lf.open(vlog.fpath(fid), os.O_RDWR,
583+
flags := os.O_RDWR
584+
if vlog.opt.ReadOnly {
585+
flags = os.O_RDONLY
586+
}
587+
if err := lf.open(vlog.fpath(fid), flags,
581588
2*vlog.opt.ValueLogFileSize); err != nil {
582589
return y.Wrapf(err, "Open existing file: %q", lf.path)
583590
}
584591
// We shouldn't delete the maxFid file.
585-
if lf.size.Load() == vlogHeaderSize && fid != vlog.maxFid {
592+
if lf.size.Load() == vlogHeaderSize && fid != vlog.maxFid && !vlog.opt.ReadOnly {
586593
vlog.opt.Infof("Deleting empty file: %s", lf.path)
587594
if err := lf.Delete(); err != nil {
588595
return y.Wrapf(err, "while trying to delete empty file: %s", lf.path)
@@ -1093,7 +1100,7 @@ func (vlog *valueLog) runGC(discardRatio float64) error {
10931100
}
10941101

10951102
func (vlog *valueLog) updateDiscardStats(stats map[uint32]int64) {
1096-
if vlog.opt.InMemory {
1103+
if vlog.opt.InMemory || vlog.opt.ReadOnly {
10971104
return
10981105
}
10991106
for fid, discard := range stats {

0 commit comments

Comments
 (0)