对于拥有多台电脑,或者经常换电脑的人来说,文件系统备份是一个常见性问题。比如,将个人电脑和办公室电脑的home目录进行备份,每个人的不同电脑上,home目录上应该有一些相同的文件,如Document、sources(个人存放代码和工具的目录)、read(个人存放电子书的目录)、install(个人安装程序的目录)等等。在长期使用的过程中,不同电脑上都会对原本相同的文件目录做不同的修改(大部分是添加新文件),这样怎么备份不同电脑上的home目录呢?
方法一,对每个电脑上的home目录都做独立备份。如果home目录比较小到还好,如果非常大(这应该是常见情况),这样会导致空间极大浪费,没有利用不同home目录上存在相同文件的特征。
方法二,使用cp -u,只拷贝比较新的或者目标目录中不存在的文件。在实践过程中,发现即使在文件的内容没有改变的情况下,文件的modify time也是很容易被修改的,cp一个文件时,本身也会修改文件的modify time。所以使用cp -u时会拷贝原本并没有改变的文件,浪费大量时间。
方法三,增量备份,首先记录当前home目录与备份home目录中改变的部分,再将改变的部分从当前home目录拷贝到备份home目录中。这样原本没有变化的部分不用拷贝,可以节约时间。而且对于多台电脑,可以只备份不同的部分,共用原本相同的部分,类似合并操作。
所以综合比较,增量备份对于文件系统的备份有非常大的优势。最近在学习docker时,受到naiveDiffDriver实现的启发,根据它实现了一个增量备份工具deepcopy。该工具在备份时,分为两个步骤:1 记录改变的部分,2 将改变的部分增量拷贝到备份的文件系统中。下面简单介绍下它的实现。
记录改变部分,该工具首先利用path/filepath.walk来同时遍历当前home目录和备份home目录,并利用system.Lstat获取每个文件的状态信息,通过树形结构的FileInfo保存下来,然后根据文件的size大小来判断不同:
for name, newChild := range info.children {
oldChild, _ := oldChildren[name]
if oldChild != nil {
// change?
oldStat := oldChild.stat
newStat := newChild.stat
if (oldStat.Size() != newStat.Size() && oldStat.Mode()&syscall.S_IFDIR != syscall.S_IFDIR) ||
bytes.Compare(oldChild.capability, newChild.capability) != 0 {
change := Change{
Path: newChild.path(),
Kind: ChangeModify,
}
*changes = append(*changes, change)
newChild.added = true
}
// Remove from copy so we can detect deletions
delete(oldChildren, name)
}
newChild.addChanges(oldChild, changes)
} 写代码:
func() {
ta := &tarAppender{
TarWriter: tar.NewWriter(writer),
Buffer: pools.BufioWriter32KPool.Get(nil),
SeenFiles: make(map[uint64]string),
}
// this buffer is needed for the duration of this piped stream
defer pools.BufioWriter32KPool.Put(ta.Buffer)
for _, change := range changes {
if change.Kind != ChangeDelete {
path := filepath.Join(dir, change.Path)
if err := ta.addTarFile(path, change.Path[1:]); err != nil {
log.Debugf("Can't add file %s to tar: %s", path, err)
}
}
}
// Make sure to check the error on Close.
if err := ta.TarWriter.Close(); err != nil {
log.Debugf("Can't close layer: %s", err)
}
if err := writer.Close(); err != nil {
log.Debugf("failed close Changes writer: %s", err)
}
}func UnpackLayer(dest string, layer ArchiveReader) (size int64, err error) {
tr := tar.NewReader(layer)
trBuf := pools.BufioReader32KPool.Get(tr)
defer pools.BufioReader32KPool.Put(trBuf)
var dirs []*tar.Header
aufsTempdir := ""
aufsHardlinks := make(map[string]*tar.Header)
// Iterate through the files in the archive.
for {
hdr, err := tr.Next()
if err == io.EOF {
// end of tar archive
break
}
if err != nil {
return 0, err
}
size += hdr.Size
// Normalize name, for safety and for a simple is-root check
hdr.Name = filepath.Clean(hdr.Name)
if !strings.HasSuffix(hdr.Name, "/") {
parent := filepath.Dir(hdr.Name)
parentPath := filepath.Join(dest, parent)
if _, err := os.Lstat(parentPath); err != nil && os.IsNotExist(err) {
err = os.MkdirAll(parentPath, 0600)
if err != nil {
return 0, err
}
}
}
path := filepath.Join(dest, hdr.Name)
rel, err := filepath.Rel(dest, path)
if err != nil {
return 0, err
}
if strings.HasPrefix(rel, "..") {
return 0, breakoutError(fmt.Errorf("%q is outside of %q", hdr.Name, dest))
}
base := filepath.Base(path)
if true {
if fi, err := os.Lstat(path); err == nil {
if !(fi.IsDir() && hdr.Typeflag == tar.TypeDir) {
if err := os.RemoveAll(path); err != nil {
return 0, err
}
}
}
trBuf.Reset(tr)
srcData := io.Reader(trBuf)
srcHdr := hdr
// Hard links into /.wh..wh.plnk don't work, as we don't extract that directory, so
// we manually retarget these into the temporary files we extracted them into
if hdr.Typeflag == tar.TypeLink && strings.HasPrefix(filepath.Clean(hdr.Linkname), ".wh..wh.plnk") {
linkBasename := filepath.Base(hdr.Linkname)
srcHdr = aufsHardlinks[linkBasename]
if srcHdr == nil {
return 0, fmt.Errorf("Invalid aufs hardlink")
}
tmpFile, err := os.Open(filepath.Join(aufsTempdir, linkBasename))
if err != nil {
return 0, err
}
defer tmpFile.Close()
srcData = tmpFile
}
if err := createTarFile(path, dest, srcHdr, srcData, true); err != nil {
return 0, err
}
// Directory mtimes must be handled at the end to avoid further
// file creation in them to modify the directory mtime
if hdr.Typeflag == tar.TypeDir {
dirs = append(dirs, hdr)
}
}
} 该工具的完整代码请看https://github.com/xuriwuyun/deepcopy。该代码非常原始,仅提供参考。原文地址:http://blog.csdn.net/xuriwuyun/article/details/45692079