尝试从文件和目录创建tar.gz文件时出现“写太长”错误

时间:2016-07-19 09:41:42

标签: go gzip tar gz

所以我试图从多个目录和文件中创建tar.gz文件。具有相同用法的东西:

tar -cvzf sometarfile.tar.gz somedir/ someotherdir/ somefile.json somefile.xml

假设目录中包含其他目录。 我有这个作为输入:

    paths := []string{
      "somedir/",
      "someotherdir/",
      "somefile.json",
      "somefile.xml",
    }

并使用这些:

    func TarFilesDirs(paths []string, tarFilePath string ) error {
       // set up the output file
       file, err := os.Create(tarFilePath)
       if err != nil {
           return err
       }

       defer file.Close()
       // set up the gzip writer
       gz := gzip.NewWriter(file)
       defer gz.Close()

       tw := tar.NewWriter(gz)
       defer tw.Close()

       // add each file/dir as needed into the current tar archive
       for _,i := range paths {
          if err := tarit(i, tw); err != nil {
               return err
          }
       }

       return nil
   }

func tarit(source string, tw *tar.Writer) error {
    info, err := os.Stat(source)
    if err != nil {
        return nil
    }

    var baseDir string
    if info.IsDir() {
        baseDir = filepath.Base(source)
    }

    return filepath.Walk(source,
        func(path string, info os.FileInfo, err error) error {
            if err != nil {
                return err
            }

            header, err := tar.FileInfoHeader(info, info.Name())
            if err != nil {
                return err
            }

            if baseDir != "" {
                header.Name = filepath.Join(baseDir, strings.TrimPrefix(path, source))
            }

            if err := tw.WriteHeader(header); err != nil {
                return err
            }

            if info.IsDir() {
                return nil
            }

            file, err := os.Open(path)
            if err != nil {
                return err
            }

            defer file.Close()

            _, err = io.Copy(tw, file)
            if err != nil {
                log.Println("failing here")
                return err
            }

            return err
        })
}

问题:如果目录很大,我会:

archive/tar: write too long

错误,当我删除它时一切正常。

出于想法而浪费了很多时间试图寻找解决方案......

有什么想法吗?

由于

3 个答案:

答案 0 :(得分:5)

在我更仔细地查看tar.FileInfoHeader doc之前,我遇到了类似的问题:

  

FileInfoHeader从fi创建部分填充的标头。如果fi描述符号链接,则FileInfoHeader将链接记录为链接目标。如果fi描述目录,则会在名称后附加斜杠。由于os.FileInfo的Name方法仅返回其描述的文件的基本名称,因此可能需要修改返回标头的Name字段以提供文件的完整路径名。

基本上,FileInfoHeader不能保证在用WriteHeader写入之前填写所有头字段,如果查看实现,则只在regular文件上设置Size字段。您的代码片段似乎只处理目录,这意味着如果您遇到任何其他非常规文件,您编写大小为零的标头然后尝试将磁盘上可能非零大小的特殊文件复制到tar中。 Go返回ErrWriteTooLong以阻止您创建损坏的tar。

我想出了这个,从那以后就没有问题了。

id

答案 1 :(得分:0)

  

将写入写入tar存档中的当前条目。如果在WriteHeader之后写入多于hdr.Size的字节,则Write返回错误ErrWriteTooLong。

您可以在标题中添加Size选项。 Haven没试过,但也许这有帮助...

另见https://golang.org/pkg/archive/tar/

答案 2 :(得分:0)

由于您只看到一个大目录的问题,我认为以下修复方法可能无济于事,但这将解决从可能不断增长的文件创建tar的问题。

在我的情况下,问题是当我们创建tar标头时,header.Size(位于tar.FileInfoHeader内部)在该时刻被设置为文件大小(info.Size())。

当我们稍后在代码中尝试打开相关文件(os.Open)并复制其内容(io.Copy)时,与以前设置tar标头大小相比,我们冒着复制更多数据的风险,因为该文件可能具有同时增长。

这段代码将确保我们仅将tar标头的大小设置为以下方式复制大量数据:

_, err = io.**CopyN**(tw, file, info.Size())
if err != nil {
    log.Println("failing here")
    return err
}