func (a *AuthorizeWaitState) Handle(ctx *StateContext, c Controller) (State, bool) { log.Debugf("handle authorize wait state") intvl := c.GetUpdatePollInterval() log.Debugf("wait %v before next authorization attempt", intvl) return a.StateAfterWait(bootstrappedState, a, intvl) }
func (u *AuthClient) Request(api ApiRequester, server string, dataSrc AuthDataMessenger) ([]byte, error) { req, err := makeAuthRequest(server, dataSrc) if err != nil { return nil, errors.Wrapf(err, "failed to build authorization request") } log.Debugf("making authorization request to server %s with req: %s", server, req) rsp, err := api.Do(req) if err != nil { return nil, errors.Wrapf(err, "failed to execute authorization request") } defer rsp.Body.Close() log.Debugf("got response: %v", rsp) switch rsp.StatusCode { case http.StatusUnauthorized: return nil, AuthErrorUnauthorized case http.StatusOK: log.Debugf("receive response data") data, err := ioutil.ReadAll(rsp.Body) if err != nil { return nil, errors.Wrapf(err, "failed to receive authorization response data") } log.Debugf("received response data %v", data) return data, nil default: return nil, errors.Errorf("unexpected authorization status %v", rsp.StatusCode) } }
func (u *UpdateCheckWaitState) Handle(ctx *StateContext, c Controller) (State, bool) { log.Debugf("handle update check wait state") intvl := c.GetUpdatePollInterval() log.Debugf("wait %v before next poll", intvl) return u.StateAfterWait(updateCheckState, u, intvl) }
func (m *MenderAuthManager) MakeAuthRequest() (*client.AuthRequest, error) { var err error authd := client.AuthReqData{} idata, err := m.idSrc.Get() if err != nil { return nil, errors.Wrapf(err, "failed to obtain identity data") } authd.IdData = idata // fill device public key authd.Pubkey, err = m.keyStore.PublicPEM() if err != nil { return nil, errors.Wrapf(err, "failed to obtain device public key") } tentok := strings.TrimSpace(string(m.tenantToken)) log.Debugf("tenant token: %s", tentok) // fill tenant token authd.TenantToken = string(tentok) // fetch sequence number num, err := m.seqNum.Get() if err != nil { return nil, errors.Wrapf(err, "failed to obtain sequence number") } authd.SeqNumber = num log.Debugf("authorization data: %v", authd) reqdata, err := authd.ToBytes() if err != nil { return nil, errors.Wrapf(err, "failed to convert auth request data") } // generate signature sig, err := m.keyStore.Sign(reqdata) if err != nil { return nil, errors.Wrapf(err, "failed to sign auth request") } return &client.AuthRequest{ Data: reqdata, Token: client.AuthToken(tentok), Signature: sig, }, nil }
func (u *UpdateInstallState) Handle(ctx *StateContext, c Controller) (State, bool) { // make sure to close the stream with image data defer u.imagein.Close() // start deployment logging if err := DeploymentLogger.Enable(u.update.ID); err != nil { return NewUpdateErrorState(NewTransientError(err), u.update), false } if err := StoreStateData(ctx.store, StateData{ Id: u.Id(), UpdateInfo: u.update, }); err != nil { log.Errorf("failed to store state data in install state: %v", err) return NewUpdateErrorState(NewTransientError(err), u.update), false } // report installing, don't care about errors c.ReportUpdateStatus(u.update, client.StatusInstalling) log.Debugf("handle update install state") if err := c.InstallUpdate(u.imagein, u.size); err != nil { log.Errorf("update install failed: %s", err) return NewUpdateErrorState(NewTransientError(err), u.update), false } return NewRebootState(u.update), false }
// Returns a byte stream which is a download of the given link. func (u *UpdateClient) FetchUpdate(api ApiRequester, url string) (io.ReadCloser, int64, error) { req, err := makeUpdateFetchRequest(url) if err != nil { return nil, -1, errors.Wrapf(err, "failed to create update fetch request") } r, err := api.Do(req) if err != nil { log.Error("Can not fetch update image: ", err) return nil, -1, errors.Wrapf(err, "update fetch request failed") } log.Debugf("Received fetch update response %v+", r) if r.StatusCode != http.StatusOK { r.Body.Close() log.Errorf("Error fetching shcheduled update info: code (%d)", r.StatusCode) return nil, -1, errors.New("Error receiving scheduled update information.") } if r.ContentLength < 0 { r.Body.Close() return nil, -1, errors.New("Will not continue with unknown image size.") } else if r.ContentLength < u.minImageSize { r.Body.Close() log.Errorf("Image smaller than expected. Expected: %d, received: %d", u.minImageSize, r.ContentLength) return nil, -1, errors.New("Image size is smaller than expected. Aborting.") } return r.Body, r.ContentLength, nil }
// Open an entry for reading. func (d DirStore) OpenRead(name string) (io.ReadCloser, error) { f, err := os.Open(path.Join(d.basepath, name)) if err != nil { log.Debugf("I/O read error for entry %v: %v", name, err) return nil, err } return f, nil }
// This will be run manually from command line ONLY func doRootfs(device installer.UInstaller, args runOptionsType, dt string) error { var image io.ReadCloser var imageSize int64 var err error var upclient client.Updater if args == (runOptionsType{}) { return errors.New("rootfs called without needed parameters") } log.Debug("Starting device update.") updateLocation := *args.imageFile if strings.HasPrefix(updateLocation, "http:") || strings.HasPrefix(updateLocation, "https:") { log.Infof("Performing remote update from: [%s].", updateLocation) var ac *client.ApiClient // we are having remote update ac, err = client.New(args.Config) if err != nil { return errors.New("Can not initialize client for performing network update.") } upclient = client.NewUpdate() log.Debug("Client initialized. Start downloading image.") image, imageSize, err = upclient.FetchUpdate(ac, updateLocation) log.Debugf("Image downloaded: %d [%v] [%v]", imageSize, image, err) } else { // perform update from local file log.Infof("Start updating from local image file: [%s]", updateLocation) image, imageSize, err = FetchUpdateFromFile(updateLocation) log.Debugf("Feting update from file results: [%v], %d, %v", image, imageSize, err) } if image == nil || err != nil { return errors.Wrapf(err, "rootfs: error while updating image from command line") } defer image.Close() return installer.Install(image, dt, device) }
func (iu *InventoryUpdateState) Handle(ctx *StateContext, c Controller) (State, bool) { err := c.InventoryRefresh() if err != nil { log.Warnf("failed to refresh inventory: %v", err) } else { log.Debugf("inventory refresh complete") } return updateCheckWaitState, false }
func (m *mender) needsBootstrap() bool { if m.forceBootstrap { return true } if !m.authMgr.HasKey() { log.Debugf("needs keys") return true } return false }
func (i *InitState) Handle(ctx *StateContext, c Controller) (State, bool) { // make sure that deployment logging is disabled DeploymentLogger.Disable() log.Debugf("handle init state") if err := c.Bootstrap(); err != nil { log.Errorf("bootstrap failed: %s", err) return NewErrorState(err), false } return bootstrappedState, false }
func (b *BootstrappedState) Handle(ctx *StateContext, c Controller) (State, bool) { log.Debugf("handle bootstrapped state") if err := c.Authorize(); err != nil { log.Errorf("authorize failed: %v", err) if !err.IsFatal() { return authorizeWaitState, false } else { return NewErrorState(err), false } } return authorizedState, false }
// wait and return true if wait was completed (false if canceled) func (cs *CancellableState) Wait(wait time.Duration) bool { ticker := time.NewTicker(wait) defer ticker.Stop() select { case <-ticker.C: log.Debugf("wait complete") return true case <-cs.cancel: log.Infof("wait canceled") } return false }
func (d *device) getInactivePartition() (string, error) { inactivePartition, err := d.GetInactive() if err != nil { return "", errors.New("Error obtaining inactive partition: " + err.Error()) } log.Debugf("Marking inactive partition (%s) as the new boot candidate.", inactivePartition) partitionNumber := inactivePartition[len(inactivePartition)-1:] if _, err := strconv.Atoi(partitionNumber); err != nil { return "", errors.New("Invalid inactive partition: " + inactivePartition) } return partitionNumber, nil }
func (p *partitions) getAndCacheActivePartition(rootChecker func(StatCommander, string, *syscall.Stat_t) bool, getMountedDevices func(string) ([]string, error)) (string, error) { mountData, err := p.Command("mount").Output() if err != nil { return "", err } mountCandidate := getRootCandidateFromMount(mountData) rootDevice := getRootDevice(p) if rootDevice == nil { return "", errors.New("Can not find root device") } // First check if mountCandidate matches rootDevice if mountCandidate != "" { if rootChecker(p, mountCandidate, rootDevice) { p.active = mountCandidate log.Debugf("Setting active partition from mount candidate: %s", p.active) return p.active, nil } // If not see if we are lucky somewhere else } const devDir string = "/dev" mountedDevices, err := getMountedDevices(devDir) if err != nil { return "", err } activePartition, err := getRootFromMountedDevices(p, rootChecker, mountedDevices, rootDevice) if err != nil { return "", err } bootEnvBootPart, err := getBootEnvActivePartition(p.BootEnvReadWriter) if err != nil { return "", err } if checkBootEnvAndRootPartitionMatch(bootEnvBootPart, activePartition) { p.active = activePartition log.Debug("Setting active partition: ", activePartition) return p.active, nil } log.Error("Mounted root '" + activePartition + "' does not match boot environment mender_boot_part: " + bootEnvBootPart) return "", ErrorNoMatchBootPartRootPart }
func (usr *UpdateStatusReportState) Handle(ctx *StateContext, c Controller) (State, bool) { // start deployment logging; no error checking // we can do nothing here; either we will have the logs or not... DeploymentLogger.Enable(usr.update.ID) if err := StoreStateData(ctx.store, StateData{ Id: usr.Id(), UpdateInfo: usr.update, UpdateStatus: usr.status, }); err != nil { log.Errorf("failed to store state data in update status report state: %v", err) return NewReportErrorState(usr.update, usr.status), false } err, wasInterupted := usr.trySend(sendStatus, c) if wasInterupted { return usr, false } if err != nil { log.Errorf("failed to send status to server: %v", err) return NewReportErrorState(usr.update, usr.status), false } if usr.status == client.StatusFailure { log.Debugf("attempting to upload deployment logs for failed update") err, wasInterupted = usr.trySend(sendDeploymentLogs, c) if wasInterupted { return usr, false } if err != nil { log.Errorf("failed to send deployment logs to server: %v", err) return NewReportErrorState(usr.update, usr.status), false } } log.Debug("reporting complete") // stop deployment logging as the update is completed at this point DeploymentLogger.Disable() // status reported, logs uploaded if needed, remove state data RemoveStateData(ctx.store) return initState, false }
func (uc *UpdateCommitState) Handle(ctx *StateContext, c Controller) (State, bool) { // start deployment logging if err := DeploymentLogger.Enable(uc.update.ID); err != nil { return NewUpdateErrorState(NewTransientError(err), uc.update), false } log.Debugf("handle update commit state") err := c.CommitUpdate() if err != nil { log.Errorf("update commit failed: %s", err) // TODO: should we rollback? return NewUpdateStatusReportState(uc.update, client.StatusFailure), false } // update is commited now; report status return NewUpdateStatusReportState(uc.update, client.StatusSuccess), false }
func (d *device) InstallUpdate(image io.ReadCloser, size int64) error { log.Debugf("Trying to install update of size: %d", size) if image == nil || size < 0 { return errors.New("Have invalid update. Aborting.") } inactivePartition, err := d.GetInactive() if err != nil { return err } b := &BlockDevice{Path: inactivePartition} if bsz, err := b.Size(); err != nil { log.Errorf("failed to read size of block device %s: %v", inactivePartition, err) return err } else if bsz < uint64(size) { log.Errorf("update (%v bytes) is larger than the size of device %s (%v bytes)", size, inactivePartition, bsz) return syscall.ENOSPC } w, err := io.Copy(b, image) if err != nil { log.Errorf("failed to write image data to device %v: %v", inactivePartition, err) } log.Infof("wrote %v/%v bytes of update to device %v", w, size, inactivePartition) if cerr := b.Close(); cerr != nil { log.Errorf("closing device %v failed: %v", inactivePartition, cerr) if err != nil { return cerr } } return err }
func (k *Keystore) Load() error { inf, err := k.store.OpenRead(k.keyName) if err != nil { if os.IsNotExist(err) { log.Debugf("private key does not exist") return errNoKeys } else { return err } } defer inf.Close() k.private, err = loadFromPem(inf) if err != nil { log.Errorf("failed to load key: %s", err) return err } return nil }
func loadFromPem(in io.Reader) (*rsa.PrivateKey, error) { data, err := ioutil.ReadAll(in) if err != nil { return nil, err } block, _ := pem.Decode(data) if block == nil { return nil, errors.New("failed to decode block") } log.Debugf("block type: %s", block.Type) key, err := x509.ParsePKCS1PrivateKey(block.Bytes) if err != nil { return nil, err } return key, nil }
func (u *UpdateFetchState) Handle(ctx *StateContext, c Controller) (State, bool) { if err := StoreStateData(ctx.store, StateData{ Id: u.Id(), UpdateInfo: u.update, }); err != nil { log.Errorf("failed to store state data in fetch state: %v", err) return NewUpdateErrorState(NewTransientError(err), u.update), false } // report downloading, don't care about errors c.ReportUpdateStatus(u.update, client.StatusDownloading) log.Debugf("handle update fetch state") in, size, err := c.FetchUpdate(u.update.Image.URI) if err != nil { log.Errorf("update fetch failed: %s", err) return NewUpdateErrorState(NewTransientError(err), u.update), false } return NewUpdateInstallState(in, size, u.update), false }
// Report status information to the backend func (i *InventoryClient) Submit(api ApiRequester, url string, data interface{}) error { req, err := makeInventorySubmitRequest(url, data) if err != nil { return errors.Wrapf(err, "failed to prepare inventory submit request") } r, err := api.Do(req) if err != nil { log.Error("failed to submit inventory data: ", err) return errors.Wrapf(err, "inventory submit failed") } defer r.Body.Close() if r.StatusCode != http.StatusOK { log.Errorf("got unexpected HTTP status when submitting to inventory: %v", r.StatusCode) return errors.Errorf("inventory submit failed, bad status %v", r.StatusCode) } log.Debugf("inventory update sent, response %v", r) return nil }
// Report status information to the backend func (u *StatusClient) Report(api ApiRequester, url string, report StatusReport) error { req, err := makeStatusReportRequest(url, report) if err != nil { return errors.Wrapf(err, "failed to prepare status report request") } r, err := api.Do(req) if err != nil { log.Error("failed to report status: ", err) return errors.Wrapf(err, "reporting status failed") } defer r.Body.Close() // HTTP 204 No Content if r.StatusCode != http.StatusNoContent { log.Errorf("got unexpected HTTP status when reporting status: %v", r.StatusCode) return errors.Errorf("reporting status failed, bad status %v", r.StatusCode) } log.Debugf("status reported, response %v", r) return nil }
// Report status information to the backend func (u *LogUploadClient) Upload(api ApiRequester, url string, logs LogData) error { req, err := makeLogUploadRequest(url, &logs) if err != nil { return errors.Wrapf(err, "failed to prepare log upload request") } r, err := api.Do(req) if err != nil { log.Error("failed to upload logs: ", err) return errors.Wrapf(err, "uploading logs failed") } defer r.Body.Close() // HTTP 204 No Content if r.StatusCode != http.StatusNoContent { log.Errorf("got unexpected HTTP status when uploading log: %v", r.StatusCode) return errors.Errorf("uploading logs failed, bad status %v", r.StatusCode) } log.Debugf("logs uploaded, response %v", r) return nil }
func (u *UpdateCheckState) Handle(ctx *StateContext, c Controller) (State, bool) { log.Debugf("handle update check state") update, err := c.CheckUpdate() if err != nil { if err.Cause() == os.ErrExist { // We are already running image which we are supposed to install. // Just report successful update and return to normal operations. return NewUpdateStatusReportState(*update, client.StatusSuccess), false } log.Errorf("update check failed: %s", err) // maybe transient error? return NewErrorState(err), false } if update != nil { // custom state data? return NewUpdateFetchState(*update), false } return inventoryUpdateState, false }
// Check if new update is available. In case of errors, returns nil and error // that occurred. If no update is available *UpdateResponse is nil, otherwise it // contains update information. func (m *mender) CheckUpdate() (*client.UpdateResponse, menderError) { currentImageID := m.GetCurrentImageID() //TODO: if currentImageID == "" { // return errors.New("") // } haveUpdate, err := m.updater.GetScheduledUpdate(m.api.Request(m.authToken), m.config.ServerURL) if err != nil { // remove authentication token if device is not authorized if err == client.ErrNotAuthorized { if remErr := m.authMgr.RemoveAuthToken(); remErr != nil { log.Warn("can not remove rejected authentication token") } } log.Error("Error receiving scheduled update data: ", err) return nil, NewTransientError(err) } if haveUpdate == nil { log.Debug("no updates available") return nil, nil } update, ok := haveUpdate.(client.UpdateResponse) if !ok { return nil, NewTransientError(errors.Errorf("not an update response?")) } log.Debugf("received update response: %v", update) if update.Image.YoctoID == currentImageID { log.Info("Attempting to upgrade to currently installed image ID, not performing upgrade.") return &update, NewTransientError(os.ErrExist) } return &update, nil }
func (p *partitions) getAndCacheInactivePartition() (string, error) { if p.rootfsPartA == "" || p.rootfsPartB == "" { return "", ErrorPartitionNumberNotSet } if p.rootfsPartA == p.rootfsPartB { return "", ErrorPartitionNumberSame } active, err := p.GetActive() if err != nil { return "", err } if active == p.rootfsPartA { p.inactive = p.rootfsPartB } else if active == p.rootfsPartB { p.inactive = p.rootfsPartA } else { return "", ErrorPartitionNoMatchActive } log.Debugf("Detected inactive partition %s, based on active partition %s", p.inactive, active) return p.inactive, nil }