aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorOphestra <cat@gensokyo.uk>2025-08-25 21:31:45 +0900
committerOphestra <cat@gensokyo.uk>2025-08-25 21:57:38 +0900
commit26cafe3e8066d9ae65230ff0aa17a5533cfa92f8 (patch)
tree59d19335710976b7246b8ea3b1ff484a3de08f45
parent125f150784f78db8c3323167c3a638cc1bad086e (diff)
hst/fs: implement link fstype
Symlinks do not require special treatment, and doing this allows placing links in order. Signed-off-by: Ophestra <cat@gensokyo.uk>
-rw-r--r--hst/fs.go9
-rw-r--r--hst/fs_test.go10
-rw-r--r--hst/fslink.go58
-rw-r--r--hst/fslink_test.go49
4 files changed, 126 insertions, 0 deletions
diff --git a/hst/fs.go b/hst/fs.go
index a2a75a78..1d330951 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 af8738f6..00186d20 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 00000000..8bba9b49
--- /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 "<invalid>"
+ }
+
+ 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 00000000..c8583544
--- /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, "<invalid>"},
+ {"zero", new(hst.FSLink), false, nil, nil, nil, "<invalid>"},
+
+ {"deref rel", &hst.FSLink{Target: m("/"), Linkname: ":3", Dereference: true}, false, nil, nil, nil, "<invalid>"},
+ {"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"},
+ })
+}