fix: sidecar relative path joining

This commit is contained in:
Ben McClelland
2026-04-24 14:53:26 -07:00
parent 28fcd9cd90
commit 731f2f0adc
4 changed files with 63 additions and 7 deletions

View File

@@ -49,6 +49,7 @@ func NewSideCar(dir string) (SideCar, error) {
// RetrieveAttribute retrieves the value of a specific attribute for an object or a bucket.
func (s SideCar) RetrieveAttribute(_ *os.File, bucket, object, attribute string) ([]byte, error) {
bucket, object = trimVolume(bucket), trimVolume(object)
metadir := filepath.Join(s.dir, bucket, object, sidecarmeta)
if object == "" {
metadir = filepath.Join(s.dir, bucket, sidecarmeta)
@@ -68,6 +69,7 @@ func (s SideCar) RetrieveAttribute(_ *os.File, bucket, object, attribute string)
// StoreAttribute stores the value of a specific attribute for an object or a bucket.
func (s SideCar) StoreAttribute(_ *os.File, bucket, object, attribute string, value []byte) error {
bucket, object = trimVolume(bucket), trimVolume(object)
metadir := filepath.Join(s.dir, bucket, object, sidecarmeta)
if object == "" {
metadir = filepath.Join(s.dir, bucket, sidecarmeta)
@@ -114,6 +116,7 @@ func (s SideCar) StoreAttribute(_ *os.File, bucket, object, attribute string, va
// DeleteAttribute removes the value of a specific attribute for an object or a bucket.
func (s SideCar) DeleteAttribute(bucket, object, attribute string) error {
bucket, object = trimVolume(bucket), trimVolume(object)
metadir := filepath.Join(s.dir, bucket, object, sidecarmeta)
if object == "" {
metadir = filepath.Join(s.dir, bucket, sidecarmeta)
@@ -135,6 +138,7 @@ func (s SideCar) DeleteAttribute(bucket, object, attribute string) error {
// ListAttributes lists all attributes for an object or a bucket.
func (s SideCar) ListAttributes(bucket, object string) ([]string, error) {
bucket, object = trimVolume(bucket), trimVolume(object)
metadir := filepath.Join(s.dir, bucket, object, sidecarmeta)
if object == "" {
metadir = filepath.Join(s.dir, bucket, sidecarmeta)
@@ -160,6 +164,7 @@ func (s SideCar) ListAttributes(bucket, object string) ([]string, error) {
// When object is empty the entire bucket sidecar directory is removed,
// cleaning up any orphaned object or multipart metadata within it.
func (s SideCar) DeleteAttributes(bucket, object string) error {
bucket, object = trimVolume(bucket), trimVolume(object)
if object == "" {
// Remove the entire bucket sidecar directory so that orphaned
// object/multipart metadata does not accumulate after DeleteBucket.
@@ -184,8 +189,9 @@ func (s SideCar) DeleteAttributes(bucket, object string) error {
// newObject so that path-based lookups continue to work after the data
// directory has been renamed.
func (s SideCar) RenameObject(bucket, oldObject, newObject string) error {
oldPath := filepath.Join(s.dir, bucket, oldObject)
newPath := filepath.Join(s.dir, bucket, newObject)
bucket = trimVolume(bucket)
oldPath := filepath.Join(s.dir, bucket, trimVolume(oldObject))
newPath := filepath.Join(s.dir, bucket, trimVolume(newObject))
if err := os.MkdirAll(filepath.Dir(newPath), 0777); err != nil {
if errors.Is(err, syscall.ENOSPC) {

View File

@@ -0,0 +1,20 @@
// Copyright 2025 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 !windows
package meta
// trimVolume is a no-op on non-Windows platforms.
func trimVolume(p string) string { return p }

View File

@@ -0,0 +1,29 @@
// Copyright 2025 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 windows
package meta
import (
"path/filepath"
"strings"
)
// trimVolume strips the Windows drive letter (e.g. "C:") and any leading
// path separator from p so it can be safely used as a sub-path component
// inside filepath.Join without becoming an absolute root.
func trimVolume(p string) string {
return strings.TrimLeft(p[len(filepath.VolumeName(p)):], `\/`)
}

View File

@@ -109,7 +109,7 @@ func (tmp *tmpfile) link() error {
backoffMs := initialBackoffMs
for {
err = backend.MoveFile(tempname, objPath, defaultFilePerm)
if !errors.Is(err, syscall.ENOENT) {
if !os.IsNotExist(err) {
break
}
// The parent directory may have been concurrently removed; backoff and retry.
@@ -117,11 +117,12 @@ func (tmp *tmpfile) link() error {
sleepWithJitter(backoffMs)
backoffMs = min((backoffMs * 2), maxBackoffMs)
err = backend.MkdirAll(filepath.Dir(objPath), tmp.uid, tmp.gid,
// 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)
if err != nil {
return fmt.Errorf("recreate parent dir: %w", err)
}
}
return err
}