github.com/minio/minio@v0.0.0-20240328213742-3f72439b8a27/internal/ioutil/ioutil.go (about) 1 // Copyright (c) 2015-2021 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 ioutil implements some I/O utility functions which are not covered 19 // by the standard library. 20 package ioutil 21 22 import ( 23 "context" 24 "errors" 25 "io" 26 "os" 27 "runtime/debug" 28 "sync" 29 "time" 30 31 "github.com/dustin/go-humanize" 32 "github.com/minio/minio/internal/disk" 33 ) 34 35 // Block sizes constant. 36 const ( 37 BlockSizeSmall = 32 * humanize.KiByte // Default r/w block size for smaller objects. 38 BlockSizeLarge = 1 * humanize.MiByte // Default r/w block size for normal objects. 39 BlockSizeReallyLarge = 4 * humanize.MiByte // Default r/w block size for very large objects. 40 ) 41 42 // aligned sync.Pool's 43 var ( 44 ODirectPoolXLarge = sync.Pool{ 45 New: func() interface{} { 46 b := disk.AlignedBlock(BlockSizeReallyLarge) 47 return &b 48 }, 49 } 50 ODirectPoolLarge = sync.Pool{ 51 New: func() interface{} { 52 b := disk.AlignedBlock(BlockSizeLarge) 53 return &b 54 }, 55 } 56 ODirectPoolSmall = sync.Pool{ 57 New: func() interface{} { 58 b := disk.AlignedBlock(BlockSizeSmall) 59 return &b 60 }, 61 } 62 ) 63 64 // WriteOnCloser implements io.WriteCloser and always 65 // executes at least one write operation if it is closed. 66 // 67 // This can be useful within the context of HTTP. At least 68 // one write operation must happen to send the HTTP headers 69 // to the peer. 70 type WriteOnCloser struct { 71 io.Writer 72 hasWritten bool 73 } 74 75 func (w *WriteOnCloser) Write(p []byte) (int, error) { 76 w.hasWritten = true 77 return w.Writer.Write(p) 78 } 79 80 // Close closes the WriteOnCloser. It behaves like io.Closer. 81 func (w *WriteOnCloser) Close() error { 82 if !w.hasWritten { 83 _, err := w.Write(nil) 84 if err != nil { 85 return err 86 } 87 } 88 if closer, ok := w.Writer.(io.Closer); ok { 89 return closer.Close() 90 } 91 return nil 92 } 93 94 // HasWritten returns true if at least one write operation was performed. 95 func (w *WriteOnCloser) HasWritten() bool { return w.hasWritten } 96 97 // WriteOnClose takes an io.Writer and returns an ioutil.WriteOnCloser. 98 func WriteOnClose(w io.Writer) *WriteOnCloser { 99 return &WriteOnCloser{w, false} 100 } 101 102 type ioret[V any] struct { 103 val V 104 err error 105 } 106 107 // DeadlineWriter deadline writer with timeout 108 type DeadlineWriter struct { 109 io.WriteCloser 110 timeout time.Duration 111 err error 112 } 113 114 // WithDeadline will execute a function with a deadline and return a value of a given type. 115 // If the deadline/context passes before the function finishes executing, 116 // the zero value and the context error is returned. 117 func WithDeadline[V any](ctx context.Context, timeout time.Duration, work func(ctx context.Context) (result V, err error)) (result V, err error) { 118 ctx, cancel := context.WithTimeout(ctx, timeout) 119 defer cancel() 120 121 c := make(chan ioret[V], 1) 122 go func() { 123 v, err := work(ctx) 124 c <- ioret[V]{val: v, err: err} 125 }() 126 127 select { 128 case v := <-c: 129 return v.val, v.err 130 case <-ctx.Done(): 131 var zero V 132 return zero, ctx.Err() 133 } 134 } 135 136 // DeadlineWorker implements the deadline/timeout resiliency pattern. 137 type DeadlineWorker struct { 138 timeout time.Duration 139 } 140 141 // NewDeadlineWorker constructs a new DeadlineWorker with the given timeout. 142 // To return values, use the WithDeadline helper instead. 143 func NewDeadlineWorker(timeout time.Duration) *DeadlineWorker { 144 dw := &DeadlineWorker{ 145 timeout: timeout, 146 } 147 return dw 148 } 149 150 // Run runs the given function, passing it a stopper channel. If the deadline passes before 151 // the function finishes executing, Run returns context.DeadlineExceeded to the caller. 152 // channel so that the work function can attempt to exit gracefully. 153 // Multiple calls to Run will run independently of each other. 154 func (d *DeadlineWorker) Run(work func() error) error { 155 c := make(chan ioret[struct{}], 1) 156 t := time.NewTimer(d.timeout) 157 go func() { 158 c <- ioret[struct{}]{val: struct{}{}, err: work()} 159 }() 160 161 select { 162 case r := <-c: 163 if !t.Stop() { 164 <-t.C 165 } 166 return r.err 167 case <-t.C: 168 return context.DeadlineExceeded 169 } 170 } 171 172 // NewDeadlineWriter wraps a writer to make it respect given deadline 173 // value per Write(). If there is a blocking write, the returned Writer 174 // will return whenever the timer hits (the return values are n=0 175 // and err=context.DeadlineExceeded.) 176 func NewDeadlineWriter(w io.WriteCloser, timeout time.Duration) io.WriteCloser { 177 return &DeadlineWriter{WriteCloser: w, timeout: timeout} 178 } 179 180 func (w *DeadlineWriter) Write(buf []byte) (int, error) { 181 if w.err != nil { 182 return 0, w.err 183 } 184 185 n, err := WithDeadline[int](context.Background(), w.timeout, func(ctx context.Context) (int, error) { 186 return w.WriteCloser.Write(buf) 187 }) 188 w.err = err 189 return n, err 190 } 191 192 // Close closer interface to close the underlying closer 193 func (w *DeadlineWriter) Close() error { 194 err := w.WriteCloser.Close() 195 w.err = err 196 if err == nil { 197 w.err = errors.New("we are closed") // Avoids any reuse on the Write() side. 198 } 199 return err 200 } 201 202 // LimitWriter implements io.WriteCloser. 203 // 204 // This is implemented such that we want to restrict 205 // an enscapsulated writer upto a certain length 206 // and skip a certain number of bytes. 207 type LimitWriter struct { 208 io.Writer 209 skipBytes int64 210 wLimit int64 211 } 212 213 // Write implements the io.Writer interface limiting upto 214 // configured length, also skips the first N bytes. 215 func (w *LimitWriter) Write(p []byte) (n int, err error) { 216 n = len(p) 217 var n1 int 218 if w.skipBytes > 0 { 219 if w.skipBytes >= int64(len(p)) { 220 w.skipBytes -= int64(len(p)) 221 return n, nil 222 } 223 p = p[w.skipBytes:] 224 w.skipBytes = 0 225 } 226 if w.wLimit == 0 { 227 return n, nil 228 } 229 if w.wLimit < int64(len(p)) { 230 n1, err = w.Writer.Write(p[:w.wLimit]) 231 w.wLimit -= int64(n1) 232 return n, err 233 } 234 n1, err = w.Writer.Write(p) 235 w.wLimit -= int64(n1) 236 return n, err 237 } 238 239 // Close closes the LimitWriter. It behaves like io.Closer. 240 func (w *LimitWriter) Close() error { 241 if closer, ok := w.Writer.(io.Closer); ok { 242 return closer.Close() 243 } 244 return nil 245 } 246 247 // LimitedWriter takes an io.Writer and returns an ioutil.LimitWriter. 248 func LimitedWriter(w io.Writer, skipBytes int64, limit int64) *LimitWriter { 249 return &LimitWriter{w, skipBytes, limit} 250 } 251 252 type nopCloser struct { 253 io.Writer 254 } 255 256 func (nopCloser) Close() error { return nil } 257 258 // NopCloser returns a WriteCloser with a no-op Close method wrapping 259 // the provided Writer w. 260 func NopCloser(w io.Writer) io.WriteCloser { 261 return nopCloser{w} 262 } 263 264 // SkipReader skips a given number of bytes and then returns all 265 // remaining data. 266 type SkipReader struct { 267 io.Reader 268 269 skipCount int64 270 } 271 272 func (s *SkipReader) Read(p []byte) (int, error) { 273 l := int64(len(p)) 274 if l == 0 { 275 return 0, nil 276 } 277 for s.skipCount > 0 { 278 if l > s.skipCount { 279 l = s.skipCount 280 } 281 n, err := s.Reader.Read(p[:l]) 282 if err != nil { 283 return 0, err 284 } 285 s.skipCount -= int64(n) 286 } 287 return s.Reader.Read(p) 288 } 289 290 // NewSkipReader - creates a SkipReader 291 func NewSkipReader(r io.Reader, n int64) io.Reader { 292 return &SkipReader{r, n} 293 } 294 295 var copyBufPool = sync.Pool{ 296 New: func() interface{} { 297 b := make([]byte, 32*1024) 298 return &b 299 }, 300 } 301 302 // Copy is exactly like io.Copy but with reusable buffers. 303 func Copy(dst io.Writer, src io.Reader) (written int64, err error) { 304 bufp := copyBufPool.Get().(*[]byte) 305 buf := *bufp 306 defer copyBufPool.Put(bufp) 307 308 return io.CopyBuffer(dst, src, buf) 309 } 310 311 // SameFile returns if the files are same. 312 func SameFile(fi1, fi2 os.FileInfo) bool { 313 if !os.SameFile(fi1, fi2) { 314 return false 315 } 316 if !fi1.ModTime().Equal(fi2.ModTime()) { 317 return false 318 } 319 if fi1.Mode() != fi2.Mode() { 320 return false 321 } 322 return fi1.Size() == fi2.Size() 323 } 324 325 // DirectioAlignSize - DirectIO alignment needs to be 4K. Defined here as 326 // directio.AlignSize is defined as 0 in MacOS causing divide by 0 error. 327 const DirectioAlignSize = 4096 328 329 // CopyAligned - copies from reader to writer using the aligned input 330 // buffer, it is expected that input buffer is page aligned to 331 // 4K page boundaries. Without passing aligned buffer may cause 332 // this function to return error. 333 // 334 // This code is similar in spirit to io.Copy but it is only to be 335 // used with DIRECT I/O based file descriptor and it is expected that 336 // input writer *os.File not a generic io.Writer. Make sure to have 337 // the file opened for writes with syscall.O_DIRECT flag. 338 func CopyAligned(w io.Writer, r io.Reader, alignedBuf []byte, totalSize int64, file *os.File) (int64, error) { 339 if totalSize == 0 { 340 return 0, nil 341 } 342 343 var written int64 344 for { 345 buf := alignedBuf 346 if totalSize > 0 { 347 remaining := totalSize - written 348 if remaining < int64(len(buf)) { 349 buf = buf[:remaining] 350 } 351 } 352 353 nr, err := io.ReadFull(r, buf) 354 eof := errors.Is(err, io.EOF) || errors.Is(err, io.ErrUnexpectedEOF) 355 if err != nil && !eof { 356 return written, err 357 } 358 359 buf = buf[:nr] 360 var ( 361 n int 362 un int 363 nw int64 364 ) 365 366 remain := len(buf) % DirectioAlignSize 367 if remain == 0 { 368 // buf is aligned for directio write() 369 n, err = w.Write(buf) 370 nw = int64(n) 371 } else { 372 if remain < len(buf) { 373 n, err = w.Write(buf[:len(buf)-remain]) 374 if err != nil { 375 return written, err 376 } 377 nw = int64(n) 378 } 379 380 // Disable O_DIRECT on fd's on unaligned buffer 381 // perform an amortized Fdatasync(fd) on the fd at 382 // the end, this is performed by the caller before 383 // closing 'w'. 384 if err = disk.DisableDirectIO(file); err != nil { 385 return written, err 386 } 387 388 // buf is not aligned, hence use writeUnaligned() 389 // for the remainder 390 un, err = w.Write(buf[len(buf)-remain:]) 391 nw += int64(un) 392 } 393 394 if nw > 0 { 395 written += nw 396 } 397 398 if err != nil { 399 return written, err 400 } 401 402 if nw != int64(len(buf)) { 403 return written, io.ErrShortWrite 404 } 405 406 if totalSize > 0 && written == totalSize { 407 // we have written the entire stream, return right here. 408 return written, nil 409 } 410 411 if eof { 412 // We reached EOF prematurely but we did not write everything 413 // that we promised that we would write. 414 if totalSize > 0 && written != totalSize { 415 return written, io.ErrUnexpectedEOF 416 } 417 return written, nil 418 } 419 } 420 } 421 422 // SafeClose safely closes any channel of any type 423 func SafeClose[T any](c chan<- T) { 424 if c != nil { 425 close(c) 426 return 427 } 428 // Print stack to check who is sending `c` as `nil` 429 // without crashing the server. 430 debug.PrintStack() 431 }