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  }