1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142
//! sopht is a program for managing long running processes in a (slightly) more
//! sophisticated way than something like [tmux](https://github.com/tmux/tmux).
//! this library currently implements the IPC protocol sopht uses to pass
//! commands and responses between clients and a server, as well as the
//! machinery required for starting and managing child processes
//!
pub mod ipc;
pub mod manager;
pub mod process;
pub use ipc::{create_client_connection, socket_file_path, Connection};
pub use manager::{
ChangeRestartPolicyArgs, ExitStatus, ProcessStatus, RestartPolicy,
SendArgs, StartArgs, State,
};
pub use process::{Process, PID};
use serde::{Deserialize, Serialize};
use std::io;
/// specialized [`Result`](std::result::Result) type for return values from
/// sopht functions
pub type Result<T> = std::result::Result<T, Error>;
/// union of all errors resulting from sopht functions. this type implements
/// `Display` and `Error`, and is the error type of [`sopht::Result`](Result).
#[derive(Debug)]
pub enum Error {
/// something went wrong with a socket. wraps [`io::Error`]
SocketError(io::Error),
/// failure to serialize data. wraps [`serde_json::Error`]
SerializeError(serde_json::Error),
/// failure to deserialize data. wraps [`serde_json::Error`]
DeserializeError(serde_json::Error),
/// the current user has no home directory, which is needed for the sopht
/// socket
NoHomeDir,
/// the connection betwixt server and client was closed
ConnectionClosed,
/// the wrong kind of message was received
WrongMessageKind,
/// the supplied command string for starting a process is empty
EmptyProgram,
/// encountered an error while interacting with a child process
ProcessError(io::Error),
/// the PID supplied is invalid
InvalidPID,
/// the process did not stay alive for long enough to be considered started
StayAliveError,
/// error creating sopht directories
DirectoryCreationError(io::Error),
}
/// represents a command to be executed by a server. this should never be
/// received by a client.
#[derive(PartialEq, Serialize, Deserialize, Debug)]
pub enum Command {
/// greets the client. kinda like a ping command, but friendlier
Hello,
/// shuts down the server gracefully
Shutdown,
/// starts a process given a command string
Start(StartArgs),
/// requests the output of a process given its PID
Output(PID),
/// requests the status of all processes
Status(Option<usize>),
/// stops a process given its PID
Stop(PID),
/// stops, then starts a process again given its PID
Restart(PID),
/// send output to a process given its PID and a string
Send(SendArgs),
/// change the restart policy of a process given its PID and the new policy
ChangeRestartPolicy(ChangeRestartPolicyArgs),
}
/// represents a response from the server to the client. this should never
/// be received by a server. additionally, this structure is subject to change
/// in the future as responses get more detailed
#[derive(Serialize, Deserialize, Debug)]
pub struct Response {
/// whether or not the command succeeded
pub success: bool,
/// the message the command returned
pub message: String,
/// the server may optionally decide to send a list of process statuses to
/// display to the user. this is not always the entire status!
pub status: Option<Vec<ProcessStatus>>,
}
/// union of all different message types. currently the only two message types
/// are `Command` and `Response`. the `Command` variant should only ever be
/// sent by clients and received by servers, and the `Response` variant should
/// only ever be received by clients and sent by servers
#[derive(Serialize, Deserialize)]
pub enum Message {
Command(Command),
Response(Response),
}
/// attempts to create the local directories required for sopht
pub fn create_local_dirs() -> Result<()> {
let fp = socket_file_path()?;
match std::fs::create_dir_all(fp.parent().unwrap()) {
Ok(_) => Ok(()),
Err(e) => Err(Error::DirectoryCreationError(e)),
}
}
impl std::fmt::Display for Error {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Error::SocketError(e) => write!(f, "{}", e),
Error::SerializeError(e) => write!(f, "{}", e),
Error::DeserializeError(e) => {
write!(f, "{}", e)
}
Error::NoHomeDir => write!(f, "no home directory found for user"),
Error::ConnectionClosed => write!(f, "connection closed"),
Error::WrongMessageKind => write!(f, "got wrong kind of message"),
Error::EmptyProgram => write!(f, "empty program string"),
Error::ProcessError(e) => write!(f, "{}", e),
Error::InvalidPID => write!(f, "invalid PID"),
Error::StayAliveError => {
write!(f, "process did not stay alive long enough")
}
Error::DirectoryCreationError(e) => write!(f, "{}", e),
}
}
}
impl std::error::Error for Error {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
Error::SocketError(e) => Some(e),
Error::SerializeError(e) => Some(e),
Error::DeserializeError(e) => Some(e),
Error::ProcessError(e) => Some(e),
Error::DirectoryCreationError(e) => Some(e),
_ => None,
}
}
}