This commit improves the behaviour of 'backup' and 'snapshot' by treating symbolic links properly, as well as writes proper reports.
132 lines
2.8 KiB
Go
132 lines
2.8 KiB
Go
package fsx
|
|
|
|
import (
|
|
"errors"
|
|
"io"
|
|
"io/fs"
|
|
"os"
|
|
"path/filepath"
|
|
)
|
|
|
|
var ErrCopySameFile = errors.New("src and dst must be different")
|
|
|
|
// CopyFile copies a file from src to dst.
|
|
// When src points to a symbolic link, will copy the symbolic link.
|
|
//
|
|
// When dst and src are the same file, returns ErrCopySameFile.
|
|
func CopyFile(dst, src string) error {
|
|
if SameFile(src, dst) {
|
|
return ErrCopySameFile
|
|
}
|
|
|
|
// open the source
|
|
srcFile, err := os.Open(src)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer srcFile.Close()
|
|
|
|
// stat it to get the mode!
|
|
srcStat, err := srcFile.Stat()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// open or create the destination
|
|
dstFile, err := os.OpenFile(dst, os.O_WRONLY|os.O_CREATE, srcStat.Mode())
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer dstFile.Close()
|
|
|
|
// and do the copy!
|
|
_, err = io.Copy(dstFile, srcFile)
|
|
return err
|
|
}
|
|
|
|
// CopyLink copies a link from src to dst.
|
|
// If dst already exists, it is deleted and then re-created.
|
|
func CopyLink(dst, src string) error {
|
|
// if they're the same file that is an error
|
|
if SameFile(dst, src) {
|
|
return ErrCopySameFile
|
|
}
|
|
|
|
// read the link target
|
|
target, err := os.Readlink(src)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// delete it if it already exists
|
|
if Exists(dst) {
|
|
if err := os.Remove(dst); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
// make the symbolic link!
|
|
return os.Symlink(target, dst)
|
|
}
|
|
|
|
var ErrDstFile = errors.New("dst is a file")
|
|
|
|
// CopyDirectory copies the directory src to dst recursively.
|
|
//
|
|
// Existing files and directories are overwritten.
|
|
// When a directory already exists, additional files are not deleted.
|
|
//
|
|
// onCopy, when not nil, is called for each file or directory being copied.
|
|
func CopyDirectory(dst, src string, onCopy func(dst, src string)) error {
|
|
// sanity checks
|
|
if SameFile(src, dst) {
|
|
return ErrCopySameFile
|
|
}
|
|
if IsFile(dst) {
|
|
return ErrDstFile
|
|
}
|
|
|
|
return filepath.WalkDir(src, func(path string, d fs.DirEntry, err error) error {
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// determine the real target path
|
|
var relpath string
|
|
relpath, err = filepath.Rel(src, path)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
dst := filepath.Join(dst, relpath)
|
|
|
|
// call the hook
|
|
if onCopy != nil {
|
|
onCopy(dst, src)
|
|
}
|
|
|
|
// stat the directory, so that we can get mode, and info later!
|
|
info, err := d.Info()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// if we have a symbolic link, copy the link!
|
|
if info.Mode()&os.ModeSymlink != 0 {
|
|
return CopyLink(dst, path)
|
|
}
|
|
|
|
// if we got a file, we should copy it normally
|
|
if !d.IsDir() {
|
|
return CopyFile(dst, path)
|
|
}
|
|
|
|
// create the directory, but ignore an error if the directory already exists.
|
|
// this is so that we can copy one tree into another tree.
|
|
err = os.Mkdir(dst, info.Mode())
|
|
if os.IsExist(err) && IsDirectory(dst) {
|
|
err = nil
|
|
}
|
|
|
|
return err
|
|
})
|
|
}
|