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  }