Skip to content

Commit

Permalink
prevent MoveBucket from moving a bucket across two different db files
Browse files Browse the repository at this point in the history
Signed-off-by: Mustafa Elbehery <melbeher@redhat.com>
  • Loading branch information
Elbehery committed Jan 10, 2024
1 parent 4a059b4 commit ff0b03b
Show file tree
Hide file tree
Showing 3 changed files with 134 additions and 2 deletions.
7 changes: 6 additions & 1 deletion bucket.go
Original file line number Diff line number Diff line change
Expand Up @@ -343,10 +343,15 @@ func (b *Bucket) MoveBucket(key []byte, dstBucket *Bucket) (err error) {

if b.tx.db == nil || dstBucket.tx.db == nil {
return errors.ErrTxClosed
} else if !dstBucket.Writable() {
} else if !b.Writable() || !dstBucket.Writable() {
return errors.ErrTxNotWritable
}

if b.tx.db.Path() != dstBucket.tx.db.Path() || b.tx != dstBucket.tx {
lg.Errorf("The source and target buckets are not in the same db file, source bucket in %s and target bucket in %s", b.tx.db.Path(), dstBucket.tx.db.Path())
return errors.ErrDifferentDB
}

newKey := cloneBytes(key)

// Move cursor to correct position.
Expand Down
4 changes: 4 additions & 0 deletions errors/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -77,4 +77,8 @@ var (
// ErrSameBuckets is returned when trying to move a sub-bucket between
// source and target buckets, while source and target buckets are the same.
ErrSameBuckets = errors.New("the source and target are the same bucket")

// ErrDifferentDB is returned when trying to move a sub-bucket between
// source and target buckets, while source and target buckets are in different database files.
ErrDifferentDB = errors.New("the source and target buckets are in different database files")
)
125 changes: 124 additions & 1 deletion movebucket_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,7 @@ func TestTx_MoveBucket(t *testing.T) {
for _, tc := range testCases {

t.Run(tc.name, func(*testing.T) {
db := btesting.MustCreateDBWithOption(t, &bbolt.Options{PageSize: 4096})
db := btesting.MustCreateDBWithOption(t, &bbolt.Options{PageSize: pageSize})

dumpBucketBeforeMoving := filepath.Join(t.TempDir(), "dbBeforeMove")
dumpBucketAfterMoving := filepath.Join(t.TempDir(), "dbAfterMove")
Expand Down Expand Up @@ -216,6 +216,129 @@ func TestTx_MoveBucket(t *testing.T) {
}
}

func TestBucket_MoveBucket_DiffDB(t *testing.T) {
srcBucketPath := []string{"sb1", "sb2"}
dstBucketPath := []string{"db1", "db2"}
bucketToMove := "bucketToMove"
expectedErr := errors.ErrDifferentDB

var srcBucket *bbolt.Bucket

t.Log("Creating source bucket and populate some data")
db1 := btesting.MustCreateDBWithOption(t, &bbolt.Options{PageSize: pageSize})
err := db1.Update(func(tx *bbolt.Tx) error {
srcBucket = prepareBuckets(t, tx, srcBucketPath...)
return nil
})
db1.MustClose()
require.NoError(t, err)

t.Log("Creating target bucket and populate some data")
db2 := btesting.MustCreateDBWithOption(t, &bbolt.Options{PageSize: pageSize})
err = db2.Update(func(tx *bbolt.Tx) error {
prepareBuckets(t, tx, dstBucketPath...)
return nil
})
db2.MustClose()
require.NoError(t, err)

t.Log("Re-opening source bucket in read-only mode")
srcDB, sErr := bbolt.Open(db1.Path(), 0400, &bbolt.Options{ReadOnly: true})
require.NoError(t, sErr)
defer srcDB.Close()
tx, sErr := srcDB.Begin(false)
defer tx.Rollback()
srcBucket = prepareBuckets(t, tx, srcBucketPath...)

t.Log("Re-opening target bucket in RW mode")
dstDB, dErr := bbolt.Open(db2.Path(), 0600, bbolt.DefaultOptions)
require.NoError(t, dErr)

t.Log("Moving the sub-bucket")
err = dstDB.Update(func(tx *bbolt.Tx) error {
dstBucket := prepareBuckets(t, tx, dstBucketPath...)
mErr := srcBucket.MoveBucket([]byte(bucketToMove), dstBucket)
require.Equal(t, expectedErr, mErr)

return nil
})
require.NoError(t, err)
}

func TestBucket_MoveBucket_DiffTx(t *testing.T) {
testCases := []struct {
name string
srcBucketPath []string
srcRWTx bool
bucketToMove string
dstBucketPath []string
dstRWTx bool
expectedErr error
}{
{
name: "src is RWTx and target is RTx",
srcRWTx: true,
srcBucketPath: []string{"sb1", "sb2"},
bucketToMove: "bucketToMove",
dstBucketPath: []string{"db1", "db2"},
dstRWTx: false,
expectedErr: errors.ErrTxNotWritable,
},
{
name: "src is RTx and target is RWTx",
srcRWTx: false,
srcBucketPath: []string{"sb1", "sb2"},
bucketToMove: "bucketToMove",
dstBucketPath: []string{"db1", "db2"},
dstRWTx: true,
expectedErr: errors.ErrTxNotWritable,
},
}

for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
var srcBucket *bbolt.Bucket
var dstBucket *bbolt.Bucket

t.Log("Creating source and target buckets and populate some data")
db := btesting.MustCreateDBWithOption(t, &bbolt.Options{PageSize: pageSize})
err := db.Update(func(tx *bbolt.Tx) error {
srcBucket = prepareBuckets(t, tx, tc.srcBucketPath...)
dstBucket = prepareBuckets(t, tx, tc.dstBucketPath...)
return nil
})
db.MustClose()
require.NoError(t, err)

t.Log("Re-opening the database")
dB, dErr := bbolt.Open(db.Path(), 0600, bbolt.DefaultOptions)
require.NoError(t, dErr)
defer dB.Close()

t.Log("Re-opening source bucket")
sTx, sErr := dB.Begin(tc.srcRWTx)
defer sTx.Rollback()
require.NoError(t, sErr)
srcBucket = prepareBuckets(t, sTx, tc.srcBucketPath...)

t.Log("Re-opening target bucket")
dTx, dErr := dB.Begin(tc.dstRWTx)
defer dTx.Rollback()
require.NoError(t, dErr)
dstBucket = prepareBuckets(t, dTx, tc.dstBucketPath...)

t.Log("Moving the sub-bucket")
err = dB.View(func(tx *bbolt.Tx) error {
mErr := srcBucket.MoveBucket([]byte(tc.bucketToMove), dstBucket)
require.Equal(t, tc.expectedErr, mErr)

return nil
})
require.NoError(t, err)
})
}
}

// prepareBuckets opens the bucket chain. For each bucket in the chain,
// open it if existed, otherwise create it and populate sample data.
func prepareBuckets(t testing.TB, tx *bbolt.Tx, buckets ...string) *bbolt.Bucket {
Expand Down

0 comments on commit ff0b03b

Please sign in to comment.