【问题标题】:Getting `write too long` error when trying to create tar.gz file from file and directories尝试从文件和目录创建 tar.gz 文件时出现“写入时间过长”错误
【发布时间】:2016-07-19 09:41:42
【问题描述】:

所以我试图从多个目录和文件中创建一个 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

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

没有想法,浪费了很多时间试图找到解决方案......

有什么想法吗?

谢谢

【问题讨论】:

    标签: go tar gzip


    【解决方案1】:

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

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

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

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

        if err := filepath.Walk(directory, func(path string, info os.FileInfo, err error) error {
            if err != nil {
                return check(err)
            }
    
            var link string
            if info.Mode()&os.ModeSymlink == os.ModeSymlink {
                if link, err = os.Readlink(path); err != nil {
                    return check(err)
                }
            }
    
            header, err := tar.FileInfoHeader(info, link)
            if err != nil {
                return check(err)
            }
    
            header.Name = filepath.Join(baseDir, strings.TrimPrefix(path, directory))
            if err = tw.WriteHeader(header); err != nil {
                return check(err)
            }
    
            if !info.Mode().IsRegular() { //nothing more to do for non-regular
                return nil
            }
    
            fh, err := os.Open(path)
            if err != nil {
                return check(err)
            }
            defer fh.Close()
    
            if _, err = io.CopyBuffer(tw, fh, buf); err != nil {
                return check(err)
            }
            return nil
    })
    

    【讨论】:

    • 也有可能是文件在磁盘上发生了变化,变长了。
    • 请注意这一点 - 某些文件系统无法准确列出 attrs 中的文件大小,例如 /proc。我已经看到当流氓存档试图包含 /proc/PID/attr/current 这是 SELinux 上下文时发生这种情况。文件 attr 将始终报告大小为零,但读取会找到数据。在 GoLang 中,它总是会产生“写得太长”的错误,而 GNU tar/libz 则没有问题。请注意您用于归档的文件系统以及它是否准确地报告了长度。
    【解决方案2】:

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

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

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

    【讨论】:

      【解决方案3】:

      由于您仅在大目录中看到此问题,我认为以下修复可能无济于事,但这将解决从可能持续增长的文件创建 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
      }
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2016-09-26
        • 2021-07-22
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2011-05-16
        • 2022-06-13
        • 2022-06-15
        相关资源
        最近更新 更多