package s3api
import (
"errors"
"strings"
"github.com/seaweedfs/seaweedfs/weed/glog"
"github.com/seaweedfs/seaweedfs/weed/pb/filer_pb"
"github.com/seaweedfs/seaweedfs/weed/s3api/s3_constants"
"github.com/seaweedfs/seaweedfs/weed/s3api/s3err"
"github.com/seaweedfs/seaweedfs/weed/util"
)
// An explicit directory marker - the key "dir/" created by PutObject on a
// trailing-slash key - is stored as the filer directory itself rather than as an
// object beside it. That makes it a poor fit for versioning: a delete marker would
// have to replace an entry that other keys live under, and the history would have to
// sit inside the directory it describes, where a listing keeps meeting it.
//
// So the key is not versioned. Deleting it does what deleting it in an unversioned
// bucket already does: the directory is removed when nothing is left under it, and
// demoted to a plain directory when children remain. Listings need no version lookup
// to tell what a directory stands for, and a bucket made of directory markers costs
// the same to list versioned as unversioned.
// deleteDirectoryMarker removes the key "
/". Callers hold the object write lock,
// so the entry this decides about cannot change between the read and the delete.
func (s3a *S3ApiServer) deleteDirectoryMarker(bucket, object string) s3err.ErrorCode {
markerDir := s3a.bucketDir(bucket) + "/" + strings.TrimSuffix(strings.TrimPrefix(object, "/"), "/")
dir, name := util.FullPath(markerDir).DirAndName()
entry, err := s3a.getEntry(dir, name)
switch {
case errors.Is(err, filer_pb.ErrNotFound):
return s3err.ErrNone // deleting a key that is not there is a success
case err != nil:
// The entry may be a file a child write promoted to a directory, whose data
// belongs to the key without the trailing slash. Deleting without knowing
// would destroy it, so fail and leave the retry to the client.
glog.Errorf("deleteDirectoryMarker: cannot read %s/%s: %v", bucket, object, err)
return s3err.ErrInternalError
case len(entry.GetChunks()) > 0 || entry.IsInRemoteOnly():
// A promoted file, not a marker: "dir/" does not name its data.
glog.V(2).Infof("deleteDirectoryMarker: %s/%s holds uploaded data, leaving it alone", bucket, object)
return s3err.ErrNone
}
// Drop a history an older build recorded for this key. Nothing writes one now, and
// leaving it behind keeps reporting the key in ListObjectVersions, so a history we
// cannot read or remove fails the delete rather than half finishing it.
switch _, historyErr := s3a.getEntry(markerDir, s3_constants.VersionsFolder); {
case historyErr == nil:
if rmErr := s3a.rm(markerDir, s3_constants.VersionsFolder, true, true); rmErr != nil {
glog.Errorf("deleteDirectoryMarker: failed to remove stale history of %s/%s: %v", bucket, object, rmErr)
return s3err.ErrInternalError
}
case !errors.Is(historyErr, filer_pb.ErrNotFound):
glog.Errorf("deleteDirectoryMarker: cannot read stale history of %s/%s: %v", bucket, object, historyErr)
return s3err.ErrInternalError
}
if err := s3a.WithFilerClient(false, func(client filer_pb.SeaweedFilerClient) error {
return s3a.deleteUnversionedObjectWithClient(client, bucket, object, false)
}); err != nil {
glog.Errorf("deleteDirectoryMarker: failed to delete %s/%s: %v", bucket, object, err)
return s3err.ErrInternalError
}
return s3err.ErrNone
}