@zowe/cli
Version:
Zowe CLI is a command line interface (CLI) that provides a simple and streamlined way to interact with IBM z/OS.
217 lines • 9.05 kB
JavaScript
"use strict";
/*
* This program and the accompanying materials are made available under the terms of the
* Eclipse Public License v2.0 which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-v20.html
*
* SPDX-License-Identifier: EPL-2.0
*
* Copyright Contributors to the Zowe Project.
*
*/
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.DaemonClient = void 0;
const path = require("path");
const stream_1 = require("stream");
const imperative_1 = require("@zowe/imperative");
const DaemonUtil_1 = require("./DaemonUtil");
/**
* Class for handling client connections to our persistent service (e.g. daemon mode)
* @export
* @class DaemonClient
*/
class DaemonClient {
/**
* Creates an instance of DaemonClient.
* @param {net.Socket} mClient
* @param {net.Server} mServer
* @param {string} mOwner
* @memberof DaemonClient
*/
constructor(mClient, mServer, mOwner) {
this.mClient = mClient;
this.mServer = mServer;
this.mOwner = mOwner;
/**
* The number of stdin bytes remaining to read from the daemon client.
*/
this.stdinBytesRemaining = 0;
}
/**
* Run an instance of this client and wait for proper events
* @memberof DaemonClient
*/
run() {
imperative_1.Imperative.api.appLogger.trace('daemon client connected');
this.mClient.on('end', this.end.bind(this));
this.mClient.on('close', this.close.bind(this));
this.mClient.on('data', this.data.bind(this));
}
/**
* End event handler triggered when client disconnects
* @private
* @memberof DaemonClient
*/
end() {
imperative_1.Imperative.api.appLogger.trace('daemon client disconnected');
}
/**
* Close event handler triggered when client closes connection
* @private
* @memberof DaemonClient
*/
close() {
imperative_1.Imperative.api.appLogger.trace('client closed');
}
/**
* Shutdown the daemon server cleanly. This is triggered when our EXE
* sends Control-C in the stdin property of its request object.
* @private
* @memberof DaemonClient
*/
shutdown() {
imperative_1.Imperative.api.appLogger.debug("shutting down");
const pidFilePath = path.join(DaemonUtil_1.DaemonUtil.getDaemonDir(), "daemon_pid.json");
if (imperative_1.IO.existsSync(pidFilePath)) {
try {
imperative_1.IO.deleteFile(pidFilePath);
}
catch (err) {
imperative_1.Imperative.api.appLogger.error("Failed to delete file '" + pidFilePath +
"'\nDetails = " + err.message);
}
}
this.mClient.end();
this.mServer.close();
}
/**
* Create readable stream for stdin data received from the daemon client.
* @param data First chunk of stdin data
* @param expectedLength Expected byte length of stdin data
* @private
* @memberof DaemonClient
*/
createStdinStream(data, expectedLength) {
const stream = new stream_1.PassThrough();
stream.write(data);
this.stdinBytesRemaining = expectedLength - data.byteLength;
if (this.stdinBytesRemaining > 0) {
// Handle really large stdin data that is buffered and received in multiple chunks
this.mClient.pipe(stream);
const outer = this; // eslint-disable-line @typescript-eslint/no-this-alias
this.mClient.on("data", function listener(data) {
outer.stdinBytesRemaining -= data.byteLength;
if (outer.stdinBytesRemaining <= 0) {
outer.mClient.removeListener("data", listener);
outer.mClient.unpipe(stream);
stream.end();
}
});
}
else {
stream.end();
}
return stream;
}
/**
* Data event handler triggered for whenever data comes in on a connection
* @private
* @param {Buffer} data
* @memberof DaemonClient
*/
data(data) {
return __awaiter(this, void 0, void 0, function* () {
if (this.stdinBytesRemaining > 0)
return;
// Split JSON body and binary data from multipart response
const jsonEndIdx = data.indexOf("}" + imperative_1.DaemonRequest.EOW_DELIMITER);
let jsonData;
let stdinData;
try {
jsonData = JSON.parse((jsonEndIdx !== -1 ? data.subarray(0, jsonEndIdx + 1) : data).toString());
stdinData = jsonEndIdx !== -1 ? data.subarray(jsonEndIdx + 2) : undefined;
}
catch (error) {
imperative_1.Imperative.api.appLogger.logError(new imperative_1.ImperativeError({
msg: "Failed to parse data received from daemon client",
causeErrors: error
}));
// eslint-disable-next-line @typescript-eslint/no-magic-numbers
imperative_1.Imperative.api.appLogger.trace("First 1024 bytes of daemon request:\n", data.subarray(0, 1024).toString());
const responsePayload = imperative_1.DaemonRequest.create({
stderr: "Failed to parse data received from daemon client:\n" + error.stack,
exitCode: 1
});
this.mClient.write(responsePayload);
this.mClient.end();
return;
}
let requestUser = undefined;
if (jsonData.user != null) {
try {
requestUser = Buffer.from(jsonData.user, 'base64').toString();
}
catch (err) {
imperative_1.Imperative.api.appLogger.error("The user field on a daemon request was malformed.");
}
}
if (requestUser == null || requestUser === '') {
// Someone tried connecting but is missing something important.
imperative_1.Imperative.api.appLogger.warn("A connection was attempted without a valid user.");
const responsePayload = imperative_1.DaemonRequest.create({
stderr: "The daemon client did not supply user information or supplied bad information.\n",
exitCode: 1
});
this.mClient.write(responsePayload);
this.mClient.end();
return;
}
else if (requestUser != this.mOwner) {
// Someone else is trying to use the daemon, and should be stopped.
imperative_1.Imperative.api.appLogger.warn("The user '" + requestUser + "' attempted to connect.");
const responsePayload = imperative_1.DaemonRequest.create({
stderr: "The user '" + requestUser + "' cannot use this daemon.\n",
exitCode: 1
});
this.mClient.write(responsePayload);
this.mClient.end();
return;
}
if (jsonData.stdin != null) {
if (jsonData.stdin !== DaemonClient.CTRL_C_CHAR) {
// This data is related to a prompt reply so we ignore it
return;
}
else if (this.mServer) {
// Ctrl+C signal was sent so we shutdown the server
this.shutdown();
}
}
else {
imperative_1.Imperative.commandLine = jsonData.argv.join(" ");
imperative_1.Imperative.api.appLogger.trace(`daemon input command: ${imperative_1.Imperative.commandLine}`);
const context = { stream: this.mClient, response: jsonData };
if (stdinData != null) {
context.stdinStream = this.createStdinStream(stdinData, jsonData.stdinLength);
}
imperative_1.Imperative.parse(jsonData.argv, context);
}
});
}
}
exports.DaemonClient = DaemonClient;
/**
* The character sent when Ctrl+C is pressed to terminate a process.
* @internal
*/
DaemonClient.CTRL_C_CHAR = "\x03";
//# sourceMappingURL=DaemonClient.js.map