Merge 1c460a38536faedf0d7a8318fb9a616a28d35dc7 into 4d38424e6cbb32d07c74d3b4d760af7afb35742d

This commit is contained in:
Paras Raba 2025-03-19 07:22:03 +01:00 committed by GitHub
commit 8ddac2b7b3
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 84 additions and 2 deletions

View File

@ -50,6 +50,10 @@ func init() {
Advanced: true,
Default: fs.SizeSuffix(0),
Help: "Auto-update checksum for files smaller than this size (disabled by default).",
}, {
Name: "read_only",
Default: false,
Help: "Set the db in read only mode",
}},
})
}
@ -60,6 +64,7 @@ type Options struct {
Hashes fs.CommaSepList `config:"hashes"`
AutoSize fs.SizeSuffix `config:"auto_size"`
MaxAge fs.Duration `config:"max_age"`
ReadOnly bool `config:"read_only"`
}
// Fs represents a wrapped fs.Fs

View File

@ -4,10 +4,14 @@ import (
"context"
"fmt"
"os"
"strings"
"testing"
"time"
"github.com/rclone/rclone/fs"
"github.com/rclone/rclone/fs/config/configmap"
"github.com/rclone/rclone/fs/config/obscure"
"github.com/rclone/rclone/fs/object"
"github.com/rclone/rclone/fs/operations"
"github.com/rclone/rclone/fstest"
"github.com/rclone/rclone/fstest/fstests"
@ -75,6 +79,38 @@ func (f *Fs) InternalTest(t *testing.T) {
t.Skip("hasher is not supported on this OS")
}
t.Run("UploadFromCrypt", f.testUploadFromCrypt)
t.Run("ReadOnlyFlag", f.testReadOnlyFlag)
}
var _ fstests.InternalTester = (*Fs)(nil)
type testGetter struct{}
func (s *testGetter) Get(key string) (value string, ok bool) {
switch key {
case "remote":
return "/hasher-test", true
case "hashes":
return "md5", true
case "max_age":
return "off", true
case "auto_size":
return "0", true
case "read_only":
return "true", true
default:
return key, true
}
}
func (f *Fs) testReadOnlyFlag(t *testing.T) {
ctx := context.Background()
mapper := configmap.New()
mapper.AddGetter(&testGetter{}, 1)
hasherFs, err := NewFs(ctx, "hasher-test", "/hasher-test", mapper)
assert.NoError(t, err)
assert.NotNil(t, hasherFs)
fileInfo := object.NewStaticObjectInfo("hasher-test", time.Now(), 128, true, nil, nil)
_, err = hasherFs.Put(ctx, strings.NewReader("dogs and cats"), fileInfo)
assert.Error(t, err)
assert.Contains(t, err.Error(), "read-only file system")
}

View File

@ -11,6 +11,7 @@ import (
"github.com/rclone/rclone/fs"
"github.com/rclone/rclone/fs/hash"
"github.com/rclone/rclone/fs/operations"
"github.com/rclone/rclone/lib/kv"
)
// obtain hash for an object
@ -58,12 +59,17 @@ func (o *Object) putHashes(ctx context.Context, rawHashes hashMap) error {
// set hashes for a path without any validation
func (f *Fs) putRawHashes(ctx context.Context, key, fp string, hashes operations.HashSums) error {
return f.db.Do(true, &kvPut{
err := f.db.Do(true, &kvPut{
key: key,
fp: fp,
hashes: hashes,
age: time.Duration(f.opt.MaxAge),
})
if f.opt.ReadOnly && errors.Is(err, kv.ErrReadOnly) {
fs.Debugf(nil, "database in read only mode")
return nil
}
return err
}
// Hash returns the selected checksum of the file or "" if unavailable.

View File

@ -33,6 +33,7 @@ type DB struct {
refs int
bolt *bbolt.DB
mu sync.Mutex
readOnly bool
canWrite bool
queue chan *request
lockTime time.Duration
@ -138,6 +139,10 @@ func (db *DB) open(ctx context.Context, forWrite bool) (err error) {
}
_ = db.close()
if db.readOnly && forWrite {
return ErrReadOnly
}
db.canWrite = forWrite
if !forWrite {
// mitigate https://github.com/etcd-io/bbolt/issues/98
@ -228,6 +233,11 @@ func (db *DB) Do(write bool, op Op) error {
return r.err
}
// ReadOnly setter the db.readOnly
func (db *DB) ReadOnly(b bool) {
db.readOnly = b
}
// request encapsulates a synchronous operation and its results
type request struct {
op Op

View File

@ -66,3 +66,27 @@ func TestKvExit(t *testing.T) {
Exit()
assert.Equal(t, 0, len(dbMap))
}
func TestDbReadOnly(t *testing.T) {
require.Equal(t, 0, len(dbMap), "no databases can be started initially")
ctx := context.Background()
db, err := Start(ctx, "test", nil)
require.NoError(t, err)
require.NotNil(t, db)
assert.False(t, db.readOnly)
// set db in read only
db.ReadOnly(true)
assert.True(t, db.readOnly)
// write op is not allowed in read only mode, should throw error
err = db.Do(true, nil)
assert.ErrorIs(t, err, ErrReadOnly)
db.ReadOnly(false)
err = db.Do(true, &opStop{})
assert.NoError(t, err)
}

View File

@ -10,6 +10,7 @@ var (
ErrEmpty = errors.New("database empty")
ErrInactive = errors.New("database stopped")
ErrUnsupported = errors.New("unsupported on this OS")
ErrReadOnly = errors.New("database in read-only mode")
)
// Op represents a database operation