running-process 3.3.0__tar.gz → 3.4.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {running_process-3.3.0 → running_process-3.4.1}/Cargo.lock +18 -7
- {running_process-3.3.0 → running_process-3.4.1}/Cargo.toml +1 -1
- {running_process-3.3.0 → running_process-3.4.1}/PKG-INFO +1 -1
- {running_process-3.3.0 → running_process-3.4.1}/crates/running-process-client/Cargo.toml +1 -1
- {running_process-3.3.0 → running_process-3.4.1}/crates/running-process-client/src/client.rs +148 -7
- running_process-3.4.1/crates/running-process-client/src/lib.rs +11 -0
- running_process-3.4.1/crates/running-process-client/src/pipe_session.rs +359 -0
- running_process-3.4.1/crates/running-process-client/src/pty_session.rs +464 -0
- {running_process-3.3.0 → running_process-3.4.1}/crates/running-process-core/Cargo.toml +1 -1
- {running_process-3.3.0 → running_process-3.4.1}/crates/running-process-core/src/lib.rs +298 -10
- {running_process-3.3.0 → running_process-3.4.1}/crates/running-process-core/src/public_symbols.rs +11 -0
- {running_process-3.3.0 → running_process-3.4.1}/crates/running-process-core/tests/process_core_test.rs +143 -0
- running_process-3.4.1/crates/running-process-proto/proto/daemon.proto +780 -0
- {running_process-3.3.0 → running_process-3.4.1}/crates/running-process-py/Cargo.toml +3 -3
- {running_process-3.3.0 → running_process-3.4.1}/pyproject.toml +1 -1
- {running_process-3.3.0 → running_process-3.4.1}/src/running_process/__init__.py +1 -1
- running_process-3.4.1/src/running_process/pty/__init__.py +152 -0
- running_process-3.4.1/src/running_process/pty/_command.py +124 -0
- running_process-3.4.1/src/running_process/pty/_console_io.py +107 -0
- running_process-3.4.1/src/running_process/pty/_errors.py +39 -0
- running_process-3.4.1/src/running_process/pty/_idle_helpers.py +288 -0
- running_process-3.4.1/src/running_process/pty/_idle_state.py +51 -0
- running_process-3.4.1/src/running_process/pty/_interactive.py +184 -0
- running_process-3.4.1/src/running_process/pty/_process_helpers.py +34 -0
- running_process-3.3.0/src/running_process/pty.py → running_process-3.4.1/src/running_process/pty/_pseudo_terminal.py +49 -1073
- running_process-3.4.1/src/running_process/pty/_terminal_strip.py +130 -0
- running_process-3.4.1/src/running_process/pty/_types.py +171 -0
- running_process-3.4.1/src/running_process/pty/_wait_input.py +28 -0
- running_process-3.4.1/src/running_process/running_process/__init__.py +21 -0
- running_process-3.3.0/src/running_process/running_process.py → running_process-3.4.1/src/running_process/running_process/_core.py +21 -279
- running_process-3.4.1/src/running_process/running_process/_helpers.py +113 -0
- running_process-3.4.1/src/running_process/running_process/_iter.py +64 -0
- running_process-3.4.1/src/running_process/running_process/_subprocess.py +33 -0
- running_process-3.4.1/src/running_process/running_process/_types.py +104 -0
- running_process-3.3.0/crates/running-process-client/src/lib.rs +0 -7
- running_process-3.3.0/crates/running-process-proto/proto/daemon.proto +0 -377
- {running_process-3.3.0 → running_process-3.4.1}/LICENSE +0 -0
- {running_process-3.3.0 → running_process-3.4.1}/README.md +0 -0
- {running_process-3.3.0 → running_process-3.4.1}/crates/running-process-client/src/paths.rs +0 -0
- {running_process-3.3.0 → running_process-3.4.1}/crates/running-process-core/src/console_detect.rs +0 -0
- {running_process-3.3.0 → running_process-3.4.1}/crates/running-process-core/src/containment.rs +0 -0
- {running_process-3.3.0 → running_process-3.4.1}/crates/running-process-core/src/originator.rs +0 -0
- {running_process-3.3.0 → running_process-3.4.1}/crates/running-process-core/src/pty/mod.rs +0 -0
- {running_process-3.3.0 → running_process-3.4.1}/crates/running-process-core/src/pty/native_pty_process.rs +0 -0
- {running_process-3.3.0 → running_process-3.4.1}/crates/running-process-core/src/pty/pty_posix.rs +0 -0
- {running_process-3.3.0 → running_process-3.4.1}/crates/running-process-core/src/pty/pty_windows.rs +0 -0
- {running_process-3.3.0 → running_process-3.4.1}/crates/running-process-core/src/pty/terminal_input.rs +0 -0
- {running_process-3.3.0 → running_process-3.4.1}/crates/running-process-core/src/rust_debug.rs +0 -0
- {running_process-3.3.0 → running_process-3.4.1}/crates/running-process-core/src/spawn.rs +0 -0
- {running_process-3.3.0 → running_process-3.4.1}/crates/running-process-core/src/spawn_imp_unix.rs +0 -0
- {running_process-3.3.0 → running_process-3.4.1}/crates/running-process-core/src/spawn_imp_windows.rs +0 -0
- {running_process-3.3.0 → running_process-3.4.1}/crates/running-process-core/src/tests.rs +0 -0
- {running_process-3.3.0 → running_process-3.4.1}/crates/running-process-core/tests/containment_test.rs +0 -0
- {running_process-3.3.0 → running_process-3.4.1}/crates/running-process-core/tests/fs_adversarial_test.rs +0 -0
- {running_process-3.3.0 → running_process-3.4.1}/crates/running-process-core/tests/originator_test.rs +0 -0
- {running_process-3.3.0 → running_process-3.4.1}/crates/running-process-core/tests/pty_conhost_job_test.rs +0 -0
- {running_process-3.3.0 → running_process-3.4.1}/crates/running-process-core/tests/spawn_test.rs +0 -0
- {running_process-3.3.0 → running_process-3.4.1}/crates/running-process-proto/Cargo.toml +0 -0
- {running_process-3.3.0 → running_process-3.4.1}/crates/running-process-proto/build.rs +0 -0
- {running_process-3.3.0 → running_process-3.4.1}/crates/running-process-proto/src/lib.rs +0 -0
- {running_process-3.3.0 → running_process-3.4.1}/crates/running-process-py/src/containment.rs +0 -0
- {running_process-3.3.0 → running_process-3.4.1}/crates/running-process-py/src/daemon_client.rs +0 -0
- {running_process-3.3.0 → running_process-3.4.1}/crates/running-process-py/src/debug_traces.rs +0 -0
- {running_process-3.3.0 → running_process-3.4.1}/crates/running-process-py/src/helpers.rs +0 -0
- {running_process-3.3.0 → running_process-3.4.1}/crates/running-process-py/src/idle_detector.rs +0 -0
- {running_process-3.3.0 → running_process-3.4.1}/crates/running-process-py/src/lib.rs +0 -0
- {running_process-3.3.0 → running_process-3.4.1}/crates/running-process-py/src/metrics.rs +0 -0
- {running_process-3.3.0 → running_process-3.4.1}/crates/running-process-py/src/originator.rs +0 -0
- {running_process-3.3.0 → running_process-3.4.1}/crates/running-process-py/src/pid_tracking.rs +0 -0
- {running_process-3.3.0 → running_process-3.4.1}/crates/running-process-py/src/priority.rs +0 -0
- {running_process-3.3.0 → running_process-3.4.1}/crates/running-process-py/src/process.rs +0 -0
- {running_process-3.3.0 → running_process-3.4.1}/crates/running-process-py/src/process_tree.rs +0 -0
- {running_process-3.3.0 → running_process-3.4.1}/crates/running-process-py/src/pty_buffer.rs +0 -0
- {running_process-3.3.0 → running_process-3.4.1}/crates/running-process-py/src/pty_process.rs +0 -0
- {running_process-3.3.0 → running_process-3.4.1}/crates/running-process-py/src/public_symbols.rs +0 -0
- {running_process-3.3.0 → running_process-3.4.1}/crates/running-process-py/src/py_native_process.rs +0 -0
- {running_process-3.3.0 → running_process-3.4.1}/crates/running-process-py/src/registry.rs +0 -0
- {running_process-3.3.0 → running_process-3.4.1}/crates/running-process-py/src/signal_bool.rs +0 -0
- {running_process-3.3.0 → running_process-3.4.1}/crates/running-process-py/src/terminal_input.rs +0 -0
- {running_process-3.3.0 → running_process-3.4.1}/crates/running-process-py/src/tests/control_churn.rs +0 -0
- {running_process-3.3.0 → running_process-3.4.1}/crates/running-process-py/src/tests/expect_match.rs +0 -0
- {running_process-3.3.0 → running_process-3.4.1}/crates/running-process-py/src/tests/idle_detector.rs +0 -0
- {running_process-3.3.0 → running_process-3.4.1}/crates/running-process-py/src/tests/mod.rs +0 -0
- {running_process-3.3.0 → running_process-3.4.1}/crates/running-process-py/src/tests/parse_command.rs +0 -0
- {running_process-3.3.0 → running_process-3.4.1}/crates/running-process-py/src/tests/process_tree.rs +0 -0
- {running_process-3.3.0 → running_process-3.4.1}/crates/running-process-py/src/tests/pty_buffer.rs +0 -0
- {running_process-3.3.0 → running_process-3.4.1}/crates/running-process-py/src/tests/pty_process.rs +0 -0
- {running_process-3.3.0 → running_process-3.4.1}/crates/running-process-py/src/tests/registry.rs +0 -0
- {running_process-3.3.0 → running_process-3.4.1}/crates/running-process-py/src/tests/signal_bool.rs +0 -0
- {running_process-3.3.0 → running_process-3.4.1}/crates/running-process-py/src/tests/terminal_input.rs +0 -0
- {running_process-3.3.0 → running_process-3.4.1}/crates/test-watchdog/Cargo.toml +0 -0
- {running_process-3.3.0 → running_process-3.4.1}/crates/test-watchdog/src/lib.rs +0 -0
- {running_process-3.3.0 → running_process-3.4.1}/src/running_process/assets/example.txt +0 -0
- {running_process-3.3.0 → running_process-3.4.1}/src/running_process/cli.py +0 -0
- {running_process-3.3.0 → running_process-3.4.1}/src/running_process/command_render.py +0 -0
- {running_process-3.3.0 → running_process-3.4.1}/src/running_process/compat.py +0 -0
- {running_process-3.3.0 → running_process-3.4.1}/src/running_process/console_encoding.py +0 -0
- {running_process-3.3.0 → running_process-3.4.1}/src/running_process/daemon.py +0 -0
- {running_process-3.3.0 → running_process-3.4.1}/src/running_process/dashboard.py +0 -0
- {running_process-3.3.0 → running_process-3.4.1}/src/running_process/exit_status.py +0 -0
- {running_process-3.3.0 → running_process-3.4.1}/src/running_process/expect.py +0 -0
- {running_process-3.3.0 → running_process-3.4.1}/src/running_process/interrupt_handler.py +0 -0
- {running_process-3.3.0 → running_process-3.4.1}/src/running_process/launch.py +0 -0
- {running_process-3.3.0 → running_process-3.4.1}/src/running_process/line_iterator.py +0 -0
- {running_process-3.3.0 → running_process-3.4.1}/src/running_process/output_formatter.py +0 -0
- {running_process-3.3.0 → running_process-3.4.1}/src/running_process/priority.py +0 -0
- {running_process-3.3.0 → running_process-3.4.1}/src/running_process/process_utils.py +0 -0
- {running_process-3.3.0 → running_process-3.4.1}/src/running_process/processor_cli.py +0 -0
- {running_process-3.3.0 → running_process-3.4.1}/src/running_process/running_process_manager.py +0 -0
|
@@ -210,7 +210,7 @@ checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28"
|
|
|
210
210
|
|
|
211
211
|
[[package]]
|
|
212
212
|
name = "daemon-trampoline"
|
|
213
|
-
version = "3.
|
|
213
|
+
version = "3.4.1"
|
|
214
214
|
dependencies = [
|
|
215
215
|
"libc",
|
|
216
216
|
"serde",
|
|
@@ -1036,7 +1036,7 @@ checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a"
|
|
|
1036
1036
|
|
|
1037
1037
|
[[package]]
|
|
1038
1038
|
name = "running-process-client"
|
|
1039
|
-
version = "3.
|
|
1039
|
+
version = "3.4.1"
|
|
1040
1040
|
dependencies = [
|
|
1041
1041
|
"dirs",
|
|
1042
1042
|
"interprocess",
|
|
@@ -1047,7 +1047,7 @@ dependencies = [
|
|
|
1047
1047
|
|
|
1048
1048
|
[[package]]
|
|
1049
1049
|
name = "running-process-core"
|
|
1050
|
-
version = "3.
|
|
1050
|
+
version = "3.4.1"
|
|
1051
1051
|
dependencies = [
|
|
1052
1052
|
"libc",
|
|
1053
1053
|
"portable-pty",
|
|
@@ -1061,7 +1061,7 @@ dependencies = [
|
|
|
1061
1061
|
|
|
1062
1062
|
[[package]]
|
|
1063
1063
|
name = "running-process-daemon"
|
|
1064
|
-
version = "3.
|
|
1064
|
+
version = "3.4.1"
|
|
1065
1065
|
dependencies = [
|
|
1066
1066
|
"bytes",
|
|
1067
1067
|
"clap",
|
|
@@ -1088,7 +1088,7 @@ dependencies = [
|
|
|
1088
1088
|
|
|
1089
1089
|
[[package]]
|
|
1090
1090
|
name = "running-process-proto"
|
|
1091
|
-
version = "3.
|
|
1091
|
+
version = "3.4.1"
|
|
1092
1092
|
dependencies = [
|
|
1093
1093
|
"prost",
|
|
1094
1094
|
"prost-build",
|
|
@@ -1098,7 +1098,7 @@ dependencies = [
|
|
|
1098
1098
|
|
|
1099
1099
|
[[package]]
|
|
1100
1100
|
name = "running-process-py"
|
|
1101
|
-
version = "3.
|
|
1101
|
+
version = "3.4.1"
|
|
1102
1102
|
dependencies = [
|
|
1103
1103
|
"interprocess",
|
|
1104
1104
|
"libc",
|
|
@@ -1114,7 +1114,7 @@ dependencies = [
|
|
|
1114
1114
|
|
|
1115
1115
|
[[package]]
|
|
1116
1116
|
name = "runpm-cli"
|
|
1117
|
-
version = "3.
|
|
1117
|
+
version = "3.4.1"
|
|
1118
1118
|
dependencies = [
|
|
1119
1119
|
"anyhow",
|
|
1120
1120
|
"clap",
|
|
@@ -1368,6 +1368,10 @@ dependencies = [
|
|
|
1368
1368
|
"running-process-core",
|
|
1369
1369
|
]
|
|
1370
1370
|
|
|
1371
|
+
[[package]]
|
|
1372
|
+
name = "testbin-emitter"
|
|
1373
|
+
version = "0.0.0"
|
|
1374
|
+
|
|
1371
1375
|
[[package]]
|
|
1372
1376
|
name = "testbin-env-dump"
|
|
1373
1377
|
version = "0.0.0"
|
|
@@ -1387,6 +1391,13 @@ dependencies = [
|
|
|
1387
1391
|
"running-process-core",
|
|
1388
1392
|
]
|
|
1389
1393
|
|
|
1394
|
+
[[package]]
|
|
1395
|
+
name = "testbin-stubborn"
|
|
1396
|
+
version = "0.0.0"
|
|
1397
|
+
dependencies = [
|
|
1398
|
+
"libc",
|
|
1399
|
+
]
|
|
1400
|
+
|
|
1390
1401
|
[[package]]
|
|
1391
1402
|
name = "thiserror"
|
|
1392
1403
|
version = "1.0.69"
|
|
@@ -3,7 +3,7 @@ resolver = "2"
|
|
|
3
3
|
members = ["crates/running-process-core", "crates/running-process-proto", "crates/running-process-client", "crates/running-process-py", "crates/test-watchdog"]
|
|
4
4
|
|
|
5
5
|
[workspace.package]
|
|
6
|
-
version = "3.
|
|
6
|
+
version = "3.4.1"
|
|
7
7
|
edition = "2021"
|
|
8
8
|
rust-version = "1.85"
|
|
9
9
|
license = "BSD-3-Clause"
|
|
@@ -9,7 +9,7 @@ homepage.workspace = true
|
|
|
9
9
|
description = "Lightweight synchronous IPC client for the running-process daemon"
|
|
10
10
|
|
|
11
11
|
[dependencies]
|
|
12
|
-
running-process-proto = { path = "../running-process-proto", version = "3.
|
|
12
|
+
running-process-proto = { path = "../running-process-proto", version = "3.4.1" }
|
|
13
13
|
prost = "0.14"
|
|
14
14
|
interprocess = "2"
|
|
15
15
|
dirs = "6"
|
|
@@ -9,12 +9,14 @@ use interprocess::local_socket::Stream;
|
|
|
9
9
|
use interprocess::TryClone;
|
|
10
10
|
use prost::Message;
|
|
11
11
|
use running_process_proto::daemon::{
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
12
|
+
BulkTerminateSessionsRequest, BulkTerminateSessionsResponse, DaemonRequest, DaemonResponse,
|
|
13
|
+
GetProcessTreeRequest, GetSessionBacklogRequest, GetSessionBacklogResponse, KeyValue,
|
|
14
|
+
KillTreeRequest, KillZombiesRequest, ListActiveRequest, ListByOriginatorRequest, PingRequest,
|
|
15
|
+
PipeStreamKind, PurgeExitedSessionsRequest, PurgeExitedSessionsResponse, RequestType,
|
|
16
|
+
ResizePtySessionRequest, ServiceConfig, ServiceDeleteRequest, ServiceDescribeRequest,
|
|
17
|
+
ServiceFlushRequest, ServiceListRequest, ServiceLogsRequest, ServiceRestartRequest,
|
|
18
|
+
ServiceResurrectRequest, ServiceSaveRequest, ServiceStartRequest, ServiceStopRequest,
|
|
19
|
+
ShutdownRequest, SpawnDaemonRequest as ProtoSpawnDaemonRequest, StatusCode, StatusRequest,
|
|
18
20
|
};
|
|
19
21
|
use std::io::{BufReader, BufWriter, Read, Write};
|
|
20
22
|
use std::path::PathBuf;
|
|
@@ -270,7 +272,7 @@ impl DaemonClient {
|
|
|
270
272
|
// -----------------------------------------------------------------------
|
|
271
273
|
|
|
272
274
|
/// Allocate the next request ID.
|
|
273
|
-
fn next_request_id(&self) -> u64 {
|
|
275
|
+
pub(crate) fn next_request_id(&self) -> u64 {
|
|
274
276
|
self.next_id.fetch_add(1, Ordering::Relaxed)
|
|
275
277
|
}
|
|
276
278
|
|
|
@@ -615,6 +617,145 @@ impl DaemonClient {
|
|
|
615
617
|
};
|
|
616
618
|
self.send_request(request)
|
|
617
619
|
}
|
|
620
|
+
|
|
621
|
+
/// Resize a PTY session without going through an attach
|
|
622
|
+
/// (#130 M5 follow-up). The new size persists for the lifetime of
|
|
623
|
+
/// the session; subsequent attaches can override it via their own
|
|
624
|
+
/// rows/cols fields.
|
|
625
|
+
pub fn resize_pty_session(
|
|
626
|
+
&mut self,
|
|
627
|
+
session_id: &str,
|
|
628
|
+
rows: u16,
|
|
629
|
+
cols: u16,
|
|
630
|
+
) -> Result<(), ClientError> {
|
|
631
|
+
let request = DaemonRequest {
|
|
632
|
+
id: self.next_request_id(),
|
|
633
|
+
r#type: RequestType::ResizePtySession.into(),
|
|
634
|
+
protocol_version: 1,
|
|
635
|
+
client_name: String::from("running-process-client"),
|
|
636
|
+
resize_pty_session: Some(ResizePtySessionRequest {
|
|
637
|
+
session_id: session_id.into(),
|
|
638
|
+
rows: rows as u32,
|
|
639
|
+
cols: cols as u32,
|
|
640
|
+
}),
|
|
641
|
+
..Default::default()
|
|
642
|
+
};
|
|
643
|
+
let response = self.send_request(request)?;
|
|
644
|
+
if response.code != StatusCode::Ok as i32 {
|
|
645
|
+
let code =
|
|
646
|
+
StatusCode::try_from(response.code).unwrap_or(StatusCode::UnknownRequest);
|
|
647
|
+
return Err(ClientError::Server {
|
|
648
|
+
code,
|
|
649
|
+
message: response.message,
|
|
650
|
+
});
|
|
651
|
+
}
|
|
652
|
+
Ok(())
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
/// Purge exited sessions from both daemon-side registries (#130 M9
|
|
656
|
+
/// H4). Returns counts of PTY and pipe sessions reaped.
|
|
657
|
+
pub fn purge_exited_sessions(
|
|
658
|
+
&mut self,
|
|
659
|
+
originator: &str,
|
|
660
|
+
) -> Result<PurgeExitedSessionsResponse, ClientError> {
|
|
661
|
+
let request = DaemonRequest {
|
|
662
|
+
id: self.next_request_id(),
|
|
663
|
+
r#type: RequestType::PurgeExitedSessions.into(),
|
|
664
|
+
protocol_version: 1,
|
|
665
|
+
client_name: String::from("running-process-client"),
|
|
666
|
+
purge_exited_sessions: Some(PurgeExitedSessionsRequest {
|
|
667
|
+
originator: originator.into(),
|
|
668
|
+
}),
|
|
669
|
+
..Default::default()
|
|
670
|
+
};
|
|
671
|
+
let response = self.send_request(request)?;
|
|
672
|
+
if response.code != StatusCode::Ok as i32 {
|
|
673
|
+
let code =
|
|
674
|
+
StatusCode::try_from(response.code).unwrap_or(StatusCode::UnknownRequest);
|
|
675
|
+
return Err(ClientError::Server {
|
|
676
|
+
code,
|
|
677
|
+
message: response.message,
|
|
678
|
+
});
|
|
679
|
+
}
|
|
680
|
+
response
|
|
681
|
+
.purge_exited_sessions
|
|
682
|
+
.ok_or_else(|| ClientError::Server {
|
|
683
|
+
code: StatusCode::Internal,
|
|
684
|
+
message: "purge_exited_sessions response missing payload".into(),
|
|
685
|
+
})
|
|
686
|
+
}
|
|
687
|
+
|
|
688
|
+
/// Schedule termination of every session older than the threshold
|
|
689
|
+
/// (#130 M9 H4). `older_than_secs=0` terminates everything in scope.
|
|
690
|
+
pub fn bulk_terminate_sessions(
|
|
691
|
+
&mut self,
|
|
692
|
+
older_than_secs: u64,
|
|
693
|
+
originator: &str,
|
|
694
|
+
grace_ms: u32,
|
|
695
|
+
) -> Result<BulkTerminateSessionsResponse, ClientError> {
|
|
696
|
+
let request = DaemonRequest {
|
|
697
|
+
id: self.next_request_id(),
|
|
698
|
+
r#type: RequestType::BulkTerminateSessions.into(),
|
|
699
|
+
protocol_version: 1,
|
|
700
|
+
client_name: String::from("running-process-client"),
|
|
701
|
+
bulk_terminate_sessions: Some(BulkTerminateSessionsRequest {
|
|
702
|
+
older_than_secs,
|
|
703
|
+
originator: originator.into(),
|
|
704
|
+
grace_ms,
|
|
705
|
+
}),
|
|
706
|
+
..Default::default()
|
|
707
|
+
};
|
|
708
|
+
let response = self.send_request(request)?;
|
|
709
|
+
if response.code != StatusCode::Ok as i32 {
|
|
710
|
+
let code =
|
|
711
|
+
StatusCode::try_from(response.code).unwrap_or(StatusCode::UnknownRequest);
|
|
712
|
+
return Err(ClientError::Server {
|
|
713
|
+
code,
|
|
714
|
+
message: response.message,
|
|
715
|
+
});
|
|
716
|
+
}
|
|
717
|
+
response
|
|
718
|
+
.bulk_terminate_sessions
|
|
719
|
+
.ok_or_else(|| ClientError::Server {
|
|
720
|
+
code: StatusCode::Internal,
|
|
721
|
+
message: "bulk_terminate_sessions response missing payload".into(),
|
|
722
|
+
})
|
|
723
|
+
}
|
|
724
|
+
|
|
725
|
+
/// Snapshot a PTY or pipe session's output backlog without consuming
|
|
726
|
+
/// it. For pipe sessions, `pipe_stream` selects between stdout and
|
|
727
|
+
/// stderr (default stdout). For PTY sessions `pipe_stream` is ignored.
|
|
728
|
+
/// Returns `None` when the session is not found.
|
|
729
|
+
pub fn get_session_backlog(
|
|
730
|
+
&mut self,
|
|
731
|
+
session_id: &str,
|
|
732
|
+
pipe_stream: PipeStreamKind,
|
|
733
|
+
) -> Result<Option<GetSessionBacklogResponse>, ClientError> {
|
|
734
|
+
let request = DaemonRequest {
|
|
735
|
+
id: self.next_request_id(),
|
|
736
|
+
r#type: RequestType::GetSessionBacklog.into(),
|
|
737
|
+
protocol_version: 1,
|
|
738
|
+
client_name: String::from("running-process-client"),
|
|
739
|
+
get_session_backlog: Some(GetSessionBacklogRequest {
|
|
740
|
+
session_id: session_id.into(),
|
|
741
|
+
pipe_stream: pipe_stream as i32,
|
|
742
|
+
}),
|
|
743
|
+
..Default::default()
|
|
744
|
+
};
|
|
745
|
+
let response = self.send_request(request)?;
|
|
746
|
+
if response.code == StatusCode::NotFound as i32 {
|
|
747
|
+
return Ok(None);
|
|
748
|
+
}
|
|
749
|
+
if response.code != StatusCode::Ok as i32 {
|
|
750
|
+
let code =
|
|
751
|
+
StatusCode::try_from(response.code).unwrap_or(StatusCode::UnknownRequest);
|
|
752
|
+
return Err(ClientError::Server {
|
|
753
|
+
code,
|
|
754
|
+
message: response.message,
|
|
755
|
+
});
|
|
756
|
+
}
|
|
757
|
+
Ok(response.get_session_backlog)
|
|
758
|
+
}
|
|
618
759
|
}
|
|
619
760
|
|
|
620
761
|
// ---------------------------------------------------------------------------
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
pub mod client;
|
|
2
|
+
pub mod paths;
|
|
3
|
+
pub mod pipe_session;
|
|
4
|
+
pub mod pty_session;
|
|
5
|
+
|
|
6
|
+
pub use client::{
|
|
7
|
+
connect_or_start, daemonize_command, launch_detached, ClientError, DaemonClient,
|
|
8
|
+
SpawnCommandRequest, SpawnedDaemon,
|
|
9
|
+
};
|
|
10
|
+
pub use pipe_session::{PipeSpawnRequest, PipeStreamAttachment, SpawnedPipeSession};
|
|
11
|
+
pub use pty_session::{AttachError, PtyAttachment, PtySpawnRequest, SpawnedPtySession};
|
|
@@ -0,0 +1,359 @@
|
|
|
1
|
+
//! Client-side helpers for daemon-owned pipe-backed sessions
|
|
2
|
+
//! (issue #130 milestone 3).
|
|
3
|
+
//!
|
|
4
|
+
//! Mirrors [`crate::pty_session`] for the pipe case. Sessions are spawned,
|
|
5
|
+
//! listed, detached, and terminated via the regular [`DaemonClient`] RPC
|
|
6
|
+
//! channel. Stdin is also an RPC (`write_pipe_stdin`). Stdout/stderr are
|
|
7
|
+
//! attached via [`PipeStreamAttachment`], which owns its own connection
|
|
8
|
+
//! and pumps `PipeStreamFrame` payloads.
|
|
9
|
+
|
|
10
|
+
use crate::client::{ClientError, DaemonClient};
|
|
11
|
+
use crate::paths;
|
|
12
|
+
use interprocess::local_socket::Stream;
|
|
13
|
+
use interprocess::TryClone;
|
|
14
|
+
use prost::Message;
|
|
15
|
+
use running_process_proto::daemon::{
|
|
16
|
+
AttachPipeStreamRequest, AttachPipeStreamResponse, DaemonRequest, DaemonResponse,
|
|
17
|
+
DetachPipeStreamRequest, KeyValue, ListPipeSessionsRequest, ListPipeSessionsResponse,
|
|
18
|
+
PipeSessionInfo, PipeStreamFrame, PipeStreamKind, RequestType, SpawnPipeSessionRequest,
|
|
19
|
+
SpawnPipeSessionResponse, StatusCode, TerminatePipeSessionRequest, WritePipeStdinRequest,
|
|
20
|
+
WritePipeStdinResponse,
|
|
21
|
+
};
|
|
22
|
+
use std::io::{BufReader, BufWriter, Read, Write};
|
|
23
|
+
use std::path::PathBuf;
|
|
24
|
+
|
|
25
|
+
// ---------------------------------------------------------------------------
|
|
26
|
+
// Spawn / list / terminate / write helpers
|
|
27
|
+
// ---------------------------------------------------------------------------
|
|
28
|
+
|
|
29
|
+
#[derive(Debug, Clone)]
|
|
30
|
+
pub struct PipeSpawnRequest {
|
|
31
|
+
pub argv: Vec<String>,
|
|
32
|
+
pub cwd: Option<PathBuf>,
|
|
33
|
+
pub env: Vec<(String, String)>,
|
|
34
|
+
pub clear_inherited_env: bool,
|
|
35
|
+
pub originator: Option<String>,
|
|
36
|
+
pub merge_stderr_into_stdout: bool,
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
impl PipeSpawnRequest {
|
|
40
|
+
pub fn new<S: Into<String>>(argv: impl IntoIterator<Item = S>) -> Self {
|
|
41
|
+
Self {
|
|
42
|
+
argv: argv.into_iter().map(Into::into).collect(),
|
|
43
|
+
cwd: None,
|
|
44
|
+
env: Vec::new(),
|
|
45
|
+
clear_inherited_env: false,
|
|
46
|
+
originator: None,
|
|
47
|
+
merge_stderr_into_stdout: false,
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
pub fn with_cwd(mut self, cwd: impl Into<PathBuf>) -> Self {
|
|
52
|
+
self.cwd = Some(cwd.into());
|
|
53
|
+
self
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
pub fn with_originator(mut self, originator: impl Into<String>) -> Self {
|
|
57
|
+
self.originator = Some(originator.into());
|
|
58
|
+
self
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
pub fn merge_stderr(mut self) -> Self {
|
|
62
|
+
self.merge_stderr_into_stdout = true;
|
|
63
|
+
self
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
pub fn with_envs<I, K, V>(mut self, env: I) -> Self
|
|
67
|
+
where
|
|
68
|
+
I: IntoIterator<Item = (K, V)>,
|
|
69
|
+
K: Into<String>,
|
|
70
|
+
V: Into<String>,
|
|
71
|
+
{
|
|
72
|
+
self.env = env
|
|
73
|
+
.into_iter()
|
|
74
|
+
.map(|(k, v)| (k.into(), v.into()))
|
|
75
|
+
.collect();
|
|
76
|
+
self
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
#[derive(Debug, Clone)]
|
|
81
|
+
pub struct SpawnedPipeSession {
|
|
82
|
+
pub session_id: String,
|
|
83
|
+
pub pid: u32,
|
|
84
|
+
pub created_at: f64,
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
impl DaemonClient {
|
|
88
|
+
pub fn spawn_pipe_session(
|
|
89
|
+
&mut self,
|
|
90
|
+
request: &PipeSpawnRequest,
|
|
91
|
+
) -> Result<SpawnedPipeSession, ClientError> {
|
|
92
|
+
let proto = SpawnPipeSessionRequest {
|
|
93
|
+
argv: request.argv.clone(),
|
|
94
|
+
cwd: request
|
|
95
|
+
.cwd
|
|
96
|
+
.as_ref()
|
|
97
|
+
.map(|p| p.to_string_lossy().into_owned())
|
|
98
|
+
.unwrap_or_default(),
|
|
99
|
+
env: request
|
|
100
|
+
.env
|
|
101
|
+
.iter()
|
|
102
|
+
.map(|(k, v)| KeyValue {
|
|
103
|
+
key: k.clone(),
|
|
104
|
+
value: v.clone(),
|
|
105
|
+
})
|
|
106
|
+
.collect(),
|
|
107
|
+
clear_inherited_env: request.clear_inherited_env,
|
|
108
|
+
originator: request.originator.clone().unwrap_or_default(),
|
|
109
|
+
merge_stderr_into_stdout: request.merge_stderr_into_stdout,
|
|
110
|
+
};
|
|
111
|
+
let daemon_request = DaemonRequest {
|
|
112
|
+
id: self.next_request_id(),
|
|
113
|
+
r#type: RequestType::SpawnPipeSession.into(),
|
|
114
|
+
protocol_version: 1,
|
|
115
|
+
client_name: "running-process-client".into(),
|
|
116
|
+
spawn_pipe_session: Some(proto),
|
|
117
|
+
..Default::default()
|
|
118
|
+
};
|
|
119
|
+
let response = self.send_request(daemon_request)?;
|
|
120
|
+
ensure_ok(&response)?;
|
|
121
|
+
let payload: SpawnPipeSessionResponse = response
|
|
122
|
+
.spawn_pipe_session
|
|
123
|
+
.ok_or_else(|| ClientError::Server {
|
|
124
|
+
code: StatusCode::Internal,
|
|
125
|
+
message: "spawn_pipe_session response missing payload".into(),
|
|
126
|
+
})?;
|
|
127
|
+
Ok(SpawnedPipeSession {
|
|
128
|
+
session_id: payload.session_id,
|
|
129
|
+
pid: payload.pid,
|
|
130
|
+
created_at: payload.created_at,
|
|
131
|
+
})
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
pub fn list_pipe_sessions(
|
|
135
|
+
&mut self,
|
|
136
|
+
originator_filter: &str,
|
|
137
|
+
) -> Result<Vec<PipeSessionInfo>, ClientError> {
|
|
138
|
+
let req = DaemonRequest {
|
|
139
|
+
id: self.next_request_id(),
|
|
140
|
+
r#type: RequestType::ListPipeSessions.into(),
|
|
141
|
+
protocol_version: 1,
|
|
142
|
+
client_name: "running-process-client".into(),
|
|
143
|
+
list_pipe_sessions: Some(ListPipeSessionsRequest {
|
|
144
|
+
originator: originator_filter.into(),
|
|
145
|
+
}),
|
|
146
|
+
..Default::default()
|
|
147
|
+
};
|
|
148
|
+
let response = self.send_request(req)?;
|
|
149
|
+
ensure_ok(&response)?;
|
|
150
|
+
let payload: ListPipeSessionsResponse = response
|
|
151
|
+
.list_pipe_sessions
|
|
152
|
+
.ok_or_else(|| ClientError::Server {
|
|
153
|
+
code: StatusCode::Internal,
|
|
154
|
+
message: "list_pipe_sessions response missing payload".into(),
|
|
155
|
+
})?;
|
|
156
|
+
Ok(payload.sessions)
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
pub fn detach_pipe_stream(
|
|
160
|
+
&mut self,
|
|
161
|
+
session_id: &str,
|
|
162
|
+
stream: PipeStreamKind,
|
|
163
|
+
) -> Result<(), ClientError> {
|
|
164
|
+
let req = DaemonRequest {
|
|
165
|
+
id: self.next_request_id(),
|
|
166
|
+
r#type: RequestType::DetachPipeStream.into(),
|
|
167
|
+
protocol_version: 1,
|
|
168
|
+
client_name: "running-process-client".into(),
|
|
169
|
+
detach_pipe_stream: Some(DetachPipeStreamRequest {
|
|
170
|
+
session_id: session_id.into(),
|
|
171
|
+
stream: stream as i32,
|
|
172
|
+
}),
|
|
173
|
+
..Default::default()
|
|
174
|
+
};
|
|
175
|
+
let response = self.send_request(req)?;
|
|
176
|
+
ensure_ok(&response)?;
|
|
177
|
+
Ok(())
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
pub fn terminate_pipe_session(
|
|
181
|
+
&mut self,
|
|
182
|
+
session_id: &str,
|
|
183
|
+
grace_ms: u32,
|
|
184
|
+
) -> Result<(), ClientError> {
|
|
185
|
+
let req = DaemonRequest {
|
|
186
|
+
id: self.next_request_id(),
|
|
187
|
+
r#type: RequestType::TerminatePipeSession.into(),
|
|
188
|
+
protocol_version: 1,
|
|
189
|
+
client_name: "running-process-client".into(),
|
|
190
|
+
terminate_pipe_session: Some(TerminatePipeSessionRequest {
|
|
191
|
+
session_id: session_id.into(),
|
|
192
|
+
grace_ms,
|
|
193
|
+
}),
|
|
194
|
+
..Default::default()
|
|
195
|
+
};
|
|
196
|
+
let response = self.send_request(req)?;
|
|
197
|
+
ensure_ok(&response)?;
|
|
198
|
+
Ok(())
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
pub fn write_pipe_stdin(
|
|
202
|
+
&mut self,
|
|
203
|
+
session_id: &str,
|
|
204
|
+
data: &[u8],
|
|
205
|
+
close_after: bool,
|
|
206
|
+
) -> Result<u64, ClientError> {
|
|
207
|
+
let req = DaemonRequest {
|
|
208
|
+
id: self.next_request_id(),
|
|
209
|
+
r#type: RequestType::WritePipeStdin.into(),
|
|
210
|
+
protocol_version: 1,
|
|
211
|
+
client_name: "running-process-client".into(),
|
|
212
|
+
write_pipe_stdin: Some(WritePipeStdinRequest {
|
|
213
|
+
session_id: session_id.into(),
|
|
214
|
+
data: data.to_vec(),
|
|
215
|
+
close: close_after,
|
|
216
|
+
}),
|
|
217
|
+
..Default::default()
|
|
218
|
+
};
|
|
219
|
+
let response = self.send_request(req)?;
|
|
220
|
+
ensure_ok(&response)?;
|
|
221
|
+
let payload: WritePipeStdinResponse = response
|
|
222
|
+
.write_pipe_stdin
|
|
223
|
+
.ok_or_else(|| ClientError::Server {
|
|
224
|
+
code: StatusCode::Internal,
|
|
225
|
+
message: "write_pipe_stdin response missing payload".into(),
|
|
226
|
+
})?;
|
|
227
|
+
Ok(payload.bytes_written)
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
fn ensure_ok(response: &DaemonResponse) -> Result<(), ClientError> {
|
|
232
|
+
if response.code == StatusCode::Ok as i32 {
|
|
233
|
+
return Ok(());
|
|
234
|
+
}
|
|
235
|
+
let code = StatusCode::try_from(response.code).unwrap_or(StatusCode::UnknownRequest);
|
|
236
|
+
Err(ClientError::Server {
|
|
237
|
+
code,
|
|
238
|
+
message: response.message.clone(),
|
|
239
|
+
})
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
// ---------------------------------------------------------------------------
|
|
243
|
+
// PipeStreamAttachment
|
|
244
|
+
// ---------------------------------------------------------------------------
|
|
245
|
+
|
|
246
|
+
pub struct PipeStreamAttachment {
|
|
247
|
+
reader: BufReader<Stream>,
|
|
248
|
+
pub initial_backlog: Vec<u8>,
|
|
249
|
+
pub bytes_missed: u64,
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
#[derive(Debug)]
|
|
253
|
+
pub enum PipeAttachError {
|
|
254
|
+
Connect(std::io::Error),
|
|
255
|
+
Io(std::io::Error),
|
|
256
|
+
Decode(prost::DecodeError),
|
|
257
|
+
Server { code: StatusCode, message: String },
|
|
258
|
+
MissingPayload,
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
impl std::fmt::Display for PipeAttachError {
|
|
262
|
+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
263
|
+
match self {
|
|
264
|
+
Self::Connect(e) => write!(f, "pipe attach connect failed: {e}"),
|
|
265
|
+
Self::Io(e) => write!(f, "pipe attach io error: {e}"),
|
|
266
|
+
Self::Decode(e) => write!(f, "pipe attach decode error: {e}"),
|
|
267
|
+
Self::Server { code, message } => {
|
|
268
|
+
write!(f, "pipe attach server error {code:?}: {message}")
|
|
269
|
+
}
|
|
270
|
+
Self::MissingPayload => write!(f, "pipe attach response missing payload"),
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
impl std::error::Error for PipeAttachError {}
|
|
276
|
+
|
|
277
|
+
impl PipeStreamAttachment {
|
|
278
|
+
pub fn attach(
|
|
279
|
+
scope_hash: Option<&str>,
|
|
280
|
+
session_id: &str,
|
|
281
|
+
stream: PipeStreamKind,
|
|
282
|
+
steal: bool,
|
|
283
|
+
) -> Result<Self, PipeAttachError> {
|
|
284
|
+
let socket_path = paths::socket_path(scope_hash);
|
|
285
|
+
Self::attach_to(&socket_path, session_id, stream, steal)
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
pub fn attach_to(
|
|
289
|
+
socket_path: &str,
|
|
290
|
+
session_id: &str,
|
|
291
|
+
stream: PipeStreamKind,
|
|
292
|
+
steal: bool,
|
|
293
|
+
) -> Result<Self, PipeAttachError> {
|
|
294
|
+
let name = paths::make_socket_name(socket_path).map_err(PipeAttachError::Connect)?;
|
|
295
|
+
use interprocess::local_socket::traits::Stream as _;
|
|
296
|
+
let s = Stream::connect(name).map_err(PipeAttachError::Connect)?;
|
|
297
|
+
let s_clone = s.try_clone().map_err(PipeAttachError::Connect)?;
|
|
298
|
+
let mut reader = BufReader::new(s);
|
|
299
|
+
let mut writer = BufWriter::new(s_clone);
|
|
300
|
+
|
|
301
|
+
let attach_request = DaemonRequest {
|
|
302
|
+
id: 1,
|
|
303
|
+
r#type: RequestType::AttachPipeStream.into(),
|
|
304
|
+
protocol_version: 1,
|
|
305
|
+
client_name: "running-process-client".into(),
|
|
306
|
+
attach_pipe_stream: Some(AttachPipeStreamRequest {
|
|
307
|
+
session_id: session_id.into(),
|
|
308
|
+
stream: stream as i32,
|
|
309
|
+
steal,
|
|
310
|
+
}),
|
|
311
|
+
..Default::default()
|
|
312
|
+
};
|
|
313
|
+
write_length_prefixed(&mut writer, &attach_request.encode_to_vec())
|
|
314
|
+
.map_err(PipeAttachError::Io)?;
|
|
315
|
+
// We do not need writer after this, but keep it alive via reader's
|
|
316
|
+
// duplex socket. Drop here.
|
|
317
|
+
drop(writer);
|
|
318
|
+
|
|
319
|
+
let response_bytes = read_length_prefixed(&mut reader).map_err(PipeAttachError::Io)?;
|
|
320
|
+
let response = DaemonResponse::decode(&response_bytes[..]).map_err(PipeAttachError::Decode)?;
|
|
321
|
+
if response.code != StatusCode::Ok as i32 {
|
|
322
|
+
let code = StatusCode::try_from(response.code).unwrap_or(StatusCode::UnknownRequest);
|
|
323
|
+
return Err(PipeAttachError::Server {
|
|
324
|
+
code,
|
|
325
|
+
message: response.message,
|
|
326
|
+
});
|
|
327
|
+
}
|
|
328
|
+
let payload: AttachPipeStreamResponse = response
|
|
329
|
+
.attach_pipe_stream
|
|
330
|
+
.ok_or(PipeAttachError::MissingPayload)?;
|
|
331
|
+
|
|
332
|
+
Ok(Self {
|
|
333
|
+
reader,
|
|
334
|
+
initial_backlog: payload.backlog,
|
|
335
|
+
bytes_missed: payload.bytes_missed,
|
|
336
|
+
})
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
pub fn recv_frame(&mut self) -> Result<PipeStreamFrame, PipeAttachError> {
|
|
340
|
+
let bytes = read_length_prefixed(&mut self.reader).map_err(PipeAttachError::Io)?;
|
|
341
|
+
PipeStreamFrame::decode(&bytes[..]).map_err(PipeAttachError::Decode)
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
fn write_length_prefixed<W: Write>(w: &mut W, payload: &[u8]) -> Result<(), std::io::Error> {
|
|
346
|
+
let len = payload.len() as u32;
|
|
347
|
+
w.write_all(&len.to_be_bytes())?;
|
|
348
|
+
w.write_all(payload)?;
|
|
349
|
+
w.flush()
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
fn read_length_prefixed<R: Read>(r: &mut R) -> Result<Vec<u8>, std::io::Error> {
|
|
353
|
+
let mut len_buf = [0u8; 4];
|
|
354
|
+
r.read_exact(&mut len_buf)?;
|
|
355
|
+
let len = u32::from_be_bytes(len_buf) as usize;
|
|
356
|
+
let mut buf = vec![0u8; len];
|
|
357
|
+
r.read_exact(&mut buf)?;
|
|
358
|
+
Ok(buf)
|
|
359
|
+
}
|