// Write writes data `p` to underlying block device. Will automatically open // the device in a write mode. Otherwise, behaves like io.Writer. func (bd *BlockDevice) Write(p []byte) (int, error) { if bd.out == nil { log.Infof("opening device %s for writing", bd.Path) out, err := os.OpenFile(bd.Path, os.O_WRONLY, 0) if err != nil { return 0, err } size, err := BlockDeviceGetSizeOf(out) if err != nil { log.Errorf("failed to read block device size: %v", err) out.Close() return 0, err } log.Infof("partition %s size: %v", bd.Path, size) bd.out = out bd.w = &utils.LimitedWriter{ W: out, N: size, } } w, err := bd.w.Write(p) if err != nil { log.Errorf("written %v out of %v bytes to partition %s: %v", w, len(p), bd.Path, err) } return w, err }
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 }
func (e *RebootState) Handle(ctx *StateContext, c Controller) (State, bool) { // start deployment logging if err := DeploymentLogger.Enable(e.update.ID); err != nil { return NewUpdateErrorState(NewTransientError(err), e.update), false } if err := StoreStateData(ctx.store, StateData{ Id: e.Id(), UpdateInfo: e.update, }); err != nil { // too late to do anything now, update is installed and enabled, let's play // along and reboot log.Errorf("failed to store state data in reboot state: %v, "+ "continuing with reboot", err) } c.ReportUpdateStatus(e.update, client.StatusRebooting) log.Info("rebooting device") if err := c.Reboot(); err != nil { log.Errorf("error rebooting device: %v", err) return NewErrorState(NewFatalError(err)), false } // we can not reach this point // stop deployment logging DeploymentLogger.Disable() return doneState, false }
// verifyAuth checks that client is authorized and returns false if not. // ClientTestServer.Auth.Verify must be true for verification to take place. // Client token must match ClientTestServer.Auth.Token. func (cts *ClientTestServer) verifyAuth(w http.ResponseWriter, r *http.Request) bool { if cts.Auth.Verify { hv := r.Header.Get("Authorization") if hv == "" { log.Errorf("no authorization header") w.WriteHeader(http.StatusUnauthorized) return false } if !strings.HasPrefix(hv, "Bearer ") { log.Errorf("bad authorization value: %v", hv) w.WriteHeader(http.StatusUnauthorized) return false } s := strings.SplitN(hv, " ", 2) tok := s[1] if !bytes.Equal(cts.Auth.Token, []byte(tok)) { log.Errorf("bad token, got %s expected %s", hv, cts.Auth.Token) w.WriteHeader(http.StatusUnauthorized) return false } } return true }
// 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 }
func (id *InventoryDataRunner) Get() (client.InventoryData, error) { tools, err := listRunnable(id.dir) if err != nil { return nil, errors.Wrapf(err, "failed to list tools for inventory data") } idec := NewInventoryDataDecoder() for _, t := range tools { cmd := id.cmd.Command(t) out, err := cmd.StdoutPipe() if err != nil { log.Errorf("failed to open stdout for inventory tool %s: %v", t, err) continue } if err := cmd.Start(); err != nil { log.Errorf("inventory tool %s failed with status: %v", t, err) continue } p := utils.KeyValParser{} if err := p.Parse(out); err != nil { log.Warnf("inventory tool %s returned unparsable output: %v", t, err) continue } if err := cmd.Wait(); err != nil { log.Warnf("inventory tool %s wait failed: %v", t, err) } idec.AppendFromRaw(p.Collect()) } return idec.GetInventoryData(), nil }
func (a *AuthorizedState) Handle(ctx *StateContext, c Controller) (State, bool) { // restore previous state information sd, err := LoadStateData(ctx.store) // tricky part - try to figure out if there's an update in progress, if so // proceed to UpdateCommitState; in case of errors that occur either now or // when the update was being feched/installed previously, try to handle them // gracefully // handle easy case first, no update info present, means no update in progress if err != nil && os.IsNotExist(err) { log.Debug("no update in progress, proceed") return inventoryUpdateState, false } if err != nil { log.Errorf("failed to restore update information: %v", err) me := NewFatalError(errors.Wrapf(err, "failed to restore update information")) // report update error with unknown deployment ID // TODO: fill current image ID? return NewUpdateErrorState(me, client.UpdateResponse{ ID: "unknown", }), false } log.Infof("handling state: %v", sd.Id) // chack last known status switch sd.Id { // update process was finished; check what is the status of update case MenderStateReboot: return NewUpdateVerifyState(sd.UpdateInfo), false // update prosess was initialized but stopped in the middle case MenderStateUpdateFetch, MenderStateUpdateInstall: // TODO: for now we just continue sending error report to the server // in future we might want to have some recovery option here me := NewFatalError(errors.New("update process was interrupted")) return NewUpdateErrorState(me, sd.UpdateInfo), false // there was some error while reporting update status case MenderStateUpdateStatusReport: log.Infof("restoring update status report state") if sd.UpdateStatus != client.StatusFailure && sd.UpdateStatus != client.StatusSuccess { return NewUpdateStatusReportState(sd.UpdateInfo, client.StatusError), false } // check what is exact state of update before reporting anything return NewUpdateVerifyState(sd.UpdateInfo), false // this should not happen default: log.Errorf("got invalid update state: %v", sd.Id) me := NewFatalError(errors.New("got invalid update state")) return NewUpdateErrorState(me, sd.UpdateInfo), false } }
func (m *MenderAuthManager) GenerateKey() error { if err := m.keyStore.Generate(); err != nil { log.Errorf("failed to generate device key: %v", err) return errors.Wrapf(err, "failed to generate device key") } if err := m.keyStore.Save(); err != nil { log.Errorf("failed to save device key: %s", err) return NewFatalError(err) } return nil }
// Close closes underlying block device automatically syncing any unwritten // data. Othewise, behaves like io.Closer. func (bd *BlockDevice) Close() error { if bd.out != nil { if err := bd.out.Sync(); err != nil { log.Errorf("failed to fsync partition %s: %v", bd.Path, err) return err } if err := bd.out.Close(); err != nil { log.Errorf("failed to close partition %s: %v", bd.Path, err) } bd.out = nil bd.w = nil } return nil }
func getManifestData(dataType, manifestFile string) string { // This is where Yocto stores buid information manifest, err := os.Open(manifestFile) if err != nil { log.Error("Can not read manifest data.") return "" } scanner := bufio.NewScanner(manifest) for scanner.Scan() { line := strings.TrimSpace(scanner.Text()) log.Debug("Read data from device manifest file: ", line) if strings.HasPrefix(line, dataType) { log.Debug("Found needed line: ", line) lineID := strings.Split(line, "=") if len(lineID) != 2 { log.Errorf("Broken device manifest file: (%v)", lineID) return "" } log.Debug("Current manifest data: ", strings.TrimSpace(lineID[1])) return strings.TrimSpace(lineID[1]) } } if err := scanner.Err(); err != nil { log.Error(err) } return "" }
func (cts *ClientTestServer) inventoryReq(w http.ResponseWriter, r *http.Request) { log.Infof("got inventory request %v", r) cts.Inventory.Called = true if !isMethod(http.MethodPatch, w, r) { return } if !isContentType("application/json", w, r) { return } if !cts.verifyAuth(w, r) { return } var attrs []client.InventoryAttribute if err := fromJSON(r.Body, &attrs); err != nil { log.Errorf("failed to parse attrs data: %v", err) w.WriteHeader(http.StatusBadRequest) return } log.Infof("got attrs: %v", attrs) cts.Inventory.Attrs = attrs w.WriteHeader(http.StatusOK) }
func (usr *UpdateStatusReportState) trySend(send SendData, c Controller) (error, bool) { poll := c.GetUpdatePollInterval() if poll == 0 { poll = 5 * time.Second } maxAttempts := int(maxReportSendingTime / poll) for usr.triesSendingReport < maxAttempts { log.Infof("attempting to report data of deployment [%v] to the backend;"+ " deployment status [%v], try %d", usr.update.ID, usr.status, usr.triesSendingReport) if err := send(usr.update, usr.status, c); err != nil { log.Errorf("failed to report data %v: %v", usr.status, err.Cause()) // error reporting status or sending logs; // wait for some time before trying again if wc := usr.Wait(c.GetUpdatePollInterval()); wc == false { // if the waiting was interrupted don't increase triesSendingReport return nil, true } usr.triesSendingReport++ continue } // reset counter usr.triesSendingReport = 0 return nil, false } return NewFatalError(errors.New("error sending data to server")), false }
func sendDeploymentLogs(update client.UpdateResponse, status string, c Controller) menderError { logs, err := DeploymentLogger.GetLogs(update.ID) if err != nil { log.Errorf("Failed to get deployment logs for deployment [%v]: %v", update.ID, err) // there is nothing more we can do here return NewFatalError(errors.New("can not get deployment logs from file")) } if err = c.UploadLog(update, logs); err != nil { // we got error while sending deployment logs to server; log.Errorf("failed to report deployment logs: %v", err) return NewFatalError(errors.Wrapf(err, "failed to send deployment logs")) } return nil }
// NewDBStore creates an instance of Store backed by LMDB database. DBStore uses // a single file for DB data (named `DBStoreName`). Parameter `dirpath` is a // directory where the file will be stored. Returns nil if initialization // failed. func NewDBStore(dirpath string) *DBStore { env, err := lmdb.NewEnv() if err != nil { log.Errorf("failed to create DB environment: %v", err) return nil } if err := env.Open(path.Join(dirpath, DBStoreName), lmdb.NoSubdir, 0600); err != nil { log.Errorf("failed to open DB environment: %v", err) return nil } return &DBStore{ env: env, } }
func (cts *ClientTestServer) logReq(w http.ResponseWriter, r *http.Request, id string) { log.Infof("got log request deployment ID: %v, %v", id, r) cts.Log.Called = true if !isMethod(http.MethodPut, w, r) { return } if !isContentType("application/json", w, r) { return } if !cts.verifyAuth(w, r) { return } logs, err := ioutil.ReadAll(r.Body) if err != nil { log.Errorf("error when receiving logs: %v", err) w.WriteHeader(http.StatusBadRequest) return } log.Infof("got logs: %v", logs) cts.Log.Logs = logs w.WriteHeader(http.StatusNoContent) }
func (m *mender) InventoryRefresh() error { ic := client.NewInventory() idg := NewInventoryDataRunner(path.Join(getDataDirPath(), "inventory")) idata, err := idg.Get() if err != nil { // at least report device type log.Errorf("failed to obtain inventory data: %s", err.Error()) } reqAttr := []client.InventoryAttribute{ {"device_type", m.GetDeviceType()}, {"image_id", m.GetCurrentImageID()}, {"client_version", VersionString()}, } if idata == nil { idata = make(client.InventoryData, 0, len(reqAttr)) } idata.ReplaceAttributes(reqAttr) if idata == nil { log.Infof("no inventory data to submit") return nil } err = ic.Submit(m.api.Request(m.authToken), m.config.ServerURL, idata) if err != nil { return errors.Wrapf(err, "failed to submit inventory data") } return nil }
func (cts *ClientTestServer) statusReq(w http.ResponseWriter, r *http.Request, id string) { log.Infof("got status request deployment ID: %v, %v", id, r) cts.Status.Called = true if !isMethod(http.MethodPut, w, r) { return } if !isContentType("application/json", w, r) { return } if !cts.verifyAuth(w, r) { return } var report client.StatusReport if err := fromJSON(r.Body, &report); err != nil { log.Errorf("failed to parse status data: %v", err) w.WriteHeader(http.StatusBadRequest) return } cts.Status.Status = report.Status w.WriteHeader(http.StatusNoContent) }
func (d *menderDaemon) Cleanup() { if d.store != nil { if err := d.store.Close(); err != nil { log.Errorf("failed to close data store: %v", err) } d.store = nil } }
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 isMethod(method string, w http.ResponseWriter, r *http.Request) bool { if r.Method != method { log.Errorf("method verification failed, expected %v got %v", method, r.Method) w.WriteHeader(http.StatusMethodNotAllowed) return false } return true }
// Commit a file from temporary copy to the actual name. Under the hood, does a // os.Rename() from a temp file (one with ~ suffix) to the actual name func (d DirStore) CommitFile(name string) error { from := d.getTempPath(name) to := d.getPath(name) err := os.Rename(from, to) if err != nil { log.Errorf("I/O commit error for entry %v: %v", name, err) } return err }
func isContentType(ct string, w http.ResponseWriter, r *http.Request) bool { rct := r.Header.Get("Content-Type") if ct != rct { log.Errorf("content-type verification failed, expected %v got %v", ct, rct) w.WriteHeader(http.StatusUnsupportedMediaType) return false } return true }
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 (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 (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 }
func (rs *RollbackState) Handle(ctx *StateContext, c Controller) (State, bool) { DeploymentLogger.Enable(rs.update.ID) log.Info("performing rollback") // swap active and inactive partitions if err := c.Rollback(); err != nil { log.Errorf("swapping active and inactive partitions failed: %s", err) // TODO: what can we do here return NewErrorState(NewFatalError(err)), false } DeploymentLogger.Disable() return NewRebootState(rs.update), false }
func (uv *UpdateVerifyState) Handle(ctx *StateContext, c Controller) (State, bool) { // start deployment logging if err := DeploymentLogger.Enable(uv.update.ID); err != nil { return NewUpdateErrorState(NewTransientError(err), uv.update), false } log.Debug("handle update verify state") // look at the update flag has, haserr := c.HasUpgrade() if haserr != nil { log.Errorf("has upgrade check failed: %v", haserr) me := NewFatalError(errors.Wrapf(haserr, "failed to perform 'has upgrade' check")) return NewUpdateErrorState(me, uv.update), false } if has { if uv.update.Image.YoctoID == c.GetCurrentImageID() { log.Infof("successfully running with new image %v", c.GetCurrentImageID()) // update info and has upgrade flag are there, we're running the new // update, everything looks good, proceed with committing return NewUpdateCommitState(uv.update), false } else { // seems like we're running in a different image than expected from update // information, best report an error log.Errorf("running with image %v, expected updated image %v", c.GetCurrentImageID(), uv.update.Image.YoctoID) return NewUpdateStatusReportState(uv.update, client.StatusFailure), false } } // HasUpgrade() returned false // most probably booting new image failed and u-boot rolledback to // previous image log.Errorf("update info for deployment %v present, but update flag is not set;"+ " running rollback image (previous active partition)", uv.update.ID) return NewUpdateStatusReportState(uv.update, client.StatusFailure), false }
func (res *ReportErrorState) Handle(ctx *StateContext, c Controller) (State, bool) { log.Errorf("handling report error state with status: %v", res.status) switch res.status { case client.StatusSuccess: // error while reporting success; rollback return NewRollbackState(res.update), false case client.StatusFailure: // error while reporting failure; // start from scratch as previous update was broken RemoveStateData(ctx.store) return initState, false case client.StatusError: // TODO: go back to init? log.Errorf("error while performing update: %v (%v)", res.status, res.update) RemoveStateData(ctx.store) return initState, false default: // should not end up here return doneState, 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 }
// Open an entry for writing. Under the hood, opens a temporary file (with // 'name~' name) using os.O_WRONLY|os.O_CREAT flags, with default mode 0600. // Once writing to temp file is done, the caller should run Commit() method of // the WriteCloserCommitter interface. func (d DirStore) OpenWrite(name string) (WriteCloserCommitter, error) { f, err := os.OpenFile(d.getTempPath(name), os.O_WRONLY|os.O_CREATE, 0600) if err != nil { log.Errorf("I/O write error for entry %v: %v", name, err) return nil, err } wrc := &DirFile{ WriteCloser: f, name: name, dirstore: &d, } return wrc, nil }