
Previously hardlinking to an unmodified file or linking to a file which was touched by not detected as modified caused a new file to be created on unpack. This new file and the original source file were not linked since no link record was created in the tar. This change addresses this by adding links for all hardlinks to a file when it is detected as changed. These links will be written after the source file is written and may occur out of order in regard to file name. Signed-off-by: Derek McGowan <derek@mcgstyle.net>
121 lines
3.0 KiB
Go
121 lines
3.0 KiB
Go
package fs
|
|
|
|
import (
|
|
"io/ioutil"
|
|
"os"
|
|
"path/filepath"
|
|
"sync"
|
|
|
|
"github.com/pkg/errors"
|
|
)
|
|
|
|
var (
|
|
bufferPool = &sync.Pool{
|
|
New: func() interface{} {
|
|
return make([]byte, 32*1024)
|
|
},
|
|
}
|
|
)
|
|
|
|
// CopyDir copies the directory from src to dst.
|
|
// Most efficient copy of files is attempted.
|
|
func CopyDir(dst, src string) error {
|
|
inodes := map[uint64]string{}
|
|
return copyDirectory(dst, src, inodes)
|
|
}
|
|
|
|
func copyDirectory(dst, src string, inodes map[uint64]string) error {
|
|
stat, err := os.Stat(src)
|
|
if err != nil {
|
|
return errors.Wrapf(err, "failed to stat %s", src)
|
|
}
|
|
if !stat.IsDir() {
|
|
return errors.Errorf("source is not directory")
|
|
}
|
|
|
|
if st, err := os.Stat(dst); err != nil {
|
|
if err := os.Mkdir(dst, stat.Mode()); err != nil {
|
|
return errors.Wrapf(err, "failed to mkdir %s", dst)
|
|
}
|
|
} else if !st.IsDir() {
|
|
return errors.Errorf("cannot copy to non-directory: %s", dst)
|
|
} else {
|
|
if err := os.Chmod(dst, stat.Mode()); err != nil {
|
|
return errors.Wrapf(err, "failed to chmod on %s", dst)
|
|
}
|
|
}
|
|
|
|
fis, err := ioutil.ReadDir(src)
|
|
if err != nil {
|
|
return errors.Wrapf(err, "failed to read %s", src)
|
|
}
|
|
|
|
if err := copyFileInfo(stat, dst); err != nil {
|
|
return errors.Wrapf(err, "failed to copy file info for %s", dst)
|
|
}
|
|
|
|
for _, fi := range fis {
|
|
source := filepath.Join(src, fi.Name())
|
|
target := filepath.Join(dst, fi.Name())
|
|
|
|
switch {
|
|
case fi.IsDir():
|
|
if err := copyDirectory(target, source, inodes); err != nil {
|
|
return err
|
|
}
|
|
continue
|
|
case (fi.Mode() & os.ModeType) == 0:
|
|
link, err := getLinkSource(target, fi, inodes)
|
|
if err != nil {
|
|
return errors.Wrap(err, "failed to get hardlink")
|
|
}
|
|
if link != "" {
|
|
if err := os.Link(link, target); err != nil {
|
|
return errors.Wrap(err, "failed to create hard link")
|
|
}
|
|
} else if err := copyFile(source, target); err != nil {
|
|
return errors.Wrap(err, "failed to copy files")
|
|
}
|
|
case (fi.Mode() & os.ModeSymlink) == os.ModeSymlink:
|
|
link, err := os.Readlink(source)
|
|
if err != nil {
|
|
return errors.Wrapf(err, "failed to read link: %s", source)
|
|
}
|
|
if err := os.Symlink(link, target); err != nil {
|
|
return errors.Wrapf(err, "failed to create symlink: %s", target)
|
|
}
|
|
case (fi.Mode() & os.ModeDevice) == os.ModeDevice:
|
|
if err := copyDevice(target, fi); err != nil {
|
|
return errors.Wrapf(err, "failed to create device")
|
|
}
|
|
default:
|
|
// TODO: Support pipes and sockets
|
|
return errors.Wrapf(err, "unsupported mode %s", fi.Mode())
|
|
}
|
|
if err := copyFileInfo(fi, target); err != nil {
|
|
return errors.Wrap(err, "failed to copy file info")
|
|
}
|
|
|
|
if err := copyXAttrs(target, source); err != nil {
|
|
return errors.Wrap(err, "failed to copy xattrs")
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func copyFile(source, target string) error {
|
|
src, err := os.Open(source)
|
|
if err != nil {
|
|
return errors.Wrapf(err, "failed to open source %s", source)
|
|
}
|
|
defer src.Close()
|
|
tgt, err := os.Create(target)
|
|
if err != nil {
|
|
return errors.Wrapf(err, "failed to open target %s", target)
|
|
}
|
|
defer tgt.Close()
|
|
|
|
return copyFileContent(tgt, src)
|
|
}
|