尝试从文件和目录创建 tar.gz 文件时出现“写入太长”错误
Getting `write too long` error when trying to create tar.gz file from file and directories
所以我试图从多个目录和文件创建一个 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
错误,删除后一切正常。
运行 没有想法,为此浪费了很多时间试图找到解决方案...
有什么想法吗?
谢谢
Write writes to the current entry in the tar archive. Write returns the error ErrWriteTooLong if more than hdr.Size bytes are written after WriteHeader.
有一个 Size
选项可以添加到 Header。没试过,但也许有帮助...
在仔细查看 tar.FileInfoHeader 文档之前,我遇到了类似的问题:
FileInfoHeader creates a partially-populated Header from fi. If fi describes a symlink, FileInfoHeader records link as the link target. If fi describes a directory, a slash is appended to the name. Because os.FileInfo's Name method returns only the base name of the file it describes, it may be necessary to modify the Name field of the returned header to provide the full path name of the file.
本质上,FileInfoHeader 不能保证在您使用 WriteHeader 写入之前填写所有 header 字段,如果您查看实现,Size 字段仅在 regular 文件上设置.您的代码片段似乎只处理目录,这意味着如果您遇到任何其他非常规文件,您将 header 写入大小为零,然后尝试在磁盘上复制一个可能 non-zero 大小的特殊文件进入tar。转到 returns 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
})
由于您只在大型目录中看到此问题,我认为以下修复可能无济于事,但这将解决从可能不断增长的文件创建 tar 的问题。
在我的例子中,问题是当我们创建 tar header 时,header.Size(在 tar.FileInfoHeader 内)正在设置文件大小( info.Size()) 在那个时刻。
当我们稍后在代码中尝试打开相关文件 (os.Open) 并复制其内容 (io.Copy) 时,我们冒着复制比我们之前设置的更多数据的风险 tar header 大小,因为文件可能在此期间增长。
这段代码将确保我们只复制我们将 tar header 大小设置为:
的数据
_, err = io.**CopyN**(tw, file, info.Size())
if err != nil {
log.Println("failing here")
return err
}
所以我试图从多个目录和文件创建一个 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
错误,删除后一切正常。
运行 没有想法,为此浪费了很多时间试图找到解决方案...
有什么想法吗?
谢谢
Write writes to the current entry in the tar archive. Write returns the error ErrWriteTooLong if more than hdr.Size bytes are written after WriteHeader.
有一个 Size
选项可以添加到 Header。没试过,但也许有帮助...
在仔细查看 tar.FileInfoHeader 文档之前,我遇到了类似的问题:
FileInfoHeader creates a partially-populated Header from fi. If fi describes a symlink, FileInfoHeader records link as the link target. If fi describes a directory, a slash is appended to the name. Because os.FileInfo's Name method returns only the base name of the file it describes, it may be necessary to modify the Name field of the returned header to provide the full path name of the file.
本质上,FileInfoHeader 不能保证在您使用 WriteHeader 写入之前填写所有 header 字段,如果您查看实现,Size 字段仅在 regular 文件上设置.您的代码片段似乎只处理目录,这意味着如果您遇到任何其他非常规文件,您将 header 写入大小为零,然后尝试在磁盘上复制一个可能 non-zero 大小的特殊文件进入tar。转到 returns 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
})
由于您只在大型目录中看到此问题,我认为以下修复可能无济于事,但这将解决从可能不断增长的文件创建 tar 的问题。
在我的例子中,问题是当我们创建 tar header 时,header.Size(在 tar.FileInfoHeader 内)正在设置文件大小( info.Size()) 在那个时刻。
当我们稍后在代码中尝试打开相关文件 (os.Open) 并复制其内容 (io.Copy) 时,我们冒着复制比我们之前设置的更多数据的风险 tar header 大小,因为文件可能在此期间增长。
这段代码将确保我们只复制我们将 tar header 大小设置为:
的数据_, err = io.**CopyN**(tw, file, info.Size())
if err != nil {
log.Println("failing here")
return err
}