问题描述
我正在尝试使用此功能在我解压缩文件后解压缩文件,但是,当它解压缩时,会丢失一些文件夹,我不知道为什么。当我通过 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
}
解决方法
这是一些示例代码:
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
的另一个答案已经非常好,但我只想补充两点:
- 您可以同时解压 gzip 和读取 tar 文件,而不是在两者之间创建临时文件。您也可以直接从 URL 流式传输文件并在下载时提取它
- 有人可能会创建一个恶意的 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。