mirror of
https://github.com/versity/versitygw.git
synced 2026-09-23 16:34:18 +00:00
Conditional PUTs require a lock primitive that excludes competing gateway processes sharing a backend filesystem. A successful flock call does not prove that property: some clustered filesystem configurations accept flock but scope it to one node, silently leaving cross-gateway check-and-publish races open. Add an object-lock mode that lets operators select flock or fcntl for filesystems where that primitive is cluster-coherent, local for the existing per-process behavior, or none to reject conditional writes with NotImplemented. Keep the legacy disable flag as an alias for local. Shared lock modes now verify the selected primitive on the root lock filesystem during startup and fail closed if it cannot be used. Runtime lock failures no longer silently downgrade to process-local exclusion. The startup check cannot establish cross-node coherence, so that remains an explicit operator requirement. ScoutFS defaults to none since posix locks are not cluster consistent, but allow setting local for single node deployments. Fixes #2351
251 lines
8.0 KiB
Go
251 lines
8.0 KiB
Go
// Copyright 2023 Versity Software
|
|
// This file is licensed under the Apache License, Version 2.0
|
|
// (the "License"); you may not use this file except in compliance
|
|
// with the License. You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing,
|
|
// software distributed under the License is distributed on an
|
|
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
|
// KIND, either express or implied. See the License for the
|
|
// specific language governing permissions and limitations
|
|
// under the License.
|
|
|
|
package gwcli
|
|
|
|
import (
|
|
"fmt"
|
|
"io/fs"
|
|
"math"
|
|
|
|
"github.com/urfave/cli/v2"
|
|
"github.com/versity/versitygw/backend/meta"
|
|
"github.com/versity/versitygw/backend/posix"
|
|
)
|
|
|
|
// maxFilePerms is the highest accepted value for the file-perms option. The
|
|
// posix backend only applies permission bits to new objects, so anything
|
|
// above this would be silently dropped.
|
|
const maxFilePerms = 0777
|
|
|
|
var (
|
|
chownuid, chowngid bool
|
|
bucketlinks bool
|
|
versioningDir string
|
|
dirPerms uint
|
|
filePerms uint
|
|
sidecar string
|
|
nometa bool
|
|
forceNoTmpFile bool
|
|
forceNoCopyFileRange bool
|
|
forceNoObjLockFile bool
|
|
objectLockMode string
|
|
enableODirect bool
|
|
actionsConcurrency int
|
|
ioBufferSize int
|
|
defaultEtag string
|
|
dataIntegrityEtag bool
|
|
)
|
|
|
|
// PosixCommand returns the "posix" subcommand, common to all versitygw
|
|
// binaries.
|
|
func PosixCommand() *cli.Command {
|
|
return &cli.Command{
|
|
Name: "posix",
|
|
Usage: "posix filesystem storage backend",
|
|
Description: `Any posix filesystem that supports extended attributes. The top level
|
|
directory for the gateway must be provided. All sub directories of the
|
|
top level directory are treated as buckets, and all files/directories
|
|
below the "bucket directory" are treated as the objects. The object
|
|
name is split on "/" separator to translate to posix storage.
|
|
For example:
|
|
top level: /mnt/fs/gwroot
|
|
bucket: mybucket
|
|
object: a/b/c/myobject
|
|
will be translated into the file /mnt/fs/gwroot/mybucket/a/b/c/myobject`,
|
|
Action: runPosix,
|
|
Flags: []cli.Flag{
|
|
&cli.BoolFlag{
|
|
Name: "chuid",
|
|
Usage: "chown newly created files and directories to client account UID",
|
|
EnvVars: []string{"VGW_CHOWN_UID"},
|
|
Destination: &chownuid,
|
|
},
|
|
&cli.BoolFlag{
|
|
Name: "chgid",
|
|
Usage: "chown newly created files and directories to client account GID",
|
|
EnvVars: []string{"VGW_CHOWN_GID"},
|
|
Destination: &chowngid,
|
|
},
|
|
&cli.BoolFlag{
|
|
Name: "bucketlinks",
|
|
Usage: "allow symlinked directories at bucket level to be treated as buckets",
|
|
EnvVars: []string{"VGW_BUCKET_LINKS"},
|
|
Destination: &bucketlinks,
|
|
},
|
|
&cli.StringFlag{
|
|
Name: "versioning-dir",
|
|
Usage: "the directory path to enable bucket versioning",
|
|
EnvVars: []string{"VGW_VERSIONING_DIR"},
|
|
Destination: &versioningDir,
|
|
},
|
|
&cli.UintFlag{
|
|
Name: "dir-perms",
|
|
Usage: "default directory permissions for new directories",
|
|
EnvVars: []string{"VGW_DIR_PERMS"},
|
|
Destination: &dirPerms,
|
|
DefaultText: "0755",
|
|
Value: 0755,
|
|
},
|
|
&cli.UintFlag{
|
|
Name: "file-perms",
|
|
Usage: "default file permissions for new objects",
|
|
EnvVars: []string{"VGW_FILE_PERMS"},
|
|
Destination: &filePerms,
|
|
DefaultText: "0644",
|
|
Value: 0644,
|
|
},
|
|
&cli.StringFlag{
|
|
Name: "sidecar",
|
|
Usage: "use provided sidecar directory to store metadata",
|
|
EnvVars: []string{"VGW_META_SIDECAR"},
|
|
Destination: &sidecar,
|
|
},
|
|
&cli.IntFlag{
|
|
Name: "concurrency",
|
|
Usage: "maximum concurrent actions allowed",
|
|
EnvVars: []string{"VGW_POSIX_CONCURRENCY"},
|
|
Value: 5000,
|
|
Destination: &actionsConcurrency,
|
|
},
|
|
&cli.IntFlag{
|
|
Name: "io-buffer-size",
|
|
Usage: "buffer size in bytes used by POSIX put/get/part read and write paths (<=0 uses backend default 1MiB)",
|
|
EnvVars: []string{"VGW_POSIX_IO_BUFFER_SIZE"},
|
|
Value: 1024 * 1024,
|
|
Destination: &ioBufferSize,
|
|
},
|
|
&cli.BoolFlag{
|
|
Name: "nometa",
|
|
Usage: "disable metadata storage",
|
|
EnvVars: []string{"VGW_META_NONE"},
|
|
Destination: &nometa,
|
|
},
|
|
&cli.BoolFlag{
|
|
Name: "disableotmp",
|
|
Usage: "disable O_TMPFILE support for new objects",
|
|
EnvVars: []string{"VGW_DISABLE_OTMP"},
|
|
Destination: &forceNoTmpFile,
|
|
},
|
|
&cli.BoolFlag{
|
|
Name: "disable-copy-file-range",
|
|
Usage: "explicitly copy multipart upload parts instead of using copy_file_range (which may hang with some NFS servers)",
|
|
EnvVars: []string{"VGW_DISABLE_COPY_FILE_RANGE"},
|
|
Destination: &forceNoCopyFileRange,
|
|
},
|
|
&cli.BoolFlag{
|
|
Name: "disable-object-lock-file",
|
|
Usage: "deprecated alias for --object-lock-mode=local",
|
|
EnvVars: []string{"VGW_DISABLE_OBJECT_LOCK_FILE"},
|
|
Destination: &forceNoObjLockFile,
|
|
},
|
|
&cli.StringFlag{
|
|
Name: "object-lock-mode",
|
|
Usage: "lock mode for conditional object publishes: flock, fcntl, local, or none",
|
|
EnvVars: []string{"VGW_OBJECT_LOCK_MODE"},
|
|
DefaultText: "flock",
|
|
Destination: &objectLockMode,
|
|
},
|
|
&cli.BoolFlag{
|
|
Name: "enable-odirect",
|
|
Usage: "enable best-effort O_DIRECT for object data reads/writes",
|
|
EnvVars: []string{"VGW_ENABLE_O_DIRECT"},
|
|
Destination: &enableODirect,
|
|
},
|
|
&cli.StringFlag{
|
|
Name: "default-etag",
|
|
Usage: "default ETag value returned for objects that do not have a stored etag attribute (e.g. files placed on the filesystem outside of versitygw)",
|
|
EnvVars: []string{"VGW_DEFAULT_ETAG"},
|
|
Destination: &defaultEtag,
|
|
},
|
|
&cli.BoolFlag{
|
|
Name: "data-integrity-etag",
|
|
Usage: "use data-integrity checksum-derived ETags instead of MD5-based ETags (PUT object ETag, multipart part ETags, and completed multipart object ETag)",
|
|
EnvVars: []string{"VGW_DATA_INTEGRITY_ETAG"},
|
|
Destination: &dataIntegrityEtag,
|
|
},
|
|
},
|
|
}
|
|
}
|
|
|
|
func runPosix(ctx *cli.Context) error {
|
|
if ctx.NArg() == 0 {
|
|
return fmt.Errorf("no directory provided for operation")
|
|
}
|
|
|
|
gwroot := (ctx.Args().Get(0))
|
|
|
|
if dirPerms > math.MaxUint32 {
|
|
return fmt.Errorf("invalid directory permissions: %d", dirPerms)
|
|
}
|
|
|
|
if filePerms > maxFilePerms {
|
|
return fmt.Errorf("invalid file permissions: %o, must be within 0000-0777", filePerms)
|
|
}
|
|
|
|
if nometa && sidecar != "" {
|
|
return fmt.Errorf("cannot use both nometa and sidecar metadata")
|
|
}
|
|
|
|
if actionsConcurrency <= 0 {
|
|
return fmt.Errorf("concurrency must be positive, got %d", actionsConcurrency)
|
|
}
|
|
|
|
opts := posix.PosixOpts{
|
|
ChownUID: chownuid,
|
|
ChownGID: chowngid,
|
|
BucketLinks: bucketlinks,
|
|
VersioningDir: versioningDir,
|
|
ForceNoTmpFile: forceNoTmpFile,
|
|
ForceNoCopyFileRange: forceNoCopyFileRange,
|
|
ForceNoObjLockFile: forceNoObjLockFile,
|
|
ObjectLockMode: posix.ObjectLockMode(objectLockMode),
|
|
EnableODirect: enableODirect,
|
|
ValidateBucketNames: DisableStrictBucketNames,
|
|
Concurrency: actionsConcurrency,
|
|
IOBufferSize: ioBufferSize,
|
|
CopyObjectThreshold: CopyObjectThreshold,
|
|
DefaultEtag: defaultEtag,
|
|
DataIntegrityEtag: dataIntegrityEtag,
|
|
}
|
|
opts.SetNewDirPerm(fs.FileMode(dirPerms))
|
|
opts.SetNewFilePerm(fs.FileMode(filePerms))
|
|
|
|
var ms meta.MetadataStorer
|
|
switch {
|
|
case sidecar != "":
|
|
sc, err := meta.NewSideCar(sidecar)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to init sidecar metadata: %w", err)
|
|
}
|
|
ms = sc
|
|
opts.SideCarDir = sidecar
|
|
case nometa:
|
|
ms = meta.NoMeta{}
|
|
default:
|
|
ms = meta.XattrMeta{}
|
|
err := meta.XattrMeta{}.Test(gwroot)
|
|
if err != nil {
|
|
return fmt.Errorf("xattr check failed: %w", err)
|
|
}
|
|
}
|
|
|
|
be, err := posix.New(gwroot, ms, opts)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to init posix backend: %w", err)
|
|
}
|
|
|
|
return RunGateway(ctx.Context, be)
|
|
}
|