// Copyright 2019 The gVisor Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package fs import ( "io" "sync/atomic" "gvisor.googlesource.com/gvisor/pkg/secio" "gvisor.googlesource.com/gvisor/pkg/sentry/context" "gvisor.googlesource.com/gvisor/pkg/syserror" ) // Splice moves data to this file, directly from another. // // Offsets are updated only if DstOffset and SrcOffset are set. func Splice(ctx context.Context, dst *File, src *File, opts SpliceOpts) (int64, error) { // Verify basic file flag permissions. if !dst.Flags().Write || !src.Flags().Read { return 0, syserror.EBADF } // Check whether or not the objects being sliced are stream-oriented // (i.e. pipes or sockets). If yes, we elide checks and offset locks. srcPipe := IsPipe(src.Dirent.Inode.StableAttr) || IsSocket(src.Dirent.Inode.StableAttr) dstPipe := IsPipe(dst.Dirent.Inode.StableAttr) || IsSocket(dst.Dirent.Inode.StableAttr) if !dstPipe && !opts.DstOffset && !srcPipe && !opts.SrcOffset { switch { case dst.UniqueID < src.UniqueID: // Acquire dst first. if !dst.mu.Lock(ctx) { return 0, syserror.ErrInterrupted } defer dst.mu.Unlock() if !src.mu.Lock(ctx) { return 0, syserror.ErrInterrupted } defer src.mu.Unlock() case dst.UniqueID > src.UniqueID: // Acquire src first. if !src.mu.Lock(ctx) { return 0, syserror.ErrInterrupted } defer src.mu.Unlock() if !dst.mu.Lock(ctx) { return 0, syserror.ErrInterrupted } defer dst.mu.Unlock() case dst.UniqueID == src.UniqueID: // Acquire only one lock; it's the same file. This is a // bit of a edge case, but presumably it's possible. if !dst.mu.Lock(ctx) { return 0, syserror.ErrInterrupted } defer dst.mu.Unlock() } // Use both offsets (locked). opts.DstStart = dst.offset opts.SrcStart = src.offset } else if !dstPipe && !opts.DstOffset { // Acquire only dst. if !dst.mu.Lock(ctx) { return 0, syserror.ErrInterrupted } defer dst.mu.Unlock() opts.DstStart = dst.offset // Safe: locked. } else if !srcPipe && !opts.SrcOffset { // Acquire only src. if !src.mu.Lock(ctx) { return 0, syserror.ErrInterrupted } defer src.mu.Unlock() opts.SrcStart = src.offset // Safe: locked. } // Check append-only mode and the limit. if !dstPipe { if dst.Flags().Append { if opts.DstOffset { // We need to acquire the lock. if !dst.mu.Lock(ctx) { return 0, syserror.ErrInterrupted } defer dst.mu.Unlock() } // Figure out the appropriate offset to use. if err := dst.offsetForAppend(ctx, &opts.DstStart); err != nil { return 0, err } } // Enforce file limits. limit, ok := dst.checkLimit(ctx, opts.DstStart) switch { case ok && limit == 0: return 0, syserror.ErrExceedsFileSizeLimit case ok && limit < opts.Length: opts.Length = limit // Cap the write. } } // Attempt to do a WriteTo; this is likely the most efficient. // // The underlying implementation may be able to donate buffers. newOpts := SpliceOpts{ Length: opts.Length, SrcStart: opts.SrcStart, SrcOffset: !srcPipe, Dup: opts.Dup, DstStart: opts.DstStart, DstOffset: !dstPipe, } n, err := src.FileOperations.WriteTo(ctx, src, dst, newOpts) if n == 0 && err != nil { // Attempt as a ReadFrom. If a WriteTo, a ReadFrom may also // be more efficient than a copy if buffers are cached or readily // available. (It's unlikely that they can actually be donate n, err = dst.FileOperations.ReadFrom(ctx, dst, src, newOpts) } if n == 0 && err != nil { // If we've failed up to here, and at least one of the sources // is a pipe or socket, then we can't properly support dup. // Return an error indicating that this operation is not // supported. if (srcPipe || dstPipe) && newOpts.Dup { return 0, syserror.EINVAL } // We failed to splice the files. But that's fine; we just fall // back to a slow path in this case. This copies without doing // any mode changes, so should still be more efficient. var ( r io.Reader w io.Writer ) fw := &lockedWriter{ Ctx: ctx, File: dst, } if newOpts.DstOffset { // Use the provided offset. w = secio.NewOffsetWriter(fw, newOpts.DstStart) } else { // Writes will proceed with no offset. w = fw } fr := &lockedReader{ Ctx: ctx, File: src, } if newOpts.SrcOffset { // Limit to the given offset and length. r = io.NewSectionReader(fr, opts.SrcStart, opts.Length) } else { // Limit just to the given length. r = &io.LimitedReader{fr, opts.Length} } // Copy between the two. n, err = io.Copy(w, r) } // Update offsets, if required. if n > 0 { if !dstPipe && !opts.DstOffset { atomic.StoreInt64(&dst.offset, dst.offset+n) } if !srcPipe && !opts.SrcOffset { atomic.StoreInt64(&src.offset, src.offset+n) } } return n, err }