io.Copy() 稀疏文件很大

Sparse files are huge with io.Copy()

我想将文件从一个地方复制到另一个地方,但问题是我要处理很多稀疏文件。

有没有什么(简单的)方法可以复制稀疏文件而不会在目的地变得很大?

我的基本代码:

out, err := os.Create(bricks[0] + "/" + fileName)
in, err := os.Open(event.Name)
io.Copy(out, in)

一些背景理论

请注意,io.Copy() 通过管道传输原始字节 - 一旦您认为它将数据从 io.Reader 传输到 io.Writer 提供 Read([]byte)Write([]byte),对应。 因此,io.Copy() 绝对能够处理任何提供的来源 字节和绝对消耗它们的任何接收器。

另一方面,文件中漏洞的位置是 "side-channel" 信息,"classic" 系统调用(例如 read(2))向用户隐藏了这些信息。 io.Copy() 无法以任何方式传达此类边信道信息。

IOW,最初,文件稀疏性只是在用户背后高效存储数据的想法。

所以,不,io.Copy() 本身无法处理稀疏文件。

怎么办

您需要更深入地使用 syscall 包和一些手动修补来实现所有这些。

要处理漏洞,您应该为 lseek(2) 系统调用使用 SEEK_HOLESEEK_DATA 特殊值,虽然它们在形式上是非标准的,但受 all major platforms.

遗憾的是,不存在对那些 "whence" 位置的支持 既不在库存 syscall 包中(从 Go 1.8.1 开始) 也不在 golang.org/x/sys 树中。

但不要害怕,有两个简单的步骤:

  1. 首先,股票syscall.Seek()实际映射到lseek(2) 在相关平台上。

  2. 接下来,您需要找出 SEEK_HOLESEEK_DATA 您需要支持的平台。

    Note that they are free to be different between different platforms!

    说,在我的 Linux 系统上我可以做简单的事情

    $ grep -E 'SEEK_(HOLE|DATA)' </usr/include/unistd.h 
    #  define SEEK_DATA     3       /* Seek to next data.  */
    #  define SEEK_HOLE     4       /* Seek to next hole.  */
    

    ...计算出这些符号的值。

现在,比方说,您在包中创建一个 Linux 特定文件 包含类似

的东西
// +build linux

const (
    SEEK_DATA = 3
    SEEK_HOLE = 4
)

然后将这些值与 syscall.Seek() 一起使用。

要传递给 syscall.Seek() 和朋友的文件描述符 可以使用 Fd() 方法从打开的文件中获取 os.File 个值。

读取时使用的模式是检测包含数据的区域,并从中读取数据——参见 this 的一个示例。

请注意,这涉及读取稀疏文件;但是如果你真的想 transfer 它们稀疏——也就是说,保留它们中的 属性,——情况就更复杂了:它看起来甚至更少便携,因此需要进行一些研究和实验。

在 Linux 上,您似乎可以尝试将 fallocate(2)FALLOC_FL_PUNCH_HOLE | FALLOC_FL_KEEP_SIZE 尝试在 您正在写入的文件的末尾;如果那合法地失败了 (使用 syscall.EOPNOTSUPP),你只需将你正在读取的漏洞所覆盖的尽可能多的归零块铲到目标文件中——希望如此 OS 会做正确的事,并自行将它们变成一个洞。

请注意,一些文件系统根本不支持漏洞——作为一个概念。 一个例子是 FAT 家族中的文件系统。 我要引导您的是,无法创建稀疏文件可能 实际上是你的目标文件系统的属性。

您可能会对 Go issue #13548 "archive/tar: add support for writing tar containing sparse files" 感兴趣。


另外注意:您还可以考虑检查复制源文件的目标目录是否与源文件位于同一文件系统中,如果是这样,请使用 syscall.Rename()(在 POSIX系统) 或 os.Rename() 将文件移动到不同的目录 w/o 实际上是在复制它的数据。

您不需要求助于系统调用。

package main

import "os"

func main() {
    f, _ := os.Create("/tmp/sparse.dat")
    f.Write([]byte("start"))
    f.Seek(1024*1024*10, 0)
    f.Write([]byte("end"))
}

然后你会看到:

$ ls -l /tmp/sparse.dat
-rw-rw-r-- 1 soren soren 10485763 Jun 25 14:29 /tmp/sparse.dat
$ du /tmp/sparse.dat
8   /tmp/sparse.dat

确实不能按原样使用 io.Copy。相反,您需要实现 io.Copy 的替代方案,它从 src 读取一个块,检查它是否全部 '[=15=]'。如果是,只需 dst.Seek(len(chunk), os.SEEK_CUR) 跳过 dst 中的那部分。该特定实现留作 reader :)

的练习