From 18466cfd02fdd25394a8c7c808189b58a05dc780 Mon Sep 17 00:00:00 2001 From: Ophestra Date: Tue, 11 Feb 2025 16:34:47 +0900 Subject: [PATCH] helper/proc: declare generic extra files interface Helpers use extra files for various purposes. This provides a generic interface for implementing the fulfillment of these extra files without having to specifically handle them in the process creation code. Signed-off-by: Ophestra --- helper/proc/files.go | 91 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 91 insertions(+) diff --git a/helper/proc/files.go b/helper/proc/files.go index 712713a..f518cd0 100644 --- a/helper/proc/files.go +++ b/helper/proc/files.go @@ -1,10 +1,101 @@ package proc import ( + "context" "os" "os/exec" + "syscall" + "time" ) +var FulfillmentTimeout = 2 * time.Second + +// A File is an extra file with deferred initialisation. +type File interface { + // Init initialises File state. Init must not be called more than once. + Init(fd uintptr, v **os.File) + // Fd returns the fd value set on initialisation. + Fd() uintptr + // ErrCount returns count of error values emitted during fulfillment. + ErrCount() int + // Fulfill is called prior to process creation and must populate its corresponding file address. + // Error values sent to ec must match the return value of ErrCount. + // Fulfill must not be called more than once. + Fulfill(ctx context.Context, ec chan<- error) error +} + +// Fulfill calls the [File.Fulfill] method on all files, starts cmd and blocks until all fulfillment completes. +func Fulfill(ctx context.Context, cmd *exec.Cmd, files []File) (err error) { + var ecs int + for _, o := range files { + ecs += o.ErrCount() + } + ec := make(chan error, ecs) + + c, cancel := context.WithTimeout(ctx, FulfillmentTimeout) + defer cancel() + + for _, o := range files { + err = o.Fulfill(c, ec) + if err != nil { + return + } + } + + if err = cmd.Start(); err != nil { + return + } + + for ecs > 0 { + select { + case err = <-ec: + ecs-- + if err != nil { + break + } + case <-c.Done(): + err = syscall.ECANCELED + break + } + } + return +} + +// InitFile initialises f as part of the slice extraFiles points to, +// and returns its final fd value. +func InitFile(f File, extraFiles *[]*os.File) (fd uintptr) { + fd = ExtraFileSlice(extraFiles, nil) + f.Init(fd, &(*extraFiles)[len(*extraFiles)-1]) + return +} + +// BaseFile implements the Init method of the File interface and provides indirect access to extra file state. +type BaseFile struct { + fd uintptr + v **os.File +} + +func (f *BaseFile) Init(fd uintptr, v **os.File) { + if v == nil || fd < 3 { + panic("invalid extra file initial state") + } + if f.v != nil { + panic("extra file initialised twice") + } + f.fd, f.v = fd, v +} + +func (f *BaseFile) Fd() uintptr { + if f.v == nil { + panic("use of uninitialised extra file") + } + return f.fd +} + +func (f *BaseFile) Set(v *os.File) { + *f.v = v // runtime guards against use before init +} + func ExtraFile(cmd *exec.Cmd, f *os.File) (fd uintptr) { return ExtraFileSlice(&cmd.ExtraFiles, f) }