Пример #1
0
// 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
}
Пример #2
0
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
}
Пример #3
0
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
}
Пример #4
0
// 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
}
Пример #5
0
// 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
}
Пример #6
0
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
}
Пример #7
0
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
	}
}
Пример #8
0
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
}
Пример #9
0
// 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
}
Пример #10
0
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 ""
}
Пример #11
0
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)
}
Пример #12
0
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
}
Пример #13
0
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
}
Пример #14
0
// 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,
	}
}
Пример #15
0
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)
}
Пример #16
0
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
}
Пример #17
0
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)
}
Пример #18
0
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
	}
}
Пример #19
0
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
}
Пример #20
0
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
}
Пример #21
0
// 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
}
Пример #22
0
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
}
Пример #23
0
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
}
Пример #24
0
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
}
Пример #25
0
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
}
Пример #26
0
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
}
Пример #27
0
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
}
Пример #28
0
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
	}
}
Пример #29
0
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
}
Пример #30
0
// 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
}