diff --git a/fs/cpio/create.go b/fs/cpio/create.go index b2bfccacc..9e55dcc0c 100644 --- a/fs/cpio/create.go +++ b/fs/cpio/create.go @@ -74,9 +74,19 @@ func CreateFS(ctx context.Context, output string, source string, opts ...CpioCre return fmt.Errorf("could not create CPIO archive from OCI image: %w", err) } case fsutils.IsCpioFile(source): - if err := c.CreateFSFromCpio(ctx, writer, source); err != nil { + // If the source is already a CPIO file, we can simply copy its contents to the output. + // This means we close the writer and re-open is as a simple byte writer. + writer.Close() + _, err := f.Seek(0, io.SeekStart) + if err != nil { + return fmt.Errorf("could not seek to start of CPIO archive: %w", err) + } + + if err := c.CreateFSFromCpio(ctx, io.Writer(f), source); err != nil { return fmt.Errorf("could not create CPIO archive from CPIO file: %w", err) } + case fsutils.IsErofsFile(source): + return fmt.Errorf("creating CPIO from EroFS files is not currently supported") case fsutils.IsTarFile(source), fsutils.IsTarGzFile(source): if err := c.CreateFSFromTar(ctx, writer, source); err != nil { @@ -553,7 +563,7 @@ func (c *createOptions) CreateFSFromDirectory(ctx context.Context, writer *cpio. } // CreateFSFromCpio creates a CPIO filesystem from an existing CPIO file. -func (c *createOptions) CreateFSFromCpio(ctx context.Context, writer *cpio.Writer, source string) error { +func (c *createOptions) CreateFSFromCpio(ctx context.Context, writer io.Writer, source string) error { // Open and copy all contents from 'source' to the writer f, err := os.Open(source) if err != nil { diff --git a/fsutils/fsutils.go b/fsutils/fsutils.go index 1cd240269..10845a965 100644 --- a/fsutils/fsutils.go +++ b/fsutils/fsutils.go @@ -126,6 +126,16 @@ func IsCpioFile(path string) bool { return string(magic) == "070701" || string(magic) == "070702" } +// IsRegularFile checks if the given path is a regular file. +func IsRegularFile(path string) bool { + fi, err := os.Stat(path) + if err != nil { + return false + } + + return fi.Mode().IsRegular() +} + type FileInfo struct { Uid int Gid int diff --git a/initrd/detect.go b/initrd/detect.go index 8c658529a..c68392cec 100644 --- a/initrd/detect.go +++ b/initrd/detect.go @@ -7,6 +7,8 @@ package initrd import ( "context" "fmt" + + "kraftkit.sh/log" ) // New attempts to return the builder for a supplied path which @@ -14,14 +16,32 @@ import ( func New(ctx context.Context, path string, opts ...InitrdOption) (Initrd, error) { if builder, err := NewFromDockerfile(ctx, path, opts...); err == nil { return builder, nil - } else if builder, err := NewFromTarball(ctx, path, opts...); err == nil { + } else { + log.G(ctx).Tracef("could not build initrd from Dockerfile: %s", err) + } + + if builder, err := NewFromTarball(ctx, path, opts...); err == nil { return builder, nil - } else if builder, err := NewFromFile(ctx, path, opts...); err == nil { + } else { + log.G(ctx).Tracef("could not build initrd from tarball: %s", err) + } + + if builder, err := NewFromFile(ctx, path, opts...); err == nil { return builder, nil - } else if builder, err := NewFromDirectory(ctx, path, opts...); err == nil { + } else { + log.G(ctx).Tracef("could not build initrd from file: %s", err) + } + + if builder, err := NewFromDirectory(ctx, path, opts...); err == nil { return builder, nil - } else if builder, err := NewFromOCIImage(ctx, path, opts...); err == nil { + } else { + log.G(ctx).Tracef("could not build initrd from directory: %s", err) + } + + if builder, err := NewFromOCIImage(ctx, path, opts...); err == nil { return builder, nil + } else { + log.G(ctx).Tracef("could not build initrd from OCI image: %s", err) } return nil, fmt.Errorf("could not determine how to build initrd from: %s", path) diff --git a/initrd/directory.go b/initrd/directory.go index 4f6c24525..e3c906909 100644 --- a/initrd/directory.go +++ b/initrd/directory.go @@ -79,6 +79,10 @@ func (initrd *directory) Build(ctx context.Context) (string, error) { } switch initrd.opts.fsType { + case FsTypeUnknown: + return "", fmt.Errorf("cannot build initrd from directory with unknown filesystem type") + case FsTypeFile: + return "", fmt.Errorf("cannot build initrd from directory with file output type") case FsTypeErofs: return initrd.opts.output, erofs.CreateFS(ctx, initrd.opts.output, initrd.path, erofs.WithAllRoot(!initrd.opts.keepOwners), diff --git a/initrd/dockerfile.go b/initrd/dockerfile.go index b8f974e3d..ff8e52a8c 100644 --- a/initrd/dockerfile.go +++ b/initrd/dockerfile.go @@ -417,6 +417,10 @@ func (initrd *dockerfile) Build(ctx context.Context) (string, error) { } switch initrd.opts.fsType { + case FsTypeUnknown: + return "", fmt.Errorf("cannot build initrd from dockerfile with unknown filesystem type") + case FsTypeFile: + return "", fmt.Errorf("cannot build initrd from dockerfile with file output type") case FsTypeErofs: return initrd.opts.output, erofs.CreateFS(ctx, initrd.opts.output, tarOutput.Name(), erofs.WithAllRoot(!initrd.opts.keepOwners), diff --git a/initrd/file.go b/initrd/file.go index 8dc7e35f2..420e67e0c 100644 --- a/initrd/file.go +++ b/initrd/file.go @@ -12,6 +12,7 @@ import ( "kraftkit.sh/fs/cpio" "kraftkit.sh/fs/erofs" + "kraftkit.sh/fsutils" ) type file struct { @@ -70,7 +71,16 @@ func (initrd *file) Build(ctx context.Context) (string, error) { return "", fmt.Errorf("CPIO archive path is the same as the source path, this is not allowed as it creates corrupted archives") } +reevaluateFsType: switch initrd.opts.fsType { + case FsTypeUnknown: + initrd.opts.fsType = detectFsType(initrd.path) + if initrd.opts.fsType == FsTypeUnknown { + return "", fmt.Errorf("could not detect filesystem type of input file, please specify it explicitly via the --fs-type flag") + } + goto reevaluateFsType + case FsTypeFile: + return initrd.opts.output, copyFile(initrd.path, initrd.opts.output) case FsTypeErofs: return initrd.opts.output, erofs.CreateFS(ctx, initrd.opts.output, initrd.path, erofs.WithAllRoot(!initrd.opts.keepOwners), @@ -84,6 +94,23 @@ func (initrd *file) Build(ctx context.Context) (string, error) { } } +// detectFsType attempts to convert from the 'unknown' filesystem type to one +// of the known types like 'cpio'/'erofs'/'file'. +func detectFsType(source string) FsType { + switch { + case fsutils.IsErofsFile(source): + return FsTypeErofs + case fsutils.IsCpioFile(source): + return FsTypeCpio + case fsutils.IsTarFile(source), + fsutils.IsTarGzFile(source), + fsutils.IsRegularFile(source): + return FsTypeFile + default: + return FsTypeUnknown + } +} + // Options implements Initrd. func (initrd *file) Options() InitrdOptions { return initrd.opts diff --git a/initrd/ociimage.go b/initrd/ociimage.go index 758a61ca2..5f71f4a6d 100644 --- a/initrd/ociimage.go +++ b/initrd/ociimage.go @@ -196,6 +196,10 @@ func (initrd *ociimage) Build(ctx context.Context) (string, error) { } switch initrd.opts.fsType { + case FsTypeUnknown: + return "", fmt.Errorf("cannot build initrd from oci image with unknown filesystem type") + case FsTypeFile: + return "", fmt.Errorf("cannot build initrd from oci image with file output type") case FsTypeErofs: return initrd.opts.output, erofs.CreateFS(ctx, initrd.opts.output, ociTarballPath, erofs.WithAllRoot(!initrd.opts.keepOwners), diff --git a/initrd/options.go b/initrd/options.go index 6ac00594a..f0f1063ff 100644 --- a/initrd/options.go +++ b/initrd/options.go @@ -128,8 +128,16 @@ func WithOutput(output string) InitrdOption { // WithOutputType sets the output type of the resulting root filesystem. func WithOutputType(fsType FsType) InitrdOption { return func(opts *InitrdOptions) error { - opts.fsType = fsType - return nil + if fsType == "" { + return nil + } + for _, validType := range FsTypes() { + if fsType == validType { + opts.fsType = fsType + return nil + } + } + return fmt.Errorf("invalid output type '%s', valid types are: %v", fsType, FsTypeNames()) } } @@ -207,6 +215,8 @@ func FsTypes() []FsType { return []FsType{ FsTypeCpio, FsTypeErofs, + FsTypeFile, + FsTypeUnknown, } } diff --git a/initrd/tarball.go b/initrd/tarball.go index f83bd27fc..52b268d7a 100644 --- a/initrd/tarball.go +++ b/initrd/tarball.go @@ -70,6 +70,10 @@ func (initrd *tarball) Build(ctx context.Context) (string, error) { } switch initrd.opts.fsType { + case FsTypeUnknown: + return "", fmt.Errorf("cannot build initrd from tarball with unknown filesystem type") + case FsTypeFile: + return initrd.opts.output, copyFile(initrd.path, initrd.opts.output) case FsTypeErofs: return initrd.opts.output, erofs.CreateFS(ctx, initrd.opts.output, initrd.path, erofs.WithAllRoot(!initrd.opts.keepOwners), diff --git a/initrd/utils.go b/initrd/utils.go index 40516f634..3517dd0f9 100644 --- a/initrd/utils.go +++ b/initrd/utils.go @@ -9,6 +9,7 @@ import ( "fmt" "io" "os" + "path/filepath" ) func compressFiles(output string, path string) error { @@ -48,3 +49,27 @@ func compressFiles(output string, path string) error { return nil } + +func copyFile(src, dst string) error { + input, err := os.Open(src) + if err != nil { + return fmt.Errorf("opening source file: %w", err) + } + defer input.Close() + + if err := os.MkdirAll(filepath.Dir(dst), 0o755); err != nil { + return fmt.Errorf("creating destination directory: %w", err) + } + + output, err := os.Create(dst) + if err != nil { + return fmt.Errorf("creating destination file: %w", err) + } + defer output.Close() + + if _, err := io.Copy(output, input); err != nil { + return fmt.Errorf("copying file contents: %w", err) + } + + return nil +}