Skip to content

Instantly share code, notes, and snippets.

@jcorbin
Created September 17, 2019 22:56
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jcorbin/fd0716bd0bc4c49cd22d73e4aad23b38 to your computer and use it in GitHub Desktop.
Save jcorbin/fd0716bd0bc4c49cd22d73e4aad23b38 to your computer and use it in GitHub Desktop.
package unzip
import (
"archive/zip"
"context"
"fmt"
"io"
"os"
"path/filepath"
"runtime"
"strings"
"golang.org/x/sync/errgroup"
)
func unzipInto(ctx context.Context, dirName string, arch *zip.Reader) (rerr error) {
numProcs := runtime.GOMAXPROCS(-1)
step := len(arch.File) / numProcs
if step < 1 {
step = 1
}
eg, ctx := errgroup.WithContext(ctx)
for i := 0; i < len(arch.File); i += step {
shard := arch.File[i:]
if len(shard) > step {
shard = shard[:step]
}
eg.Go(func() error {
var un unzipper
for _, zf := range shard {
if err := ctx.Err(); err != nil {
return nil
}
if err := un.unzipFile(zf, dirName); err != nil {
return err
}
}
return nil
})
}
return eg.Wait()
}
type unzipper struct {
copyBuffer [32 * 1024]byte
}
func (un *unzipper) unzipFile(zf *zip.File, destDir string) (rerr error) {
// check that zip file path does not try to "slip" out of the destination directory
path := filepath.Join(destDir, zf.Name)
if prefix := filepath.Clean(destDir) + string(os.PathSeparator); !strings.HasPrefix(path, prefix) {
return fmt.Errorf("illegal zip file name %q", zf.Name)
}
// ensure parent directory
if err := os.MkdirAll(filepath.Dir(path), os.ModePerm); err != nil && os.IsNotExist(err) {
return err
}
// create directory entry
info := zf.FileInfo()
if info.IsDir() {
return os.Mkdir(path, info.Mode())
}
// extract file contents
rc, err := zf.Open()
if err != nil {
return err
}
defer func() {
if cerr := rc.Close(); rerr == nil {
rerr = cerr
}
}()
// exclusively create output file, and copy contents out of the archive
out, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_EXCL, info.Mode())
if err != nil {
return err
}
_, err = io.CopyBuffer(out, rc, un.copyBuffer[:])
if cerr := out.Close(); err == nil {
err = cerr
}
return err
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment