github.com/vmware/govmomi@v0.43.0/object/datastore_file.go (about)

     1  /*
     2  Copyright (c) 2016-2017 VMware, Inc. All Rights Reserved.
     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 object
    18  
    19  import (
    20  	"bytes"
    21  	"context"
    22  	"errors"
    23  	"fmt"
    24  	"io"
    25  	"net/http"
    26  	"os"
    27  	"path"
    28  	"sync"
    29  	"time"
    30  
    31  	"github.com/vmware/govmomi/vim25/soap"
    32  )
    33  
    34  // DatastoreFile implements io.Reader, io.Seeker and io.Closer interfaces for datastore file access.
    35  type DatastoreFile struct {
    36  	d    Datastore
    37  	ctx  context.Context
    38  	name string
    39  
    40  	buf    io.Reader
    41  	body   io.ReadCloser
    42  	length int64
    43  	offset struct {
    44  		read, seek int64
    45  	}
    46  }
    47  
    48  // Open opens the named file relative to the Datastore.
    49  func (d Datastore) Open(ctx context.Context, name string) (*DatastoreFile, error) {
    50  	return &DatastoreFile{
    51  		d:      d,
    52  		name:   name,
    53  		length: -1,
    54  		ctx:    ctx,
    55  	}, nil
    56  }
    57  
    58  // Read reads up to len(b) bytes from the DatastoreFile.
    59  func (f *DatastoreFile) Read(b []byte) (int, error) {
    60  	if f.offset.read != f.offset.seek {
    61  		// A Seek() call changed the offset, we need to issue a new GET
    62  		_ = f.Close()
    63  
    64  		f.offset.read = f.offset.seek
    65  	} else if f.buf != nil {
    66  		// f.buf + f behaves like an io.MultiReader
    67  		n, err := f.buf.Read(b)
    68  		if err == io.EOF {
    69  			f.buf = nil // buffer has been drained
    70  		}
    71  		if n > 0 {
    72  			return n, nil
    73  		}
    74  	}
    75  
    76  	body, err := f.get()
    77  	if err != nil {
    78  		return 0, err
    79  	}
    80  
    81  	n, err := body.Read(b)
    82  
    83  	f.offset.read += int64(n)
    84  	f.offset.seek += int64(n)
    85  
    86  	return n, err
    87  }
    88  
    89  // Close closes the DatastoreFile.
    90  func (f *DatastoreFile) Close() error {
    91  	var err error
    92  
    93  	if f.body != nil {
    94  		err = f.body.Close()
    95  		f.body = nil
    96  	}
    97  
    98  	f.buf = nil
    99  
   100  	return err
   101  }
   102  
   103  // Seek sets the offset for the next Read on the DatastoreFile.
   104  func (f *DatastoreFile) Seek(offset int64, whence int) (int64, error) {
   105  	switch whence {
   106  	case io.SeekStart:
   107  	case io.SeekCurrent:
   108  		offset += f.offset.seek
   109  	case io.SeekEnd:
   110  		if f.length < 0 {
   111  			_, err := f.Stat()
   112  			if err != nil {
   113  				return 0, err
   114  			}
   115  		}
   116  		offset += f.length
   117  	default:
   118  		return 0, errors.New("Seek: invalid whence")
   119  	}
   120  
   121  	// allow negative SeekStart for initial Range request
   122  	if offset < 0 {
   123  		return 0, errors.New("Seek: invalid offset")
   124  	}
   125  
   126  	f.offset.seek = offset
   127  
   128  	return offset, nil
   129  }
   130  
   131  type fileStat struct {
   132  	file   *DatastoreFile
   133  	header http.Header
   134  }
   135  
   136  func (s *fileStat) Name() string {
   137  	return path.Base(s.file.name)
   138  }
   139  
   140  func (s *fileStat) Size() int64 {
   141  	return s.file.length
   142  }
   143  
   144  func (s *fileStat) Mode() os.FileMode {
   145  	return 0
   146  }
   147  
   148  func (s *fileStat) ModTime() time.Time {
   149  	return time.Now() // no Last-Modified
   150  }
   151  
   152  func (s *fileStat) IsDir() bool {
   153  	return false
   154  }
   155  
   156  func (s *fileStat) Sys() interface{} {
   157  	return s.header
   158  }
   159  
   160  func statusError(res *http.Response) error {
   161  	if res.StatusCode == http.StatusNotFound {
   162  		return os.ErrNotExist
   163  	}
   164  	return errors.New(res.Status)
   165  }
   166  
   167  // Stat returns the os.FileInfo interface describing file.
   168  func (f *DatastoreFile) Stat() (os.FileInfo, error) {
   169  	// TODO: consider using Datastore.Stat() instead
   170  	u, p, err := f.d.downloadTicket(f.ctx, f.name, &soap.Download{Method: "HEAD"})
   171  	if err != nil {
   172  		return nil, err
   173  	}
   174  
   175  	res, err := f.d.Client().DownloadRequest(f.ctx, u, p)
   176  	if err != nil {
   177  		return nil, err
   178  	}
   179  
   180  	if res.StatusCode != http.StatusOK {
   181  		return nil, statusError(res)
   182  	}
   183  
   184  	f.length = res.ContentLength
   185  
   186  	return &fileStat{f, res.Header}, nil
   187  }
   188  
   189  func (f *DatastoreFile) get() (io.Reader, error) {
   190  	if f.body != nil {
   191  		return f.body, nil
   192  	}
   193  
   194  	u, p, err := f.d.downloadTicket(f.ctx, f.name, nil)
   195  	if err != nil {
   196  		return nil, err
   197  	}
   198  
   199  	if f.offset.read != 0 {
   200  		p.Headers = map[string]string{
   201  			"Range": fmt.Sprintf("bytes=%d-", f.offset.read),
   202  		}
   203  	}
   204  
   205  	res, err := f.d.Client().DownloadRequest(f.ctx, u, p)
   206  	if err != nil {
   207  		return nil, err
   208  	}
   209  
   210  	switch res.StatusCode {
   211  	case http.StatusOK:
   212  		f.length = res.ContentLength
   213  	case http.StatusPartialContent:
   214  		var start, end int
   215  		cr := res.Header.Get("Content-Range")
   216  		_, err = fmt.Sscanf(cr, "bytes %d-%d/%d", &start, &end, &f.length)
   217  		if err != nil {
   218  			f.length = -1
   219  		}
   220  	case http.StatusRequestedRangeNotSatisfiable:
   221  		// ok: Read() will return io.EOF
   222  	default:
   223  		return nil, statusError(res)
   224  	}
   225  
   226  	if f.length < 0 {
   227  		_ = res.Body.Close()
   228  		return nil, errors.New("unable to determine file size")
   229  	}
   230  
   231  	f.body = res.Body
   232  
   233  	return f.body, nil
   234  }
   235  
   236  func lastIndexLines(s []byte, line *int, include func(l int, m string) bool) (int64, bool) {
   237  	i := len(s) - 1
   238  	done := false
   239  
   240  	for i > 0 {
   241  		o := bytes.LastIndexByte(s[:i], '\n')
   242  		if o < 0 {
   243  			break
   244  		}
   245  
   246  		msg := string(s[o+1 : i+1])
   247  		if !include(*line, msg) {
   248  			done = true
   249  			break
   250  		} else {
   251  			i = o
   252  			*line++
   253  		}
   254  	}
   255  
   256  	return int64(i), done
   257  }
   258  
   259  // Tail seeks to the position of the last N lines of the file.
   260  func (f *DatastoreFile) Tail(n int) error {
   261  	return f.TailFunc(n, func(line int, _ string) bool { return n > line })
   262  }
   263  
   264  // TailFunc will seek backwards in the datastore file until it hits a line that does
   265  // not satisfy the supplied `include` function.
   266  func (f *DatastoreFile) TailFunc(lines int, include func(line int, message string) bool) error {
   267  	// Read the file in reverse using bsize chunks
   268  	const bsize = int64(1024 * 16)
   269  
   270  	fsize, err := f.Seek(0, io.SeekEnd)
   271  	if err != nil {
   272  		return err
   273  	}
   274  
   275  	if lines == 0 {
   276  		return nil
   277  	}
   278  
   279  	chunk := int64(-1)
   280  
   281  	buf := bytes.NewBuffer(make([]byte, 0, bsize))
   282  	line := 0
   283  
   284  	for {
   285  		var eof bool
   286  		var pos int64
   287  
   288  		nread := bsize
   289  
   290  		offset := chunk * bsize
   291  		remain := fsize + offset
   292  
   293  		if remain < 0 {
   294  			if pos, err = f.Seek(0, io.SeekStart); err != nil {
   295  				return err
   296  			}
   297  
   298  			nread = bsize + remain
   299  			eof = true
   300  		} else if pos, err = f.Seek(offset, io.SeekEnd); err != nil {
   301  			return err
   302  		}
   303  
   304  		if _, err = io.CopyN(buf, f, nread); err != nil {
   305  			if err != io.EOF {
   306  				return err
   307  			}
   308  		}
   309  
   310  		b := buf.Bytes()
   311  		idx, done := lastIndexLines(b, &line, include)
   312  
   313  		if done {
   314  			if chunk == -1 {
   315  				// We found all N lines in the last chunk of the file.
   316  				// The seek offset is also now at the current end of file.
   317  				// Save this buffer to avoid another GET request when Read() is called.
   318  				buf.Next(int(idx + 1))
   319  				f.buf = buf
   320  				return nil
   321  			}
   322  
   323  			if _, err = f.Seek(pos+idx+1, io.SeekStart); err != nil {
   324  				return err
   325  			}
   326  
   327  			break
   328  		}
   329  
   330  		if eof {
   331  			if remain < 0 {
   332  				// We found < N lines in the entire file, so seek to the start.
   333  				_, _ = f.Seek(0, io.SeekStart)
   334  			}
   335  			break
   336  		}
   337  
   338  		chunk--
   339  		buf.Reset()
   340  	}
   341  
   342  	return nil
   343  }
   344  
   345  type followDatastoreFile struct {
   346  	r *DatastoreFile
   347  	c chan struct{}
   348  	i time.Duration
   349  	o sync.Once
   350  }
   351  
   352  // Read reads up to len(b) bytes from the DatastoreFile being followed.
   353  // This method will block until data is read, an error other than io.EOF is returned or Close() is called.
   354  func (f *followDatastoreFile) Read(p []byte) (int, error) {
   355  	offset := f.r.offset.seek
   356  	stop := false
   357  
   358  	for {
   359  		n, err := f.r.Read(p)
   360  		if err != nil && err == io.EOF {
   361  			_ = f.r.Close() // GET request body has been drained.
   362  			if stop {
   363  				return n, err
   364  			}
   365  			err = nil
   366  		}
   367  
   368  		if n > 0 {
   369  			return n, err
   370  		}
   371  
   372  		select {
   373  		case <-f.c:
   374  			// Wake up and stop polling once the body has been drained
   375  			stop = true
   376  		case <-time.After(f.i):
   377  		}
   378  
   379  		info, serr := f.r.Stat()
   380  		if serr != nil {
   381  			// Return EOF rather than 404 if the file goes away
   382  			if serr == os.ErrNotExist {
   383  				_ = f.r.Close()
   384  				return 0, io.EOF
   385  			}
   386  			return 0, serr
   387  		}
   388  
   389  		if info.Size() < offset {
   390  			// assume file has be truncated
   391  			offset, err = f.r.Seek(0, io.SeekStart)
   392  			if err != nil {
   393  				return 0, err
   394  			}
   395  		}
   396  	}
   397  }
   398  
   399  // Close will stop Follow polling and close the underlying DatastoreFile.
   400  func (f *followDatastoreFile) Close() error {
   401  	f.o.Do(func() { close(f.c) })
   402  	return nil
   403  }
   404  
   405  // Follow returns an io.ReadCloser to stream the file contents as data is appended.
   406  func (f *DatastoreFile) Follow(interval time.Duration) io.ReadCloser {
   407  	return &followDatastoreFile{
   408  		r: f,
   409  		c: make(chan struct{}),
   410  		i: interval,
   411  	}
   412  }