|
|
|
@ -1,5 +1,5 @@
|
|
|
|
|
use crate::{
|
|
|
|
|
data::{Request, Response, ResponsePayload},
|
|
|
|
|
data::{DirEntry, FileType, Request, RequestPayload, Response, ResponsePayload},
|
|
|
|
|
net::Transport,
|
|
|
|
|
opt::{CommonOpt, ConvertToIpAddrError, ListenSubcommand},
|
|
|
|
|
};
|
|
|
|
@ -8,9 +8,11 @@ use fork::{daemon, Fork};
|
|
|
|
|
use log::*;
|
|
|
|
|
use orion::aead::SecretKey;
|
|
|
|
|
use std::{string::FromUtf8Error, sync::Arc};
|
|
|
|
|
use tokio::{io, net::TcpListener};
|
|
|
|
|
|
|
|
|
|
pub type Result = std::result::Result<(), Error>;
|
|
|
|
|
use tokio::{
|
|
|
|
|
io::{self, AsyncWriteExt},
|
|
|
|
|
net::TcpListener,
|
|
|
|
|
};
|
|
|
|
|
use walkdir::WalkDir;
|
|
|
|
|
|
|
|
|
|
#[derive(Debug, Display, Error, From)]
|
|
|
|
|
pub enum Error {
|
|
|
|
@ -20,7 +22,7 @@ pub enum Error {
|
|
|
|
|
Utf8Error(FromUtf8Error),
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn run(cmd: ListenSubcommand, opt: CommonOpt) -> Result {
|
|
|
|
|
pub fn run(cmd: ListenSubcommand, opt: CommonOpt) -> Result<(), Error> {
|
|
|
|
|
if cmd.daemon {
|
|
|
|
|
// NOTE: We keep the stdin, stdout, stderr open so we can print out the pid with the parent
|
|
|
|
|
match daemon(false, true) {
|
|
|
|
@ -44,7 +46,7 @@ pub fn run(cmd: ListenSubcommand, opt: CommonOpt) -> Result {
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async fn run_async(cmd: ListenSubcommand, _opt: CommonOpt, is_forked: bool) -> Result {
|
|
|
|
|
async fn run_async(cmd: ListenSubcommand, _opt: CommonOpt, is_forked: bool) -> Result<(), Error> {
|
|
|
|
|
let addr = cmd.host.to_ip_addr(cmd.use_ipv6)?;
|
|
|
|
|
let socket_addrs = cmd.port.make_socket_addrs(addr);
|
|
|
|
|
|
|
|
|
@ -98,9 +100,13 @@ async fn run_async(cmd: ListenSubcommand, _opt: CommonOpt, is_forked: bool) -> R
|
|
|
|
|
request.payload.as_ref()
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
// Process the request, converting any error into an error response
|
|
|
|
|
let response = Response::from_payload_with_origin(
|
|
|
|
|
ResponsePayload::Error {
|
|
|
|
|
description: String::from("Unimplemented"),
|
|
|
|
|
match process_request_payload(request.payload).await {
|
|
|
|
|
Ok(payload) => payload,
|
|
|
|
|
Err(x) => ResponsePayload::Error {
|
|
|
|
|
description: x.to_string(),
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
request.id,
|
|
|
|
|
);
|
|
|
|
@ -136,3 +142,149 @@ fn publish_data(port: u16, key: &SecretKey) {
|
|
|
|
|
hex::encode(key.unprotected_as_bytes())
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async fn process_request_payload(
|
|
|
|
|
payload: RequestPayload,
|
|
|
|
|
) -> Result<ResponsePayload, Box<dyn std::error::Error>> {
|
|
|
|
|
match payload {
|
|
|
|
|
RequestPayload::FileRead { path } => Ok(ResponsePayload::Blob {
|
|
|
|
|
data: tokio::fs::read(path).await?,
|
|
|
|
|
}),
|
|
|
|
|
|
|
|
|
|
RequestPayload::FileReadText { path } => Ok(ResponsePayload::Text {
|
|
|
|
|
data: tokio::fs::read_to_string(path).await?,
|
|
|
|
|
}),
|
|
|
|
|
|
|
|
|
|
RequestPayload::FileWrite {
|
|
|
|
|
path,
|
|
|
|
|
input: _,
|
|
|
|
|
data,
|
|
|
|
|
} => {
|
|
|
|
|
tokio::fs::write(path, data).await?;
|
|
|
|
|
Ok(ResponsePayload::Ok)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
RequestPayload::FileAppend {
|
|
|
|
|
path,
|
|
|
|
|
input: _,
|
|
|
|
|
data,
|
|
|
|
|
} => {
|
|
|
|
|
let mut file = tokio::fs::OpenOptions::new()
|
|
|
|
|
.append(true)
|
|
|
|
|
.open(path)
|
|
|
|
|
.await?;
|
|
|
|
|
file.write_all(&data).await?;
|
|
|
|
|
Ok(ResponsePayload::Ok)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
RequestPayload::DirRead { path, all } => {
|
|
|
|
|
// Traverse, but don't include root directory in entries (hence min depth 1)
|
|
|
|
|
let dir = WalkDir::new(path.as_path()).min_depth(1);
|
|
|
|
|
|
|
|
|
|
// If all, will recursively traverse, otherwise just return directly from dir
|
|
|
|
|
let dir = if all { dir } else { dir.max_depth(1) };
|
|
|
|
|
|
|
|
|
|
// TODO: Support both returning errors and successfully-traversed entries
|
|
|
|
|
// TODO: Support returning full paths instead of always relative?
|
|
|
|
|
Ok(ResponsePayload::DirEntries {
|
|
|
|
|
entries: dir
|
|
|
|
|
.into_iter()
|
|
|
|
|
.map(|e| {
|
|
|
|
|
e.map(|e| DirEntry {
|
|
|
|
|
path: e.path().strip_prefix(path.as_path()).unwrap().to_path_buf(),
|
|
|
|
|
file_type: if e.file_type().is_dir() {
|
|
|
|
|
FileType::Dir
|
|
|
|
|
} else if e.file_type().is_file() {
|
|
|
|
|
FileType::File
|
|
|
|
|
} else {
|
|
|
|
|
FileType::SymLink
|
|
|
|
|
},
|
|
|
|
|
depth: e.depth(),
|
|
|
|
|
})
|
|
|
|
|
})
|
|
|
|
|
.collect::<Result<Vec<DirEntry>, walkdir::Error>>()?,
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
RequestPayload::DirCreate { path, all } => {
|
|
|
|
|
if all {
|
|
|
|
|
tokio::fs::create_dir_all(path).await?;
|
|
|
|
|
} else {
|
|
|
|
|
tokio::fs::create_dir(path).await?;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Ok(ResponsePayload::Ok)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
RequestPayload::Remove { path, force } => {
|
|
|
|
|
let path_metadata = tokio::fs::metadata(path.as_path()).await?;
|
|
|
|
|
if path_metadata.is_dir() {
|
|
|
|
|
if force {
|
|
|
|
|
tokio::fs::remove_dir_all(path).await?;
|
|
|
|
|
} else {
|
|
|
|
|
tokio::fs::remove_dir(path).await?;
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
tokio::fs::remove_file(path).await?;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Ok(ResponsePayload::Ok)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
RequestPayload::Copy { src, dst } => {
|
|
|
|
|
let src_metadata = tokio::fs::metadata(src.as_path()).await?;
|
|
|
|
|
if src_metadata.is_dir() {
|
|
|
|
|
for entry in WalkDir::new(src.as_path())
|
|
|
|
|
.min_depth(1)
|
|
|
|
|
.follow_links(false)
|
|
|
|
|
.into_iter()
|
|
|
|
|
.filter_entry(|e| e.file_type().is_file() || e.path_is_symlink())
|
|
|
|
|
{
|
|
|
|
|
let entry = entry?;
|
|
|
|
|
|
|
|
|
|
// Get unique portion of path relative to src
|
|
|
|
|
// NOTE: Because we are traversing files that are all within src, this
|
|
|
|
|
// should always succeed
|
|
|
|
|
let local_src = entry.path().strip_prefix(src.as_path()).unwrap();
|
|
|
|
|
|
|
|
|
|
// Get the file without any directories
|
|
|
|
|
let local_src_file_name = local_src.file_name().unwrap();
|
|
|
|
|
|
|
|
|
|
// Get the directory housing the file
|
|
|
|
|
// NOTE: Because we enforce files/symlinks, there will always be a parent
|
|
|
|
|
let local_src_dir = local_src.parent().unwrap();
|
|
|
|
|
|
|
|
|
|
// Map out the path to the destination
|
|
|
|
|
let dst_parent_dir = dst.join(local_src_dir);
|
|
|
|
|
|
|
|
|
|
// Create the destination directory for the file when copying
|
|
|
|
|
tokio::fs::create_dir_all(dst_parent_dir.as_path()).await?;
|
|
|
|
|
|
|
|
|
|
// Perform copying from entry to destination
|
|
|
|
|
let dst_file = dst_parent_dir.join(local_src_file_name);
|
|
|
|
|
tokio::fs::copy(entry.path(), dst_file).await?;
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
tokio::fs::copy(src, dst).await?;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Ok(ResponsePayload::Ok)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
RequestPayload::Rename { src, dst } => {
|
|
|
|
|
tokio::fs::rename(src, dst).await?;
|
|
|
|
|
|
|
|
|
|
Ok(ResponsePayload::Ok)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
RequestPayload::ProcRun { cmd, args, detach } => todo!(),
|
|
|
|
|
|
|
|
|
|
RequestPayload::ProcConnect { id } => todo!(),
|
|
|
|
|
|
|
|
|
|
RequestPayload::ProcKill { id } => todo!(),
|
|
|
|
|
|
|
|
|
|
RequestPayload::ProcStdin { id, data } => todo!(),
|
|
|
|
|
|
|
|
|
|
RequestPayload::ProcList {} => todo!(),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|