Files
versitygw/backend/posix/without_otmpfile.go
T
Kyd CaoandBen McClelland 45a532e6a6 feat: add configurable file permissions for new objects
Replace the hardcoded 0644 defaultFilePerm with a NewFilePerm option on
the posix and scoutfs backends, exposed as the --file-perms flag and
VGW_FILE_PERMS env var alongside the existing dir-perms option.

The mode passed to open() is masked by the process umask, so the
O_TMPFILE path now chmods explicitly to match the CreateTemp fallback
path and give new objects the configured mode regardless of umask.
2026-08-28 08:51:03 -07:00

142 lines
3.6 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.
//go:build !linux
package posix
import (
"crypto/sha256"
"errors"
"fmt"
"io/fs"
"os"
"path/filepath"
"syscall"
"github.com/versity/versitygw/auth"
"github.com/versity/versitygw/backend"
"github.com/versity/versitygw/s3err"
)
const (
initialBackoffMs = 1
maxBackoffMs = 1024 // ~1 second
)
type tmpfile struct {
f *os.File
bucket string
objname string
// Retained for compatibility with shared tmpfile methods in otmpfile_common.
isOTmp bool
procFDName string
useODirect bool
size int64
newDirPerm fs.FileMode
newFilePerm fs.FileMode
uid int
gid int
doChown bool
}
func (p *Posix) openTmpFile(dir, bucket, obj string, size int64, acct auth.Account, _ bool, _ bool, allowODirect odirectPolicy) (*tmpfile, error) {
uid, gid, doChown := p.getChownIDs(acct)
if p.enableODirect && bool(allowODirect) {
warnODirectUnsupportedOnce("openTmpFile-nonlinux", os.ErrInvalid)
}
// Create a temp file for upload while in progress (see link comments below).
var err error
err = backend.MkdirAll(dir, uid, gid, doChown, p.newDirPerm)
if err != nil {
if errors.Is(err, syscall.EROFS) {
return nil, s3err.GetAPIError(s3err.ErrMethodNotAllowed)
}
return nil, fmt.Errorf("make temp dir: %w", err)
}
f, err := os.CreateTemp(dir,
fmt.Sprintf("%x.", sha256.Sum256([]byte(obj))))
if err != nil {
if errors.Is(err, syscall.EROFS) {
return nil, s3err.GetAPIError(s3err.ErrMethodNotAllowed)
}
return nil, fmt.Errorf("create temp file: %w", err)
}
if doChown {
err := f.Chown(uid, gid)
if err != nil {
f.Close()
os.Remove(f.Name())
return nil, fmt.Errorf("set temp file ownership: %w", err)
}
}
return &tmpfile{
f: f,
bucket: bucket,
objname: obj,
isOTmp: false,
procFDName: "",
useODirect: false,
size: size,
newDirPerm: p.newDirPerm,
newFilePerm: p.newFilePerm,
uid: uid,
gid: gid,
doChown: doChown,
}, nil
}
func (tmp *tmpfile) link() error {
tempname := tmp.f.Name()
objPath := filepath.Join(tmp.bucket, tmp.objname)
// reset default file mode because CreateTemp uses 0600
tmp.f.Chmod(tmp.newFilePerm)
err := tmp.f.Close()
if err != nil {
return fmt.Errorf("close tmpfile: %w", err)
}
backoffMs := initialBackoffMs
for {
err = backend.MoveFile(tempname, objPath, tmp.newFilePerm)
if !os.IsNotExist(err) {
break
}
// The parent directory may have been concurrently removed; backoff and retry.
// Add jitter to avoid synchronized retry waves.
sleepWithJitter(backoffMs)
backoffMs = min((backoffMs * 2), maxBackoffMs)
// Best-effort: recreate the parent directory. Ignore errors here;
// if recreation fails transiently (e.g. Windows pending-delete on
// a recently removed directory), the next MoveFile attempt will
// return os.IsNotExist again and we will retry.
_ = backend.MkdirAll(filepath.Dir(objPath), tmp.uid, tmp.gid,
tmp.doChown, tmp.newDirPerm)
}
return err
}
func (tmp *tmpfile) cleanup() {
tmp.f.Close()
os.Remove(tmp.f.Name())
}