Skip to content

Commit

Permalink
feat(initramfs): add ReadLinkFS
Browse files Browse the repository at this point in the history
In preparation for golang/go#49580, this
commit adds an own simplified implementation of ReadLinkFS. It adds
helpers to extend a simple fstest.MapFS into a ReadLinkFS.
  • Loading branch information
aibor committed Nov 17, 2024
1 parent 0f82b53 commit 65af42d
Show file tree
Hide file tree
Showing 4 changed files with 128 additions and 26 deletions.
54 changes: 35 additions & 19 deletions internal/initramfs/cpio.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ package initramfs
import (
"io"
"io/fs"
"os"

"github.com/cavaliergopher/cpio"
)
Expand All @@ -26,14 +27,7 @@ func NewCPIOFSWriter(w io.Writer) *CPIOFSWriter {
//
// It walks the directory tree starting at the root of the filesystem adding
// each file to the tar archive while maintaining the directory structure.
// The [fs.FS.Open] must not follow symlinks. This is the case for [FS] and
// most implementations in the standard library, except for [os.DirFS].
//
// TODO: Consider switching to [fs.ReadLinkFS] once available. See
// https://github.com/golang/go/issues/49580
//
//nolint:godox
func (w *CPIOFSWriter) AddFS(fsys fs.FS) error {
func (w *CPIOFSWriter) AddFS(fsys ReadLinkFS) error {
return fs.WalkDir(fsys, ".", func( //nolint:wrapcheck
name string, d fs.DirEntry, err error,
) error {
Expand Down Expand Up @@ -68,26 +62,48 @@ func (w *CPIOFSWriter) AddFS(fsys fs.FS) error {
}
}

err = w.writeBody(fsys, name, info.Mode().Type())
if err != nil {
return &PathError{
Op: "write body",
Path: name,
Err: err,
}
}

return nil
})
}

func (w *CPIOFSWriter) writeBody(
fsys ReadLinkFS,
name string,
typ fs.FileMode,
) error {
switch typ {
case os.ModeDir:
// Directories do not have a body and fail on [fs.File.Read].
if info.IsDir() {
return nil
return nil
case fs.ModeSymlink:
linkName, err := fsys.ReadLink(name)
if err != nil {
return err //nolint:wrapcheck
}

_, err = w.Write([]byte(linkName))

return err //nolint:wrapcheck
case 0:
file, err := fsys.Open(name)
if err != nil {
return err //nolint:wrapcheck
}
defer file.Close()

_, err = io.Copy(w, file)
if err != nil {
return &PathError{
Op: "write body",
Path: name,
Err: err,
}
}

return nil
})
return err //nolint:wrapcheck
default:
return ErrFileInvalid
}
}
6 changes: 3 additions & 3 deletions internal/initramfs/cpio_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import (
)

func TestCPIOFSWriter_AddFS(t *testing.T) {
testFS := fstest.MapFS{
sourceFS := fstest.MapFS{
".": &fstest.MapFile{
Mode: fs.ModeDir,
},
Expand All @@ -47,7 +47,7 @@ func TestCPIOFSWriter_AddFS(t *testing.T) {

w := initramfs.NewCPIOFSWriter(&archive)

err := w.AddFS(testFS)
err := w.AddFS(initramfs.WithReadLinkNoFollowOpen(sourceFS))
require.NoError(t, err)

r := cpio.NewReader(&archive)
Expand Down Expand Up @@ -77,5 +77,5 @@ func TestCPIOFSWriter_AddFS(t *testing.T) {
}
}

assert.Equal(t, testFS, extractedFS)
assert.Equal(t, sourceFS, extractedFS)
}
87 changes: 87 additions & 0 deletions internal/initramfs/readlinkfs.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
// SPDX-FileCopyrightText: 2024 Tobias Böhm <[email protected]>
//
// SPDX-License-Identifier: GPL-3.0-or-later

package initramfs

import "io/fs"

// ReadLinkFS is a [fs.FS] with an additional method for reading the target of
// a symbolic link.
//
// Replace with [fs.ReadLinkFS] once available. See
// https://github.com/golang/go/issues/49580
type ReadLinkFS interface {
fs.FS

// ReadLink returns the destination a symbolic link points to. Returns an
// error if the file at name is not a symbolic link or cannot be read.
ReadLink(name string) (string, error)
}

type readLinkFS struct {
fs.FS
readLinkFn ReadLinkFunc
}

// ReadLink implements [ReadLinkFS].
func (fsys *readLinkFS) ReadLink(name string) (string, error) {
return fsys.readLinkFn(name)
}

// ReadLinkFunc returns the destination of a symbolic link or an error in case
// the file at name is not a symbolic link or cannot be read.
type ReadLinkFunc func(name string) (string, error)

// WithReadLinkFunc extends the given [fs.FS] with the given [ReadLinkFunc]
// into a new [ReadLinkFS].
//
//nolint:ireturn
func WithReadLinkFunc(fsys fs.FS, readLinkFn ReadLinkFunc) ReadLinkFS {
return &readLinkFS{
FS: fsys,
readLinkFn: readLinkFn,
}
}

// WithReadLinkNoFollowOpen extends the given [fs.FS] into a new [ReadLinkFS].
//
// The source [fs.FS]'s Open method must not follow symbolic links. It must open
// them directly so the destination can be read and returned by the ReadLink
// method.
//
//nolint:ireturn
func WithReadLinkNoFollowOpen(fsys fs.FS) ReadLinkFS {
return WithReadLinkFunc(fsys, func(name string) (string, error) {
file, err := fsys.Open(name)
if err != nil {
return "", err //nolint:wrapcheck
}
defer file.Close()

info, err := file.Stat()
if err != nil {
return "", err //nolint:wrapcheck
}

if info.Mode().Type() != fs.ModeSymlink {
return "", &PathError{
Op: "readlink",
Path: name,
Err: ErrFileInvalid,
}
}

b := make([]byte, info.Size())

if _, err := file.Read(b); err != nil {
return "", &PathError{
Op: "readlink",
Path: name,
Err: err,
}
}

return string(b), nil
})
}
7 changes: 3 additions & 4 deletions internal/virtrun/initramfs.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ package virtrun
import (
"context"
"fmt"
"io/fs"
"log/slog"
"os"
"slices"
Expand Down Expand Up @@ -113,13 +112,13 @@ func buildFS(f initramfs.FSAdder, cfg Initramfs, libs sys.LibCollection) error {
// WriteFSToTempFile writes the given [fs.FS] as CPIO archive into a new
// temporary file in the given directory.
//
// It returns the path to the created file. If tmpDir is the empty string the
// It returns the path to the created file. If dir is the empty string the
// default directory is used as returned by [os.TempDir].
//
// The caller is responsible for removing the file once it is not needed
// anymore.
func WriteFSToTempFile(fsys fs.FS, tmpDir string) (string, error) {
file, err := os.CreateTemp(tmpDir, "initramfs")
func WriteFSToTempFile(fsys initramfs.ReadLinkFS, dir string) (string, error) {
file, err := os.CreateTemp(dir, "initramfs")
if err != nil {
return "", fmt.Errorf("create temp file: %w", err)
}
Expand Down

0 comments on commit 65af42d

Please sign in to comment.