UNPKG

@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
"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