mirror of
https://github.com/balena-io/etcher.git
synced 2025-04-23 06:47:17 +00:00

This adds a new command recognition for message type "log", to enable `console.log()`ing arbitrary data to the parent process console. Not sure if we'd really want this in, but it's very handy for debugging, and would keep us from writing these 3 lines every time we want to inspect something in between those processes. Change-Type: minor
199 lines
5.1 KiB
JavaScript
199 lines
5.1 KiB
JavaScript
/*
|
|
* Copyright 2016 resin.io
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
|
|
'use strict';
|
|
|
|
const EventEmitter = require('events').EventEmitter;
|
|
const _ = require('lodash');
|
|
const childProcess = require('child_process');
|
|
const ipc = require('node-ipc');
|
|
const rendererUtils = require('./renderer-utils');
|
|
const cli = require('./cli');
|
|
const CONSTANTS = require('./constants');
|
|
const EXIT_CODES = require('../shared/exit-codes');
|
|
const robot = require('../shared/robot');
|
|
|
|
/**
|
|
* @summary Perform a write
|
|
* @function
|
|
* @public
|
|
*
|
|
* @param {String} image - image
|
|
* @param {Object} drive - drive
|
|
* @param {Object} options - options
|
|
* @returns {EventEmitter} event emitter
|
|
*
|
|
* @example
|
|
* const child = childWriter.write('path/to/rpi.img', {
|
|
* device: '/dev/disk2'
|
|
* }, {
|
|
* validateWriteOnSuccess: true,
|
|
* unmountOnSuccess: true
|
|
* });
|
|
*
|
|
* child.on('progress', (state) => {
|
|
* console.log(state);
|
|
* });
|
|
*
|
|
* child.on('error', (error) => {
|
|
* throw error;
|
|
* });
|
|
*
|
|
* child.on('done', () => {
|
|
* console.log('Validation was successful!');
|
|
* });
|
|
*/
|
|
exports.write = (image, drive, options) => {
|
|
const emitter = new EventEmitter();
|
|
|
|
const argv = cli.getArguments({
|
|
entryPoint: rendererUtils.getApplicationEntryPoint(),
|
|
image,
|
|
device: drive.device,
|
|
validateWriteOnSuccess: options.validateWriteOnSuccess,
|
|
unmountOnSuccess: options.unmountOnSuccess
|
|
});
|
|
|
|
// There might be multiple Etcher instances running at
|
|
// the same time, therefore we must ensure each IPC
|
|
// server/client has a different name.
|
|
process.env.IPC_SERVER_ID = `etcher-server-${process.pid}`;
|
|
process.env.IPC_CLIENT_ID = `etcher-client-${process.pid}`;
|
|
|
|
ipc.config.id = process.env.IPC_SERVER_ID;
|
|
ipc.config.silent = true;
|
|
ipc.serve();
|
|
|
|
/**
|
|
* @summary Safely terminate the IPC server
|
|
* @function
|
|
* @private
|
|
*
|
|
* @example
|
|
* terminateServer();
|
|
*/
|
|
const terminateServer = () => {
|
|
|
|
// Turns out we need to destroy all sockets for
|
|
// the server to actually close. Otherwise, it
|
|
// just stops receiving any further connections,
|
|
// but remains open if there are active ones.
|
|
_.each(ipc.server.sockets, (socket) => {
|
|
socket.destroy();
|
|
});
|
|
|
|
ipc.server.stop();
|
|
};
|
|
|
|
/**
|
|
* @summary Emit an error to the client
|
|
* @function
|
|
* @private
|
|
*
|
|
* @param {Error} error - error
|
|
*
|
|
* @example
|
|
* emitError(new Error('foo bar'));
|
|
*/
|
|
const emitError = (error) => {
|
|
terminateServer();
|
|
emitter.emit('error', error);
|
|
};
|
|
|
|
/**
|
|
* @summary Bridge robot message to the child writer caller
|
|
* @function
|
|
* @private
|
|
*
|
|
* @param {String} message - robot message
|
|
*
|
|
* @example
|
|
* bridgeRobotMessage(robot.buildMessage('foo', {
|
|
* bar: 'baz'
|
|
* }));
|
|
*/
|
|
const bridgeRobotMessage = (message) => {
|
|
try {
|
|
const parsedMessage = robot.parseMessage(message);
|
|
|
|
// These are lighweight accessor methods for
|
|
// the properties of the parsed message
|
|
const messageCommand = robot.getCommand(parsedMessage);
|
|
const messageData = robot.getData(parsedMessage);
|
|
|
|
// The error object is decomposed by the CLI for serialisation
|
|
// purposes. We compose it back to an `Error` here in order
|
|
// to provide better encapsulation.
|
|
if (messageCommand === 'error') {
|
|
emitError(robot.recomposeErrorMessage(parsedMessage));
|
|
} else if (messageCommand === 'log') {
|
|
console.log(messageData);
|
|
} else {
|
|
emitter.emit(messageCommand, messageData);
|
|
}
|
|
|
|
} catch (error) {
|
|
emitError(error);
|
|
}
|
|
};
|
|
|
|
ipc.server.on('error', emitError);
|
|
ipc.server.on('message', bridgeRobotMessage);
|
|
|
|
ipc.server.on('start', () => {
|
|
const child = childProcess.fork(CONSTANTS.WRITER_PROXY_SCRIPT, argv, {
|
|
silent: true,
|
|
env: process.env
|
|
});
|
|
|
|
child.stdout.on('data', (data) => {
|
|
console.info(`WRITER: ${data.toString()}`);
|
|
});
|
|
|
|
child.stderr.on('data', (data) => {
|
|
bridgeRobotMessage(data.toString());
|
|
|
|
// This function causes the `close` event to be emitted
|
|
child.kill();
|
|
|
|
});
|
|
|
|
child.on('error', emitError);
|
|
|
|
child.on('close', (code) => {
|
|
terminateServer();
|
|
|
|
if (code === EXIT_CODES.CANCELLED) {
|
|
return emitter.emit('done', {
|
|
cancelled: true
|
|
});
|
|
}
|
|
|
|
// We shouldn't emit the `done` event manually here
|
|
// since the writer process will take care of it.
|
|
if (code === EXIT_CODES.SUCCESS || code === EXIT_CODES.VALIDATION_ERROR) {
|
|
return null;
|
|
}
|
|
|
|
return emitError(new Error(`Child process exited with error code: ${code}`));
|
|
});
|
|
});
|
|
|
|
ipc.server.start();
|
|
|
|
return emitter;
|
|
};
|