diff --git a/backend/posix/parentdir_other.go b/backend/posix/parentdir_other.go new file mode 100644 index 00000000..c5b67954 --- /dev/null +++ b/backend/posix/parentdir_other.go @@ -0,0 +1,25 @@ +// Copyright 2026 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 posix + +import ( + "github.com/versity/versitygw/s3err" +) + +func handleParentDirError(_ string) error { + return s3err.GetAPIError(s3err.ErrObjectParentIsFile) +} diff --git a/backend/posix/parentdir_windows.go b/backend/posix/parentdir_windows.go new file mode 100644 index 00000000..d9d96c2a --- /dev/null +++ b/backend/posix/parentdir_windows.go @@ -0,0 +1,46 @@ +// Copyright 2026 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 posix + +import ( + "os" + "path/filepath" + + "github.com/versity/versitygw/s3err" +) + +func handleParentDirError(name string) error { + dir := filepath.Dir(name) + + // Walk up the directory hierarchy + for dir != "." && dir != "/" { + d, statErr := os.Stat(dir) + if statErr == nil { + // Path component exists + if !d.IsDir() { + // Found a file in the ancestor path + return s3err.GetAPIError(s3err.ErrObjectParentIsFile) + } + // Found a valid directory ancestor, parent truly doesn't exist + break + } + // Continue checking parent directories + dir = filepath.Dir(dir) + } + // Parent doesn't exist or is a directory, treat as ENOENT + return nil +} diff --git a/backend/posix/posix.go b/backend/posix/posix.go index edcbe468..88f0e159 100644 --- a/backend/posix/posix.go +++ b/backend/posix/posix.go @@ -3052,7 +3052,10 @@ func (p *Posix) PutObjectWithPostFunc(ctx context.Context, po s3response.PutObje return s3response.PutObjectOutput{}, s3err.GetAPIError(s3err.ErrKeyTooLong) } if errors.Is(err, syscall.ENOTDIR) { - return s3response.PutObjectOutput{}, s3err.GetAPIError(s3err.ErrObjectParentIsFile) + parentErr := handleParentDirError(name) + if parentErr != nil { + return s3response.PutObjectOutput{}, parentErr + } } if err != nil && !errors.Is(err, fs.ErrNotExist) { return s3response.PutObjectOutput{}, fmt.Errorf("stat object: %w", err)