微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

在 golang 中提取 tarball 时丢失文件

如何解决在 golang 中提取 tarball 时丢失文件

我正在尝试使用此功能在我解压缩文件后解压缩文件,但是,当它解压缩时,会丢失一些文件夹,我不知道为什么。当我通过 gui 打开创建的 tarfile 时,UnGzip 工作正常,因此不包含该功能

func main() {
fileUrl := "https://www.clamav.net/downloads/production/clamav-0.103.1.tar.gz"
filePath := "clamav-0.103.1.tar.gz"
tempFolder := "temp"
    
err := os.Mkdir(tempFolder,0755)
if err != nil {
    panic(err)
}

err = DownloadFile(filePath,fileUrl)
if err != nil {
    panic(err)
}
fmt.Println("Downloaded: " + fileUrl)

UnGzip(filePath,tempFolder + "/clamav.tar")
UnTar(tempFolder + "/clamav.tar",tempFolder + "/clamAV/")
//err := os.RemoveAll("tempFolder")
//if err != nil {
    //panic(err)
//}

}

func UnTar(tarball,target string) error {
reader,err := os.Open(tarball)
if err != nil {
    return err
}
defer reader.Close()
tarReader := tar.NewReader(reader)

for {
    header,err := tarReader.Next()
    if err == io.EOF {
        break
    } else if err != nil {
        return err
    }

    path := filepath.Join(target,header.Name)
    info := header.FileInfo()
    if info.IsDir() {
        if err = os.MkdirAll(path,info.Mode()); err != nil {
            return err
        }
        continue
    }

    file,err := os.OpenFile(path,os.O_CREATE|os.O_Trunc|os.O_WRONLY,info.Mode())
    if err != nil {
        return err
    }
    defer file.Close()
    _,err = io.copy(file,tarReader)
    if err != nil {
        return err
    }
}
return nil

}

这是我应该得到的:want 这就是我所拥有的:have

解决方法

这是一些示例代码:

package main

import (
   "archive/tar"
   "compress/gzip"
   "io"
   "os"
   "path"
)

func extract(source string) error {
   file,err := os.Open(source)
   if err != nil { return err }
   defer file.Close()
   gzRead,err := gzip.NewReader(file)
   if err != nil { return err }
   defer gzRead.Close()
   tarRead := tar.NewReader(gzRead)
   for {
      cur,err := tarRead.Next()
      if err == io.EOF { break } else if err != nil { return err }
      os.MkdirAll(path.Dir(cur.Name),os.ModeDir)
      switch cur.Typeflag {
      case tar.TypeReg:
         create,err := os.Create(cur.Name)
         if err != nil { return err }
         defer create.Close()
         create.ReadFrom(tarRead)
      case tar.TypeLink:
         os.Link(cur.Linkname,cur.Name)
      }
   }
   return nil
}

用法:

package main

func main() {
   extract("clamav-0.103.1.tar.gz")
}
,

对于每个进程允许的打开文件数,您可能会遇到 ulimit。运行带有 ulimit 标志的 -a,我认为默认的 open files 限制是 1024。tarball 有 2758 个文件。

这是因为您在处理 tarReader 的 for 循环中推迟关闭文件描述符。

要修复它,请在处理完每个文件时关闭它们:

func UnTar(tarball,target string) error {
    reader,err := os.Open(tarball)
    if err != nil {
        return err 
    }   
    defer reader.Close()
    tarReader := tar.NewReader(reader)

    for {
        header,err := tarReader.Next()
        if err == io.EOF {
            break
        } else if err != nil {
            return err 
        }

        path := filepath.Join(target,header.Name)
        info := header.FileInfo()
        if info.IsDir() {
            if err = os.MkdirAll(path,info.Mode()); err != nil {
                return err 
            }
            continue
        }

        err = processOneFile(tarReader,path,info.Mode())
        if err != nil {
            return err 
        }
    }   
    return nil 
}

func processOneFile(tarReader io.Reader,filePath string,fileMode os.FileMode) error {
    file,err := os.OpenFile(filePath,os.O_CREATE|os.O_TRUNC|os.O_WRONLY,fileMode)
    if err != nil {
        return err 
    }   
    defer file.Close() // close error discarded
    _,err = io.Copy(file,tarReader)
    return err 
}
,

虽然关于 ulimit 的另一个答案已经非常好,但我只想补充两点:

  1. 您可以同时解压 gzip 和读取 tar 文件,而不是在两者之间创建临时文件。您也可以直接从 URL 流式传输文件并在下载时提取它
  2. 有人可能会创建一个恶意的 tar.gz 文件,让您的代码使用 zipslip 之类的东西覆盖重要文件(特别是如果您的程序以 root 身份运行,有人可能会注入 ../../../../etc/passwd 之类的文件路径并因此覆盖该文件,甚至可能编辑 crontab 文件并以这种方式执行代码?),您可能应该检查一下

考虑到这一点,我们可以编写一个直接从 io.Reader 中提取的函数,该函数还检查目标目录之外的任何路径:

// untargz decompresses a gzipped tar stream to the directory specified by target.
// Note that `file` should be closed by the caller
func untargz(file io.Reader,targetDir string) (err error) {
    gz,err := gzip.NewReader(file)
    if err != nil {
        return
    }
    // This does not close file
    defer gz.Close()

    tarReader := tar.NewReader(gz)

    for {
        header,err := tarReader.Next()
        if err == io.EOF {
            break
        } else if err != nil {
            return err
        }

        // This can be dangerous,similar to zipslip
        path := filepath.Join(targetDir,header.Name)

        // Check for ZipSlip. More Info: https://snyk.io/research/zip-slip-vulnerability#go
        if !strings.HasPrefix(path,filepath.Clean(targetDir)+string(os.PathSeparator)) {
            err = fmt.Errorf("%s: illegal file path",path)
            return err
        }

        info := header.FileInfo()
        if info.IsDir() {
            if err = os.MkdirAll(path,info.Mode()); err != nil {
                return err
            }
            continue
        }

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

        _,tarReader)
        if err != nil {
            file.Close()
            return err
        }

        err = file.Close()
        if err != nil {
            return err
        }
    }

    return nil
}

考虑一下如果在该目录中的文件之后声明一个目录会发生什么可能也是有益的(因为 os.Create 失败),但该函数不处理这种情况。

>

此函数可用于直接流式传输到输出目录,但老实说,我不确定这是否是您想要的:

func main() {
    resp,err := http.Get(`https://www.clamav.net/downloads/production/clamav-0.103.1.tar.gz`)
    if err != nil {
        panic(err)
    }
    defer resp.Body.Close()

    err = untargz(bufio.NewReader(resp.Body),"out")
    if err != nil {
        panic(err)
    }

    println("Done")
}

您可以找到完整的文件 here

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。