diff --git a/hst/fs.go b/hst/fs.go index 926d623..3cecac8 100644 --- a/hst/fs.go +++ b/hst/fs.go @@ -45,6 +45,9 @@ type Ops interface { Root(host *check.Absolute, flags int) Ops // Etc appends an op that expands host /etc into a toplevel symlink mirror with /etc semantics. Etc(host *check.Absolute, prefix string) Ops + + // Daemon appends an op that starts a daemon in the container and blocks until target appears. + Daemon(target, path *check.Absolute, args ...string) Ops } // ApplyState holds the address of [Ops] and any relevant application state. @@ -124,6 +127,12 @@ func (f *FilesystemConfigJSON) MarshalJSON() ([]byte, error) { *FSLink }{fsType{FilesystemLink}, cv} + case *FSDaemon: + v = &struct { + fsType + *FSDaemon + }{fsType{FilesystemDaemon}, cv} + default: return nil, FSImplError{f.FilesystemConfig} } @@ -152,6 +161,9 @@ func (f *FilesystemConfigJSON) UnmarshalJSON(data []byte) error { case FilesystemLink: *f = FilesystemConfigJSON{new(FSLink)} + case FilesystemDaemon: + *f = FilesystemConfigJSON{new(FSDaemon)} + default: return FSTypeError(t.Type) } diff --git a/hst/fs_test.go b/hst/fs_test.go index 4b07909..fdef9bc 100644 --- a/hst/fs_test.go +++ b/hst/fs_test.go @@ -84,6 +84,16 @@ func TestFilesystemConfigJSON(t *testing.T) { }, 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}`}, + + {"daemon", hst.FilesystemConfigJSON{ + FilesystemConfig: &hst.FSDaemon{ + Target: m("/run/user/1971/pulse/native"), + Exec: m("/run/current-system/sw/bin/pipewire-pulse"), + Args: []string{"-v"}, + }, + }, nil, + `{"type":"daemon","dst":"/run/user/1971/pulse/native","path":"/run/current-system/sw/bin/pipewire-pulse","args":["-v"]}`, + `{"fs":{"type":"daemon","dst":"/run/user/1971/pulse/native","path":"/run/current-system/sw/bin/pipewire-pulse","args":["-v"]},"magic":3236757504}`}, } for _, tc := range testCases { @@ -345,6 +355,10 @@ func (p opsAdapter) Etc(host *check.Absolute, prefix string) hst.Ops { return opsAdapter{p.Ops.Etc(host, prefix)} } +func (p opsAdapter) Daemon(target, path *check.Absolute, args ...string) hst.Ops { + return opsAdapter{p.Ops.Daemon(target, path, args...)} +} + func m(pathname string) *check.Absolute { return check.MustAbs(pathname) } func ms(pathnames ...string) []*check.Absolute { as := make([]*check.Absolute, len(pathnames)) diff --git a/hst/fsdaemon.go b/hst/fsdaemon.go new file mode 100644 index 0000000..fd3f375 --- /dev/null +++ b/hst/fsdaemon.go @@ -0,0 +1,48 @@ +package hst + +import ( + "encoding/gob" + + "hakurei.app/container/check" +) + +func init() { gob.Register(new(FSDaemon)) } + +// FilesystemDaemon is the type string of a daemon. +const FilesystemDaemon = "daemon" + +// FSDaemon represents a daemon to be started in the container. +type FSDaemon struct { + // Pathname indicating readiness of daemon. + Target *check.Absolute `json:"dst"` + // Absolute pathname to daemon executable file. + Exec *check.Absolute `json:"path"` + // Arguments (excl. first) passed to daemon. + Args []string `json:"args"` +} + +func (d *FSDaemon) Valid() bool { return d != nil && d.Target != nil && d.Exec != nil } + +func (d *FSDaemon) Path() *check.Absolute { + if !d.Valid() { + return nil + } + return d.Target +} + +func (d *FSDaemon) Host() []*check.Absolute { return nil } + +func (d *FSDaemon) Apply(z *ApplyState) { + if !d.Valid() { + return + } + z.Daemon(d.Target, d.Exec, d.Args...) +} + +func (d *FSDaemon) String() string { + if !d.Valid() { + return "" + } + + return "daemon:" + d.Target.String() +} diff --git a/hst/fsdaemon_test.go b/hst/fsdaemon_test.go new file mode 100644 index 0000000..1e78d62 --- /dev/null +++ b/hst/fsdaemon_test.go @@ -0,0 +1,29 @@ +package hst_test + +import ( + "testing" + + "hakurei.app/container" + "hakurei.app/hst" +) + +func TestFSDaemon(t *testing.T) { + t.Parallel() + + checkFs(t, []fsTestCase{ + {"nil", (*hst.FSDaemon)(nil), false, nil, nil, nil, ""}, + {"zero", new(hst.FSDaemon), false, nil, nil, nil, ""}, + + {"pipewire-pulse", &hst.FSDaemon{ + Target: m("/run/user/1971/pulse/native"), + Exec: m("/run/current-system/sw/bin/pipewire-pulse"), + Args: []string{"-v"}, + }, true, container.Ops{ + &container.DaemonOp{ + Target: m("/run/user/1971/pulse/native"), + Path: m("/run/current-system/sw/bin/pipewire-pulse"), + Args: []string{"-v"}, + }, + }, m("/run/user/1971/pulse/native"), nil, `daemon:/run/user/1971/pulse/native`}, + }) +} diff --git a/internal/outcome/spcontainer.go b/internal/outcome/spcontainer.go index 28697b4..768e6bc 100644 --- a/internal/outcome/spcontainer.go +++ b/internal/outcome/spcontainer.go @@ -382,6 +382,10 @@ func (p opsAdapter) Link(target *check.Absolute, linkName string, dereference bo return opsAdapter{p.Ops.Link(target, linkName, dereference)} } +func (p opsAdapter) Daemon(target, path *check.Absolute, args ...string) hst.Ops { + return opsAdapter{p.Ops.Daemon(target, path, args...)} +} + func (p opsAdapter) Root(host *check.Absolute, flags int) hst.Ops { return opsAdapter{p.Ops.Root(host, flags)} }