github.com/cloud-foundations/dominator@v0.0.0-20221004181915-6e4fee580046/lib/fsutil/copy.go (about)

     1  package fsutil
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  	"io"
     7  	"os"
     8  	"path"
     9  	"syscall"
    10  )
    11  
    12  const (
    13  	dirPerms = syscall.S_IRWXU | syscall.S_IRGRP | syscall.S_IXGRP |
    14  		syscall.S_IROTH | syscall.S_IXOTH
    15  )
    16  
    17  func copyToFile(destFilename string, perm os.FileMode, reader io.Reader,
    18  	length uint64) error {
    19  	tmpFilename := destFilename + "~"
    20  	destFile, err := os.OpenFile(tmpFilename,
    21  		os.O_CREATE|os.O_TRUNC|os.O_WRONLY, perm)
    22  	if err != nil {
    23  		return err
    24  	}
    25  	defer os.Remove(tmpFilename)
    26  	defer destFile.Close()
    27  	var nCopied int64
    28  	iLength := int64(length)
    29  	if length < 1 {
    30  		if _, err := io.Copy(destFile, reader); err != nil {
    31  			return fmt.Errorf("error copying: %s", err)
    32  		}
    33  	} else {
    34  		if nCopied, err = io.CopyN(destFile, reader, iLength); err != nil {
    35  			return fmt.Errorf("error copying: %s", err)
    36  		}
    37  		if nCopied != iLength {
    38  			return fmt.Errorf("expected length: %d, got: %d for: %s\n",
    39  				length, nCopied, tmpFilename)
    40  		}
    41  	}
    42  	return os.Rename(tmpFilename, destFilename)
    43  }
    44  
    45  func copyTree(destDir, sourceDir string,
    46  	copyFunc func(destFilename, sourceFilename string,
    47  		mode os.FileMode) error) error {
    48  	file, err := os.Open(sourceDir)
    49  	if err != nil {
    50  		if os.IsNotExist(err) {
    51  			return nil
    52  		}
    53  		return err
    54  	}
    55  	names, err := file.Readdirnames(-1)
    56  	file.Close()
    57  	if err != nil {
    58  		return err
    59  	}
    60  	for _, name := range names {
    61  		sourceFilename := path.Join(sourceDir, name)
    62  		destFilename := path.Join(destDir, name)
    63  		var stat syscall.Stat_t
    64  		if err := syscall.Lstat(sourceFilename, &stat); err != nil {
    65  			return errors.New(sourceFilename + ": " + err.Error())
    66  		}
    67  		switch stat.Mode & syscall.S_IFMT {
    68  		case syscall.S_IFDIR:
    69  			if err := os.Mkdir(destFilename, dirPerms); err != nil {
    70  				if !os.IsExist(err) {
    71  					return err
    72  				}
    73  			}
    74  			err := copyTree(destFilename, sourceFilename, copyFunc)
    75  			if err != nil {
    76  				return err
    77  			}
    78  		case syscall.S_IFREG:
    79  			err := copyFunc(destFilename, sourceFilename,
    80  				os.FileMode(stat.Mode)&os.ModePerm)
    81  			if err != nil {
    82  				return err
    83  			}
    84  		case syscall.S_IFLNK:
    85  			sourceTarget, err := os.Readlink(sourceFilename)
    86  			if err != nil {
    87  				return errors.New(sourceFilename + ": " + err.Error())
    88  			}
    89  			if destTarget, err := os.Readlink(destFilename); err == nil {
    90  				if sourceTarget == destTarget {
    91  					continue
    92  				}
    93  			}
    94  			os.Remove(destFilename)
    95  			if err := os.Symlink(sourceTarget, destFilename); err != nil {
    96  				return err
    97  			}
    98  		default:
    99  			return errors.New("unsupported file type")
   100  		}
   101  	}
   102  	return nil
   103  }
   104  
   105  func copyFile(destFilename, sourceFilename string, mode os.FileMode) error {
   106  	if mode == 0 {
   107  		var stat syscall.Stat_t
   108  		if err := syscall.Stat(sourceFilename, &stat); err != nil {
   109  			return errors.New(sourceFilename + ": " + err.Error())
   110  		}
   111  		mode = os.FileMode(stat.Mode & syscall.S_IFMT)
   112  	}
   113  	sourceFile, err := os.Open(sourceFilename)
   114  	if err != nil {
   115  		return errors.New(sourceFilename + ": " + err.Error())
   116  	}
   117  	defer sourceFile.Close()
   118  	return copyToFile(destFilename, mode, sourceFile, 0)
   119  }