internal/helper: relocate from helper
All checks were successful
Test / Create distribution (push) Successful in 33s
Test / Sandbox (push) Successful in 2m26s
Test / Hakurei (push) Successful in 3m15s
Test / Hpkg (push) Successful in 4m8s
Test / Sandbox (race detector) (push) Successful in 4m16s
Test / Hakurei (race detector) (push) Successful in 5m5s
Test / Flake checks (push) Successful in 1m23s
All checks were successful
Test / Create distribution (push) Successful in 33s
Test / Sandbox (push) Successful in 2m26s
Test / Hakurei (push) Successful in 3m15s
Test / Hpkg (push) Successful in 4m8s
Test / Sandbox (race detector) (push) Successful in 4m16s
Test / Hakurei (race detector) (push) Successful in 5m5s
Test / Flake checks (push) Successful in 1m23s
This package is ugly and is pending removal only kept alive by xdg-dbus-proxy. Its exported symbols are made available until v0.4.0 where it will be removed for #24. Signed-off-by: Ophestra <cat@gensokyo.uk>
This commit is contained in:
63
helper/proc/deprecated.go
Normal file
63
helper/proc/deprecated.go
Normal file
@@ -0,0 +1,63 @@
|
||||
// Deprecated: This package will be removed in 0.4.
|
||||
package proc
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
"os"
|
||||
"os/exec"
|
||||
"time"
|
||||
_ "unsafe" // for go:linkname
|
||||
|
||||
"hakurei.app/internal/helper/proc"
|
||||
)
|
||||
|
||||
//go:linkname FulfillmentTimeout hakurei.app/internal/helper/proc.FulfillmentTimeout
|
||||
var FulfillmentTimeout time.Duration
|
||||
|
||||
// A File is an extra file with deferred initialisation.
|
||||
type File = proc.File
|
||||
|
||||
// ExtraFilesPre is a linked list storing addresses of [os.File].
|
||||
type ExtraFilesPre = proc.ExtraFilesPre
|
||||
|
||||
// Fulfill calls the [File.Fulfill] method on all files, starts cmd and blocks until all fulfillment completes.
|
||||
//
|
||||
//go:linkname Fulfill hakurei.app/internal/helper/proc.Fulfill
|
||||
func Fulfill(ctx context.Context,
|
||||
v *[]*os.File, start func() error,
|
||||
files []File, extraFiles *ExtraFilesPre,
|
||||
) (err error)
|
||||
|
||||
// InitFile initialises f as part of the slice extraFiles points to,
|
||||
// and returns its final fd value.
|
||||
//
|
||||
//go:linkname InitFile hakurei.app/internal/helper/proc.InitFile
|
||||
func InitFile(f File, extraFiles *ExtraFilesPre) (fd uintptr)
|
||||
|
||||
// BaseFile implements the Init method of the File interface and provides indirect access to extra file state.
|
||||
type BaseFile = proc.BaseFile
|
||||
|
||||
//go:linkname ExtraFile hakurei.app/internal/helper/proc.ExtraFile
|
||||
func ExtraFile(cmd *exec.Cmd, f *os.File) (fd uintptr)
|
||||
|
||||
//go:linkname ExtraFileSlice hakurei.app/internal/helper/proc.ExtraFileSlice
|
||||
func ExtraFileSlice(extraFiles *[]*os.File, f *os.File) (fd uintptr)
|
||||
|
||||
// NewWriterTo returns a [File] that receives content from wt on fulfillment.
|
||||
//
|
||||
//go:linkname NewWriterTo hakurei.app/internal/helper/proc.NewWriterTo
|
||||
func NewWriterTo(wt io.WriterTo) File
|
||||
|
||||
// NewStat returns a [File] implementing the behaviour
|
||||
// of the receiving end of xdg-dbus-proxy stat fd.
|
||||
//
|
||||
//go:linkname NewStat hakurei.app/internal/helper/proc.NewStat
|
||||
func NewStat(s *io.Closer) File
|
||||
|
||||
var (
|
||||
//go:linkname ErrStatFault hakurei.app/internal/helper/proc.ErrStatFault
|
||||
ErrStatFault error
|
||||
//go:linkname ErrStatRead hakurei.app/internal/helper/proc.ErrStatRead
|
||||
ErrStatRead error
|
||||
)
|
||||
@@ -1,162 +0,0 @@
|
||||
package proc
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
"os/exec"
|
||||
"sync/atomic"
|
||||
"syscall"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
var FulfillmentTimeout = 2 * time.Second
|
||||
|
||||
func init() {
|
||||
if testing.Testing() {
|
||||
FulfillmentTimeout *= 10
|
||||
}
|
||||
}
|
||||
|
||||
// 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) uintptr
|
||||
// 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.
|
||||
// Calls to dispatchErr must match the return value of ErrCount.
|
||||
// Fulfill must not be called more than once.
|
||||
Fulfill(ctx context.Context, dispatchErr func(error)) error
|
||||
}
|
||||
|
||||
// ExtraFilesPre is a linked list storing addresses of [os.File].
|
||||
type ExtraFilesPre struct {
|
||||
n *ExtraFilesPre
|
||||
v *os.File
|
||||
}
|
||||
|
||||
// Append grows the list by one entry and returns an address of the address of [os.File] stored in the new entry.
|
||||
func (f *ExtraFilesPre) Append() (uintptr, **os.File) { return f.append(3) }
|
||||
|
||||
// Files returns a slice pointing to a continuous segment of memory containing all addresses stored in f in order.
|
||||
func (f *ExtraFilesPre) Files() []*os.File { return f.copy(make([]*os.File, 0, f.len())) }
|
||||
|
||||
func (f *ExtraFilesPre) append(i uintptr) (uintptr, **os.File) {
|
||||
if f.n == nil {
|
||||
f.n = new(ExtraFilesPre)
|
||||
return i, &f.v
|
||||
}
|
||||
return f.n.append(i + 1)
|
||||
}
|
||||
func (f *ExtraFilesPre) len() uintptr {
|
||||
if f == nil {
|
||||
return 0
|
||||
}
|
||||
return f.n.len() + 1
|
||||
}
|
||||
func (f *ExtraFilesPre) copy(e []*os.File) []*os.File {
|
||||
if f == nil {
|
||||
// the public methods ensure the first call is never nil;
|
||||
// the last element is unused, slice it off here
|
||||
return e[:len(e)-1]
|
||||
}
|
||||
return f.n.copy(append(e, f.v))
|
||||
}
|
||||
|
||||
// Fulfill calls the [File.Fulfill] method on all files, starts cmd and blocks until all fulfillment completes.
|
||||
func Fulfill(ctx context.Context,
|
||||
v *[]*os.File, start func() error,
|
||||
files []File, extraFiles *ExtraFilesPre,
|
||||
) (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 _, f := range files {
|
||||
err = f.Fulfill(c, makeDispatchErr(f, ec))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
*v = extraFiles.Files()
|
||||
if err = start(); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
for ecs > 0 {
|
||||
select {
|
||||
case err = <-ec:
|
||||
ecs--
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
case <-ctx.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 *ExtraFilesPre) (fd uintptr) { return f.Init(extraFiles.Append()) }
|
||||
|
||||
// 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) uintptr {
|
||||
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
|
||||
return fd
|
||||
}
|
||||
|
||||
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 makeDispatchErr(f File, ec chan<- error) func(error) {
|
||||
c := new(atomic.Int32)
|
||||
c.Store(int32(f.ErrCount()))
|
||||
return func(err error) {
|
||||
if c.Add(-1) < 0 {
|
||||
panic("unexpected error dispatches")
|
||||
}
|
||||
ec <- err
|
||||
}
|
||||
}
|
||||
|
||||
func ExtraFile(cmd *exec.Cmd, f *os.File) (fd uintptr) {
|
||||
return ExtraFileSlice(&cmd.ExtraFiles, f)
|
||||
}
|
||||
|
||||
func ExtraFileSlice(extraFiles *[]*os.File, f *os.File) (fd uintptr) {
|
||||
// ExtraFiles: If non-nil, entry i becomes file descriptor 3+i.
|
||||
fd = uintptr(3 + len(*extraFiles))
|
||||
*extraFiles = append(*extraFiles, f)
|
||||
return
|
||||
}
|
||||
@@ -1,110 +0,0 @@
|
||||
package proc
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"io"
|
||||
"os"
|
||||
"runtime"
|
||||
)
|
||||
|
||||
// NewWriterTo returns a [File] that receives content from wt on fulfillment.
|
||||
func NewWriterTo(wt io.WriterTo) File { return &writeToFile{wt: wt} }
|
||||
|
||||
// writeToFile exports the read end of a pipe with data written by an [io.WriterTo].
|
||||
type writeToFile struct {
|
||||
wt io.WriterTo
|
||||
BaseFile
|
||||
}
|
||||
|
||||
func (f *writeToFile) ErrCount() int { return 3 }
|
||||
func (f *writeToFile) Fulfill(ctx context.Context, dispatchErr func(error)) error {
|
||||
r, w, err := os.Pipe()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
f.Set(r)
|
||||
|
||||
done := make(chan struct{})
|
||||
go func() {
|
||||
_, err = f.wt.WriteTo(w)
|
||||
dispatchErr(err)
|
||||
dispatchErr(w.Close())
|
||||
close(done)
|
||||
runtime.KeepAlive(r)
|
||||
}()
|
||||
go func() {
|
||||
select {
|
||||
case <-done:
|
||||
dispatchErr(nil)
|
||||
case <-ctx.Done():
|
||||
dispatchErr(w.Close()) // this aborts WriteTo with file already closed
|
||||
runtime.KeepAlive(r)
|
||||
}
|
||||
}()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// NewStat returns a [File] implementing the behaviour
|
||||
// of the receiving end of xdg-dbus-proxy stat fd.
|
||||
func NewStat(s *io.Closer) File { return &statFile{s: s} }
|
||||
|
||||
var (
|
||||
ErrStatFault = errors.New("generic stat fd fault")
|
||||
ErrStatRead = errors.New("unexpected stat behaviour")
|
||||
)
|
||||
|
||||
// statFile implements xdg-dbus-proxy stat fd behaviour.
|
||||
type statFile struct {
|
||||
s *io.Closer
|
||||
BaseFile
|
||||
}
|
||||
|
||||
func (f *statFile) ErrCount() int { return 2 }
|
||||
func (f *statFile) Fulfill(ctx context.Context, dispatchErr func(error)) error {
|
||||
r, w, err := os.Pipe()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
f.Set(w)
|
||||
|
||||
done := make(chan struct{})
|
||||
go func() {
|
||||
defer close(done)
|
||||
var n int
|
||||
|
||||
n, err = r.Read(make([]byte, 1))
|
||||
switch n {
|
||||
case -1:
|
||||
if err == nil {
|
||||
err = ErrStatFault
|
||||
}
|
||||
dispatchErr(err)
|
||||
case 0:
|
||||
if err == nil {
|
||||
err = ErrStatRead
|
||||
}
|
||||
dispatchErr(err)
|
||||
case 1:
|
||||
dispatchErr(err)
|
||||
default:
|
||||
panic("unreachable")
|
||||
}
|
||||
runtime.KeepAlive(w)
|
||||
}()
|
||||
|
||||
go func() {
|
||||
select {
|
||||
case <-done:
|
||||
dispatchErr(nil)
|
||||
case <-ctx.Done():
|
||||
dispatchErr(r.Close()) // this aborts Read with file already closed
|
||||
runtime.KeepAlive(w)
|
||||
}
|
||||
}()
|
||||
|
||||
// this gets closed by the caller
|
||||
*f.s = r
|
||||
return nil
|
||||
}
|
||||
Reference in New Issue
Block a user