diff --git a/cmd/api-errors.go b/cmd/api-errors.go index 4209ebc38..47c8879fd 100644 --- a/cmd/api-errors.go +++ b/cmd/api-errors.go @@ -180,6 +180,7 @@ const ( // Minio extended errors. ErrReadQuorum ErrWriteQuorum + ErrParentIsObject ErrStorageFull ErrRequestBodyParse ErrObjectExistsAsDirectory @@ -858,6 +859,11 @@ var errorCodes = errorCodeMap{ Description: "Storage backend has reached its minimum free disk threshold. Please delete a few objects to proceed.", HTTPStatusCode: http.StatusInsufficientStorage, }, + ErrParentIsObject: { + Code: "XMinioParentIsObject", + Description: "Object-prefix is already an object, please choose a different object-prefix name.", + HTTPStatusCode: http.StatusBadRequest, + }, ErrRequestBodyParse: { Code: "XMinioRequestBodyParse", Description: "The request body failed to parse.", @@ -1561,6 +1567,8 @@ func toAPIErrorCode(ctx context.Context, err error) (apiErr APIErrorCode) { apiErr = ErrObjectExistsAsDirectory case PrefixAccessDenied: apiErr = ErrAccessDenied + case ParentIsObject: + apiErr = ErrParentIsObject case BucketNameInvalid: apiErr = ErrInvalidBucketName case BucketNotFound: diff --git a/cmd/disk-cache-fs.go b/cmd/disk-cache-fs.go index aea1bc12d..82f706c17 100644 --- a/cmd/disk-cache-fs.go +++ b/cmd/disk-cache-fs.go @@ -332,7 +332,7 @@ func (cfs *cacheFSObjects) PutObject(ctx context.Context, bucket string, object if isObjectDir(object, data.Size()) { // Check if an object is present as one of the parent dir. if fs.parentDirIsObject(ctx, bucket, path.Dir(object)) { - return ObjectInfo{}, toObjectErr(errFileAccessDenied, bucket, object) + return ObjectInfo{}, toObjectErr(errFileParentIsFile, bucket, object) } if err = mkdirAll(pathJoin(fs.fsPath, bucket, object), 0777); err != nil { return ObjectInfo{}, toObjectErr(err, bucket, object) @@ -350,7 +350,7 @@ func (cfs *cacheFSObjects) PutObject(ctx context.Context, bucket string, object // Check if an object is present as one of the parent dir. if fs.parentDirIsObject(ctx, bucket, path.Dir(object)) { - return ObjectInfo{}, toObjectErr(errFileAccessDenied, bucket, object) + return ObjectInfo{}, toObjectErr(errFileParentIsFile, bucket, object) } // Validate input data size and it can never be less than zero. diff --git a/cmd/fs-v1-multipart.go b/cmd/fs-v1-multipart.go index b64541dc3..257b33fa5 100644 --- a/cmd/fs-v1-multipart.go +++ b/cmd/fs-v1-multipart.go @@ -487,7 +487,7 @@ func (fs *FSObjects) CompleteMultipartUpload(ctx context.Context, bucket string, // Check if an object is present as one of the parent dir. if fs.parentDirIsObject(ctx, bucket, pathutil.Dir(object)) { - return oi, toObjectErr(errFileAccessDenied, bucket, object) + return oi, toObjectErr(errFileParentIsFile, bucket, object) } if _, err := fs.statBucketDir(ctx, bucket); err != nil { diff --git a/cmd/fs-v1.go b/cmd/fs-v1.go index 0374013ef..6d67fd9d9 100644 --- a/cmd/fs-v1.go +++ b/cmd/fs-v1.go @@ -839,8 +839,7 @@ func (fs *FSObjects) putObject(ctx context.Context, bucket string, object string if isObjectDir(object, data.Size()) { // Check if an object is present as one of the parent dir. if fs.parentDirIsObject(ctx, bucket, path.Dir(object)) { - logger.LogIf(ctx, errFileAccessDenied) - return ObjectInfo{}, toObjectErr(errFileAccessDenied, bucket, object) + return ObjectInfo{}, toObjectErr(errFileParentIsFile, bucket, object) } if err = mkdirAll(pathJoin(fs.fsPath, bucket, object), 0777); err != nil { logger.LogIf(ctx, err) @@ -859,8 +858,7 @@ func (fs *FSObjects) putObject(ctx context.Context, bucket string, object string // Check if an object is present as one of the parent dir. if fs.parentDirIsObject(ctx, bucket, path.Dir(object)) { - logger.LogIf(ctx, errFileAccessDenied) - return ObjectInfo{}, toObjectErr(errFileAccessDenied, bucket, object) + return ObjectInfo{}, toObjectErr(errFileParentIsFile, bucket, object) } // Validate input data size and it can never be less than zero. diff --git a/cmd/fs-v1_test.go b/cmd/fs-v1_test.go index b590774bf..50676e6c5 100644 --- a/cmd/fs-v1_test.go +++ b/cmd/fs-v1_test.go @@ -230,8 +230,8 @@ func TestFSPutObject(t *testing.T) { if err == nil { t.Fatal("Unexpected should fail here, backend corruption occurred") } - if nerr, ok := err.(PrefixAccessDenied); !ok { - t.Fatalf("Expected PrefixAccessDenied, got %#v", err) + if nerr, ok := err.(ParentIsObject); !ok { + t.Fatalf("Expected ParentIsObject, got %#v", err) } else { if nerr.Bucket != "bucket" { t.Fatalf("Expected 'bucket', got %s", nerr.Bucket) @@ -245,8 +245,8 @@ func TestFSPutObject(t *testing.T) { if err == nil { t.Fatal("Unexpected should fail here, backned corruption occurred") } - if nerr, ok := err.(PrefixAccessDenied); !ok { - t.Fatalf("Expected PrefixAccessDenied, got %#v", err) + if nerr, ok := err.(ParentIsObject); !ok { + t.Fatalf("Expected ParentIsObject, got %#v", err) } else { if nerr.Bucket != "bucket" { t.Fatalf("Expected 'bucket', got %s", nerr.Bucket) diff --git a/cmd/object-api-errors.go b/cmd/object-api-errors.go index 8bf75f12f..d148299f6 100644 --- a/cmd/object-api-errors.go +++ b/cmd/object-api-errors.go @@ -19,6 +19,7 @@ package cmd import ( "fmt" "io" + "path" ) // Converts underlying storage error. Convenience function written to @@ -47,6 +48,13 @@ func toObjectErr(err error, params ...string) error { Object: params[1], } } + case errFileParentIsFile: + if len(params) >= 2 { + err = ParentIsObject{ + Bucket: params[0], + Object: params[1], + } + } case errIsNotRegular: if len(params) >= 2 { err = ObjectExistsAsDirectory{ @@ -182,6 +190,13 @@ func (e PrefixAccessDenied) Error() string { return "Prefix access is denied: " + e.Bucket + "/" + e.Object } +// ParentIsObject object access is denied. +type ParentIsObject GenericError + +func (e ParentIsObject) Error() string { + return "Parent is object " + e.Bucket + "/" + path.Dir(e.Object) +} + // BucketExists bucket exists. type BucketExists GenericError diff --git a/cmd/os-reliable.go b/cmd/os-reliable.go index f95cea06e..9fd2f3351 100644 --- a/cmd/os-reliable.go +++ b/cmd/os-reliable.go @@ -142,6 +142,10 @@ func renameAll(srcFilePath, dstFilePath string) (err error) { return fmt.Errorf("%s (%s)->(%s)", errCrossDeviceLink, srcFilePath, dstFilePath) case os.IsNotExist(err): return errFileNotFound + case os.IsExist(err): + // This is returned only when destination is a directory and we + // are attempting a rename from file to directory. + return errIsNotRegular default: return err } diff --git a/cmd/storage-errors.go b/cmd/storage-errors.go index 7327416ae..1a5c09e22 100644 --- a/cmd/storage-errors.go +++ b/cmd/storage-errors.go @@ -66,9 +66,12 @@ var errVolumeNotEmpty = errors.New("volume is not empty") // errVolumeAccessDenied - cannot access volume, insufficient permissions. var errVolumeAccessDenied = errors.New("volume access denied") -// errVolumeAccessDenied - cannot access file, insufficient permissions. +// errFileAccessDenied - cannot access file, insufficient permissions. var errFileAccessDenied = errors.New("file access denied") +// errFileParentIsFile - cannot have overlapping objects, parent is already a file. +var errFileParentIsFile = errors.New("parent is a file") + // errBitrotHashAlgoInvalid - the algo for bit-rot hash // verification is empty or invalid. var errBitrotHashAlgoInvalid = errors.New("bit-rot hash algorithm is invalid") diff --git a/cmd/xl-v1-multipart.go b/cmd/xl-v1-multipart.go index 20e0f3d37..27c4a5a13 100644 --- a/cmd/xl-v1-multipart.go +++ b/cmd/xl-v1-multipart.go @@ -624,7 +624,7 @@ func (xl xlObjects) CompleteMultipartUpload(ctx context.Context, bucket string, // Check if an object is present as one of the parent dir. // -- FIXME. (needs a new kind of lock). if xl.parentDirIsObject(ctx, bucket, path.Dir(object)) { - return oi, toObjectErr(errFileAccessDenied, bucket, object) + return oi, toObjectErr(errFileParentIsFile, bucket, object) } // Calculate s3 compatible md5sum for complete multipart. diff --git a/cmd/xl-v1-object.go b/cmd/xl-v1-object.go index 5b6cf0253..c12333589 100644 --- a/cmd/xl-v1-object.go +++ b/cmd/xl-v1-object.go @@ -577,7 +577,7 @@ func (xl xlObjects) putObject(ctx context.Context, bucket string, object string, // -- FIXME. (needs a new kind of lock). // -- FIXME (this also causes performance issue when disks are down). if xl.parentDirIsObject(ctx, bucket, path.Dir(object)) { - return ObjectInfo{}, toObjectErr(errFileAccessDenied, bucket, object) + return ObjectInfo{}, toObjectErr(errFileParentIsFile, bucket, object) } if err = xl.putObjectDir(ctx, minioMetaTmpBucket, tempObj, writeQuorum); err != nil { @@ -608,7 +608,7 @@ func (xl xlObjects) putObject(ctx context.Context, bucket string, object string, // -- FIXME. (needs a new kind of lock). // -- FIXME (this also causes performance issue when disks are down). if xl.parentDirIsObject(ctx, bucket, path.Dir(object)) { - return ObjectInfo{}, toObjectErr(errFileAccessDenied, bucket, object) + return ObjectInfo{}, toObjectErr(errFileParentIsFile, bucket, object) } // Limit the reader to its provided size if specified.