Browse Source

Send message from client to server when stdin is closed

Serj Kalichev 6 months ago
parent
commit
8e7d580f7f
5 changed files with 119 additions and 26 deletions
  1. 24 16
      bin/klish/klish.c
  2. 2 0
      klish/ktp.h
  3. 20 0
      klish/ktp/ktp_session.c
  4. 71 10
      klish/ktp/ktpd_session.c
  5. 2 0
      klish/ktp_session.h

+ 24 - 16
bin/klish/klish.c

@@ -272,12 +272,14 @@ int main(int argc, char **argv)
 
 	retval = 0;
 err:
-	if (tinyrl_busy(tinyrl))
-		faux_error_free(ktp_session_error(ktp));
 	// Restore stdin mode
 	fcntl(STDIN_FILENO, F_SETFL, stdin_flags);
 	reset_hotkey_table(&ctx);
-	tinyrl_free(tinyrl);
+	if (tinyrl) {
+		if (tinyrl_busy(tinyrl))
+			faux_error_free(ktp_session_error(ktp));
+		tinyrl_free(tinyrl);
+	}
 	ktp_session_free(ktp);
 	faux_eloop_free(eloop);
 	ktp_disconnect(unix_sock);
@@ -571,13 +573,19 @@ static bool_t stdin_cb(faux_eloop_t *eloop, faux_eloop_type_e type,
 	ctx_t *ctx = (ctx_t *)udata;
 	ktp_session_state_e state = KTP_SESSION_STATE_ERROR;
 	faux_eloop_info_fd_t *info = (faux_eloop_info_fd_t *)associated_data;
+	bool_t close_stdin = BOOL_FALSE;
 
 	if (!ctx)
 		return BOOL_FALSE;
 
-	// Some errors or fd is closed so stop session
-	if (info->revents & (POLLHUP | POLLERR | POLLNVAL))
-		rc = BOOL_FALSE;
+	// Some errors or fd is closed so stop interactive session
+	// Non-interactive session just removes stdin callback
+	if (info->revents & (POLLHUP | POLLERR | POLLNVAL)) {
+		if (ctx->mode == MODE_INTERACTIVE)
+			rc = BOOL_FALSE;
+		faux_eloop_del_fd(eloop, STDIN_FILENO);
+		close_stdin = BOOL_TRUE;
+	}
 
 	state = ktp_session_state(ctx->ktp);
 
@@ -585,11 +593,9 @@ static bool_t stdin_cb(faux_eloop_t *eloop, faux_eloop_type_e type,
 	if ((state == KTP_SESSION_STATE_IDLE) &&
 		(ctx->mode == MODE_INTERACTIVE)) {
 		tinyrl_read(ctx->tinyrl);
-		return rc;
-	}
 
 	// Command needs stdin
-	if ((state == KTP_SESSION_STATE_WAIT_FOR_CMD) &&
+	} else if ((state == KTP_SESSION_STATE_WAIT_FOR_CMD) &&
 		KTP_STATUS_IS_NEED_STDIN(ktp_session_cmd_features(ctx->ktp))) {
 		int fd = fileno(tinyrl_istream(ctx->tinyrl));
 		char buf[1024] = {};
@@ -600,16 +606,18 @@ static bool_t stdin_cb(faux_eloop_t *eloop, faux_eloop_type_e type,
 			if (bytes_readed != sizeof(buf))
 				break;
 		}
-		return rc;
-	}
+		if (close_stdin)
+			ktp_session_stdin_close(ctx->ktp);
 
-	// Here the situation when input is not allowed. Remove stdin from
-	// eloop waiting list. Else klish will get 100% CPU. Callbacks on
-	// operation completions will restore this handler.
-	faux_eloop_del_fd(eloop, STDIN_FILENO);
+	// Input is not needed
+	} else {
+		// Here the situation when input is not allowed. Remove stdin from
+		// eloop waiting list. Else klish will get 100% CPU. Callbacks on
+		// operation completions will restore this handler.
+		faux_eloop_del_fd(eloop, STDIN_FILENO);
+	}
 
 	// Happy compiler
-	eloop = eloop;
 	type = type;
 
 	return rc;

+ 2 - 0
klish/ktp.h

@@ -27,7 +27,9 @@ typedef enum {
 	KTP_AUTH = 'a',
 	KTP_AUTH_ACK = 'A',
 	KTP_KEEPALIVE = 'k',
+	KTP_STDIN_CLOSE = 'I',
 	KTP_STDOUT_CLOSE = 'O',
+	KTP_STDERR_CLOSE = 'E',
 } ktp_cmd_e;
 
 

+ 20 - 0
klish/ktp/ktp_session.c

@@ -755,6 +755,16 @@ bool_t ktp_session_stdin(ktp_session_t *ktp, const char *line, size_t line_len)
 }
 
 
