You can not select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
					
					
						
							154 lines
						
					
					
						
							4.5 KiB
						
					
					
				
			
		
		
		
			
			
			
		
		
	
	
							154 lines
						
					
					
						
							4.5 KiB
						
					
					
				
								package mount
							 | 
						|
								
							 | 
						|
								import (
							 | 
						|
									"net/http"
							 | 
						|
									"time"
							 | 
						|
								
							 | 
						|
									"github.com/hanwen/go-fuse/v2/fuse"
							 | 
						|
								
							 | 
						|
									"github.com/seaweedfs/seaweedfs/weed/glog"
							 | 
						|
									"github.com/seaweedfs/seaweedfs/weed/util"
							 | 
						|
								)
							 | 
						|
								
							 | 
						|
								// CopyFileRange copies data from one file to another from and to specified offsets.
							 | 
						|
								//
							 | 
						|
								// See https://man7.org/linux/man-pages/man2/copy_file_range.2.html
							 | 
						|
								// See https://github.com/libfuse/libfuse/commit/fe4f9428fc403fa8b99051f52d84ea5bd13f3855
							 | 
						|
								/**
							 | 
						|
								 * Copy a range of data from one file to another
							 | 
						|
								 *
							 | 
						|
								 * Niels de Vos: • libfuse: add copy_file_range() support
							 | 
						|
								 *
							 | 
						|
								 * Performs an optimized copy between two file descriptors without the
							 | 
						|
								 * additional cost of transferring data through the FUSE kernel module
							 | 
						|
								 * to user space (glibc) and then back into the FUSE filesystem again.
							 | 
						|
								 *
							 | 
						|
								 * In case this method is not implemented, applications are expected to
							 | 
						|
								 * fall back to a regular file copy.   (Some glibc versions did this
							 | 
						|
								 * emulation automatically, but the emulation has been removed from all
							 | 
						|
								 * glibc release branches.)
							 | 
						|
								 */
							 | 
						|
								func (wfs *WFS) CopyFileRange(cancel <-chan struct{}, in *fuse.CopyFileRangeIn) (written uint32, code fuse.Status) {
							 | 
						|
									// flags must equal 0 for this syscall as of now
							 | 
						|
									if in.Flags != 0 {
							 | 
						|
										return 0, fuse.EINVAL
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									// files must exist
							 | 
						|
									fhOut := wfs.GetHandle(FileHandleId(in.FhOut))
							 | 
						|
									if fhOut == nil {
							 | 
						|
										return 0, fuse.EBADF
							 | 
						|
									}
							 | 
						|
									fhIn := wfs.GetHandle(FileHandleId(in.FhIn))
							 | 
						|
									if fhIn == nil {
							 | 
						|
										return 0, fuse.EBADF
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									// lock source and target file handles
							 | 
						|
									fhOutActiveLock := fhOut.wfs.fhLockTable.AcquireLock("CopyFileRange", fhOut.fh, util.ExclusiveLock)
							 | 
						|
									defer fhOut.wfs.fhLockTable.ReleaseLock(fhOut.fh, fhOutActiveLock)
							 | 
						|
								
							 | 
						|
									if fhOut.entry == nil {
							 | 
						|
										return 0, fuse.ENOENT
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									if fhIn.fh != fhOut.fh {
							 | 
						|
										fhInActiveLock := fhIn.wfs.fhLockTable.AcquireLock("CopyFileRange", fhIn.fh, util.SharedLock)
							 | 
						|
										defer fhIn.wfs.fhLockTable.ReleaseLock(fhIn.fh, fhInActiveLock)
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									// directories are not supported
							 | 
						|
									if fhIn.entry.IsDirectory || fhOut.entry.IsDirectory {
							 | 
						|
										return 0, fuse.EISDIR
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									glog.V(4).Infof(
							 | 
						|
										"CopyFileRange %s fhIn %d -> %s fhOut %d, [%d,%d) -> [%d,%d)",
							 | 
						|
										fhIn.FullPath(), fhIn.fh,
							 | 
						|
										fhOut.FullPath(), fhOut.fh,
							 | 
						|
										in.OffIn, in.OffIn+in.Len,
							 | 
						|
										in.OffOut, in.OffOut+in.Len,
							 | 
						|
									)
							 | 
						|
								
							 | 
						|
									// Concurrent copy operations could allocate too much memory, so we want to
							 | 
						|
									// throttle our concurrency, scaling with the number of writers the mount
							 | 
						|
									// was configured with.
							 | 
						|
									if wfs.concurrentCopiersSem != nil {
							 | 
						|
										wfs.concurrentCopiersSem <- struct{}{}
							 | 
						|
										defer func() { <-wfs.concurrentCopiersSem }()
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									// We want to stream the copy operation to avoid allocating massive buffers.
							 | 
						|
									nowUnixNano := time.Now().UnixNano()
							 | 
						|
									totalCopied := int64(0)
							 | 
						|
									buff := wfs.copyBufferPool.Get().([]byte)
							 | 
						|
									defer wfs.copyBufferPool.Put(buff)
							 | 
						|
									for {
							 | 
						|
										// Comply with cancellation as best as we can, given that the underlying
							 | 
						|
										// IO functions aren't cancellation-aware.
							 | 
						|
										select {
							 | 
						|
										case <-cancel:
							 | 
						|
											glog.Warningf("canceled CopyFileRange for %s (copied %d)",
							 | 
						|
												fhIn.FullPath(), totalCopied)
							 | 
						|
											return uint32(totalCopied), fuse.EINTR
							 | 
						|
										default: // keep going
							 | 
						|
										}
							 | 
						|
								
							 | 
						|
										// We can save one IO by breaking early if we already know the next read
							 | 
						|
										// will result in zero bytes.
							 | 
						|
										remaining := int64(in.Len) - totalCopied
							 | 
						|
										readLen := min(remaining, int64(len(buff)))
							 | 
						|
										if readLen == 0 {
							 | 
						|
											break
							 | 
						|
										}
							 | 
						|
								
							 | 
						|
										// Perform the read
							 | 
						|
										offsetIn := totalCopied + int64(in.OffIn)
							 | 
						|
										numBytesRead, err := readDataByFileHandle(
							 | 
						|
											buff[:readLen], fhIn, offsetIn)
							 | 
						|
										if err != nil {
							 | 
						|
											glog.Warningf("file handle read %s %d (total %d): %v",
							 | 
						|
												fhIn.FullPath(), numBytesRead, totalCopied, err)
							 | 
						|
											return 0, fuse.EIO
							 | 
						|
										}
							 | 
						|
								
							 | 
						|
										// Break if we're done copying (no more bytes to read)
							 | 
						|
										if numBytesRead == 0 {
							 | 
						|
											break
							 | 
						|
										}
							 | 
						|
								
							 | 
						|
										offsetOut := int64(in.OffOut) + totalCopied
							 | 
						|
								
							 | 
						|
										// Detect mime type only during the beginning of our stream, since
							 | 
						|
										// DetectContentType is expecting some of the first 512 bytes of the
							 | 
						|
										// file. See [http.DetectContentType] for details.
							 | 
						|
										if offsetOut <= 512 {
							 | 
						|
											fhOut.contentType = http.DetectContentType(buff[:numBytesRead])
							 | 
						|
										}
							 | 
						|
								
							 | 
						|
										// Perform the write
							 | 
						|
										fhOut.dirtyPages.writerPattern.MonitorWriteAt(offsetOut, int(numBytesRead))
							 | 
						|
										fhOut.dirtyPages.AddPage(
							 | 
						|
											offsetOut,
							 | 
						|
											buff[:numBytesRead],
							 | 
						|
											fhOut.dirtyPages.writerPattern.IsSequentialMode(),
							 | 
						|
											nowUnixNano)
							 | 
						|
								
							 | 
						|
										// Accumulate for the next loop iteration
							 | 
						|
										totalCopied += numBytesRead
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									if totalCopied == 0 {
							 | 
						|
										return 0, fuse.OK
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									fhOut.entry.Attributes.FileSize = uint64(max(
							 | 
						|
										totalCopied+int64(in.OffOut),
							 | 
						|
										int64(fhOut.entry.Attributes.FileSize),
							 | 
						|
									))
							 | 
						|
									fhOut.entry.Content = nil
							 | 
						|
									fhOut.dirtyMetadata = true
							 | 
						|
								
							 | 
						|
									written = uint32(totalCopied)
							 | 
						|
									return written, fuse.OK
							 | 
						|
								}
							 |