From 26cafe3e8066d9ae65230ff0aa17a5533cfa92f8 Mon Sep 17 00:00:00 2001 From: Ophestra Date: Mon, 25 Aug 2025 21:31:45 +0900 Subject: [PATCH] hst/fs: implement link fstype Symlinks do not require special treatment, and doing this allows placing links in order. Signed-off-by: Ophestra --- hst/fs.go | 9 +++++++ hst/fs_test.go | 10 ++++++++ hst/fslink.go | 58 ++++++++++++++++++++++++++++++++++++++++++++++ hst/fslink_test.go | 49 +++++++++++++++++++++++++++++++++++++++ 4 files changed, 126 insertions(+) create mode 100644 hst/fslink.go create mode 100644 hst/fslink_test.go diff --git a/hst/fs.go b/hst/fs.go index a2a75a7..1d33095 100644 --- a/hst/fs.go +++ b/hst/fs.go @@ -95,6 +95,12 @@ func (f *FilesystemConfigJSON) MarshalJSON() ([]byte, error) { *FSOverlay }{fsType{FilesystemOverlay}, cv} + case *FSLink: + v = &struct { + fsType + *FSLink + }{fsType{FilesystemLink}, cv} + default: return nil, FSImplError{f.FilesystemConfig} } @@ -120,6 +126,9 @@ func (f *FilesystemConfigJSON) UnmarshalJSON(data []byte) error { case FilesystemOverlay: *f = FilesystemConfigJSON{new(FSOverlay)} + case FilesystemLink: + *f = FilesystemConfigJSON{new(FSLink)} + default: return FSTypeError(t.Type) } diff --git a/hst/fs_test.go b/hst/fs_test.go index af8738f..00186d2 100644 --- a/hst/fs_test.go +++ b/hst/fs_test.go @@ -70,6 +70,16 @@ func TestFilesystemConfigJSON(t *testing.T) { }, nil, `{"type":"overlay","dst":"/nix/store","lower":["/mnt-root/nix/.ro-store"],"upper":"/mnt-root/nix/.rw-store/upper","work":"/mnt-root/nix/.rw-store/work"}`, `{"fs":{"type":"overlay","dst":"/nix/store","lower":["/mnt-root/nix/.ro-store"],"upper":"/mnt-root/nix/.rw-store/upper","work":"/mnt-root/nix/.rw-store/work"},"magic":3236757504}`}, + + {"link", hst.FilesystemConfigJSON{ + FilesystemConfig: &hst.FSLink{ + Target: m("/run/current-system"), + Linkname: "/run/current-system", + Dereference: true, + }, + }, nil, + `{"type":"link","dst":"/run/current-system","linkname":"/run/current-system","dereference":true}`, + `{"fs":{"type":"link","dst":"/run/current-system","linkname":"/run/current-system","dereference":true},"magic":3236757504}`}, } for _, tc := range testCases { diff --git a/hst/fslink.go b/hst/fslink.go new file mode 100644 index 0000000..8bba9b4 --- /dev/null +++ b/hst/fslink.go @@ -0,0 +1,58 @@ +package hst + +import ( + "encoding/gob" + "path" + + "hakurei.app/container" +) + +func init() { gob.Register(new(FSLink)) } + +// FilesystemLink is the type string of a symbolic link. +const FilesystemLink = "link" + +// FSLink represents a symlink in the container filesystem. +type FSLink struct { + // link path in container + Target *container.Absolute `json:"dst"` + // linkname the symlink points to + Linkname string `json:"linkname"` + // whether to dereference linkname before creating the link + Dereference bool `json:"dereference,omitempty"` +} + +func (l *FSLink) Valid() bool { + if l == nil || l.Target == nil || l.Linkname == "" { + return false + } + return !l.Dereference || path.IsAbs(l.Linkname) +} + +func (l *FSLink) Path() *container.Absolute { + if !l.Valid() { + return nil + } + return l.Target +} + +func (l *FSLink) Host() []*container.Absolute { return nil } + +func (l *FSLink) Apply(z *ApplyState) { + if !l.Valid() { + return + } + z.Link(l.Target, l.Linkname, l.Dereference) +} + +func (l *FSLink) String() string { + if !l.Valid() { + return "" + } + + dereference := "" + if l.Dereference { + dereference = "*" + } + return "&" + l.Target.String() + ":" + dereference + l.Linkname +} diff --git a/hst/fslink_test.go b/hst/fslink_test.go new file mode 100644 index 0000000..c858354 --- /dev/null +++ b/hst/fslink_test.go @@ -0,0 +1,49 @@ +package hst_test + +import ( + "testing" + + "hakurei.app/container" + "hakurei.app/hst" +) + +func TestFSLink(t *testing.T) { + checkFs(t, []fsTestCase{ + {"nil", (*hst.FSLink)(nil), false, nil, nil, nil, ""}, + {"zero", new(hst.FSLink), false, nil, nil, nil, ""}, + + {"deref rel", &hst.FSLink{Target: m("/"), Linkname: ":3", Dereference: true}, false, nil, nil, nil, ""}, + {"deref", &hst.FSLink{ + Target: m("/run/current-system"), + Linkname: "/run/current-system", + Dereference: true, + }, true, container.Ops{ + &container.SymlinkOp{ + Target: m("/run/current-system"), + LinkName: "/run/current-system", + Dereference: true, + }, + }, m("/run/current-system"), nil, + "&/run/current-system:*/run/current-system"}, + + {"direct", &hst.FSLink{ + Target: m("/etc/mtab"), + Linkname: "/proc/mounts", + }, true, container.Ops{ + &container.SymlinkOp{ + Target: m("/etc/mtab"), + LinkName: "/proc/mounts", + }, + }, m("/etc/mtab"), nil, "&/etc/mtab:/proc/mounts"}, + + {"direct rel", &hst.FSLink{ + Target: m("/etc/mtab"), + Linkname: "../proc/mounts", + }, true, container.Ops{ + &container.SymlinkOp{ + Target: m("/etc/mtab"), + LinkName: "../proc/mounts", + }, + }, m("/etc/mtab"), nil, "&/etc/mtab:../proc/mounts"}, + }) +}