The keeper reads pacman's database into installed.<node> and writes it only when it differs from the key, says each change as package.changed, and keeps the last value when the list is over the bus's cap, marking the node not current until it fits again; the health names the size. The loop runs over injected states, events, clock, watch and machine, and leaves hooks (start, after a transaction, state watches, wakes) for the upgrade parts of ADR 0508.
130 lines
3.5 KiB
Go
130 lines
3.5 KiB
Go
package main
|
|
|
|
// The machine's watch (novox/hq ADR 0507 §2, §4): inotify on the directory of pacman's database lock, for the
|
|
// lock file deleted (a transaction ended), and on the mirror list's directory, for the list written or moved
|
|
// into place (reflector rewrote it). A directory is watched, not the file: the lock comes and goes, and a list
|
|
// replaced by a rename is a new file. Nothing is polled.
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"os"
|
|
"sync"
|
|
"unsafe"
|
|
|
|
"golang.org/x/sys/unix"
|
|
)
|
|
|
|
type inotify struct {
|
|
f *os.File
|
|
lockWD int
|
|
mirWD int
|
|
lock string
|
|
mirrors string
|
|
out chan Signal
|
|
// pending holds a signal of each kind until the keeper takes it, so a burst of one kind is one signal and
|
|
// never crowds out the other.
|
|
mu sync.Mutex
|
|
pending map[Signal]bool
|
|
notify chan struct{}
|
|
}
|
|
|
|
// WatchMachine watches lockDir for lockName deleted and mirrorDir for mirrorName written or moved in.
|
|
func WatchMachine(lockDir, lockName, mirrorDir, mirrorName string) (Watcher, error) {
|
|
fd, err := unix.InotifyInit1(unix.IN_CLOEXEC | unix.IN_NONBLOCK)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("inotify: %w", err)
|
|
}
|
|
w := &inotify{lock: lockName, mirrors: mirrorName, out: make(chan Signal), pending: map[Signal]bool{},
|
|
notify: make(chan struct{}, 1)}
|
|
if w.lockWD, err = unix.InotifyAddWatch(fd, lockDir, unix.IN_DELETE|unix.IN_MOVED_FROM); err != nil {
|
|
unix.Close(fd)
|
|
return nil, fmt.Errorf("watching %s: %w", lockDir, err)
|
|
}
|
|
if w.mirWD, err = unix.InotifyAddWatch(fd, mirrorDir, unix.IN_CLOSE_WRITE|unix.IN_MOVED_TO); err != nil {
|
|
unix.Close(fd)
|
|
return nil, fmt.Errorf("watching %s: %w", mirrorDir, err)
|
|
}
|
|
// a non-blocking descriptor as an os.File is read through the runtime's poller, so Close ends a read.
|
|
w.f = os.NewFile(uintptr(fd), "inotify")
|
|
go w.read()
|
|
go w.forward()
|
|
return w, nil
|
|
}
|
|
|
|
func (w *inotify) Signals() <-chan Signal { return w.out }
|
|
|
|
func (w *inotify) Close() error { return w.f.Close() }
|
|
|
|
func (w *inotify) read() {
|
|
defer close(w.notify)
|
|
buf := make([]byte, 64*(unix.SizeofInotifyEvent+unix.NAME_MAX+1))
|
|
for {
|
|
n, err := w.f.Read(buf)
|
|
if err != nil {
|
|
if !errors.Is(err, os.ErrClosed) {
|
|
fmt.Fprintf(os.Stderr, "[pacman] keeper: the machine's watch failed: %v\n", err)
|
|
}
|
|
return
|
|
}
|
|
for off := 0; off+unix.SizeofInotifyEvent <= n; {
|
|
ev := (*unix.InotifyEvent)(unsafe.Pointer(&buf[off]))
|
|
nameAt := off + unix.SizeofInotifyEvent
|
|
end := nameAt + int(ev.Len)
|
|
if end > n {
|
|
break
|
|
}
|
|
name := cstring(buf[nameAt:end])
|
|
off = end
|
|
if ev.Mask&unix.IN_Q_OVERFLOW != 0 {
|
|
// events were lost: anything may have happened, so both are read again.
|
|
w.send(LockReleased)
|
|
w.send(MirrorsRewritten)
|
|
continue
|
|
}
|
|
switch {
|
|
case int(ev.Wd) == w.lockWD && name == w.lock:
|
|
w.send(LockReleased)
|
|
case int(ev.Wd) == w.mirWD && name == w.mirrors:
|
|
w.send(MirrorsRewritten)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// send marks a signal pending; one of the same kind already waiting covers it.
|
|
func (w *inotify) send(s Signal) {
|
|
w.mu.Lock()
|
|
w.pending[s] = true
|
|
w.mu.Unlock()
|
|
select {
|
|
case w.notify <- struct{}{}:
|
|
default:
|
|
}
|
|
}
|
|
|
|
// forward hands the pending signals to the keeper, in a fixed order, until the watch ends.
|
|
func (w *inotify) forward() {
|
|
defer close(w.out)
|
|
for range w.notify {
|
|
for _, s := range []Signal{LockReleased, MirrorsRewritten} {
|
|
w.mu.Lock()
|
|
due := w.pending[s]
|
|
delete(w.pending, s)
|
|
w.mu.Unlock()
|
|
if due {
|
|
w.out <- s
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func cstring(b []byte) string {
|
|
for i, c := range b {
|
|
if c == 0 {
|
|
return string(b[:i])
|
|
}
|
|
}
|
|
return string(b)
|
|
}
|