github.com/minio/mc@v0.0.0-20240503112107-b471de8d1882/cmd/od-stream.go (about) 1 // Copyright (c) 2015-2022 MinIO, Inc. 2 // 3 // This file is part of MinIO Object Storage stack 4 // 5 // This program is free software: you can redistribute it and/or modify 6 // it under the terms of the GNU Affero General Public License as published by 7 // the Free Software Foundation, either version 3 of the License, or 8 // (at your option) any later version. 9 // 10 // This program is distributed in the hope that it will be useful 11 // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 // GNU Affero General Public License for more details. 14 // 15 // You should have received a copy of the GNU Affero General Public License 16 // along with this program. If not, see <http://www.gnu.org/licenses/>. 17 18 package cmd 19 20 import ( 21 "context" 22 "fmt" 23 "io" 24 "math" 25 "path/filepath" 26 "strconv" 27 "time" 28 29 humanize "github.com/dustin/go-humanize" 30 ) 31 32 // odSetSizes sets necessary values for object transfer. 33 func odSetSizes(odURLs URLs, args argKVS) (combinedSize int64, partSize uint64, parts int, skip int64, e error) { 34 // If parts not specified, set to 0, else scan for integer. 35 p := args.Get("parts") 36 if p == "" { 37 parts = 0 38 } else { 39 parts, e = strconv.Atoi(p) 40 if e != nil { 41 return 0, 0, 0, 0, e 42 } 43 } 44 45 // Get number of parts to skip, defaults to 0. 46 sk := args.Get("skip") 47 var skipInt int 48 if sk == "" { 49 skipInt = 0 50 } else { 51 skipInt, e = strconv.Atoi(sk) 52 if e != nil { 53 return 0, 0, 0, 0, e 54 } 55 } 56 57 sourceSize := odURLs.SourceContent.Size 58 59 // If neither parts nor size is specified, copy full file in 1 part. 60 s := args.Get("size") 61 if parts <= 1 && s == "" { 62 return sourceSize, uint64(sourceSize), 1, 0, nil 63 } 64 65 // If size is not specified, calculate the size based on parts and upload full file. 66 if s == "" { 67 partSize = uint64(math.Ceil(float64(sourceSize) / float64(parts))) 68 skip = int64(skipInt) * int64(partSize) 69 parts = parts - skipInt 70 return -1, partSize, parts, skip, nil 71 } 72 73 partSize, e = humanize.ParseBytes(s) 74 if e != nil { 75 return 0, 0, 0, 0, e 76 } 77 78 // Convert skipInt to bytes. 79 skip = int64(skipInt) * int64(partSize) 80 81 // If source has no content, calculate combined size and return. 82 // This is needed for when source is /dev/zero. 83 if sourceSize == 0 { 84 if parts == 0 { 85 parts = 1 86 } 87 combinedSize = int64(partSize * uint64(parts)) 88 return combinedSize, partSize, parts, skip, nil 89 } 90 91 // If source is smaller than part size, upload in 1 part. 92 if partSize > uint64(sourceSize) { 93 return sourceSize, uint64(sourceSize), 1, 0, nil 94 } 95 96 // If parts is not specified, calculate number of parts and upload full file. 97 if parts < 1 { 98 parts = int(math.Ceil(float64(sourceSize)/float64(partSize))) - skipInt 99 return sourceSize, partSize, parts, skip, nil 100 } 101 102 combinedSize = int64(partSize * uint64(parts)) 103 104 // If combined size is larger than source after skip, recalculate number of parts. 105 if sourceSize-skip < combinedSize { 106 combinedSize = sourceSize - skip 107 parts = int(math.Ceil(float64(sourceSize)/float64(partSize))) - skipInt 108 } 109 return combinedSize, partSize, parts, skip, nil 110 } 111 112 // odCopy copies a file/object from local to server, server to server, or local to local. 113 func odCopy(ctx context.Context, odURLs URLs, args argKVS, odType string) (odMessage, error) { 114 // Set sizes. 115 combinedSize, partSize, parts, skip, e := odSetSizes(odURLs, args) 116 if e != nil { 117 return odMessage{}, e 118 } 119 120 sourceAlias := odURLs.SourceAlias 121 sourceURL := odURLs.SourceContent.URL 122 sourcePath := filepath.ToSlash(filepath.Join(sourceAlias, sourceURL.Path)) 123 targetAlias := odURLs.TargetAlias 124 targetURL := odURLs.TargetContent.URL 125 targetPath := filepath.ToSlash(filepath.Join(targetAlias, targetURL.Path)) 126 127 getOpts := GetOptions{} 128 129 // Skip given number of parts. 130 if skip > 0 { 131 getOpts.RangeStart = skip 132 } 133 134 // Placeholder encryption key database. 135 var encKeyDB map[string][]prefixSSEPair 136 137 // Create reader from source. 138 reader, err := getSourceStreamFromURL(ctx, sourcePath, encKeyDB, getSourceOpts{GetOptions: getOpts}) 139 fatalIf(err.Trace(sourcePath), "Unable to get source stream") 140 defer reader.Close() 141 142 putOpts := PutOptions{ 143 storageClass: odURLs.TargetContent.StorageClass, 144 md5: odURLs.MD5, 145 multipartSize: partSize, 146 } 147 148 // Disable multipart on files too small for multipart upload. 149 if combinedSize < 5242880 && combinedSize > 0 { 150 putOpts.disableMultipart = true 151 } 152 153 // Used to get transfer time 154 pg := newAccounter(combinedSize) 155 156 // Write to target. 157 targetClnt, err := newClientFromAlias(targetAlias, targetURL.String()) 158 fatalIf(err.Trace(targetURL.String()), "Unable to initialize target client") 159 160 // Put object. 161 total, err := targetClnt.PutPart(ctx, reader, combinedSize, pg, putOpts) 162 fatalIf(err.Trace(targetURL.String()), "Unable to upload") 163 164 // Get upload time. 165 elapsed := time.Since(pg.startTime) 166 167 message := odMessage{ 168 Status: "success", 169 Type: odType, 170 Source: sourcePath, 171 Target: targetPath, 172 PartSize: partSize, 173 TotalSize: total, 174 Parts: parts, 175 Skip: int(uint64(skip) / partSize), 176 Elapsed: elapsed.Milliseconds(), 177 } 178 179 return message, nil 180 } 181 182 // odSetParts sets parts for object download. 183 func odSetParts(args argKVS) (parts, skip int, e error) { 184 if args.Get("size") != "" { 185 return 0, 0, fmt.Errorf("size cannot be specified getting from server") 186 } 187 188 p := args.Get("parts") 189 if p == "" { 190 return 0, 0, nil 191 } 192 parts, e = strconv.Atoi(p) 193 if e != nil { 194 return 0, 0, e 195 } 196 if parts < 1 { 197 return 0, 0, fmt.Errorf("parts must be at least 1") 198 } 199 200 sk := args.Get("skip") 201 if sk == "" { 202 skip = 0 203 } else { 204 skip, e = strconv.Atoi(sk) 205 } 206 if e != nil { 207 return 0, 0, e 208 } 209 210 return parts, skip, nil 211 } 212 213 // odDownload copies an object from server to local. 214 func odDownload(ctx context.Context, odURLs URLs, args argKVS) (odMessage, error) { 215 /// Set number of parts to get. 216 parts, skip, e := odSetParts(args) 217 if e != nil { 218 return odMessage{}, e 219 } 220 221 targetPath := odURLs.TargetContent.URL.Path 222 sourceAlias := odURLs.SourceAlias 223 sourceURL := odURLs.SourceContent.URL 224 sourcePath := filepath.ToSlash(filepath.Join(sourceAlias, sourceURL.Path)) 225 226 // Get server client. 227 cli, err := newClientFromAlias(sourceAlias, sourceURL.String()) 228 fatalIf(err, "Unable to initialize client") 229 230 var reader io.Reader 231 if parts == 0 { 232 // Get the full file. 233 reader = singleGet(ctx, cli) 234 } else { 235 // Get the file in parts. 236 reader = multiGet(ctx, cli, parts, skip) 237 } 238 239 // Accounter to get transfer time. 240 pg := newAccounter(-1) 241 242 // Upload the file. 243 total, err := putTargetStream(ctx, "", targetPath, "", "", "", 244 reader, -1, pg, PutOptions{}) 245 fatalIf(err.Trace(targetPath), "Unable to upload an object") 246 247 // Get upload time. 248 elapsed := time.Since(pg.startTime) 249 250 message := odMessage{ 251 Status: "success", 252 Type: "S3toFS", 253 Source: sourcePath, 254 Target: targetPath, 255 TotalSize: total, 256 Parts: parts, 257 Skip: skip, 258 Elapsed: elapsed.Milliseconds(), 259 } 260 261 return message, nil 262 } 263 264 // singleGet helps odDownload download a single part. 265 func singleGet(ctx context.Context, cli Client) io.ReadCloser { 266 reader, err := cli.GetPart(ctx, 0) 267 fatalIf(err, "Unable to download object") 268 269 return reader 270 } 271 272 // multiGet helps odDownload download multiple parts. 273 func multiGet(ctx context.Context, cli Client, parts, skip int) io.Reader { 274 var readers []io.Reader 275 276 // Get reader for each part. 277 for i := 1 + skip; i <= parts; i++ { 278 reader, err := cli.GetPart(ctx, parts) 279 fatalIf(err, "Unable to download part of an object") 280 readers = append(readers, reader) 281 } 282 reader := io.MultiReader(readers...) 283 284 return reader 285 }