diff --git a/boot/boot.go b/boot/boot.go index cfd17d4bbd8ef7b791b398a4a71d07e24e233917..0e65afaa3bc4b776da67c8f8accd1331b6487376 100644 --- a/boot/boot.go +++ b/boot/boot.go @@ -35,6 +35,7 @@ type Protocol interface { // RebootDelayer allows boot protocol to indicate support for postponing the reboot process. type RebootDelayer interface { SetRebootDelay(time.Duration) + RebootPending() bool } // invalidString is the used by String to represent the invalid state of enumerations. diff --git a/boot/grub/grub.go b/boot/grub/grub.go index a3586343e5106ce807bc5c22567dcd3d2b5a7ac4..771c880d348762b57e4a43c8081b5ecfa733cbf7 100644 --- a/boot/grub/grub.go +++ b/boot/grub/grub.go @@ -28,6 +28,7 @@ package grub import ( "errors" "fmt" + "sync" "time" "gitlab.com/zygoon/go-grub" @@ -68,8 +69,11 @@ var ( // Boot implements boot.Protocol for the GRUB bootloader. type Boot struct { - envPath string + envPath string + + m sync.Mutex rebootDelay time.Duration + rebootTimer *time.Timer } // New creates a new GrubBoot operating on the given GRUB environment file. @@ -83,9 +87,20 @@ func New(envPath string) *Boot { // SetRebootDelay causes the post-install handler to wait before rebooting. func (b *Boot) SetRebootDelay(d time.Duration) { + b.m.Lock() + defer b.m.Unlock() + b.rebootDelay = d } +// RebootPending returns true if a reboot is pending. +func (b *Boot) RebootPending() bool { + b.m.Lock() + defer b.m.Unlock() + + return b.rebootTimer != nil +} + // QueryActive returns the slot that is used for booting. // // Active slot is determined by the value of the SYSOTA_BOOT_ACTIVE variable. @@ -204,15 +219,21 @@ func (b *Boot) PreInstall(env *installhandler.Environment) (err error) { // PostInstall reboots the system. func (b *Boot) PostInstall(env *installhandler.Environment) error { + b.m.Lock() + if b.rebootDelay == 0 { + b.m.Unlock() + return b.Reboot(0) } // NOTE: this interacts with the exit-when-inactive feature. - _ = time.AfterFunc(b.rebootDelay, func() { + b.rebootTimer = time.AfterFunc(b.rebootDelay, func() { _ = b.Reboot(0) }) + b.m.Unlock() + return nil } diff --git a/boot/piboot/piboot.go b/boot/piboot/piboot.go index 89fa9867cbf9687d1d0f50870e01da4d67f80b27..61d4d0e35df072a0715d6006af4e8e67b26d55f4 100644 --- a/boot/piboot/piboot.go +++ b/boot/piboot/piboot.go @@ -26,6 +26,7 @@ import ( "path/filepath" "strconv" "strings" + "sync" "time" "gitlab.com/zygoon/go-raspi/pieeprom" @@ -62,7 +63,10 @@ type PiBoot struct { procMountPoint string revCode pimodel.RevisionCode serialNum pimodel.SerialNumber - rebootDelay time.Duration + + m sync.Mutex + rebootDelay time.Duration + rebootTimer *time.Timer } // New returns a new PiBoot with the given boot mount point and CPU properties. @@ -78,9 +82,20 @@ func New(bootMountPoint, procMountPoint string, revCode pimodel.RevisionCode, se // SetRebootDelay causes the post-install handler to wait before rebooting. func (b *PiBoot) SetRebootDelay(d time.Duration) { + b.m.Lock() + defer b.m.Unlock() + b.rebootDelay = d } +// RebootPending returns true if a reboot is pending. +func (b *PiBoot) RebootPending() bool { + b.m.Lock() + defer b.m.Unlock() + + return b.rebootTimer != nil +} + // BootMountPoint returns the corresponding value passed to the New. func (pi *PiBoot) BootMountPoint() string { return pi.bootMountPoint @@ -365,17 +380,25 @@ func (pi *PiBoot) PreInstall(env *installhandler.Environment) (err error) { // Reboots are required after writing to RAUC slot of type "system". // Reboot is graceful, shutting down services normally. func (pi *PiBoot) PostInstall(env *installhandler.Environment) error { - if shouldReboot(env) { - if pi.rebootDelay == 0 { - return pi.Reboot(boot.RebootTryBoot) - } + if !shouldReboot(env) { + return nil + } + + pi.m.Lock() - // NOTE: this interacts with the exit-when-inactive feature. - _ = time.AfterFunc(pi.rebootDelay, func() { - _ = pi.Reboot(boot.RebootTryBoot) - }) + if pi.rebootDelay == 0 { + pi.m.Unlock() + + return pi.Reboot(boot.RebootTryBoot) } + // NOTE: this interacts with the exit-when-inactive feature. + pi.rebootTimer = time.AfterFunc(pi.rebootDelay, func() { + _ = pi.Reboot(boot.RebootTryBoot) + }) + + pi.m.Unlock() + return nil } diff --git a/cmd/sysotad/service.go b/cmd/sysotad/service.go index fd9d370c1f62ae558c41cb6c34a2f5440d488ba8..e759811d5a3256de8c156dfb0679abf2c65bb39b 100644 --- a/cmd/sysotad/service.go +++ b/cmd/sysotad/service.go @@ -81,7 +81,8 @@ type Service struct { stateGuard ota.StateGuard confGuard ota.ConfigGuard - update updatehosted.HostedService + update updatehosted.HostedService + bootProto boot.Protocol test testhosted.HostedService exit chan<- struct{} @@ -144,6 +145,8 @@ func NewService(conn *dbus.Conn, opts ...Option) (*Service, error) { return nil, err } + svc.bootProto = bootProto + // Ask the boot protocol to delay the reboot process if this is supported. if delayer, ok := bootProto.(boot.RebootDelayer); ok { delayer.SetRebootDelay(time.Duration(svc.confGuard.Config().QuirksRebootDelay) * time.Second) @@ -233,5 +236,10 @@ func (svc *Service) SetBootMode(bootMode boot.Mode, isRollback bool) error { // IsIdle returns true if all the hosted service are idle. func (svc *Service) IsIdle() bool { + // Pending reboot always postpones service shutdown. + if proto, ok := svc.bootProto.(boot.RebootDelayer); ok && proto.RebootPending() { + return false + } + return svc.update.IsIdle() } diff --git a/cmd/sysotad/sysotad.go b/cmd/sysotad/sysotad.go index 849bf03fc17ad215f9f7f3bba3181d5979b10971..a3abe61bc0ebbc7c03092f51d305643eec36b926 100644 --- a/cmd/sysotad/sysotad.go +++ b/cmd/sysotad/sysotad.go @@ -76,12 +76,6 @@ func (cmd *Cmd) Run(ctx context.Context, _ []string) (err error) { return err } - // When reboot quirk is enabled, prolong the lifecycle of the service to - // allow it to stick around long enough to reboot the system. - if d := svc.confGuard.Config().QuirksRebootDelay; d > 0 { - cmd.Opts.IdleDuration += time.Duration(d) * time.Second - } - // Export the service to D-Bus having finished configuration. if err := svc.Export(); err != nil { return err