github.com/openflowlabs/storage@v1.12.13/drivers/copy/copy_linux.go (about)

     1  // +build cgo
     2  
     3  package copy
     4  
     5  /*
     6  #include <linux/fs.h>
     7  
     8  #ifndef FICLONE
     9  #define FICLONE		_IOW(0x94, 9, int)
    10  #endif
    11  */
    12  import "C"
    13  import (
    14  	"container/list"
    15  	"fmt"
    16  	"io"
    17  	"os"
    18  	"path/filepath"
    19  	"syscall"
    20  	"time"
    21  
    22  	"github.com/containers/storage/pkg/idtools"
    23  	"github.com/containers/storage/pkg/pools"
    24  	"github.com/containers/storage/pkg/system"
    25  	rsystem "github.com/opencontainers/runc/libcontainer/system"
    26  	"golang.org/x/sys/unix"
    27  )
    28  
    29  // Mode indicates whether to use hardlink or copy content
    30  type Mode int
    31  
    32  const (
    33  	// Content creates a new file, and copies the content of the file
    34  	Content Mode = iota
    35  	// Hardlink creates a new hardlink to the existing file
    36  	Hardlink
    37  )
    38  
    39  func copyRegular(srcPath, dstPath string, fileinfo os.FileInfo, copyWithFileRange, copyWithFileClone *bool) error {
    40  	srcFile, err := os.Open(srcPath)
    41  	if err != nil {
    42  		return err
    43  	}
    44  	defer srcFile.Close()
    45  
    46  	// If the destination file already exists, we shouldn't blow it away
    47  	dstFile, err := os.OpenFile(dstPath, os.O_WRONLY|os.O_CREATE|os.O_EXCL, fileinfo.Mode())
    48  	if err != nil {
    49  		return err
    50  	}
    51  	defer dstFile.Close()
    52  
    53  	if *copyWithFileClone {
    54  		_, _, err = unix.Syscall(unix.SYS_IOCTL, dstFile.Fd(), C.FICLONE, srcFile.Fd())
    55  		if err == nil {
    56  			return nil
    57  		}
    58  
    59  		*copyWithFileClone = false
    60  		if err == unix.EXDEV {
    61  			*copyWithFileRange = false
    62  		}
    63  	}
    64  	if *copyWithFileRange {
    65  		err = doCopyWithFileRange(srcFile, dstFile, fileinfo)
    66  		// Trying the file_clone may not have caught the exdev case
    67  		// as the ioctl may not have been available (therefore EINVAL)
    68  		if err == unix.EXDEV || err == unix.ENOSYS {
    69  			*copyWithFileRange = false
    70  		} else {
    71  			return err
    72  		}
    73  	}
    74  	return legacyCopy(srcFile, dstFile)
    75  }
    76  
    77  func doCopyWithFileRange(srcFile, dstFile *os.File, fileinfo os.FileInfo) error {
    78  	amountLeftToCopy := fileinfo.Size()
    79  
    80  	for amountLeftToCopy > 0 {
    81  		n, err := unix.CopyFileRange(int(srcFile.Fd()), nil, int(dstFile.Fd()), nil, int(amountLeftToCopy), 0)
    82  		if err != nil {
    83  			return err
    84  		}
    85  
    86  		amountLeftToCopy = amountLeftToCopy - int64(n)
    87  	}
    88  
    89  	return nil
    90  }
    91  
    92  func legacyCopy(srcFile io.Reader, dstFile io.Writer) error {
    93  	_, err := pools.Copy(dstFile, srcFile)
    94  
    95  	return err
    96  }
    97  
    98  func copyXattr(srcPath, dstPath, attr string) error {
    99  	data, err := system.Lgetxattr(srcPath, attr)
   100  	if err != nil {
   101  		return err
   102  	}
   103  	if data != nil {
   104  		if err := system.Lsetxattr(dstPath, attr, data, 0); err != nil {
   105  			return err
   106  		}
   107  	}
   108  	return nil
   109  }
   110  
   111  type fileID struct {
   112  	dev uint64
   113  	ino uint64
   114  }
   115  
   116  type dirMtimeInfo struct {
   117  	dstPath *string
   118  	stat    *syscall.Stat_t
   119  }
   120  
   121  // DirCopy copies or hardlinks the contents of one directory to another,
   122  // properly handling xattrs, and soft links
   123  //
   124  // Copying xattrs can be opted out of by passing false for copyXattrs.
   125  func DirCopy(srcDir, dstDir string, copyMode Mode, copyXattrs bool) error {
   126  	copyWithFileRange := true
   127  	copyWithFileClone := true
   128  
   129  	// This is a map of source file inodes to dst file paths
   130  	copiedFiles := make(map[fileID]string)
   131  
   132  	dirsToSetMtimes := list.New()
   133  	err := filepath.Walk(srcDir, func(srcPath string, f os.FileInfo, err error) error {
   134  		if err != nil {
   135  			return err
   136  		}
   137  
   138  		// Rebase path
   139  		relPath, err := filepath.Rel(srcDir, srcPath)
   140  		if err != nil {
   141  			return err
   142  		}
   143  
   144  		dstPath := filepath.Join(dstDir, relPath)
   145  		if err != nil {
   146  			return err
   147  		}
   148  
   149  		stat, ok := f.Sys().(*syscall.Stat_t)
   150  		if !ok {
   151  			return fmt.Errorf("Unable to get raw syscall.Stat_t data for %s", srcPath)
   152  		}
   153  
   154  		isHardlink := false
   155  
   156  		switch mode := f.Mode(); {
   157  		case mode.IsRegular():
   158  			id := fileID{dev: stat.Dev, ino: stat.Ino}
   159  			if copyMode == Hardlink {
   160  				isHardlink = true
   161  				if err2 := os.Link(srcPath, dstPath); err2 != nil {
   162  					return err2
   163  				}
   164  			} else if hardLinkDstPath, ok := copiedFiles[id]; ok {
   165  				if err2 := os.Link(hardLinkDstPath, dstPath); err2 != nil {
   166  					return err2
   167  				}
   168  			} else {
   169  				if err2 := copyRegular(srcPath, dstPath, f, &copyWithFileRange, &copyWithFileClone); err2 != nil {
   170  					return err2
   171  				}
   172  				copiedFiles[id] = dstPath
   173  			}
   174  
   175  		case mode.IsDir():
   176  			if err := os.Mkdir(dstPath, f.Mode()); err != nil && !os.IsExist(err) {
   177  				return err
   178  			}
   179  
   180  		case mode&os.ModeSymlink != 0:
   181  			link, err := os.Readlink(srcPath)
   182  			if err != nil {
   183  				return err
   184  			}
   185  
   186  			if err := os.Symlink(link, dstPath); err != nil {
   187  				return err
   188  			}
   189  
   190  		case mode&os.ModeNamedPipe != 0:
   191  			fallthrough
   192  
   193  		case mode&os.ModeSocket != 0:
   194  			if err := unix.Mkfifo(dstPath, stat.Mode); err != nil {
   195  				return err
   196  			}
   197  
   198  		case mode&os.ModeDevice != 0:
   199  			if rsystem.RunningInUserNS() {
   200  				// cannot create a device if running in user namespace
   201  				return nil
   202  			}
   203  			if err := unix.Mknod(dstPath, stat.Mode, int(stat.Rdev)); err != nil {
   204  				return err
   205  			}
   206  
   207  		default:
   208  			return fmt.Errorf("unknown file type with mode %v for %s", mode, srcPath)
   209  		}
   210  
   211  		// Everything below is copying metadata from src to dst. All this metadata
   212  		// already shares an inode for hardlinks.
   213  		if isHardlink {
   214  			return nil
   215  		}
   216  
   217  		if err := idtools.SafeLchown(dstPath, int(stat.Uid), int(stat.Gid)); err != nil {
   218  			return err
   219  		}
   220  
   221  		if copyXattrs {
   222  			if err := doCopyXattrs(srcPath, dstPath); err != nil {
   223  				return err
   224  			}
   225  		}
   226  
   227  		isSymlink := f.Mode()&os.ModeSymlink != 0
   228  
   229  		// There is no LChmod, so ignore mode for symlink. Also, this
   230  		// must happen after chown, as that can modify the file mode
   231  		if !isSymlink {
   232  			if err := os.Chmod(dstPath, f.Mode()); err != nil {
   233  				return err
   234  			}
   235  		}
   236  
   237  		// system.Chtimes doesn't support a NOFOLLOW flag atm
   238  		// nolint: unconvert
   239  		if f.IsDir() {
   240  			dirsToSetMtimes.PushFront(&dirMtimeInfo{dstPath: &dstPath, stat: stat})
   241  		} else if !isSymlink {
   242  			aTime := time.Unix(int64(stat.Atim.Sec), int64(stat.Atim.Nsec))
   243  			mTime := time.Unix(int64(stat.Mtim.Sec), int64(stat.Mtim.Nsec))
   244  			if err := system.Chtimes(dstPath, aTime, mTime); err != nil {
   245  				return err
   246  			}
   247  		} else {
   248  			ts := []syscall.Timespec{stat.Atim, stat.Mtim}
   249  			if err := system.LUtimesNano(dstPath, ts); err != nil {
   250  				return err
   251  			}
   252  		}
   253  		return nil
   254  	})
   255  	if err != nil {
   256  		return err
   257  	}
   258  	for e := dirsToSetMtimes.Front(); e != nil; e = e.Next() {
   259  		mtimeInfo := e.Value.(*dirMtimeInfo)
   260  		ts := []syscall.Timespec{mtimeInfo.stat.Atim, mtimeInfo.stat.Mtim}
   261  		if err := system.LUtimesNano(*mtimeInfo.dstPath, ts); err != nil {
   262  			return err
   263  		}
   264  	}
   265  
   266  	return nil
   267  }
   268  
   269  func doCopyXattrs(srcPath, dstPath string) error {
   270  	if err := copyXattr(srcPath, dstPath, "security.capability"); err != nil {
   271  		return err
   272  	}
   273  
   274  	// We need to copy this attribute if it appears in an overlay upper layer, as
   275  	// this function is used to copy those. It is set by overlay if a directory
   276  	// is removed and then re-created and should not inherit anything from the
   277  	// same dir in the lower dir.
   278  	return copyXattr(srcPath, dstPath, "trusted.overlay.opaque")
   279  }