...

Source file src/github.com/containerd/continuity/fs/copy.go

Documentation: github.com/containerd/continuity/fs

     1  /*
     2     Copyright The containerd Authors.
     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 fs
    18  
    19  import (
    20  	"fmt"
    21  	"io"
    22  	"os"
    23  	"path/filepath"
    24  
    25  	"github.com/sirupsen/logrus"
    26  )
    27  
    28  // XAttrErrorHandler transform a non-nil xattr error.
    29  // Return nil to ignore an error.
    30  // xattrKey can be empty for listxattr operation.
    31  type XAttrErrorHandler func(dst, src, xattrKey string, err error) error
    32  
    33  type copyDirOpts struct {
    34  	xeh XAttrErrorHandler
    35  	// xex contains a set of xattrs to exclude when copying
    36  	xex map[string]struct{}
    37  }
    38  
    39  type CopyDirOpt func(*copyDirOpts) error
    40  
    41  // WithXAttrErrorHandler allows specifying XAttrErrorHandler
    42  // If nil XAttrErrorHandler is specified (default), CopyDir stops
    43  // on a non-nil xattr error.
    44  func WithXAttrErrorHandler(xeh XAttrErrorHandler) CopyDirOpt {
    45  	return func(o *copyDirOpts) error {
    46  		o.xeh = xeh
    47  		return nil
    48  	}
    49  }
    50  
    51  // WithAllowXAttrErrors allows ignoring xattr errors.
    52  func WithAllowXAttrErrors() CopyDirOpt {
    53  	xeh := func(dst, src, xattrKey string, err error) error {
    54  		return nil
    55  	}
    56  	return WithXAttrErrorHandler(xeh)
    57  }
    58  
    59  // WithXAttrExclude allows for exclusion of specified xattr during CopyDir operation.
    60  func WithXAttrExclude(keys ...string) CopyDirOpt {
    61  	return func(o *copyDirOpts) error {
    62  		if o.xex == nil {
    63  			o.xex = make(map[string]struct{}, len(keys))
    64  		}
    65  		for _, key := range keys {
    66  			o.xex[key] = struct{}{}
    67  		}
    68  		return nil
    69  	}
    70  }
    71  
    72  // CopyDir copies the directory from src to dst.
    73  // Most efficient copy of files is attempted.
    74  func CopyDir(dst, src string, opts ...CopyDirOpt) error {
    75  	var o copyDirOpts
    76  	for _, opt := range opts {
    77  		if err := opt(&o); err != nil {
    78  			return err
    79  		}
    80  	}
    81  	inodes := map[uint64]string{}
    82  	return copyDirectory(dst, src, inodes, &o)
    83  }
    84  
    85  func copyDirectory(dst, src string, inodes map[uint64]string, o *copyDirOpts) error {
    86  	stat, err := os.Stat(src)
    87  	if err != nil {
    88  		return fmt.Errorf("failed to stat %s: %w", src, err)
    89  	}
    90  	if !stat.IsDir() {
    91  		return fmt.Errorf("source %s is not directory", src)
    92  	}
    93  
    94  	if st, err := os.Stat(dst); err != nil {
    95  		if err := os.Mkdir(dst, stat.Mode()); err != nil {
    96  			return fmt.Errorf("failed to mkdir %s: %w", dst, err)
    97  		}
    98  	} else if !st.IsDir() {
    99  		return fmt.Errorf("cannot copy to non-directory: %s", dst)
   100  	} else {
   101  		if err := os.Chmod(dst, stat.Mode()); err != nil {
   102  			return fmt.Errorf("failed to chmod on %s: %w", dst, err)
   103  		}
   104  	}
   105  
   106  	entries, err := os.ReadDir(src)
   107  	if err != nil {
   108  		return fmt.Errorf("failed to read %s: %w", src, err)
   109  	}
   110  
   111  	if err := copyFileInfo(stat, src, dst); err != nil {
   112  		return fmt.Errorf("failed to copy file info for %s: %w", dst, err)
   113  	}
   114  
   115  	if err := copyXAttrs(dst, src, o.xex, o.xeh); err != nil {
   116  		return fmt.Errorf("failed to copy xattrs: %w", err)
   117  	}
   118  
   119  	for _, entry := range entries {
   120  		source := filepath.Join(src, entry.Name())
   121  		target := filepath.Join(dst, entry.Name())
   122  
   123  		fileInfo, err := entry.Info()
   124  		if err != nil {
   125  			return fmt.Errorf("failed to get file info for %s: %w", entry.Name(), err)
   126  		}
   127  
   128  		switch {
   129  		case entry.IsDir():
   130  			if err := copyDirectory(target, source, inodes, o); err != nil {
   131  				return err
   132  			}
   133  			continue
   134  		case (fileInfo.Mode() & os.ModeType) == 0:
   135  			link, err := getLinkSource(target, fileInfo, inodes)
   136  			if err != nil {
   137  				return fmt.Errorf("failed to get hardlink: %w", err)
   138  			}
   139  			if link != "" {
   140  				if err := os.Link(link, target); err != nil {
   141  					return fmt.Errorf("failed to create hard link: %w", err)
   142  				}
   143  			} else if err := CopyFile(target, source); err != nil {
   144  				return fmt.Errorf("failed to copy files: %w", err)
   145  			}
   146  		case (fileInfo.Mode() & os.ModeSymlink) == os.ModeSymlink:
   147  			link, err := os.Readlink(source)
   148  			if err != nil {
   149  				return fmt.Errorf("failed to read link: %s: %w", source, err)
   150  			}
   151  			if err := os.Symlink(link, target); err != nil {
   152  				return fmt.Errorf("failed to create symlink: %s: %w", target, err)
   153  			}
   154  		case (fileInfo.Mode() & os.ModeDevice) == os.ModeDevice,
   155  			(fileInfo.Mode() & os.ModeNamedPipe) == os.ModeNamedPipe,
   156  			(fileInfo.Mode() & os.ModeSocket) == os.ModeSocket:
   157  			if err := copyIrregular(target, fileInfo); err != nil {
   158  				return fmt.Errorf("failed to create irregular file: %w", err)
   159  			}
   160  		default:
   161  			logrus.Warnf("unsupported mode: %s: %s", source, fileInfo.Mode())
   162  			continue
   163  		}
   164  
   165  		if err := copyFileInfo(fileInfo, source, target); err != nil {
   166  			return fmt.Errorf("failed to copy file info: %w", err)
   167  		}
   168  
   169  		if err := copyXAttrs(target, source, o.xex, o.xeh); err != nil {
   170  			return fmt.Errorf("failed to copy xattrs: %w", err)
   171  		}
   172  	}
   173  
   174  	return nil
   175  }
   176  
   177  // CopyFile copies the source file to the target.
   178  // The most efficient means of copying is used for the platform.
   179  func CopyFile(target, source string) error {
   180  	return copyFile(target, source)
   181  }
   182  
   183  func openAndCopyFile(target, source string) error {
   184  	src, err := os.Open(source)
   185  	if err != nil {
   186  		return fmt.Errorf("failed to open source %s: %w", source, err)
   187  	}
   188  	defer src.Close()
   189  	tgt, err := os.Create(target)
   190  	if err != nil {
   191  		return fmt.Errorf("failed to open target %s: %w", target, err)
   192  	}
   193  	defer tgt.Close()
   194  
   195  	_, err = io.Copy(tgt, src)
   196  	return err
   197  }
   198  

View as plain text