1/*
2 * MinIO Go Library for Amazon S3 Compatible Cloud Storage
3 * Copyright 2015-2017 MinIO, Inc.
4 *
5 * Licensed under the Apache License, Version 2.0 (the "License");
6 * you may not use this file except in compliance with the License.
7 * You may obtain a copy of the License at
8 *
9 *     http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 */
17
18package minio
19
20import (
21	"context"
22	"io"
23	"math"
24	"os"
25
26	"github.com/minio/minio-go/v7/pkg/s3utils"
27)
28
29// Verify if reader is *minio.Object
30func isObject(reader io.Reader) (ok bool) {
31	_, ok = reader.(*Object)
32	return
33}
34
35// Verify if reader is a generic ReaderAt
36func isReadAt(reader io.Reader) (ok bool) {
37	var v *os.File
38	v, ok = reader.(*os.File)
39	if ok {
40		// Stdin, Stdout and Stderr all have *os.File type
41		// which happen to also be io.ReaderAt compatible
42		// we need to add special conditions for them to
43		// be ignored by this function.
44		for _, f := range []string{
45			"/dev/stdin",
46			"/dev/stdout",
47			"/dev/stderr",
48		} {
49			if f == v.Name() {
50				ok = false
51				break
52			}
53		}
54	} else {
55		_, ok = reader.(io.ReaderAt)
56	}
57	return
58}
59
60// optimalPartInfo - calculate the optimal part info for a given
61// object size.
62//
63// NOTE: Assumption here is that for any object to be uploaded to any S3 compatible
64// object storage it will have the following parameters as constants.
65//
66//  maxPartsCount - 10000
67//  minPartSize - 128MiB
68//  maxMultipartPutObjectSize - 5TiB
69//
70func optimalPartInfo(objectSize int64, configuredPartSize uint64) (totalPartsCount int, partSize int64, lastPartSize int64, err error) {
71	// object size is '-1' set it to 5TiB.
72	var unknownSize bool
73	if objectSize == -1 {
74		unknownSize = true
75		objectSize = maxMultipartPutObjectSize
76	}
77
78	// object size is larger than supported maximum.
79	if objectSize > maxMultipartPutObjectSize {
80		err = errEntityTooLarge(objectSize, maxMultipartPutObjectSize, "", "")
81		return
82	}
83
84	var partSizeFlt float64
85	if configuredPartSize > 0 {
86		if int64(configuredPartSize) > objectSize {
87			err = errEntityTooLarge(int64(configuredPartSize), objectSize, "", "")
88			return
89		}
90
91		if !unknownSize {
92			if objectSize > (int64(configuredPartSize) * maxPartsCount) {
93				err = errInvalidArgument("Part size * max_parts(10000) is lesser than input objectSize.")
94				return
95			}
96		}
97
98		if configuredPartSize < absMinPartSize {
99			err = errInvalidArgument("Input part size is smaller than allowed minimum of 5MiB.")
100			return
101		}
102
103		if configuredPartSize > maxPartSize {
104			err = errInvalidArgument("Input part size is bigger than allowed maximum of 5GiB.")
105			return
106		}
107
108		partSizeFlt = float64(configuredPartSize)
109		if unknownSize {
110			// If input has unknown size and part size is configured
111			// keep it to maximum allowed as per 10000 parts.
112			objectSize = int64(configuredPartSize) * maxPartsCount
113		}
114	} else {
115		configuredPartSize = minPartSize
116		// Use floats for part size for all calculations to avoid
117		// overflows during float64 to int64 conversions.
118		partSizeFlt = float64(objectSize / maxPartsCount)
119		partSizeFlt = math.Ceil(partSizeFlt/float64(configuredPartSize)) * float64(configuredPartSize)
120	}
121
122	// Total parts count.
123	totalPartsCount = int(math.Ceil(float64(objectSize) / partSizeFlt))
124	// Part size.
125	partSize = int64(partSizeFlt)
126	// Last part size.
127	lastPartSize = objectSize - int64(totalPartsCount-1)*partSize
128	return totalPartsCount, partSize, lastPartSize, nil
129}
130
131// getUploadID - fetch upload id if already present for an object name
132// or initiate a new request to fetch a new upload id.
133func (c Client) newUploadID(ctx context.Context, bucketName, objectName string, opts PutObjectOptions) (uploadID string, err error) {
134	// Input validation.
135	if err := s3utils.CheckValidBucketName(bucketName); err != nil {
136		return "", err
137	}
138	if err := s3utils.CheckValidObjectName(objectName); err != nil {
139		return "", err
140	}
141
142	// Initiate multipart upload for an object.
143	initMultipartUploadResult, err := c.initiateMultipartUpload(ctx, bucketName, objectName, opts)
144	if err != nil {
145		return "", err
146	}
147	return initMultipartUploadResult.UploadID, nil
148}
149