github.com/bugraaydogar/snapd@v0.0.0-20210315170335-8c70bb858939/daemon/daemon.go (about) 1 // -*- Mode: Go; indent-tabs-mode: t -*- 2 3 /* 4 * Copyright (C) 2015-2020 Canonical Ltd 5 * 6 * This program is free software: you can redistribute it and/or modify 7 * it under the terms of the GNU General Public License version 3 as 8 * published by the Free Software Foundation. 9 * 10 * This program is distributed in the hope that it will be useful, 11 * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 * GNU General Public License for more details. 14 * 15 * You should have received a copy of the GNU General Public License 16 * along with this program. If not, see <http://www.gnu.org/licenses/>. 17 * 18 */ 19 20 package daemon 21 22 import ( 23 "bytes" 24 "context" 25 "encoding/json" 26 "fmt" 27 "net" 28 "net/http" 29 "os" 30 "os/exec" 31 "os/signal" 32 "strconv" 33 "strings" 34 "sync" 35 "time" 36 37 "github.com/gorilla/mux" 38 "gopkg.in/tomb.v2" 39 40 "github.com/snapcore/snapd/client" 41 "github.com/snapcore/snapd/dirs" 42 "github.com/snapcore/snapd/i18n" 43 "github.com/snapcore/snapd/logger" 44 "github.com/snapcore/snapd/netutil" 45 "github.com/snapcore/snapd/osutil" 46 "github.com/snapcore/snapd/overlord" 47 "github.com/snapcore/snapd/overlord/auth" 48 "github.com/snapcore/snapd/overlord/standby" 49 "github.com/snapcore/snapd/overlord/state" 50 "github.com/snapcore/snapd/polkit" 51 "github.com/snapcore/snapd/snapdenv" 52 "github.com/snapcore/snapd/store" 53 "github.com/snapcore/snapd/systemd" 54 ) 55 56 var ErrRestartSocket = fmt.Errorf("daemon stop requested to wait for socket activation") 57 58 var systemdSdNotify = systemd.SdNotify 59 60 const ( 61 daemonRestartMsg = "system is restarting" 62 systemRestartMsg = "daemon is restarting" 63 socketRestartMsg = "daemon is stopping to wait for socket activation" 64 ) 65 66 // A Daemon listens for requests and routes them to the right command 67 type Daemon struct { 68 Version string 69 overlord *overlord.Overlord 70 state *state.State 71 snapdListener net.Listener 72 snapListener net.Listener 73 connTracker *connTracker 74 serve *http.Server 75 tomb tomb.Tomb 76 router *mux.Router 77 standbyOpinions *standby.StandbyOpinions 78 79 // set to what kind of restart was requested if any 80 requestedRestart state.RestartType 81 // set to remember that we need to exit the daemon in a way that 82 // prevents systemd from restarting it 83 restartSocket bool 84 // degradedErr is set when the daemon is in degraded mode 85 degradedErr error 86 87 expectedRebootDidNotHappen bool 88 89 mu sync.Mutex 90 } 91 92 // A ResponseFunc handles one of the individual verbs for a method 93 type ResponseFunc func(*Command, *http.Request, *auth.UserState) Response 94 95 // A Command routes a request to an individual per-verb ResponseFUnc 96 type Command struct { 97 Path string 98 PathPrefix string 99 // 100 GET ResponseFunc 101 PUT ResponseFunc 102 POST ResponseFunc 103 // can guest GET? 104 GuestOK bool 105 // can non-admin GET? 106 UserOK bool 107 // is this path accessible on the snapd-snap socket? 108 SnapOK bool 109 // this path is only accessible to root 110 RootOnly bool 111 112 // can polkit grant access? set to polkit action ID if so 113 PolkitOK string 114 115 d *Daemon 116 } 117 118 type accessResult int 119 120 const ( 121 accessOK accessResult = iota 122 accessUnauthorized 123 accessForbidden 124 accessCancelled 125 ) 126 127 var polkitCheckAuthorization = polkit.CheckAuthorization 128 129 // canAccess checks the following properties: 130 // 131 // - if the user is `root` everything is allowed 132 // - if a user is logged in (via `snap login`) and the command doesn't have RootOnly, everything is allowed 133 // - POST/PUT all require `root`, or just `snap login` if not RootOnly 134 // 135 // Otherwise for GET requests the following parameters are honored: 136 // - GuestOK: anyone can access GET 137 // - UserOK: any uid on the local system can access GET 138 // - RootOnly: only root can access this 139 // - SnapOK: a snap can access this via `snapctl` 140 func (c *Command) canAccess(r *http.Request, user *auth.UserState) accessResult { 141 if c.RootOnly && (c.UserOK || c.GuestOK || c.SnapOK) { 142 // programming error 143 logger.Panicf("Command can't have RootOnly together with any *OK flag") 144 } 145 146 if user != nil && !c.RootOnly { 147 // Authenticated users do anything not requiring explicit root. 148 return accessOK 149 } 150 151 // isUser means we have a UID for the request 152 isUser := false 153 pid, uid, socket, err := ucrednetGet(r.RemoteAddr) 154 if err == nil { 155 isUser = true 156 } else if err != errNoID { 157 logger.Noticef("unexpected error when attempting to get UID: %s", err) 158 return accessForbidden 159 } 160 isSnap := (socket == dirs.SnapSocket) 161 162 // ensure that snaps can only access SnapOK things 163 if isSnap { 164 if c.SnapOK { 165 return accessOK 166 } 167 return accessUnauthorized 168 } 169 170 // the !RootOnly check is redundant, but belt-and-suspenders 171 if r.Method == "GET" && !c.RootOnly { 172 // Guest and user access restricted to GET requests 173 if c.GuestOK { 174 return accessOK 175 } 176 177 if isUser && c.UserOK { 178 return accessOK 179 } 180 } 181 182 // Remaining admin checks rely on identifying peer uid 183 if !isUser { 184 return accessUnauthorized 185 } 186 187 if uid == 0 { 188 // Superuser does anything. 189 return accessOK 190 } 191 192 if c.RootOnly { 193 return accessUnauthorized 194 } 195 196 if c.PolkitOK != "" { 197 var flags polkit.CheckFlags 198 allowHeader := r.Header.Get(client.AllowInteractionHeader) 199 if allowHeader != "" { 200 if allow, err := strconv.ParseBool(allowHeader); err != nil { 201 logger.Noticef("error parsing %s header: %s", client.AllowInteractionHeader, err) 202 } else if allow { 203 flags |= polkit.CheckAllowInteraction 204 } 205 } 206 // Pass both pid and uid from the peer ucred to avoid pid race 207 if authorized, err := polkitCheckAuthorization(pid, uid, c.PolkitOK, nil, flags); err == nil { 208 if authorized { 209 // polkit says user is authorised 210 return accessOK 211 } 212 } else if err == polkit.ErrDismissed { 213 return accessCancelled 214 } else { 215 logger.Noticef("polkit error: %s", err) 216 } 217 } 218 219 return accessUnauthorized 220 } 221 222 func (c *Command) ServeHTTP(w http.ResponseWriter, r *http.Request) { 223 st := c.d.state 224 st.Lock() 225 // TODO Look at the error and fail if there's an attempt to authenticate with invalid data. 226 user, _ := UserFromRequest(st, r) 227 st.Unlock() 228 229 // check if we are in degradedMode 230 if c.d.degradedErr != nil && r.Method != "GET" { 231 InternalError(c.d.degradedErr.Error()).ServeHTTP(w, r) 232 return 233 } 234 235 switch c.canAccess(r, user) { 236 case accessOK: 237 // nothing 238 case accessUnauthorized: 239 Unauthorized("access denied").ServeHTTP(w, r) 240 return 241 case accessForbidden: 242 Forbidden("forbidden").ServeHTTP(w, r) 243 return 244 case accessCancelled: 245 AuthCancelled("cancelled").ServeHTTP(w, r) 246 return 247 } 248 249 ctx := store.WithClientUserAgent(r.Context(), r) 250 r = r.WithContext(ctx) 251 252 var rspf ResponseFunc 253 var rsp = MethodNotAllowed("method %q not allowed", r.Method) 254 255 switch r.Method { 256 case "GET": 257 rspf = c.GET 258 case "PUT": 259 rspf = c.PUT 260 case "POST": 261 rspf = c.POST 262 } 263 264 if rspf != nil { 265 rsp = rspf(c, r, user) 266 } 267 268 if rsp, ok := rsp.(*resp); ok { 269 _, rst := st.Restarting() 270 if rst != state.RestartUnset { 271 rsp.Maintenance = maintenanceForRestartType(rst) 272 } 273 274 if rsp.Type != ResponseTypeError { 275 st.Lock() 276 count, stamp := st.WarningsSummary() 277 st.Unlock() 278 rsp.addWarningsToMeta(count, stamp) 279 } 280 } 281 282 rsp.ServeHTTP(w, r) 283 } 284 285 type wrappedWriter struct { 286 w http.ResponseWriter 287 s int 288 } 289 290 func (w *wrappedWriter) Header() http.Header { 291 return w.w.Header() 292 } 293 294 func (w *wrappedWriter) Write(bs []byte) (int, error) { 295 return w.w.Write(bs) 296 } 297 298 func (w *wrappedWriter) WriteHeader(s int) { 299 w.w.WriteHeader(s) 300 w.s = s 301 } 302 303 func (w *wrappedWriter) Flush() { 304 if f, ok := w.w.(http.Flusher); ok { 305 f.Flush() 306 } 307 } 308 309 func logit(handler http.Handler) http.Handler { 310 return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 311 ww := &wrappedWriter{w: w} 312 t0 := time.Now() 313 handler.ServeHTTP(ww, r) 314 t := time.Now().Sub(t0) 315 url := r.URL.String() 316 if !strings.Contains(url, "/changes/") { 317 logger.Debugf("%s %s %s %s %d", r.RemoteAddr, r.Method, r.URL, t, ww.s) 318 } 319 }) 320 } 321 322 // Init sets up the Daemon's internal workings. 323 // Don't call more than once. 324 func (d *Daemon) Init() error { 325 listenerMap, err := netutil.ActivationListeners() 326 if err != nil { 327 return err 328 } 329 330 // The SnapdSocket is required-- without it, die. 331 if listener, err := netutil.GetListener(dirs.SnapdSocket, listenerMap); err == nil { 332 d.snapdListener = &ucrednetListener{Listener: listener} 333 } else { 334 return fmt.Errorf("when trying to listen on %s: %v", dirs.SnapdSocket, err) 335 } 336 337 if listener, err := netutil.GetListener(dirs.SnapSocket, listenerMap); err == nil { 338 // This listener may also be nil if that socket wasn't among 339 // the listeners, so check it before using it. 340 d.snapListener = &ucrednetListener{Listener: listener} 341 } else { 342 logger.Debugf("cannot get listener for %q: %v", dirs.SnapSocket, err) 343 } 344 345 d.addRoutes() 346 347 logger.Noticef("started %v.", snapdenv.UserAgent()) 348 349 return nil 350 } 351 352 // SetDegradedMode puts the daemon into an degraded mode which will the 353 // error given in the "err" argument for commands that are not marked 354 // as readonlyOK. 355 // 356 // This is useful to report errors to the client when the daemon 357 // cannot work because e.g. a sanity check failed or the system is out 358 // of diskspace. 359 // 360 // When the system is fine again calling "DegradedMode(nil)" is enough 361 // to put the daemon into full operation again. 362 func (d *Daemon) SetDegradedMode(err error) { 363 d.degradedErr = err 364 } 365 366 func (d *Daemon) addRoutes() { 367 d.router = mux.NewRouter() 368 369 for _, c := range api { 370 c.d = d 371 if c.PathPrefix == "" { 372 d.router.Handle(c.Path, c).Name(c.Path) 373 } else { 374 d.router.PathPrefix(c.PathPrefix).Handler(c).Name(c.PathPrefix) 375 } 376 } 377 378 // also maybe add a /favicon.ico handler... 379 380 d.router.NotFoundHandler = NotFound("not found") 381 } 382 383 var ( 384 shutdownTimeout = 25 * time.Second 385 ) 386 387 type connTracker struct { 388 mu sync.Mutex 389 conns map[net.Conn]struct{} 390 } 391 392 func (ct *connTracker) CanStandby() bool { 393 ct.mu.Lock() 394 defer ct.mu.Unlock() 395 396 return len(ct.conns) == 0 397 } 398 399 func (ct *connTracker) trackConn(conn net.Conn, state http.ConnState) { 400 ct.mu.Lock() 401 defer ct.mu.Unlock() 402 // we ignore hijacked connections, if we do things with websockets 403 // we'll need custom shutdown handling for them 404 if state == http.StateNew || state == http.StateActive { 405 ct.conns[conn] = struct{}{} 406 } else { 407 delete(ct.conns, conn) 408 } 409 } 410 411 func (d *Daemon) initStandbyHandling() { 412 d.standbyOpinions = standby.New(d.state) 413 d.standbyOpinions.AddOpinion(d.connTracker) 414 d.standbyOpinions.AddOpinion(d.overlord) 415 d.standbyOpinions.AddOpinion(d.overlord.SnapManager()) 416 d.standbyOpinions.AddOpinion(d.overlord.DeviceManager()) 417 d.standbyOpinions.Start() 418 } 419 420 // Start the Daemon 421 func (d *Daemon) Start() error { 422 if d.expectedRebootDidNotHappen { 423 // we need to schedule and wait for a system restart 424 d.tomb.Kill(nil) 425 // avoid systemd killing us again while we wait 426 systemdSdNotify("READY=1") 427 return nil 428 } 429 if d.overlord == nil { 430 panic("internal error: no Overlord") 431 } 432 433 to, reasoning, err := d.overlord.StartupTimeout() 434 if err != nil { 435 return err 436 } 437 if to > 0 { 438 to = to.Round(time.Microsecond) 439 us := to.Nanoseconds() / 1000 440 logger.Noticef("adjusting startup timeout by %v (%s)", to, reasoning) 441 systemdSdNotify(fmt.Sprintf("EXTEND_TIMEOUT_USEC=%d", us)) 442 } 443 // now perform expensive overlord/manages initiliazation 444 if err := d.overlord.StartUp(); err != nil { 445 return err 446 } 447 448 d.connTracker = &connTracker{conns: make(map[net.Conn]struct{})} 449 d.serve = &http.Server{ 450 Handler: logit(d.router), 451 ConnState: d.connTracker.trackConn, 452 } 453 454 // enable standby handling 455 d.initStandbyHandling() 456 457 // before serving actual connections remove the maintenance.json file as we 458 // are no longer down for maintenance, this state most closely corresponds 459 // to state.RestartUnset 460 if err := d.updateMaintenanceFile(state.RestartUnset); err != nil { 461 return err 462 } 463 464 // the loop runs in its own goroutine 465 d.overlord.Loop() 466 467 d.tomb.Go(func() error { 468 if d.snapListener != nil { 469 d.tomb.Go(func() error { 470 if err := d.serve.Serve(d.snapListener); err != http.ErrServerClosed && d.tomb.Err() == tomb.ErrStillAlive { 471 return err 472 } 473 474 return nil 475 }) 476 } 477 478 if err := d.serve.Serve(d.snapdListener); err != http.ErrServerClosed && d.tomb.Err() == tomb.ErrStillAlive { 479 return err 480 } 481 482 return nil 483 }) 484 485 // notify systemd that we are ready 486 systemdSdNotify("READY=1") 487 return nil 488 } 489 490 // HandleRestart implements overlord.RestartBehavior. 491 func (d *Daemon) HandleRestart(t state.RestartType) { 492 d.mu.Lock() 493 defer d.mu.Unlock() 494 495 // die when asked to restart (systemd should get us back up!) etc 496 switch t { 497 case state.RestartDaemon: 498 // save the restart kind to write out a maintenance.json in a bit 499 d.requestedRestart = t 500 case state.RestartSystem, state.RestartSystemNow: 501 // try to schedule a fallback slow reboot already here 502 // in case we get stuck shutting down 503 if err := reboot(rebootWaitTimeout); err != nil { 504 logger.Noticef("%s", err) 505 } 506 507 // save the restart kind to write out a maintenance.json in a bit 508 d.requestedRestart = t 509 case state.RestartSocket: 510 // save the restart kind to write out a maintenance.json in a bit 511 d.requestedRestart = t 512 d.restartSocket = true 513 case state.StopDaemon: 514 logger.Noticef("stopping snapd as requested") 515 default: 516 logger.Noticef("internal error: restart handler called with unknown restart type: %v", t) 517 } 518 519 d.tomb.Kill(nil) 520 } 521 522 var ( 523 rebootNoticeWait = 3 * time.Second 524 rebootWaitTimeout = 10 * time.Minute 525 rebootRetryWaitTimeout = 5 * time.Minute 526 rebootMaxTentatives = 3 527 ) 528 529 func (d *Daemon) updateMaintenanceFile(rst state.RestartType) error { 530 // for unset restart, just remove the maintenance.json file 531 if rst == state.RestartUnset { 532 err := os.Remove(dirs.SnapdMaintenanceFile) 533 // only return err if the error was something other than the file not 534 // existing 535 if err != nil && !os.IsNotExist(err) { 536 return err 537 } 538 return nil 539 } 540 541 // otherwise marshal and write it out appropriately 542 b, err := json.Marshal(maintenanceForRestartType(rst)) 543 if err != nil { 544 return err 545 } 546 547 return osutil.AtomicWrite(dirs.SnapdMaintenanceFile, bytes.NewBuffer(b), 0644, 0) 548 } 549 550 // Stop shuts down the Daemon 551 func (d *Daemon) Stop(sigCh chan<- os.Signal) error { 552 // we need to schedule/wait for a system restart again 553 if d.expectedRebootDidNotHappen { 554 // make the reboot retry immediate 555 immediateReboot := true 556 return d.doReboot(sigCh, immediateReboot, rebootRetryWaitTimeout) 557 } 558 if d.overlord == nil { 559 return fmt.Errorf("internal error: no Overlord") 560 } 561 562 d.tomb.Kill(nil) 563 564 // check the state associated with a potential restart with the lock to 565 // prevent races 566 d.mu.Lock() 567 // needsFullReboot is whether the entire system will be rebooted or not as 568 // a consequence of this restart 569 needsFullReboot := (d.requestedRestart == state.RestartSystemNow || d.requestedRestart == state.RestartSystem) 570 immediateReboot := d.requestedRestart == state.RestartSystemNow 571 restartSocket := d.restartSocket 572 d.mu.Unlock() 573 574 // before not accepting any new client connections we need to write the 575 // maintenance.json file for potential clients to see after the daemon stops 576 // responding so they can read it correctly and handle the maintenance 577 if err := d.updateMaintenanceFile(d.requestedRestart); err != nil { 578 logger.Noticef("error writing maintenance file: %v", err) 579 } 580 581 d.snapdListener.Close() 582 d.standbyOpinions.Stop() 583 584 if d.snapListener != nil { 585 // stop running hooks first 586 // and do it more gracefully if we are restarting 587 hookMgr := d.overlord.HookManager() 588 if ok, _ := d.state.Restarting(); ok { 589 logger.Noticef("gracefully waiting for running hooks") 590 hookMgr.GracefullyWaitRunningHooks() 591 logger.Noticef("done waiting for running hooks") 592 } 593 hookMgr.StopHooks() 594 d.snapListener.Close() 595 } 596 597 if needsFullReboot { 598 // give time to polling clients to notice restart 599 time.Sleep(rebootNoticeWait) 600 } 601 602 // We're using the background context here because the tomb's 603 // context will likely already have been cancelled when we are 604 // called. 605 ctx, cancel := context.WithTimeout(context.Background(), shutdownTimeout) 606 d.tomb.Kill(d.serve.Shutdown(ctx)) 607 cancel() 608 609 if !needsFullReboot { 610 // tell systemd that we are stopping 611 systemdSdNotify("STOPPING=1") 612 } 613 614 if restartSocket { 615 // At this point we processed all open requests (and 616 // stopped accepting new requests) - before going into 617 // socket activated mode we need to check if any of 618 // those open requests resulted in something that 619 // prevents us from going into socket activation mode. 620 // 621 // If this is the case we do a "normal" snapd restart 622 // to process the new changes. 623 if !d.standbyOpinions.CanStandby() { 624 d.restartSocket = false 625 } 626 } 627 d.overlord.Stop() 628 629 if err := d.tomb.Wait(); err != nil { 630 if err == context.DeadlineExceeded { 631 logger.Noticef("WARNING: cannot gracefully shut down in-flight snapd API activity within: %v", shutdownTimeout) 632 // the process is shutting down anyway, so we may just 633 // as well close the active connections right now 634 d.serve.Close() 635 } else { 636 // do not stop the shutdown even if the tomb errors 637 // because we already scheduled a slow shutdown and 638 // exiting here will just restart snapd (via systemd) 639 // which will lead to confusing results. 640 if needsFullReboot { 641 logger.Noticef("WARNING: cannot stop daemon: %v", err) 642 } else { 643 return err 644 } 645 } 646 } 647 648 if needsFullReboot { 649 return d.doReboot(sigCh, immediateReboot, rebootWaitTimeout) 650 } 651 652 if d.restartSocket { 653 return ErrRestartSocket 654 } 655 656 return nil 657 } 658 659 func (d *Daemon) rebootDelay(immediate bool) (time.Duration, error) { 660 d.state.Lock() 661 defer d.state.Unlock() 662 now := time.Now() 663 // see whether a reboot had already been scheduled 664 var rebootAt time.Time 665 err := d.state.Get("daemon-system-restart-at", &rebootAt) 666 if err != nil && err != state.ErrNoState { 667 return 0, err 668 } 669 rebootDelay := 1 * time.Minute 670 if immediate { 671 rebootDelay = 0 672 } 673 if err == nil { 674 rebootDelay = rebootAt.Sub(now) 675 } else { 676 ovr := os.Getenv("SNAPD_REBOOT_DELAY") // for tests 677 if ovr != "" && !immediate { 678 d, err := time.ParseDuration(ovr) 679 if err == nil { 680 rebootDelay = d 681 } 682 } 683 rebootAt = now.Add(rebootDelay) 684 d.state.Set("daemon-system-restart-at", rebootAt) 685 } 686 return rebootDelay, nil 687 } 688 689 func (d *Daemon) doReboot(sigCh chan<- os.Signal, immediate bool, waitTimeout time.Duration) error { 690 rebootDelay, err := d.rebootDelay(immediate) 691 if err != nil { 692 return err 693 } 694 // ask for shutdown and wait for it to happen. 695 // if we exit snapd will be restared by systemd 696 if err := reboot(rebootDelay); err != nil { 697 return err 698 } 699 // wait for reboot to happen 700 logger.Noticef("Waiting for system reboot") 701 if sigCh != nil { 702 signal.Stop(sigCh) 703 if len(sigCh) > 0 { 704 // a signal arrived in between 705 return nil 706 } 707 close(sigCh) 708 } 709 time.Sleep(waitTimeout) 710 return fmt.Errorf("expected reboot did not happen") 711 } 712 713 var shutdownMsg = i18n.G("reboot scheduled to update the system") 714 715 func rebootImpl(rebootDelay time.Duration) error { 716 if rebootDelay < 0 { 717 rebootDelay = 0 718 } 719 mins := int64(rebootDelay / time.Minute) 720 cmd := exec.Command("shutdown", "-r", fmt.Sprintf("+%d", mins), shutdownMsg) 721 if out, err := cmd.CombinedOutput(); err != nil { 722 return osutil.OutputErr(out, err) 723 } 724 return nil 725 } 726 727 var reboot = rebootImpl 728 729 // Dying is a tomb-ish thing 730 func (d *Daemon) Dying() <-chan struct{} { 731 return d.tomb.Dying() 732 } 733 734 func clearReboot(st *state.State) { 735 st.Set("daemon-system-restart-at", nil) 736 st.Set("daemon-system-restart-tentative", nil) 737 } 738 739 // RebootAsExpected implements part of overlord.RestartBehavior. 740 func (d *Daemon) RebootAsExpected(st *state.State) error { 741 clearReboot(st) 742 return nil 743 } 744 745 // RebootDidNotHappen implements part of overlord.RestartBehavior. 746 func (d *Daemon) RebootDidNotHappen(st *state.State) error { 747 var nTentative int 748 err := st.Get("daemon-system-restart-tentative", &nTentative) 749 if err != nil && err != state.ErrNoState { 750 return err 751 } 752 nTentative++ 753 if nTentative > rebootMaxTentatives { 754 // giving up, proceed normally, some in-progress refresh 755 // might get rolled back!! 756 st.ClearReboot() 757 clearReboot(st) 758 logger.Noticef("snapd was restarted while a system restart was expected, snapd retried to schedule and waited again for a system restart %d times and is giving up", rebootMaxTentatives) 759 return nil 760 } 761 st.Set("daemon-system-restart-tentative", nTentative) 762 d.state = st 763 logger.Noticef("snapd was restarted while a system restart was expected, snapd will try to schedule and wait for a system restart again (tenative %d/%d)", nTentative, rebootMaxTentatives) 764 return state.ErrExpectedReboot 765 } 766 767 // New Daemon 768 func New() (*Daemon, error) { 769 d := &Daemon{} 770 ovld, err := overlord.New(d) 771 if err == state.ErrExpectedReboot { 772 // we proceed without overlord until we reach Stop 773 // where we will schedule and wait again for a system restart. 774 // ATM we cannot do that in New because we need to satisfy 775 // systemd notify mechanisms. 776 d.expectedRebootDidNotHappen = true 777 return d, nil 778 } 779 if err != nil { 780 return nil, err 781 } 782 d.overlord = ovld 783 d.state = ovld.State() 784 return d, nil 785 }