diff --git a/common/libimage/errno_unix.go b/common/libimage/errno_unix.go new file mode 100644 index 0000000000..6e0d39e6f0 --- /dev/null +++ b/common/libimage/errno_unix.go @@ -0,0 +1,7 @@ +//go:build !windows + +package libimage + +import "syscall" + +const ErrNoSpace = syscall.ENOSPC diff --git a/common/libimage/errno_windows.go b/common/libimage/errno_windows.go new file mode 100644 index 0000000000..f1c6d17f0e --- /dev/null +++ b/common/libimage/errno_windows.go @@ -0,0 +1,5 @@ +package libimage + +import "syscall" + +const ErrNoSpace = syscall.ERROR_DISK_FULL diff --git a/common/libimage/load.go b/common/libimage/load.go index 598bf39cdc..9739d9cc85 100644 --- a/common/libimage/load.go +++ b/common/libimage/load.go @@ -110,6 +110,12 @@ func (r *Runtime) Load(ctx context.Context, path string, options *LoadOptions) ( return loadedImages, err } logrus.Debugf("Error loading %s (%s): %v", path, transportName, err) + + if errors.Is(err, ErrNoSpace) { + // %.0w makes err visible to error.Unwrap() without including any text + return nil, fmt.Errorf("no space left on device%.0w", err) + } + loadErrors = append(loadErrors, fmt.Errorf("%s: %v", transportName, err)) } diff --git a/storage/pkg/chrootarchive/archive_unix.go b/storage/pkg/chrootarchive/archive_unix.go index bc649bbbe6..ba511f88f0 100644 --- a/storage/pkg/chrootarchive/archive_unix.go +++ b/storage/pkg/chrootarchive/archive_unix.go @@ -10,6 +10,7 @@ import ( "io" "io/fs" "os" + "os/exec" "path/filepath" "runtime" "strings" @@ -29,6 +30,7 @@ func (dst *unpackDestination) Close() error { return dst.root.Close() } +const statusCodeENOSPC = 2 // tarOptionsDescriptor is passed as an extra file const tarOptionsDescriptor = 3 @@ -82,6 +84,9 @@ func untar() { } if err := archive.Unpack(os.Stdin, dst, &options); err != nil { + if errors.Is(err, unix.ENOSPC) { + os.Exit(statusCodeENOSPC) + } fatal(err) } // fully consume stdin in case it is zero padded @@ -155,6 +160,14 @@ func invokeUnpack(decompressedArchive io.Reader, dest *unpackDestination, option w.Close() if err := cmd.Wait(); err != nil { + var exitErr *exec.ExitError + if errors.As(err, exitErr) { + status := exitErr.ExitCode() + if status == statusCodeENOSPC { + return unix.ENOSPC + } + } + errorOut := fmt.Errorf("unpacking failed (error: %w; output: %s)", err, output) // when `xz -d -c -q | storage-untar ...` failed on storage-untar side, // we need to exhaust `xz`'s output, otherwise the `xz` side will be