github.com/u-root/u-root@v7.0.1-0.20200915234505-ad7babab0a8e+incompatible/pkg/cp/cp.go (about)

     1  // Copyright 2018 the u-root 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 cp implements routines to copy files.
     6  //
     7  // CopyTree in particular copies entire trees of files.
     8  //
     9  // Only directories, symlinks, and regular files are currently supported.
    10  package cp
    11  
    12  import (
    13  	"errors"
    14  	"fmt"
    15  	"io"
    16  	"os"
    17  	"path/filepath"
    18  )
    19  
    20  // ErrSkip can be returned by PreCallback to skip a file.
    21  var ErrSkip = errors.New("skip")
    22  
    23  // Options are configuration options for how copying files should behave.
    24  type Options struct {
    25  	// If NoFollowSymlinks is set, Copy copies the symlink itself rather
    26  	// than following the symlink and copying the file it points to.
    27  	NoFollowSymlinks bool
    28  
    29  	// PreCallback is called on each file to be copied before it is copied
    30  	// if specified.
    31  	//
    32  	// If PreCallback returns ErrSkip, the file is skipped and Copy returns
    33  	// nil.
    34  	//
    35  	// If PreCallback returns another non-nil error, the file is not copied
    36  	// and Copy returns the error.
    37  	PreCallback func(src, dst string, srcfi os.FileInfo) error
    38  
    39  	// PostCallback is called on each file after it is copied if specified.
    40  	PostCallback func(src, dst string)
    41  }
    42  
    43  // Default are the default options. Default follows symlinks.
    44  var Default = Options{}
    45  
    46  // NoFollowSymlinks is the default options with following symlinks turned off.
    47  var NoFollowSymlinks = Options{
    48  	NoFollowSymlinks: true,
    49  }
    50  
    51  func (o Options) stat(path string) (os.FileInfo, error) {
    52  	if o.NoFollowSymlinks {
    53  		return os.Lstat(path)
    54  	}
    55  	return os.Stat(path)
    56  }
    57  
    58  // Copy copies a file at src to dst.
    59  func (o Options) Copy(src, dst string) error {
    60  	srcInfo, err := o.stat(src)
    61  	if err != nil {
    62  		return err
    63  	}
    64  
    65  	if o.PreCallback != nil {
    66  		if err := o.PreCallback(src, dst, srcInfo); err == ErrSkip {
    67  			return nil
    68  		} else if err != nil {
    69  			return err
    70  		}
    71  	}
    72  	if err := copyFile(src, dst, srcInfo); err != nil {
    73  		return err
    74  	}
    75  	if o.PostCallback != nil {
    76  		o.PostCallback(src, dst)
    77  	}
    78  	return nil
    79  }
    80  
    81  // CopyTree recursively copies all files in the src tree to dst.
    82  func (o Options) CopyTree(src, dst string) error {
    83  	return filepath.Walk(src, func(path string, fi os.FileInfo, err error) error {
    84  		if err != nil {
    85  			return err
    86  		}
    87  
    88  		rel, err := filepath.Rel(src, path)
    89  		if err != nil {
    90  			return err
    91  		}
    92  		return o.Copy(path, filepath.Join(dst, rel))
    93  	})
    94  }
    95  
    96  // Copy src file to dst file using Default's config.
    97  func Copy(src, dst string) error {
    98  	return Default.Copy(src, dst)
    99  }
   100  
   101  // CopyTree recursively copies all files in the src tree to dst using Default's
   102  // config.
   103  func CopyTree(src, dst string) error {
   104  	return Default.CopyTree(src, dst)
   105  }
   106  
   107  func copyFile(src, dst string, srcInfo os.FileInfo) error {
   108  	m := srcInfo.Mode()
   109  	switch {
   110  	case m.IsDir():
   111  		return os.Mkdir(dst, srcInfo.Mode().Perm())
   112  
   113  	case m.IsRegular():
   114  		return copyRegularFile(src, dst, srcInfo)
   115  
   116  	case m&os.ModeSymlink == os.ModeSymlink:
   117  		// Yeah, this may not make any sense logically. But this is how
   118  		// cp does it.
   119  		target, err := os.Readlink(src)
   120  		if err != nil {
   121  			return err
   122  		}
   123  		return os.Symlink(target, dst)
   124  
   125  	default:
   126  		return &os.PathError{
   127  			Op:   "copy",
   128  			Path: src,
   129  			Err:  fmt.Errorf("unsupported file mode %s", m),
   130  		}
   131  	}
   132  }
   133  
   134  func copyRegularFile(src, dst string, srcfi os.FileInfo) error {
   135  	srcf, err := os.Open(src)
   136  	if err != nil {
   137  		return err
   138  	}
   139  	defer srcf.Close()
   140  
   141  	dstf, err := os.OpenFile(dst, os.O_RDWR|os.O_CREATE|os.O_TRUNC, srcfi.Mode().Perm())
   142  	if err != nil {
   143  		return err
   144  	}
   145  	defer dstf.Close()
   146  
   147  	_, err = io.Copy(dstf, srcf)
   148  	return err
   149  }