Browse Source

Implement in and out ACTION fields

Serj Kalichev 6 months ago
parent
commit
7528e4488a

+ 9 - 8
bin/klish/interactive.c

@@ -323,11 +323,12 @@ bool_t cmd_incompleted_ack_cb(ktp_session_t *ktp, const faux_msg_t *msg, void *u
 	ctx_t *ctx = (ctx_t *)udata;
 
 	if (ktp_session_state(ktp) == KTP_SESSION_STATE_WAIT_FOR_CMD) {
-		// Interactive command. So restore stdin handler.
-		if (KTP_STATUS_IS_INTERACTIVE(ktp_session_cmd_features(ktp))) {
-			// Disable SIGINT signal
+		// Cmd need stdin so restore stdin handler
+		if (KTP_STATUS_IS_NEED_STDIN(ktp_session_cmd_features(ktp))) {
+			// Disable SIGINT signal (it is used for commands that
+			// don't need stdin. Commands with stdin can get ^C
+			// themself interactively.)
 			tinyrl_disable_isig(ctx->tinyrl);
-			// Interactive command. So restore stdin handler.
 			faux_eloop_add_fd(ktp_session_eloop(ktp), STDIN_FILENO, POLLIN,
 				stdin_cb, ctx);
 		}
@@ -363,9 +364,9 @@ static bool_t stdin_cb(faux_eloop_t *eloop, faux_eloop_type_e type,
 		return rc;
 	}
 
-	// Interactive command
+	// Command needs stdin
 	if ((state == KTP_SESSION_STATE_WAIT_FOR_CMD) &&
-		KTP_STATUS_IS_INTERACTIVE(ktp_session_cmd_features(ctx->ktp))) {
+		KTP_STATUS_IS_NEED_STDIN(ktp_session_cmd_features(ctx->ktp))) {
 		int fd = fileno(tinyrl_istream(ctx->tinyrl));
 		char buf[1024] = {};
 		ssize_t bytes_readed = 0;
@@ -476,7 +477,7 @@ static bool_t tinyrl_key_enter(tinyrl_t *tinyrl, unsigned char key)
 
 	tinyrl_reset_line(tinyrl);
 	tinyrl_set_busy(tinyrl, BOOL_TRUE);
-	// Suppose non-interactive command
+	// Suppose non-interactive command by default
 	// Caught SIGINT for non-interactive commands
 	tinyrl_enable_isig(tinyrl);
 
@@ -777,7 +778,7 @@ static bool_t interactive_stdout_cb(ktp_session_t *ktp, const char *line, size_t
 	if (
 		ctx->opts->pager_enabled && // Pager enabled within config file
 		(ctx->pager_working == TRI_UNDEFINED) && // Pager is not working
-		!(ktp_session_cmd_features(ktp) & KTP_STATUS_INTERACTIVE) // Non interactive command
+		!KTP_STATUS_IS_INTERACTIVE(ktp_session_cmd_features(ktp)) // Non interactive command
 		) {
 
 		ctx->pager_pipe = popen(ctx->opts->pager, "we");

+ 1 - 1
bin/klish/private.h

@@ -7,7 +7,7 @@
 #endif
 
 #define DEFAULT_CFGFILE "/etc/klish/klish.conf"
-#define DEFAULT_PAGER "/usr/bin/less -I -F -e -X -K -d -r"
+#define DEFAULT_PAGER "/usr/bin/less -I -F -e -X -K -d -R"
 
 /** @brief Command line and config file options
  */

+ 1 - 1
klish.conf

@@ -7,7 +7,7 @@
 
 # The klish can use external pager for non-interactive commands. By default it
 # will execute "/usr/bin/less -I -F -e -X -K -d -r" process as a pager.
-Pager="/usr/bin/less -I -F -e -X -K -d -r"
+Pager="/usr/bin/less -I -F -e -X -K -d -R"
 
 # External pager is enabled by default. But user can explicitly enable or
 # disable it. Use "y" or "n" values.

+ 15 - 2
klish.xsd

@@ -147,7 +147,11 @@
 * [interrupt="true/false"] - The boolean field that specify that action can be
 *	be interrupted by Ctrl^C. Default is false.
 *
-* [interactive="true/false"] - Is action interactive.
+* [in="true/false/tty"] - Does ACTION need input. The "tty" means action can use
+*	terminal.
+*
+* [out="true/false/tty"] - How does ACTION use output. The "tty" means action
+*	generate output for terminal.
 *
 * [exec_on="fail/success/always/never"] - ACTION's execution depends on
 *	return code of previous elements of ACTION chain. If the
@@ -182,13 +186,22 @@
 		</xs:restriction>
 	</xs:simpleType>
 
+	<xs:simpleType name="action_io_t">
+		<xs:restriction base="xs:string">
+			<xs:enumeration value="false"/>
+			<xs:enumeration value="true"/>
+			<xs:enumeration value="tty"/>
+		</xs:restriction>
+	</xs:simpleType>
+
 	<xs:complexType name="action_t">
 		<xs:simpleContent>
 			<xs:extension base="xs:string">
 				<xs:attribute name="sym" type="xs:string" use="optional"/>
 				<xs:attribute name="lock" type="xs:string" use="optional"/>
 				<xs:attribute name="interrupt" type="xs:boolean" use="optional" default="false"/>
-				<xs:attribute name="interactive" type="xs:boolean" use="optional" default="false"/>
+				<xs:attribute name="in" type="action_io_t" use="optional" default="false"/>
+				<xs:attribute name="out" type="action_io_t" use="optional" default="true"/>
 				<xs:attribute name="exec_on" type="action_cond_t" use="optional" default="success"/>
 				<xs:attribute name="update_retcode" type="xs:boolean" use="optional" default="true"/>
 				<xs:attribute name="permanent" type="xs:boolean" use="optional" default="false"/>

+ 2 - 1
klish/iaction.h

@@ -13,7 +13,8 @@ typedef struct iaction_s {
 	char *sym;
 	char *lock;
 	char *interrupt;
-	char *interactive;
+	char *in;
+	char *out;
 	char *exec_on;
 	char *update_retcode;
 	char *permanent;

+ 54 - 7
klish/ischeme/iaction.c

@@ -2,6 +2,7 @@
 #include <stdio.h>
 #include <string.h>
 #include <assert.h>
+#include <syslog.h>
 
 #include <faux/str.h>
 #include <faux/conv.h>
@@ -14,6 +15,41 @@
 #define TAG "ACTION"
 
 
+static const char * const kaction_io_e_str[] = {
+	NULL,
+	"false",
+	"true",
+	"tty",
+};
+
+
+static const char *kaction_io_e_enum2str(kaction_io_e io)
+{
+	if ((KACTION_IO_NONE == io) || (io >= KACTION_IO_MAX))
+		return NULL;
+
+	return kaction_io_e_str[io];
+}
+
+
+static kaction_io_e kaction_io_e_str2enum(const char *str)
+{
+	kaction_io_e io = KACTION_IO_NONE;
+
+	if (!str)
+		return KACTION_IO_NONE;
+
+	for (io = (KACTION_IO_NONE + 1); io < KACTION_IO_MAX; io++) {
+		if (faux_str_casecmp(str, kaction_io_e_str[io]) == 0)
+			break;
+	}
+	if (io >= KACTION_IO_MAX)
+		return KACTION_IO_NONE;
+
+	return io;
+}
+
+
 bool_t iaction_parse(const iaction_t *info, kaction_t *action, faux_error_t *error)
 {
 	bool_t retcode = BOOL_TRUE;
@@ -49,12 +85,22 @@ bool_t iaction_parse(const iaction_t *info, kaction_t *action, faux_error_t *err
 		}
 	}
 
-	// Interactive
-	if (!faux_str_is_empty(info->interactive)) {
-		bool_t b = BOOL_FALSE;
-		if (!faux_conv_str2bool(info->interactive, &b) ||
-			!kaction_set_interactive(action, b)) {
-			faux_error_add(error, TAG": Illegal 'interactive' attribute");
+	// In
+	if (!faux_str_is_empty(info->in)) {
+		kaction_io_e io = KACTION_IO_NONE;
+		if (((io = kaction_io_e_str2enum(info->in)) == KACTION_IO_NONE) ||
+			!kaction_set_in(action, io)) {
+			faux_error_add(error, TAG": Illegal 'in' attribute");
+			retcode = BOOL_FALSE;
+		}
+	}
+
+	// Out
+	if (!faux_str_is_empty(info->out)) {
+		kaction_io_e io = KACTION_IO_NONE;
+		if (((io = kaction_io_e_str2enum(info->out)) == KACTION_IO_NONE) ||
+			!kaction_set_out(action, io)) {
+			faux_error_add(error, TAG": Illegal 'out' attribute");
 			retcode = BOOL_FALSE;
 		}
 	}
@@ -152,7 +198,8 @@ char *iaction_deploy(const kaction_t *kaction, int level)
 	attr2ctext(&str, "sym", kaction_sym_ref(kaction), level + 1);
 	attr2ctext(&str, "lock", kaction_lock(kaction), level + 1);
 	attr2ctext(&str, "interrupt", faux_conv_bool2str(kaction_interrupt(kaction)), level + 1);
-	attr2ctext(&str, "interactive", faux_conv_bool2str(kaction_interactive(kaction)), level + 1);
+	attr2ctext(&str, "in", kaction_io_e_enum2str(kaction_in(kaction)), level + 1);
+	attr2ctext(&str, "out", kaction_io_e_enum2str(kaction_out(kaction)), level + 1);
 	// Exec_on
 	switch (kaction_exec_on(kaction)) {
 	case KACTION_COND_FAIL:

+ 13 - 2
klish/kaction.h

@@ -21,6 +21,14 @@ typedef enum {
 	KACTION_COND_NEVER,
 } kaction_cond_e;
 
+typedef enum {
+	KACTION_IO_NONE,
+	KACTION_IO_FALSE,
+	KACTION_IO_TRUE,
+	KACTION_IO_TTY,
+	KACTION_IO_MAX,
+} kaction_io_e;
+
 
 C_DECL_BEGIN
 
@@ -36,8 +44,11 @@ bool_t kaction_set_lock(kaction_t *action, const char *lock);
 bool_t kaction_interrupt(const kaction_t *action);
 bool_t kaction_set_interrupt(kaction_t *action, bool_t interrupt);
 
-bool_t kaction_interactive(const kaction_t *action);
-bool_t kaction_set_interactive(kaction_t *action, bool_t interactive);
+kaction_io_e kaction_in(const kaction_t *action);
+bool_t kaction_set_in(kaction_t *action, kaction_io_e in);
+
+kaction_io_e kaction_out(const kaction_t *action);
+bool_t kaction_set_out(kaction_t *action, kaction_io_e out);
 
 kaction_cond_e kaction_exec_on(const kaction_t *action);
 bool_t kaction_set_exec_on(kaction_t *action, kaction_cond_e exec_on);

+ 2 - 1
klish/kentry.h

@@ -117,7 +117,8 @@ bool_t kentry_add_actions(kentry_t *entry, kaction_t *action);
 ssize_t kentry_actions_len(const kentry_t *entry);
 kentry_actions_node_t *kentry_actions_iter(const kentry_t *entry);
 kaction_t *kentry_actions_each(kentry_actions_node_t **iter);
-bool_t kentry_interactive(const kentry_t *entry);
+kaction_io_e kentry_in(const kentry_t *entry);
+kaction_io_e kentry_out(const kentry_t *entry);
 
 // HOTKEYs
 faux_list_t *kentry_hotkeys(const kentry_t *entry);

+ 1 - 0
klish/kexec.h

@@ -57,6 +57,7 @@ kcontext_t *kexec_contexts_each(kexec_contexts_node_t **iter);
 
 bool_t kexec_continue_command_execution(kexec_t *exec, pid_t pid, int wstatus);
 bool_t kexec_exec(kexec_t *exec);
+bool_t kexec_need_stdin(const kexec_t *exec);
 bool_t kexec_interactive(const kexec_t *exec);
 bool_t kexec_set_winsize(kexec_t *exec);
 

+ 9 - 5
klish/kscheme/kaction.c

@@ -18,7 +18,8 @@ struct kaction_s {
 	kplugin_t *plugin; // Source of symbol
 	char *lock; // Named lock
 	bool_t interrupt;
-	bool_t interactive;
+	kaction_io_e in;
+	kaction_io_e out;
 	kaction_cond_e exec_on;
 	bool_t update_retcode;
 	tri_t permanent;
@@ -41,9 +42,11 @@ KSET_STR(action, lock);
 KGET_BOOL(action, interrupt);
 KSET_BOOL(action, interrupt);
 
-// Interactive
-KGET_BOOL(action, interactive);
-KSET_BOOL(action, interactive);
+// In/Out
+KGET(action, kaction_io_e, in);
+KSET(action, kaction_io_e, in);
+KGET(action, kaction_io_e, out);
+KSET(action, kaction_io_e, out);
 
 // Exec_on
 KGET(action, kaction_cond_e, exec_on);
@@ -87,7 +90,8 @@ kaction_t *kaction_new(void)
 	action->sym_ref = NULL;
 	action->lock = NULL;
 	action->interrupt = BOOL_FALSE;
-	action->interactive = BOOL_FALSE;
+	action->in = KACTION_IO_FALSE;
+	action->out = KACTION_IO_TRUE;
 	action->exec_on = KACTION_COND_SUCCESS;
 	action->update_retcode = BOOL_TRUE;
 	action->script = NULL;

+ 30 - 5
klish/kscheme/kentry.c

@@ -319,18 +319,43 @@ bool_t kentry_set_udata(kentry_t *entry, void *data, kentry_udata_free_fn free_f
 }
 
 
-bool_t kentry_interactive(const kentry_t *entry)
+// Get integral value of "in" field of all ENTRY's actions
+// false < true < tty
+kaction_io_e kentry_in(const kentry_t *entry)
 {
 	kentry_actions_node_t *iter = NULL;
 	kaction_t *action = NULL;
+	kaction_io_e io = KACTION_IO_FALSE;
 
 	if (!entry)
-		return BOOL_FALSE;
+		return io;
+	iter = kentry_actions_iter(entry);
+	while ((action = kentry_actions_each(&iter))) {
+		kaction_io_e cur_io = kaction_in(action);
+		if (cur_io > io)
+			io = cur_io;
+	}
+
+	return io;
+}
+
+
+// Get integral value of "out" field of all ENTRY's actions
+// false < true < tty
+kaction_io_e kentry_out(const kentry_t *entry)
+{
+	kentry_actions_node_t *iter = NULL;
+	kaction_t *action = NULL;
+	kaction_io_e io = KACTION_IO_FALSE;
+
+	if (!entry)
+		return io;
 	iter = kentry_actions_iter(entry);
 	while ((action = kentry_actions_each(&iter))) {
-		if (kaction_interactive(action))
-			return BOOL_TRUE;
+		kaction_io_e cur_io = kaction_out(action);
+		if (cur_io > io)
+			io = cur_io;
 	}
 
-	return BOOL_FALSE;
+	return io;
 }

+ 41 - 13
klish/ksession/kexec.c

@@ -781,27 +781,55 @@ bool_t kexec_exec(kexec_t *exec)
 }
 
 
+// If some kexec's kentry has tty as "out" then consider kexec as interactive
 bool_t kexec_interactive(const kexec_t *exec)
 {
-	faux_list_node_t *node = NULL;
+	faux_list_node_t *iter = NULL;
 	kcontext_t *context = NULL;
-	const kentry_t *entry = NULL;
 
 	assert(exec);
 	if (!exec)
 		return BOOL_FALSE;
 
-	// Only the interactivity of last context is important. Because
-	// its output can be used as input for client's pager
-	node = faux_list_tail(exec->contexts);
-	if (!node)
-		return BOOL_FALSE;
-	context = (kcontext_t *)faux_list_data(node);
-	if (!context)
-		return BOOL_FALSE;
-	entry = kcontext_command(context);
-	if (!entry)
+	iter = kexec_contexts_iter(exec);
+	while ((context = kexec_contexts_each(&iter))) {
+		const kentry_t *entry = kcontext_command(context);
+		if (!entry)
+			return BOOL_FALSE;
+		if (kentry_out(entry) == KACTION_IO_TTY)
+			return BOOL_TRUE;
+	}
+
+	return BOOL_FALSE;
+}
+
+
+// If some kexec's kentry has tty as "in" then consider kexec as need_stdin.
+// The first kentry with "in=true" also does kexec need_stdin
+bool_t kexec_need_stdin(const kexec_t *exec)
+{
+	faux_list_node_t *iter = NULL;
+	size_t num = 0;
+	kcontext_t *context = NULL;
+
+	assert(exec);
+	if (!exec)
 		return BOOL_FALSE;
 
-	return kentry_interactive(entry);
+	iter = kexec_contexts_iter(exec);
+	while ((context = kexec_contexts_each(&iter))) {
+		const kentry_t *entry = kcontext_command(context);
+		if (!entry)
+			return BOOL_FALSE;
+		// Check first command within pipeline
+		if (num == 0) {
+			if (kentry_out(entry) == KACTION_IO_TRUE)
+				return BOOL_TRUE;
+		}
+		num++;
+		if (kentry_out(entry) == KACTION_IO_TTY)
+			return BOOL_TRUE;
+	}
+
+	return BOOL_FALSE;
 }

+ 7 - 5
klish/ktp.h

@@ -48,20 +48,22 @@ typedef enum {
 	KTP_STATUS_NONE =		(uint32_t)0x00000000,
 	KTP_STATUS_ERROR =		(uint32_t)0x00000001,
 	KTP_STATUS_INCOMPLETED =	(uint32_t)0x00000002,
-	KTP_STATUS_INTERACTIVE =	(uint32_t)0x00000100,
-	KTP_STATUS_TTY_STDIN =		(uint32_t)0x00000200,
-	KTP_STATUS_TTY_STDOUT =		(uint32_t)0x00000400,
-	KTP_STATUS_TTY_STDERR =		(uint32_t)0x00000800,
+	KTP_STATUS_TTY_STDIN =		(uint32_t)0x00000100, // Client's stdin is tty
+	KTP_STATUS_TTY_STDOUT =		(uint32_t)0x00000200, // Client's stdout is tty
+	KTP_STATUS_TTY_STDERR =		(uint32_t)0x00000400, // Client's stderr is tty
+	KTP_STATUS_NEED_STDIN =		(uint32_t)0x00001000, // Server's cmd need stdin
+	KTP_STATUS_INTERACTIVE =	(uint32_t)0x00002000, // Server's stdout is for tty
 	KTP_STATUS_DRY_RUN =		(uint32_t)0x00010000,
 	KTP_STATUS_EXIT =		(uint32_t)0x80000000,
 } ktp_status_e;
 
 #define KTP_STATUS_IS_ERROR(status) (status & KTP_STATUS_ERROR)
 #define KTP_STATUS_IS_INCOMPLETED(status) (status & KTP_STATUS_INCOMPLETED)
-#define KTP_STATUS_IS_INTERACTIVE(status) (status & KTP_STATUS_INTERACTIVE)
 #define KTP_STATUS_IS_TTY_STDIN(status) (status & KTP_STATUS_TTY_STDIN)
 #define KTP_STATUS_IS_TTY_STDOUT(status) (status & KTP_STATUS_TTY_STDOUT)
 #define KTP_STATUS_IS_TTY_STDERR(status) (status & KTP_STATUS_TTY_STDERR)
+#define KTP_STATUS_IS_NEED_STDIN(status) (status & KTP_STATUS_NEED_STDIN)
+#define KTP_STATUS_IS_INTERACTIVE(status) (status & KTP_STATUS_INTERACTIVE)
 #define KTP_STATUS_IS_DRY_RUN(status) (status & KTP_STATUS_DRY_RUN)
 #define KTP_STATUS_IS_EXIT(status) (status & KTP_STATUS_EXIT)
 

+ 2 - 1
klish/ktp/ktp_session.c

@@ -399,7 +399,8 @@ static bool_t ktp_session_process_cmd_ack(ktp_session_t *ktp, const faux_msg_t *
 		// Only first 'incompleted' cmd ack sets cmd features
 		if (!ktp->cmd_features_available) {
 			ktp->cmd_features_available = BOOL_TRUE;
-			ktp->cmd_features = status & KTP_STATUS_INTERACTIVE;
+			ktp->cmd_features = status &
+				(KTP_STATUS_INTERACTIVE | KTP_STATUS_NEED_STDIN);
 		}
 		// Execute external callback
 		if (ktp->cb[KTP_SESSION_CB_CMD_ACK_INCOMPLETED].fn)

+ 2 - 0
klish/ktp/ktpd_session.c

@@ -368,6 +368,8 @@ static bool_t ktpd_session_process_cmd(ktpd_session_t *ktpd, faux_msg_t *msg)
 		ktp_status_e status = KTP_STATUS_INCOMPLETED;
 		if (kexec_interactive(ktpd->exec))
 			status |= KTP_STATUS_INTERACTIVE;
+		if (kexec_need_stdin(ktpd->exec))
+			status |= KTP_STATUS_NEED_STDIN;
 		ack = ktp_msg_preform(cmd, status);
 		faux_msg_send_async(ack, ktpd->async);
 		faux_msg_free(ack);

+ 4 - 2
klish/xml-helper/load.c

@@ -376,7 +376,8 @@ static bool_t process_action(const kxml_node_t *element, void *parent,
 	iaction.sym = kxml_node_attr(element, "sym");
 	iaction.lock = kxml_node_attr(element, "lock");
 	iaction.interrupt = kxml_node_attr(element, "interrupt");
-	iaction.interactive = kxml_node_attr(element, "interactive");
+	iaction.in = kxml_node_attr(element, "in");
+	iaction.out = kxml_node_attr(element, "out");
 	iaction.exec_on = kxml_node_attr(element, "exec_on");
 	iaction.update_retcode = kxml_node_attr(element, "update_retcode");
 	iaction.permanent = kxml_node_attr(element, "permanent");
@@ -420,7 +421,8 @@ err:
 	kxml_node_attr_free(iaction.sym);
 	kxml_node_attr_free(iaction.lock);
 	kxml_node_attr_free(iaction.interrupt);
-	kxml_node_attr_free(iaction.interactive);
+	kxml_node_attr_free(iaction.in);
+	kxml_node_attr_free(iaction.out);
 	kxml_node_attr_free(iaction.exec_on);
 	kxml_node_attr_free(iaction.update_retcode);
 	kxml_node_attr_free(iaction.permanent);