storj.io/minio@v0.0.0-20230509071714-0cbc90f649b1/pkg/ioutil/ioutil.go (about)

     1  /*
     2   * MinIO Cloud Storage, (C) 2017-2020 MinIO, Inc.
     3   *
     4   * Licensed under the Apache License, Version 2.0 (the "License");
     5   * you may not use this file except in compliance with the License.
     6   * You may obtain a copy of the License at
     7   *
     8   *     http://www.apache.org/licenses/LICENSE-2.0
     9   *
    10   * Unless required by applicable law or agreed to in writing, software
    11   * distributed under the License is distributed on an "AS IS" BASIS,
    12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13   * See the License for the specific language governing permissions and
    14   * limitations under the License.
    15   */
    16  
    17  // Package ioutil implements some I/O utility functions which are not covered
    18  // by the standard library.
    19  package ioutil
    20  
    21  import (
    22  	"bytes"
    23  	"context"
    24  	"io"
    25  	"os"
    26  	"time"
    27  
    28  	"storj.io/minio/pkg/disk"
    29  )
    30  
    31  // WriteOnCloser implements io.WriteCloser and always
    32  // executes at least one write operation if it is closed.
    33  //
    34  // This can be useful within the context of HTTP. At least
    35  // one write operation must happen to send the HTTP headers
    36  // to the peer.
    37  type WriteOnCloser struct {
    38  	io.Writer
    39  	hasWritten bool
    40  }
    41  
    42  func (w *WriteOnCloser) Write(p []byte) (int, error) {
    43  	w.hasWritten = true
    44  	return w.Writer.Write(p)
    45  }
    46  
    47  // Close closes the WriteOnCloser. It behaves like io.Closer.
    48  func (w *WriteOnCloser) Close() error {
    49  	if !w.hasWritten {
    50  		_, err := w.Write(nil)
    51  		if err != nil {
    52  			return err
    53  		}
    54  	}
    55  	if closer, ok := w.Writer.(io.Closer); ok {
    56  		return closer.Close()
    57  	}
    58  	return nil
    59  }
    60  
    61  // HasWritten returns true if at least one write operation was performed.
    62  func (w *WriteOnCloser) HasWritten() bool { return w.hasWritten }
    63  
    64  // WriteOnClose takes an io.Writer and returns an ioutil.WriteOnCloser.
    65  func WriteOnClose(w io.Writer) *WriteOnCloser {
    66  	return &WriteOnCloser{w, false}
    67  }
    68  
    69  type ioret struct {
    70  	n   int
    71  	err error
    72  }
    73  
    74  // DeadlineWriter deadline writer with context
    75  type DeadlineWriter struct {
    76  	io.WriteCloser
    77  	timeout time.Duration
    78  	err     error
    79  }
    80  
    81  // NewDeadlineWriter wraps a writer to make it respect given deadline
    82  // value per Write(). If there is a blocking write, the returned Writer
    83  // will return whenever the timer hits (the return values are n=0
    84  // and err=context.Canceled.)
    85  func NewDeadlineWriter(w io.WriteCloser, timeout time.Duration) io.WriteCloser {
    86  	return &DeadlineWriter{WriteCloser: w, timeout: timeout}
    87  }
    88  
    89  func (w *DeadlineWriter) Write(buf []byte) (int, error) {
    90  	if w.err != nil {
    91  		return 0, w.err
    92  	}
    93  
    94  	c := make(chan ioret, 1)
    95  	t := time.NewTimer(w.timeout)
    96  	defer t.Stop()
    97  
    98  	go func() {
    99  		n, err := w.WriteCloser.Write(buf)
   100  		c <- ioret{n, err}
   101  		close(c)
   102  	}()
   103  
   104  	select {
   105  	case r := <-c:
   106  		w.err = r.err
   107  		return r.n, r.err
   108  	case <-t.C:
   109  		w.err = context.Canceled
   110  		return 0, context.Canceled
   111  	}
   112  }
   113  
   114  // Close closer interface to close the underlying closer
   115  func (w *DeadlineWriter) Close() error {
   116  	return w.WriteCloser.Close()
   117  }
   118  
   119  // LimitWriter implements io.WriteCloser.
   120  //
   121  // This is implemented such that we want to restrict
   122  // an enscapsulated writer upto a certain length
   123  // and skip a certain number of bytes.
   124  type LimitWriter struct {
   125  	io.Writer
   126  	skipBytes int64
   127  	wLimit    int64
   128  }
   129  
   130  // Write implements the io.Writer interface limiting upto
   131  // configured length, also skips the first N bytes.
   132  func (w *LimitWriter) Write(p []byte) (n int, err error) {
   133  	n = len(p)
   134  	var n1 int
   135  	if w.skipBytes > 0 {
   136  		if w.skipBytes >= int64(len(p)) {
   137  			w.skipBytes = w.skipBytes - int64(len(p))
   138  			return n, nil
   139  		}
   140  		p = p[w.skipBytes:]
   141  		w.skipBytes = 0
   142  	}
   143  	if w.wLimit == 0 {
   144  		return n, nil
   145  	}
   146  	if w.wLimit < int64(len(p)) {
   147  		n1, err = w.Writer.Write(p[:w.wLimit])
   148  		w.wLimit = w.wLimit - int64(n1)
   149  		return n, err
   150  	}
   151  	n1, err = w.Writer.Write(p)
   152  	w.wLimit = w.wLimit - int64(n1)
   153  	return n, err
   154  }
   155  
   156  // Close closes the LimitWriter. It behaves like io.Closer.
   157  func (w *LimitWriter) Close() error {
   158  	if closer, ok := w.Writer.(io.Closer); ok {
   159  		return closer.Close()
   160  	}
   161  	return nil
   162  }
   163  
   164  // LimitedWriter takes an io.Writer and returns an ioutil.LimitWriter.
   165  func LimitedWriter(w io.Writer, skipBytes int64, limit int64) *LimitWriter {
   166  	return &LimitWriter{w, skipBytes, limit}
   167  }
   168  
   169  type nopCloser struct {
   170  	io.Writer
   171  }
   172  
   173  func (nopCloser) Close() error { return nil }
   174  
   175  // NopCloser returns a WriteCloser with a no-op Close method wrapping
   176  // the provided Writer w.
   177  func NopCloser(w io.Writer) io.WriteCloser {
   178  	return nopCloser{w}
   179  }
   180  
   181  // SkipReader skips a given number of bytes and then returns all
   182  // remaining data.
   183  type SkipReader struct {
   184  	io.Reader
   185  
   186  	skipCount int64
   187  }
   188  
   189  func (s *SkipReader) Read(p []byte) (int, error) {
   190  	l := int64(len(p))
   191  	if l == 0 {
   192  		return 0, nil
   193  	}
   194  	for s.skipCount > 0 {
   195  		if l > s.skipCount {
   196  			l = s.skipCount
   197  		}
   198  		n, err := s.Reader.Read(p[:l])
   199  		if err != nil {
   200  			return 0, err
   201  		}
   202  		s.skipCount -= int64(n)
   203  	}
   204  	return s.Reader.Read(p)
   205  }
   206  
   207  // NewSkipReader - creates a SkipReader
   208  func NewSkipReader(r io.Reader, n int64) io.Reader {
   209  	return &SkipReader{r, n}
   210  }
   211  
   212  // SameFile returns if the files are same.
   213  func SameFile(fi1, fi2 os.FileInfo) bool {
   214  	if !os.SameFile(fi1, fi2) {
   215  		return false
   216  	}
   217  	if !fi1.ModTime().Equal(fi2.ModTime()) {
   218  		return false
   219  	}
   220  	if fi1.Mode() != fi2.Mode() {
   221  		return false
   222  	}
   223  	if fi1.Size() != fi2.Size() {
   224  		return false
   225  	}
   226  	return true
   227  }
   228  
   229  // DirectIO alignment needs to be 4K. Defined here as
   230  // directio.AlignSize is defined as 0 in MacOS causing divide by 0 error.
   231  const directioAlignSize = 4096
   232  
   233  // CopyAligned - copies from reader to writer using the aligned input
   234  // buffer, it is expected that input buffer is page aligned to
   235  // 4K page boundaries. Without passing aligned buffer may cause
   236  // this function to return error.
   237  //
   238  // This code is similar in spirit to io.Copy but it is only to be
   239  // used with DIRECT I/O based file descriptor and it is expected that
   240  // input writer *os.File not a generic io.Writer. Make sure to have
   241  // the file opened for writes with syscall.O_DIRECT flag.
   242  func CopyAligned(w *os.File, r io.Reader, alignedBuf []byte, totalSize int64) (int64, error) {
   243  	// Writes remaining bytes in the buffer.
   244  	writeUnaligned := func(w *os.File, buf []byte) (remainingWritten int64, err error) {
   245  		// Disable O_DIRECT on fd's on unaligned buffer
   246  		// perform an amortized Fdatasync(fd) on the fd at
   247  		// the end, this is performed by the caller before
   248  		// closing 'w'.
   249  		if err = disk.DisableDirectIO(w); err != nil {
   250  			return remainingWritten, err
   251  		}
   252  		return io.Copy(w, bytes.NewReader(buf))
   253  	}
   254  
   255  	var written int64
   256  	for {
   257  		buf := alignedBuf
   258  		if totalSize != -1 {
   259  			remaining := totalSize - written
   260  			if remaining < int64(len(buf)) {
   261  				buf = buf[:remaining]
   262  			}
   263  		}
   264  		nr, err := io.ReadFull(r, buf)
   265  		eof := err == io.EOF || err == io.ErrUnexpectedEOF
   266  		if err != nil && !eof {
   267  			return written, err
   268  		}
   269  		buf = buf[:nr]
   270  		var nw int64
   271  		if len(buf)%directioAlignSize == 0 {
   272  			var n int
   273  			// buf is aligned for directio write()
   274  			n, err = w.Write(buf)
   275  			nw = int64(n)
   276  		} else {
   277  			// buf is not aligned, hence use writeUnaligned()
   278  			nw, err = writeUnaligned(w, buf)
   279  		}
   280  		if nw > 0 {
   281  			written += nw
   282  		}
   283  		if err != nil {
   284  			return written, err
   285  		}
   286  		if nw != int64(len(buf)) {
   287  			return written, io.ErrShortWrite
   288  		}
   289  
   290  		if totalSize != -1 {
   291  			if written == totalSize {
   292  				return written, nil
   293  			}
   294  		}
   295  		if eof {
   296  			return written, nil
   297  		}
   298  	}
   299  }