+bool_t ktp_session_stdin_close(ktp_session_t *ktp)
+{
+	if (!ktp_session_req(ktp, KTP_STDIN_CLOSE, NULL, 0,
+		NULL, BOOL_TRUE, BOOL_FALSE))
+		return BOOL_FALSE;
+
+	return BOOL_TRUE;
+}
+
+
 bool_t ktp_session_stdout_close(ktp_session_t *ktp)
 {
 	if (!ktp_session_req(ktp, KTP_STDOUT_CLOSE, NULL, 0,
@@ -765,6 +775,16 @@ bool_t ktp_session_stdout_close(ktp_session_t *ktp)
 }
 
 
+bool_t ktp_session_stderr_close(ktp_session_t *ktp)
+{
+	if (!ktp_session_req(ktp, KTP_STDERR_CLOSE, NULL, 0,
+		NULL, BOOL_TRUE, BOOL_FALSE))
+		return BOOL_FALSE;
+
+	return BOOL_TRUE;
+}
+
+
 bool_t ktp_session_retcode(ktp_session_t *ktp, int *retcode)
 {
 	if (!ktp)

+ 71 - 10
klish/ktp/ktpd_session.c

@@ -42,6 +42,7 @@ struct ktpd_session_s {
 	faux_eloop_t *eloop; // External link, dont's free()
 	kexec_t *exec;
 	bool_t exit;
+	bool_t stdin_must_be_closed;
 };
 
 
@@ -86,6 +87,10 @@ ktpd_session_t *ktpd_session_new(int sock, kscheme_t *scheme,
 		return NULL;
 	}
 	ktpd->exec = NULL;
+	// Client can send command to close stdin but it can't be done
+	// immediately because stdin buffer can still contain data. So really
+	// close stdin after all data is written.
+	ktpd->stdin_must_be_closed = BOOL_FALSE;
 	// Exit flag. It differs from ksession done flag because KTPD session
 	// can't exit immediately. It must finish current command processing
 	// before really stop the event loop. Note: User defined plugin
@@ -902,10 +907,8 @@ static ssize_t stdin_out(int fd, faux_buf_t *buf)
 }
 
 
-static bool_t push_stdin(faux_eloop_t *eloop, faux_eloop_type_e type,
-	void *associated_data, void *user_data)
+static bool_t push_stdin(ktpd_session_t *ktpd)
 {
-	ktpd_session_t *ktpd = (ktpd_session_t *)user_data;
 	faux_buf_t *bufin = NULL;
 	int fd = -1;
 
@@ -914,7 +917,7 @@ static bool_t push_stdin(faux_eloop_t *eloop, faux_eloop_type_e type,
 	if (!ktpd->exec)
 		return BOOL_TRUE;
 	fd = kexec_stdin(ktpd->exec);
-	if (fd < 0) // Something strange
+	if (fd < 0) // May be fd is already closed
 		return BOOL_FALSE;
 
 	bufin = kexec_bufin(ktpd->exec);
@@ -925,11 +928,10 @@ static bool_t push_stdin(faux_eloop_t *eloop, faux_eloop_type_e type,
 
 	// All data is written
 	faux_eloop_exclude_fd_event(ktpd->eloop, fd, POLLOUT);
-
-	// Happy compiler
-	eloop = eloop;
-	type = type;
-	associated_data = associated_data;
+	if (ktpd->stdin_must_be_closed) {
+		close(fd);
+		kexec_set_stdin(ktpd->exec, -1);
+	}
 
 	return BOOL_TRUE;
 }
@@ -1046,6 +1048,27 @@ static bool_t ktpd_session_process_notification(ktpd_session_t *ktpd, faux_msg_t
 }
 
 
+static bool_t ktpd_session_process_stdin_close(ktpd_session_t *ktpd,
+	faux_msg_t *msg)
+{
+	int fd = -1;
+
+	assert(ktpd);
+	assert(msg);
+
+	if (!ktpd->exec)
+		return BOOL_FALSE;
+	fd = kexec_stdin(ktpd->exec);
+	if (fd < 0)
+		return BOOL_FALSE;
+	// Schedule to close stdin
+	ktpd->stdin_must_be_closed = BOOL_TRUE;
+	push_stdin(ktpd);
+
+	return BOOL_TRUE;
+}
+
+
 static bool_t ktpd_session_process_stdout_close(ktpd_session_t *ktpd,
 	faux_msg_t *msg)
 {
@@ -1068,6 +1091,28 @@ static bool_t ktpd_session_process_stdout_close(ktpd_session_t *ktpd,
 }
 
 
+static bool_t ktpd_session_process_stderr_close(ktpd_session_t *ktpd,
+	faux_msg_t *msg)
+{
+	int fd = -1;
+
+	assert(ktpd);
+	assert(msg);
+
+	if (!ktpd->exec)
+		return BOOL_FALSE;
+	fd = kexec_stderr(ktpd->exec);
+	if (fd < 0)
+		return BOOL_FALSE;
+	close(fd);
+	// Remove already generated data from err buffer. This data is not
+	// needed any more
+	faux_buf_empty(kexec_buferr(ktpd->exec));
+
+	return BOOL_TRUE;
+}
+
+
 static bool_t ktpd_session_dispatch(ktpd_session_t *ktpd, faux_msg_t *msg)
 {
 	uint16_t cmd = 0;
@@ -1126,6 +1171,13 @@ static bool_t ktpd_session_dispatch(ktpd_session_t *ktpd, faux_msg_t *msg)
 	case KTP_NOTIFICATION:
 		ktpd_session_process_notification(ktpd, msg);
 		break;
+	case KTP_STDIN_CLOSE:
+		if (ktpd->state != KTPD_SESSION_STATE_WAIT_FOR_PROCESS) {
+			err = "No active command is running";
+			break;
+		}
+		ktpd_session_process_stdin_close(ktpd, msg);
+		break;
 	case KTP_STDOUT_CLOSE:
 		if (ktpd->state != KTPD_SESSION_STATE_WAIT_FOR_PROCESS) {
 			err = "No active command is running";
@@ -1133,6 +1185,13 @@ static bool_t ktpd_session_dispatch(ktpd_session_t *ktpd, faux_msg_t *msg)
 		}
 		ktpd_session_process_stdout_close(ktpd, msg);
 		break;
+	case KTP_STDERR_CLOSE:
+		if (ktpd->state != KTPD_SESSION_STATE_WAIT_FOR_PROCESS) {
+			err = "No active command is running";
+			break;
+		}
+		ktpd_session_process_stderr_close(ktpd, msg);
+		break;
 	default:
 		syslog(LOG_WARNING, "Unsupported command: 0x%04u", cmd);
 		err = "Unsupported command";
@@ -1297,7 +1356,7 @@ static bool_t action_stdout_ev(faux_eloop_t *eloop, faux_eloop_type_e type,
 	// getting stdout but for writing stdin too. Because pseudo-terminal
 	// uses the same fd for in and out.
 	if (info->revents & POLLOUT)
-		push_stdin(eloop, type, associated_data, user_data);
+		push_stdin(ktpd);
 
 	if (info->revents & POLLIN)
 		get_stream(ktpd, info->fd, BOOL_FALSE);
@@ -1307,6 +1366,8 @@ static bool_t action_stdout_ev(faux_eloop_t *eloop, faux_eloop_type_e type,
 	if (info->revents & (POLLHUP | POLLERR | POLLNVAL))
 		faux_eloop_del_fd(eloop, info->fd);
 
+	type = type; // Happy compiler
+
 	return BOOL_TRUE;
 }
 

+ 2 - 0
klish/ktp_session.h

@@ -95,7 +95,9 @@ bool_t ktp_session_completion(ktp_session_t *ktp, const char *line,
 	bool_t dry_run);
 bool_t ktp_session_help(ktp_session_t *ktp, const char *line);
 bool_t ktp_session_stdin(ktp_session_t *ktp, const char *line, size_t line_len);
+bool_t ktp_session_stdin_close(ktp_session_t *ktp);
 bool_t ktp_session_stdout_close(ktp_session_t *ktp);
+bool_t ktp_session_stderr_close(ktp_session_t *ktp);
 bool_t ktp_session_retcode(ktp_session_t *ktp, int *retcode);
 ktp_status_e ktp_session_cmd_features(const ktp_session_t *ktp);
 bool_t ktp_session_stdout_need_newline(ktp_session_t *ktp);