Skip to content

Commit

Permalink
Fix 'file already closed' error for compressed images (#137)
Browse files Browse the repository at this point in the history
When unpacking compressed images that are not compressed as zip archives
(e.g. gzip, xz) the image would unpack nearly all of the way and then
would fail with an error like: 'read |0: file already closed' as is
documented in GitHub issue
[61](#61).

As the decompression is offloaded to an external tool for these
compression types and the data is streamed through a reader and writer
in order to allow reporting on progress, we ensure that the exec.Cmd
instance for the external decompression tool is not torn down until the
associated reader is closed. The
[docs](https://pkg.go.dev/os/exec#Cmd.StdoutPipe) for Cmd.StdoutPipe
explain this in more detail.
  • Loading branch information
will-dee authored Apr 25, 2022
1 parent 05e5cd8 commit 2c85d60
Showing 1 changed file with 17 additions and 8 deletions.
25 changes: 17 additions & 8 deletions pkg/image/image.go
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ func (s *imageOpener) openzip(f *os.File) (Image, error) {
}

//transfer ownership
mc := &multiCloser{zippedfileReader, []io.Closer{zippedfileReader, f}, zippedfile.UncompressedSize64}
mc := &multiCloser{zippedfileReader, []io.Closer{zippedfileReader, f}, nil, zippedfile.UncompressedSize64}
f = nil

return mc, nil
Expand Down Expand Up @@ -167,7 +167,7 @@ func uncompress(f *os.File, fastcmd string, slowNewReader func(r io.Reader) (io.
}

//transfer ownership
mc := &multiCloser{r, []io.Closer{f}, 0}
mc := &multiCloser{r, []io.Closer{f}, nil, 0}
f = nil

return mc, nil
Expand All @@ -188,12 +188,8 @@ func xzFastlane(cmd string, f *os.File) (Image, error) {
return nil, err
}

go func() {
xzcat.Wait()
}()

// use mc for size estimate
mc := &multiCloser{r, []io.Closer{r}, 0}
mc := &multiCloser{r, []io.Closer{r}, xzcat, 0}

return mc, nil

Expand All @@ -202,6 +198,12 @@ func xzFastlane(cmd string, f *os.File) (Image, error) {
type multiCloser struct {
io.Reader
c []io.Closer
// As the reader that is stored in c is the StdoutPipe of an exec.Cmd
// instance, we must ensure the command isn't closed prior to the reader
// being closed otherwise we end up with and error like:
// 'read |0: file already closed'.
// See also https://pkg.go.dev/os/exec#Cmd.StdoutPipe
command *exec.Cmd

sizeEstimate uint64
}
Expand All @@ -210,7 +212,14 @@ func (n *multiCloser) Close() error {
for _, c := range n.c {
c.Close()
}
return nil

var err error
// Wait for the readers (including StdoutPipe) to be closed
// and then initiate the command shutdown if we have a command.
if n.command != nil {
err = n.command.Wait()
}
return err
}

func (f *multiCloser) SizeEstimate() uint64 { return f.sizeEstimate }

0 comments on commit 2c85d60

Please sign in to comment.