sendfile_windows.mx raw

   1  // Copyright 2011 The Go Authors. All rights reserved.
   2  // Use of this source code is governed by a BSD-style
   3  // license that can be found in the LICENSE file.
   4  
   5  package poll
   6  
   7  import (
   8  	"io"
   9  	"syscall"
  10  )
  11  
  12  // SendFile wraps the TransmitFile call.
  13  func SendFile(fd *FD, src uintptr, size int64) (written int64, err error, handled bool) {
  14  	defer func() {
  15  		TestHookDidSendFile(fd, 0, written, err, written > 0)
  16  	}()
  17  	if fd.kind == kindPipe {
  18  		// TransmitFile does not work with pipes
  19  		return 0, syscall.ESPIPE, false
  20  	}
  21  	hsrc := syscall.Handle(src)
  22  	if ft, _ := syscall.GetFileType(hsrc); ft == syscall.FILE_TYPE_PIPE {
  23  		return 0, syscall.ESPIPE, false
  24  	}
  25  
  26  	if err := fd.writeLock(); err != nil {
  27  		return 0, err, false
  28  	}
  29  	defer fd.writeUnlock()
  30  
  31  	// Get the file size so we don't read past the end of the file.
  32  	var fi syscall.ByHandleFileInformation
  33  	if err := syscall.GetFileInformationByHandle(hsrc, &fi); err != nil {
  34  		return 0, err, false
  35  	}
  36  	fileSize := int64(fi.FileSizeHigh)<<32 + int64(fi.FileSizeLow)
  37  	startpos, err := syscall.Seek(hsrc, 0, io.SeekCurrent)
  38  	if err != nil {
  39  		return 0, err, false
  40  	}
  41  	maxSize := fileSize - startpos
  42  	if size <= 0 {
  43  		size = maxSize
  44  	} else {
  45  		size = min(size, maxSize)
  46  	}
  47  
  48  	defer func() {
  49  		if written > 0 {
  50  			// Some versions of Windows (Windows 10 1803) do not set
  51  			// file position after TransmitFile completes.
  52  			// So just use Seek to set file position.
  53  			_, serr := syscall.Seek(hsrc, startpos+written, io.SeekStart)
  54  			if err != nil {
  55  				err = serr
  56  			}
  57  		}
  58  	}()
  59  
  60  	// TransmitFile can be invoked in one call with at most
  61  	// 2,147,483,646 bytes: the maximum value for a 32-bit integer minus 1.
  62  	// See https://docs.microsoft.com/en-us/windows/win32/api/mswsock/nf-mswsock-transmitfile
  63  	const maxChunkSizePerCall = int64(0x7fffffff - 1)
  64  
  65  	o := &fd.wop
  66  	o.handle = hsrc
  67  	for size > 0 {
  68  		chunkSize := maxChunkSizePerCall
  69  		if chunkSize > size {
  70  			chunkSize = size
  71  		}
  72  
  73  		off := startpos + written
  74  		o.o.Offset = uint32(off)
  75  		o.o.OffsetHigh = uint32(off >> 32)
  76  
  77  		n, err := execIO(o, func(o *operation) error {
  78  			o.qty = uint32(chunkSize)
  79  			return syscall.TransmitFile(o.fd.Sysfd, o.handle, o.qty, 0, &o.o, nil, syscall.TF_WRITE_BEHIND)
  80  		})
  81  		if err != nil {
  82  			return written, err, written > 0
  83  		}
  84  
  85  		size -= int64(n)
  86  		written += int64(n)
  87  	}
  88  
  89  	return written, nil, written > 0
  90  }
  91