github.com/sandwich-go/boost@v1.3.29/xos/copy.go (about)

     1  package xos
     2  
     3  import (
     4  	"io"
     5  	"io/fs"
     6  	"io/ioutil"
     7  	"os"
     8  	"path/filepath"
     9  )
    10  
    11  const (
    12  	// tmpPermissionForDirectory makes the destination directory writable,
    13  	// so that stuff can be copied recursively even if any original directory is NOT writable.
    14  	// See https://github.com/otiai10/copy/pull/9 for more information.
    15  	tmpPermissionForDirectory = os.FileMode(0755)
    16  )
    17  
    18  // Copy copies src to dest, doesn't matter if src is a directory or a file.
    19  func Copy(src, dest string, opt ...CopyOption) error {
    20  	info, err := os.Lstat(src)
    21  	if err != nil {
    22  		return err
    23  	}
    24  	return switchboard(src, dest, info, NewCopyOptions(opt...))
    25  }
    26  
    27  // switchboard switches proper copy functions regarding file type, etc...
    28  // If there would be anything else here, add a case to this switchboard.
    29  func switchboard(src, dest string, info os.FileInfo, opt *CopyOptions) error {
    30  	switch {
    31  	case info.Mode()&os.ModeSymlink != 0:
    32  		return onsymlink(src, dest, info, opt)
    33  	case info.IsDir():
    34  		return dcopy(src, dest, info, opt)
    35  	default:
    36  		return fcopy(src, dest, info, opt)
    37  	}
    38  }
    39  
    40  // copy decide if this src should be copied or not.
    41  // Because this "copy" could be called recursively,
    42  // "info" MUST be given here, NOT nil.
    43  func _copy(src, dest string, info os.FileInfo, opt *CopyOptions) error {
    44  	skip, err := opt.Skip(src)
    45  	if err != nil {
    46  		return err
    47  	}
    48  	if skip {
    49  		return nil
    50  	}
    51  	return switchboard(src, dest, info, opt)
    52  }
    53  
    54  // fcopy is for just a file,
    55  // with considering existence of parent directory
    56  // and file permission.
    57  func fcopy(src, dest string, info os.FileInfo, opt *CopyOptions) error {
    58  	if err := os.MkdirAll(filepath.Dir(dest), os.ModePerm); err != nil {
    59  		return err
    60  	}
    61  	f, err := os.Create(dest)
    62  	if err != nil {
    63  		return err
    64  	}
    65  	defer fclose(f, &err)
    66  	if err = os.Chmod(f.Name(), info.Mode()|opt.AddPermission); err != nil {
    67  		return err
    68  	}
    69  	var s *os.File
    70  	s, err = os.Open(src)
    71  	if err != nil {
    72  		return err
    73  	}
    74  	defer fclose(s, &err)
    75  	if _, err = io.Copy(f, s); err != nil {
    76  		return err
    77  	}
    78  	if opt.Sync {
    79  		err = f.Sync()
    80  	}
    81  	return err
    82  }
    83  
    84  // dcopy is for a directory,
    85  // with scanning contents inside the directory
    86  // and pass everything to "copy" recursively.
    87  func dcopy(srcdir, destdir string, info os.FileInfo, opt *CopyOptions) (err error) {
    88  	originalMode := info.Mode()
    89  	// Make dest dir with 0755 so that everything writable.
    90  	if err = os.MkdirAll(destdir, tmpPermissionForDirectory); err != nil {
    91  		return
    92  	}
    93  	// Recover dir mode with original one.
    94  	defer chmod(destdir, originalMode|opt.AddPermission, &err)
    95  	var contents []fs.FileInfo
    96  	contents, err = ioutil.ReadDir(srcdir)
    97  	if err != nil {
    98  		return
    99  	}
   100  	for _, content := range contents {
   101  		cs, cd := filepath.Join(srcdir, content.Name()), filepath.Join(destdir, content.Name())
   102  		if err = _copy(cs, cd, content, opt); err != nil {
   103  			// If any error, exit immediately
   104  			return
   105  		}
   106  	}
   107  	return
   108  }
   109  
   110  func onsymlink(src, dest string, info os.FileInfo, opt *CopyOptions) error {
   111  	switch opt.OnSymlink(src) {
   112  	case Shallow:
   113  		return lcopy(src, dest)
   114  	case Deep:
   115  		orig, err := filepath.EvalSymlinks(src)
   116  		if err != nil {
   117  			return err
   118  		}
   119  		info, err = os.Lstat(orig)
   120  		if err != nil {
   121  			return err
   122  		}
   123  		return _copy(orig, dest, info, opt)
   124  	case Skip:
   125  		fallthrough
   126  	default:
   127  		return nil // do nothing
   128  	}
   129  }
   130  
   131  // lcopy is for a symlink,
   132  // with just creating a new symlink by replicating src symlink.
   133  func lcopy(src, dest string) error {
   134  	linkSrc, err := os.Readlink(src)
   135  	if err != nil {
   136  		return err
   137  	}
   138  	return os.Symlink(linkSrc, dest)
   139  }
   140  
   141  // fclose ANYHOW closes file,
   142  // with asiging error raised during Close,
   143  // BUT respecting the error already reported.
   144  func fclose(f *os.File, reported *error) {
   145  	if err := f.Close(); *reported == nil {
   146  		*reported = err
   147  	}
   148  }
   149  
   150  // chmod ANYHOW changes file mode,
   151  // with asiging error raised during Chmod,
   152  // BUT respecting the error already reported.
   153  func chmod(dir string, mode os.FileMode, reported *error) {
   154  	if err := os.Chmod(dir, mode); *reported == nil {
   155  		*reported = err
   156  	}
   157  }