filer.conf: merge fsync on presence like worm (#11953)

* filer.conf: merge fsync on presence like worm

fsync is a pure preference, so OR-merging it meant a nested rule could
never turn a parent's fsync off. Make PathConf.fsync optional and merge
it most-specific-wins, matching worm: an unset rule inherits, an explicit
value overrides in either direction. readOnly and disableChunkDeletion
stay OR'ed — they are fail-closed locks quota enforcement depends on.

Pre-version-2 configs were written with EmitUnpopulated, so every rule
carries an explicit fsync=false that meant nothing; it is dropped to
unset on load so it does not read as an opt-out.

Generated with [Devin](https://devin.ai)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* filer.conf: test fsync override survives a save/reload round trip

---------

Co-authored-by: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
Chris LuandDevin authored and GitHub committed 2026-10-23 20:45:46 -07:00
1 parent 673d1a02e0
commit 8541f959a9
10 files changed
+171 -57

No files matched your search

+2 -1
View File
@@ -771,7 +771,8 @@ message FilerConf {
string replication = 3;
string ttl = 4;
string disk_type = 5;
bool fsync = 6;
// unset inherits from the enclosing path rule, set overrides it
optional bool fsync = 6;
uint32 volume_growth_count = 7;
bool read_only = 8;
string data_center = 9;
+1 -1
View File
@@ -434,7 +434,7 @@ func SyncWrite(ctx context.Context) bool {
// syncWriteContext flags ctx when the storage rule on p asks for fsync, so the
// store write is held to the same durability as the data path.
func (f *Filer) syncWriteContext(ctx context.Context, p util.FullPath) context.Context {
if f.FilerConf != nil && f.FilerConf.MatchStorageRule(string(p)).Fsync {
if f.FilerConf != nil && f.FilerConf.MatchStorageRule(string(p)).GetFsync() {
return WithSyncWrite(ctx)
}
return ctx
+21 -12
View File
@@ -33,10 +33,11 @@ const (
)
// FilerConfVersion is stamped into every configuration this build writes.
// Version 0 predates worm presence: it was written with EmitUnpopulated, so every
// rule carries an explicit "worm": false that meant nothing. Reading one back as an
// override would silently lift worm off nested paths, so it is dropped to unset.
const FilerConfVersion = 1
// Version 0 predates worm presence and version 1 predates fsync presence: both
// were written with EmitUnpopulated, so every rule carries an explicit false
// that meant nothing. Reading one back as an override would silently lift the
// setting off nested paths, so a false from an older version is dropped to unset.
const FilerConfVersion = 2
type FilerConf struct {
rules ptrie.Trie[*filer_pb.FilerConf_PathConf]
@@ -126,9 +127,12 @@ func (fc *FilerConf) LoadFromBytes(data []byte) (err error) {
func (fc *FilerConf) doLoadConf(conf *filer_pb.FilerConf) (err error) {
for _, location := range conf.Locations {
if conf.Version < FilerConfVersion && location.Worm != nil && !*location.Worm {
if conf.Version < 1 && location.Worm != nil && !*location.Worm {
location.Worm = nil
}
if conf.Version < 2 && location.Fsync != nil && !*location.Fsync {
location.Fsync = nil
}
err = fc.SetLocationConf(location)
if err != nil {
// this is not recoverable
@@ -227,7 +231,10 @@ func ClonePathConf(src *filer_pb.FilerConf_PathConf) *filer_pb.FilerConf_PathCon
if src == nil {
return &filer_pb.FilerConf_PathConf{}
}
var worm *bool
var fsync, worm *bool
if src.Fsync != nil {
fsync = proto.Bool(*src.Fsync)
}
if src.Worm != nil {
worm = proto.Bool(*src.Worm)
}
@@ -237,7 +244,7 @@ func ClonePathConf(src *filer_pb.FilerConf_PathConf) *filer_pb.FilerConf_PathCon
Replication: src.Replication,
Ttl: src.Ttl,
DiskType: src.DiskType,
Fsync: src.Fsync,
Fsync: fsync,
VolumeGrowthCount: src.VolumeGrowthCount,
ReadOnly: src.ReadOnly,
MaxFileNameLength: src.MaxFileNameLength,
@@ -484,7 +491,7 @@ func ClearBucketLifecycleDayTTLs(ctx context.Context, client filer_pb.SeaweedFil
// flag, a placement pin - is an operator's, and deleting the whole rule to
// retire its TTL would take their configuration with it.
func isLifecycleOwnedPathConf(c *filer_pb.FilerConf_PathConf) bool {
return c.GetDiskType() == "" && !c.GetFsync() && !c.GetReadOnly() &&
return c.GetDiskType() == "" && c.Fsync == nil && !c.GetReadOnly() &&
c.GetDataCenter() == "" && c.GetRack() == "" && c.GetDataNode() == "" &&
c.GetMaxFileNameLength() == 0 && !c.GetDisableChunkDeletion() &&
c.Worm == nil && c.GetWormGracePeriodSeconds() == 0 && c.GetWormRetentionTimeSeconds() == 0
@@ -507,7 +514,12 @@ func mergePathConf(a, b *filer_pb.FilerConf_PathConf) {
a.Replication = util.Nvl(b.Replication, a.Replication)
a.Ttl = util.Nvl(b.Ttl, a.Ttl)
a.DiskType = util.Nvl(b.DiskType, a.DiskType)
a.Fsync = b.Fsync || a.Fsync
// fsync and worm merge on presence, so a nested rule can turn them off.
// readOnly and disableChunkDeletion stay OR'ed on purpose: a nested rule
// must not be able to lift a lock the bucket set.
if b.Fsync != nil {
a.Fsync = proto.Bool(*b.Fsync)
}
if b.VolumeGrowthCount > 0 {
a.VolumeGrowthCount = b.VolumeGrowthCount
}
@@ -519,9 +531,6 @@ func mergePathConf(a, b *filer_pb.FilerConf_PathConf) {
a.Rack = util.Nvl(b.Rack, a.Rack)
a.DataNode = util.Nvl(b.DataNode, a.DataNode)
a.DisableChunkDeletion = b.DisableChunkDeletion || a.DisableChunkDeletion
// worm merges on presence, so a nested rule can turn it off. readOnly, fsync and
// disableChunkDeletion stay OR'ed on purpose: a nested rule must not be able to
// lift a lock the bucket set.
if b.Worm != nil {
// copy the value: a is often a scratch conf while b is a live trie entry
a.Worm = proto.Bool(*b.Worm)
+68 -1
View File
@@ -102,6 +102,54 @@ func TestWormLegacyFalseIsNotAnOverride(t *testing.T) {
assert.False(t, fc.MatchStorageRule("/buckets/b/sub/x").GetWorm())
}
func TestFsyncInheritance(t *testing.T) {
fc := NewFilerConf()
fc.doLoadConf(&filer_pb.FilerConf{
Version: FilerConfVersion,
Locations: []*filer_pb.FilerConf_PathConf{
{LocationPrefix: "/buckets/b/", Fsync: proto.Bool(true), Ttl: "7d"},
{LocationPrefix: "/buckets/b/quiet/", Collection: "quiet"},
{LocationPrefix: "/buckets/b/scratch/", Fsync: proto.Bool(false)},
{LocationPrefix: "/buckets/b/scratch/keep/", Fsync: proto.Bool(true)},
},
})
// a rule that says nothing about fsync keeps inheriting it, along with the ttl
rule := fc.MatchStorageRule("/buckets/b/quiet/x")
assert.True(t, rule.GetFsync())
assert.Equal(t, "7d", rule.Ttl)
// an explicit false turns it off, and a deeper rule turns it back on
assert.False(t, fc.MatchStorageRule("/buckets/b/scratch/x").GetFsync())
assert.True(t, fc.MatchStorageRule("/buckets/b/scratch/keep/x").GetFsync())
// paths with no rule at all are unaffected
assert.False(t, fc.MatchStorageRule("/buckets/other/x").GetFsync())
}
// TestFsyncLegacyFalseIsNotAnOverride pins that the explicit "fsync": false
// every pre-version-2 configuration carries does not read as a per-path
// opt-out.
func TestFsyncLegacyFalseIsNotAnOverride(t *testing.T) {
const conf = `{
"locations": [
{"locationPrefix": "/buckets/b/", "fsync": true},
{"locationPrefix": "/buckets/b/sub/", "collection": "sub", "fsync": false}
]
}`
for _, version := range []string{"", `"version": 0,`, `"version": 1,`} {
fc := NewFilerConf()
assert.NoError(t, fc.LoadFromBytes([]byte(`{`+version+conf[1:])))
assert.True(t, fc.MatchStorageRule("/buckets/b/sub/x").GetFsync(), version)
}
// the same file at the current version means what it says
fc := NewFilerConf()
assert.NoError(t, fc.LoadFromBytes([]byte(`{"version": 2,`+conf[1:])))
assert.False(t, fc.MatchStorageRule("/buckets/b/sub/x").GetFsync())
}
// TestWormSurvivesRoundTrip guards the write side: an explicit false has to be
// stamped along with a version that says to honor it.
func TestWormSurvivesRoundTrip(t *testing.T) {
@@ -119,6 +167,23 @@ func TestWormSurvivesRoundTrip(t *testing.T) {
assert.True(t, reloaded.MatchStorageRule("/buckets/b/other/x").GetWorm())
}
// TestFsyncSurvivesRoundTrip guards the write side: an explicit false has to be
// stamped along with a version that says to honor it.
func TestFsyncSurvivesRoundTrip(t *testing.T) {
fc := NewFilerConf()
fc.SetLocationConf(&filer_pb.FilerConf_PathConf{LocationPrefix: "/buckets/b/", Fsync: proto.Bool(true)})
fc.SetLocationConf(&filer_pb.FilerConf_PathConf{LocationPrefix: "/buckets/b/sub/", Fsync: proto.Bool(false)})
fc.SetLocationConf(&filer_pb.FilerConf_PathConf{LocationPrefix: "/buckets/b/other/", Ttl: "7d"})
var buf bytes.Buffer
assert.NoError(t, fc.ToText(&buf))
reloaded := NewFilerConf()
assert.NoError(t, reloaded.LoadFromBytes(buf.Bytes()))
assert.False(t, reloaded.MatchStorageRule("/buckets/b/sub/x").GetFsync())
assert.True(t, reloaded.MatchStorageRule("/buckets/b/other/x").GetFsync())
}
// TestClonePathConf verifies that ClonePathConf copies all exported fields.
// Uses reflection to automatically detect new fields added to the protobuf,
// ensuring the test fails if ClonePathConf is not updated for new fields.
@@ -130,7 +195,7 @@ func TestClonePathConf(t *testing.T) {
Replication: "001",
Ttl: "7d",
DiskType: "ssd",
Fsync: true,
Fsync: proto.Bool(true),
VolumeGrowthCount: 5,
ReadOnly: true,
MaxFileNameLength: 255,
@@ -189,9 +254,11 @@ func TestClonePathConf(t *testing.T) {
// Verify mutation of clone doesn't affect source
clone.Collection = "modified"
clone.ReadOnly = false
*clone.Fsync = false
*clone.Worm = false
assert.Equal(t, "test_collection", src.Collection, "Modifying clone should not affect source Collection")
assert.Equal(t, true, src.ReadOnly, "Modifying clone should not affect source ReadOnly")
assert.Equal(t, true, src.GetFsync(), "Modifying clone should not affect source Fsync")
assert.Equal(t, true, src.GetWorm(), "Modifying clone should not affect source Worm")
}
+2 -1
View File
@@ -786,7 +786,8 @@ message FilerConf {
string replication = 3;
string ttl = 4;
string disk_type = 5;
bool fsync = 6;
// unset inherits from the enclosing path rule, set overrides it
optional bool fsync = 6;
uint32 volume_growth_count = 7;
bool read_only = 8;
string data_center = 9;
+24 -22
View File
@@ -7102,20 +7102,21 @@ func (x *LocateBrokerResponse_Resource) GetResourceCount() int32 {
}
type FilerConf_PathConf struct {
state protoimpl.MessageState `protogen:"open.v1"`
LocationPrefix string `protobuf:"bytes,1,opt,name=location_prefix,json=locationPrefix,proto3" json:"location_prefix,omitempty"`
Collection string `protobuf:"bytes,2,opt,name=collection,proto3" json:"collection,omitempty"`
Replication string `protobuf:"bytes,3,opt,name=replication,proto3" json:"replication,omitempty"`
Ttl string `protobuf:"bytes,4,opt,name=ttl,proto3" json:"ttl,omitempty"`
DiskType string `protobuf:"bytes,5,opt,name=disk_type,json=diskType,proto3" json:"disk_type,omitempty"`
Fsync bool `protobuf:"varint,6,opt,name=fsync,proto3" json:"fsync,omitempty"`
VolumeGrowthCount uint32 `protobuf:"varint,7,opt,name=volume_growth_count,json=volumeGrowthCount,proto3" json:"volume_growth_count,omitempty"`
ReadOnly bool `protobuf:"varint,8,opt,name=read_only,json=readOnly,proto3" json:"read_only,omitempty"`
DataCenter string `protobuf:"bytes,9,opt,name=data_center,json=dataCenter,proto3" json:"data_center,omitempty"`
Rack string `protobuf:"bytes,10,opt,name=rack,proto3" json:"rack,omitempty"`
DataNode string `protobuf:"bytes,11,opt,name=data_node,json=dataNode,proto3" json:"data_node,omitempty"`
MaxFileNameLength uint32 `protobuf:"varint,12,opt,name=max_file_name_length,json=maxFileNameLength,proto3" json:"max_file_name_length,omitempty"`
DisableChunkDeletion bool `protobuf:"varint,13,opt,name=disable_chunk_deletion,json=disableChunkDeletion,proto3" json:"disable_chunk_deletion,omitempty"`
state protoimpl.MessageState `protogen:"open.v1"`
LocationPrefix string `protobuf:"bytes,1,opt,name=location_prefix,json=locationPrefix,proto3" json:"location_prefix,omitempty"`
Collection string `protobuf:"bytes,2,opt,name=collection,proto3" json:"collection,omitempty"`
Replication string `protobuf:"bytes,3,opt,name=replication,proto3" json:"replication,omitempty"`
Ttl string `protobuf:"bytes,4,opt,name=ttl,proto3" json:"ttl,omitempty"`
DiskType string `protobuf:"bytes,5,opt,name=disk_type,json=diskType,proto3" json:"disk_type,omitempty"`
// unset inherits from the enclosing path rule, set overrides it
Fsync *bool `protobuf:"varint,6,opt,name=fsync,proto3,oneof" json:"fsync,omitempty"`
VolumeGrowthCount uint32 `protobuf:"varint,7,opt,name=volume_growth_count,json=volumeGrowthCount,proto3" json:"volume_growth_count,omitempty"`
ReadOnly bool `protobuf:"varint,8,opt,name=read_only,json=readOnly,proto3" json:"read_only,omitempty"`
DataCenter string `protobuf:"bytes,9,opt,name=data_center,json=dataCenter,proto3" json:"data_center,omitempty"`
Rack string `protobuf:"bytes,10,opt,name=rack,proto3" json:"rack,omitempty"`
DataNode string `protobuf:"bytes,11,opt,name=data_node,json=dataNode,proto3" json:"data_node,omitempty"`
MaxFileNameLength uint32 `protobuf:"varint,12,opt,name=max_file_name_length,json=maxFileNameLength,proto3" json:"max_file_name_length,omitempty"`
DisableChunkDeletion bool `protobuf:"varint,13,opt,name=disable_chunk_deletion,json=disableChunkDeletion,proto3" json:"disable_chunk_deletion,omitempty"`
// unset inherits from the enclosing path rule, set overrides it
Worm *bool `protobuf:"varint,14,opt,name=worm,proto3,oneof" json:"worm,omitempty"`
WormGracePeriodSeconds uint64 `protobuf:"varint,15,opt,name=worm_grace_period_seconds,json=wormGracePeriodSeconds,proto3" json:"worm_grace_period_seconds,omitempty"`
@@ -7190,8 +7191,8 @@ func (x *FilerConf_PathConf) GetDiskType() string {
}
func (x *FilerConf_PathConf) GetFsync() bool {
if x != nil {
return x.Fsync
if x != nil && x.Fsync != nil {
return *x.Fsync
}
return false
}
@@ -7764,10 +7765,10 @@ const file_filer_proto_rawDesc = "" +
"\x03key\x18\x01 \x01(\fR\x03key\x12\x14\n" +
"\x05value\x18\x02 \x01(\fR\x05value\"%\n" +
"\rKvPutResponse\x12\x14\n" +
"\x05error\x18\x01 \x01(\tR\x05error\"\xc0\x05\n" +
"\x05error\x18\x01 \x01(\tR\x05error\"\xcf\x05\n" +
"\tFilerConf\x12\x18\n" +
"\aversion\x18\x01 \x01(\x05R\aversion\x12:\n" +
"\tlocations\x18\x02 \x03(\v2\x1c.filer_pb.FilerConf.PathConfR\tlocations\x1a\xdc\x04\n" +
"\tlocations\x18\x02 \x03(\v2\x1c.filer_pb.FilerConf.PathConfR\tlocations\x1a\xeb\x04\n" +
"\bPathConf\x12'\n" +
"\x0flocation_prefix\x18\x01 \x01(\tR\x0elocationPrefix\x12\x1e\n" +
"\n" +
@@ -7775,8 +7776,8 @@ const file_filer_proto_rawDesc = "" +
"collection\x12 \n" +
"\vreplication\x18\x03 \x01(\tR\vreplication\x12\x10\n" +
"\x03ttl\x18\x04 \x01(\tR\x03ttl\x12\x1b\n" +
"\tdisk_type\x18\x05 \x01(\tR\bdiskType\x12\x14\n" +
"\x05fsync\x18\x06 \x01(\bR\x05fsync\x12.\n" +
"\tdisk_type\x18\x05 \x01(\tR\bdiskType\x12\x19\n" +
"\x05fsync\x18\x06 \x01(\bH\x00R\x05fsync\x88\x01\x01\x12.\n" +
"\x13volume_growth_count\x18\a \x01(\rR\x11volumeGrowthCount\x12\x1b\n" +
"\tread_only\x18\b \x01(\bR\breadOnly\x12\x1f\n" +
"\vdata_center\x18\t \x01(\tR\n" +
@@ -7786,9 +7787,10 @@ const file_filer_proto_rawDesc = "" +
"\tdata_node\x18\v \x01(\tR\bdataNode\x12/\n" +
"\x14max_file_name_length\x18\f \x01(\rR\x11maxFileNameLength\x124\n" +
"\x16disable_chunk_deletion\x18\r \x01(\bR\x14disableChunkDeletion\x12\x17\n" +
"\x04worm\x18\x0e \x01(\bH\x00R\x04worm\x88\x01\x01\x129\n" +
"\x04worm\x18\x0e \x01(\bH\x01R\x04worm\x88\x01\x01\x129\n" +
"\x19worm_grace_period_seconds\x18\x0f \x01(\x04R\x16wormGracePeriodSeconds\x12=\n" +
"\x1bworm_retention_time_seconds\x18\x10 \x01(\x04R\x18wormRetentionTimeSecondsB\a\n" +
"\x1bworm_retention_time_seconds\x18\x10 \x01(\x04R\x18wormRetentionTimeSecondsB\b\n" +
"\x06_fsyncB\a\n" +
"\x05_worm\"\xba\x01\n" +
"&CacheRemoteObjectToLocalClusterRequest\x12\x1c\n" +
"\tdirectory\x18\x01 \x01(\tR\tdirectory\x12\x12\n" +
+1 -1
View File
@@ -1,7 +1,7 @@
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
// versions:
// - protoc-gen-go-grpc v1.6.2
// - protoc v3.21.12
// - protoc v7.35.0
// source: filer.proto
package filer_pb
+38 -4
View File
@@ -2659,6 +2659,16 @@ func (m *AtomicRenameEntryRequest) MarshalToSizedBufferVT(dAtA []byte) (int, err
i -= len(m.unknownFields)
copy(dAtA[i:], m.unknownFields)
}
if m.FailIfTargetExists {
i--
if m.FailIfTargetExists {
dAtA[i] = 1
} else {
dAtA[i] = 0
}
i--
dAtA[i] = 0x30
}
if len(m.Signatures) > 0 {
var pksize2 int
for _, num := range m.Signatures {
@@ -4984,9 +4994,9 @@ func (m *FilerConf_PathConf) MarshalToSizedBufferVT(dAtA []byte) (int, error) {
i--
dAtA[i] = 0x38
}
if m.Fsync {
if m.Fsync != nil {
i--
if m.Fsync {
if *m.Fsync {
dAtA[i] = 1
} else {
dAtA[i] = 0
@@ -7599,6 +7609,9 @@ func (m *AtomicRenameEntryRequest) SizeVT() (n int) {
}
n += 1 + protohelpers.SizeOfVarint(uint64(l)) + l
}
if m.FailIfTargetExists {
n += 2
}
n += len(m.unknownFields)
return n
}
@@ -8501,7 +8514,7 @@ func (m *FilerConf_PathConf) SizeVT() (n int) {
if l > 0 {
n += 1 + l + protohelpers.SizeOfVarint(uint64(l))
}
if m.Fsync {
if m.Fsync != nil {
n += 2
}
if m.VolumeGrowthCount != 0 {
@@ -16531,6 +16544,26 @@ func (m *AtomicRenameEntryRequest) UnmarshalVT(dAtA []byte) error {
} else {
return fmt.Errorf("proto: wrong wireType = %d for field Signatures", wireType)
}
case 6:
if wireType != 0 {
return fmt.Errorf("proto: wrong wireType = %d for field FailIfTargetExists", wireType)
}
var v int
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return protohelpers.ErrIntOverflow
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
v |= int(b&0x7F) << shift
if b < 0x80 {
break
}
}
m.FailIfTargetExists = bool(v != 0)
default:
iNdEx = preIndex
skippy, err := protohelpers.Skip(dAtA[iNdEx:])
@@ -22263,7 +22296,8 @@ func (m *FilerConf_PathConf) UnmarshalVT(dAtA []byte) error {
break
}
}
m.Fsync = bool(v != 0)
b := bool(v != 0)
m.Fsync = &b
case 7:
if wireType != 0 {
return fmt.Errorf("proto: wrong wireType = %d for field VolumeGrowthCount", wireType)
+1 -1
View File
@@ -337,7 +337,7 @@ func (fs *FilerServer) detectStorageOption(ctx context.Context, requestURI, qCol
DataNode: util.Nvl(dataNode, rule.DataNode, fs.option.DataNode),
TtlSeconds: ttlSeconds,
DiskType: util.Nvl(diskType, overlayDisk, rule.DiskType),
Fsync: rule.Fsync,
Fsync: rule.GetFsync(),
VolumeGrowthCount: rule.VolumeGrowthCount,
MaxFileNameLength: maxFileNameLength,
// A location that survives from a request parameter, a placement
+13 -13
View File
@@ -67,7 +67,7 @@ func (c *commandFsConfigure) Do(args []string, commandEnv *CommandEnv, writer io
replication := fsConfigureCommand.String("replication", "", "assign writes with this replication")
ttl := fsConfigureCommand.String("ttl", "", "assign writes with this ttl (e.g., 1m, 1h, 1d, 1w, 1y)")
diskType := fsConfigureCommand.String("disk", "", "[hdd|ssd|<tag>] hard drive or solid state drive or any tag")
fsync := fsConfigureCommand.Bool("fsync", false, "fsync for the writes")
fsync := fsConfigureCommand.Bool("fsync", false, "fsync for the writes; unset inherits from the parent path")
isReadOnly := fsConfigureCommand.Bool("readOnly", false, "disable writes")
worm := fsConfigureCommand.Bool("worm", false, "write-once-read-many, written files are readonly; unset inherits from the parent path")
wormGracePeriod := fsConfigureCommand.Uint64("wormGracePeriod", 0, "grace period before worm is enforced, in seconds")
@@ -101,7 +101,6 @@ func (c *commandFsConfigure) Do(args []string, commandEnv *CommandEnv, writer io
Collection: *collection,
Replication: *replication,
Ttl: *ttl,
Fsync: *fsync,
MaxFileNameLength: uint32(*maxFileNameLength),
DiskType: *diskType,
VolumeGrowthCount: uint32(*volumeGrowthCount),
@@ -113,10 +112,13 @@ func (c *commandFsConfigure) Do(args []string, commandEnv *CommandEnv, writer io
WormRetentionTimeSeconds: *wormRetentionTime,
}
// worm is only carried when the flag is passed, so a rule that says nothing
// about it keeps inheriting from the enclosing path
// fsync and worm are only carried when the flag is passed, so a rule that
// says nothing about them keeps inheriting from the enclosing path
fsConfigureCommand.Visit(func(f *flag.Flag) {
if f.Name == "worm" {
switch f.Name {
case "fsync":
locConf.Fsync = proto.Bool(*fsync)
case "worm":
locConf.Worm = proto.Bool(*worm)
}
})
@@ -152,17 +154,15 @@ func (c *commandFsConfigure) Do(args []string, commandEnv *CommandEnv, writer io
fc.DeleteLocationConf(*locationPrefix)
} else {
fc.AddLocationConf(locConf)
// AddLocationConf merges these boolean fields with OR, which can never
// turn a flag off; let an explicitly passed false win, e.g. -readOnly=false
// to reopen a bucket that quota enforcement locked. worm does not belong
// here: it merges on presence, so the value set above already wins.
// AddLocationConf merges this boolean field with OR, which can never
// turn it off; let an explicitly passed false win, e.g. -readOnly=false
// to reopen a bucket that quota enforcement locked. fsync and worm do
// not belong here: they merge on presence, so the values set above
// already win.
if mergedConf, found := fc.GetLocationConf(*locationPrefix); found {
fsConfigureCommand.Visit(func(f *flag.Flag) {
switch f.Name {
case "readOnly":
if f.Name == "readOnly" {
mergedConf.ReadOnly = *isReadOnly
case "fsync":
mergedConf.Fsync = *fsync
}
})
}