在golang中使用archive / tar包,似乎无法访问文件所具有的硬链接数。但是,我记得在某个地方阅读tar目录或文件可以保留硬链接。
是否有一些包可以帮助我做到这一点?
答案 0 :(得分:5)
tar
确实保留了硬链接。
这是一个示例目录,其中包含三个硬链接文件和一个带有单个链接的文件:
foo% vdir .
total 16
-rw-r--r-- 3 kostix kostix 5 Jul 12 19:37 bar.txt
-rw-r--r-- 3 kostix kostix 5 Jul 12 19:37 foo.txt
-rw-r--r-- 3 kostix kostix 5 Jul 12 19:37 test.txt
-rw-r--r-- 1 kostix kostix 9 Jul 12 19:49 xyzzy.txt
现在我们使用GNU tar
对其进行归档并验证它确实添加了链接
(因为我们没有传递--hard-dereferece
命令行选项):
foo% tar -cf ../foo.tar .
foo% tar -tvf ../foo.tar
drwxr-xr-x kostix/kostix 0 2016-07-12 19:49 ./
-rw-r--r-- kostix/kostix 9 2016-07-12 19:49 ./xyzzy.txt
-rw-r--r-- kostix/kostix 5 2016-07-12 19:37 ./bar.txt
hrw-r--r-- kostix/kostix 0 2016-07-12 19:37 ./test.txt link to ./bar.txt
hrw-r--r-- kostix/kostix 0 2016-07-12 19:37 ./foo.txt link to ./bar.txt
archive/tar
的文档是指在tar
存档上定义标准的一堆文档(不幸的是,没有一个标准:例如,GNU tar不支持POSIX扩展属性,虽然BSD tar(依赖于libarchive
),但pax
)也是如此。
在硬链接上引用它:
LNKTYPE
此标志表示链接到任何类型的另一个文件的文件, 以前存档。这些文件在Unix中由每个文件识别 相同的设备和inode号码。链接名称在。中指定 linkname字段,尾随空格。
所以,一个hadrlink是一个特殊类型的enrty('1'),它引用了一些 在之前(已经存档)文件的名称。
所以让我们创建一个游乐场示例。
我们对存档进行64位编码:
foo% base64 <../foo.tar | xclip -selection clipboard
...并写下the code。 存档包含一个目录,一个文件(类型'0')另一个文件(类型'0'),后跟两个硬链接(类型'1')。
游乐场示例的输出:
Archive entry '5': ./
Archive entry '0': ./xyzzy.txt
Archive entry '0': ./bar.txt
Archive entry '1': ./test.txt link to ./bar.txt
Archive entry '1': ./foo.txt link to ./bar.txt
所以你的链接计数代码应该:
逐个记录扫描整个档案。
记住任何常规文件(archive/tar.TypeReg
类型
或者键入archive/tar.TypeRegA
)已经处理过,并且有一个与之关联的计数器,从1开始。
嗯,实际上,你最好是独家和记录条目 除了符号链接和目录之外的所有类型 - 因为tar 存档可以包含字符和块设备的节点,以及FIFO(命名管道)。
遇到硬链接(类型archive/tar.TypeReg
)时,
Linkname
字段。2016-07-13更新
由于OP实际上想知道如何管理硬链接 源文件系统,这是更新。
主要思想是在具有POSIX语义的文件系统上:
指定文件的目录条目实际上指向特殊文件 文件系统元数据块称为“inode”。 inode包含目录条目的数量 指着它。
创建硬链接实际上只是:
ln
条款中的“链接目标”。因此,任何文件都由两个整数唯一标识: 标识托管文件系统的物理设备的“设备编号” 文件所在的位置,以及标识文件数据的inode编号。
接下来,如果两个文件具有相同的(设备,inode)对, 它们代表相同的内容。或者,如果我们换一种说法,那就是一个 是另一方的硬链接。
因此,将文件添加到tar
存档,同时保留硬链接的方式是这样的:
添加文件后,将其(device,inode)对保存到某个查找表中。
添加其他文件时,找出其(设备,inode)对和 在那张桌子上查一查。
如果找到匹配的条目,则文件的数据已经流式传输, 我们应该添加一个硬链接。
否则,表现如步骤(1)。
所以这是代码:
package main
import (
"archive/tar"
"io"
"log"
"os"
"path/filepath"
"syscall"
)
type devino struct {
Dev uint64
Ino uint64
}
func main() {
log.SetFlags(0)
if len(os.Args) != 2 {
log.Fatalf("Usage: %s DIR\n", os.Args[0])
}
seen := make(map[devino]string)
tw := tar.NewWriter(os.Stdout)
err := filepath.Walk(os.Args[1],
func(fn string, fi os.FileInfo, we error) (err error) {
if we != nil {
log.Fatal("Error processing directory", we)
}
hdr, err := tar.FileInfoHeader(fi, "")
if err != nil {
return
}
if fi.IsDir() {
err = tw.WriteHeader(hdr)
return
}
st := fi.Sys().(*syscall.Stat_t)
di := devino{
Dev: st.Dev,
Ino: st.Ino,
}
orig, ok := seen[di]
if ok {
hdr.Typeflag = tar.TypeLink
hdr.Linkname = orig
hdr.Size = 0
err = tw.WriteHeader(hdr)
return
}
fd, err := os.Open(fn)
if err != nil {
return
}
err = tw.WriteHeader(hdr)
if err != nil {
return
}
_, err = io.Copy(tw, fd)
fd.Close() // Ignoring error for a file opened R/O
if err == nil {
seen[di] = fi.Name()
}
return err
})
if err != nil {
log.Fatal(err)
}
err = tw.Close()
if err != nil {
log.Fatal(err)
}
return
}
请注意,它非常蹩脚:
它不正确地处理文件和目录名称。
它不会尝试正确使用符号链接和FIFO, 并跳过Unix域套接字等。
它假定它在POSIX环境中有效。
在非POSIX系统上,Sys()
方法调用了类型的值
os.FileInfo
可能会返回其他内容而不是POSIX'y
syscall.Stat_t
。
说,在Windows上,有多个不同的文件系统托管 “磁盘”或“驱动器”。我不知道Go如何处理。 对于这种情况,可能必须以某种方式模拟“设备编号”。
另一方面,它显示了如何处理硬链接:
您可能还想使用另一种方法来维护查找表:如果预期您的大多数文件位于同一物理文件系统上,则每个条目都会浪费uint64
作为每个条目的设备编号。因此,地图层次结构可能是明智之举:第一个将设备编号映射到另一个地图,将地址编号映射到文件名。
希望这有帮助。
答案 1 :(得分:0)
答案很好而且很详细。只是要加上我的50美分,以了解如何在提取过程中保存这些硬链接。
在tar归档文件(tr.Next())中的所有文件上循环
if f.Size == 0 && f.Linkname != "" {
// this is a hard link for another file. save it in map and create at the end
seen[abs] = filepath.Join(extractDir, f.Linkname)
continue
}
最后:
for path, target := range seen {
if err := os.Link(target, path); err != nil {
return fmt.Errorf("failed to create hard link: %v", err)
}
}