Compare commits

..

No commits in common. 'master' and 'v0.20.0-alpha.13' have entirely different histories.

@ -7,40 +7,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased]
### Fixed
- Bug in `distant fs set-permissions` where partial permissions such as `go-w`
would result in clearing all permissions
- Bug in `distant-local` implementation of `SetPermissions` where read-only
status was being set/cleared prior to Unix permissions being applied,
resulting in applying an invalid change to the permissions
## [0.20.0]
All changes described in these alpha releases:
- [Alpha 13](https://github.com/chipsenkbeil/distant/releases/tag/v0.20.0-alpha.13)
- [Alpha 12](https://github.com/chipsenkbeil/distant/releases/tag/v0.20.0-alpha.12)
- [Alpha 11](https://github.com/chipsenkbeil/distant/releases/tag/v0.20.0-alpha.11)
- [Alpha 10](https://github.com/chipsenkbeil/distant/releases/tag/v0.20.0-alpha.10)
- [Alpha 9](https://github.com/chipsenkbeil/distant/releases/tag/v0.20.0-alpha.9)
- [Alpha 8](https://github.com/chipsenkbeil/distant/releases/tag/v0.20.0-alpha.8)
- [Alpha 7](https://github.com/chipsenkbeil/distant/releases/tag/v0.20.0-alpha.7)
- [Alpha 6](https://github.com/chipsenkbeil/distant/releases/tag/v0.20.0-alpha.6)
- [Alpha 5](https://github.com/chipsenkbeil/distant/releases/tag/v0.20.0-alpha.5)
- [Alpha 4](https://github.com/chipsenkbeil/distant/releases/tag/v0.20.0-alpha.4)
- [Alpha 3](https://github.com/chipsenkbeil/distant/releases/tag/v0.20.0-alpha.3)
- [Alpha 2](https://github.com/chipsenkbeil/distant/releases/tag/v0.20.0-alpha.2)
- [Alpha 1](https://github.com/chipsenkbeil/distant/releases/tag/v0.20.0-alpha.1)
### Fixed
- When terminating a connection using `distant manager kill`, the connection is
now properly dropped, resulting servers waiting to terminate due to
`--shutdown lonely=N` to now shutdown accordingly
- Zombies from spawned servers via `distant launch manager://localhost` are now
properly terminated by checking the exit status of processes
## [0.20.0-alpha.13]
### Added

14
Cargo.lock generated

@ -813,7 +813,7 @@ dependencies = [
[[package]]
name = "distant"
version = "0.20.0"
version = "0.20.0-alpha.13"
dependencies = [
"anyhow",
"assert_cmd",
@ -859,7 +859,7 @@ dependencies = [
[[package]]
name = "distant-auth"
version = "0.20.0"
version = "0.20.0-alpha.13"
dependencies = [
"async-trait",
"derive_more",
@ -872,7 +872,7 @@ dependencies = [
[[package]]
name = "distant-core"
version = "0.20.0"
version = "0.20.0-alpha.13"
dependencies = [
"async-trait",
"bitflags 2.3.1",
@ -898,7 +898,7 @@ dependencies = [
[[package]]
name = "distant-local"
version = "0.20.0"
version = "0.20.0-alpha.13"
dependencies = [
"assert_fs",
"async-trait",
@ -926,7 +926,7 @@ dependencies = [
[[package]]
name = "distant-net"
version = "0.20.0"
version = "0.20.0-alpha.13"
dependencies = [
"async-trait",
"bytes",
@ -958,7 +958,7 @@ dependencies = [
[[package]]
name = "distant-protocol"
version = "0.20.0"
version = "0.20.0-alpha.13"
dependencies = [
"bitflags 2.3.1",
"const-str",
@ -975,7 +975,7 @@ dependencies = [
[[package]]
name = "distant-ssh2"
version = "0.20.0"
version = "0.20.0-alpha.13"
dependencies = [
"anyhow",
"assert_fs",

@ -3,7 +3,7 @@ name = "distant"
description = "Operate on a remote computer through file and process manipulation"
categories = ["command-line-utilities"]
keywords = ["cli"]
version = "0.20.0"
version = "0.20.0-alpha.13"
authors = ["Chip Senkbeil <chip@senkbeil.org>"]
edition = "2021"
homepage = "https://github.com/chipsenkbeil/distant"
@ -40,8 +40,8 @@ clap_complete = "4.3.0"
config = { version = "0.13.3", default-features = false, features = ["toml"] }
derive_more = { version = "0.99.17", default-features = false, features = ["display", "from", "error", "is_variant"] }
dialoguer = { version = "0.10.4", default-features = false }
distant-core = { version = "=0.20.0", path = "distant-core" }
distant-local = { version = "=0.20.0", path = "distant-local" }
distant-core = { version = "=0.20.0-alpha.13", path = "distant-core" }
distant-local = { version = "=0.20.0-alpha.13", path = "distant-local" }
directories = "5.0.1"
file-mode = "0.1.2"
flexi_logger = "0.25.5"
@ -65,7 +65,7 @@ winsplit = "0.1.0"
whoami = "1.4.0"
# Optional native SSH functionality
distant-ssh2 = { version = "=0.20.0", path = "distant-ssh2", default-features = false, features = ["serde"], optional = true }
distant-ssh2 = { version = "=0.20.0-alpha.13", path = "distant-ssh2", default-features = false, features = ["serde"], optional = true }
[target.'cfg(unix)'.dependencies]
fork = "0.1.21"

@ -27,7 +27,7 @@
curl -L https://sh.distant.dev | sh
# Can also use wget to the same result
wget -q -O- https://sh.distant.dev | sh
wget https://sh.distant.dev | sh
```
See https://distant.dev/getting-started/installation/unix/ for more details.
@ -45,7 +45,7 @@ See https://distant.dev/getting-started/installation/windows/ for more details.
```sh
# Start a manager in the background
distant manager listen --daemon
distant manager listen &
# SSH into a server, start distant, and connect to the distant server
distant launch ssh://example.com

@ -3,7 +3,7 @@ name = "distant-auth"
description = "Authentication library for distant, providing various implementations"
categories = ["authentication"]
keywords = ["auth", "authentication", "async"]
version = "0.20.0"
version = "0.20.0-alpha.13"
authors = ["Chip Senkbeil <chip@senkbeil.org>"]
edition = "2021"
homepage = "https://github.com/chipsenkbeil/distant"

@ -3,7 +3,7 @@ name = "distant-core"
description = "Core library for distant, enabling operation on a remote computer through file and process manipulation"
categories = ["network-programming"]
keywords = ["api", "async"]
version = "0.20.0"
version = "0.20.0-alpha.13"
authors = ["Chip Senkbeil <chip@senkbeil.org>"]
edition = "2021"
homepage = "https://github.com/chipsenkbeil/distant"
@ -16,8 +16,8 @@ async-trait = "0.1.68"
bitflags = "2.3.1"
bytes = "1.4.0"
derive_more = { version = "0.99.17", default-features = false, features = ["as_mut", "as_ref", "deref", "deref_mut", "display", "from", "error", "into", "into_iterator", "is_variant", "try_into"] }
distant-net = { version = "=0.20.0", path = "../distant-net" }
distant-protocol = { version = "=0.20.0", path = "../distant-protocol" }
distant-net = { version = "=0.20.0-alpha.13", path = "../distant-net" }
distant-protocol = { version = "=0.20.0-alpha.13", path = "../distant-protocol" }
futures = "0.3.28"
hex = "0.4.3"
log = "0.4.18"

@ -2,7 +2,7 @@
name = "distant-local"
description = "Library implementing distant API for local interactions"
categories = ["network-programming"]
version = "0.20.0"
version = "0.20.0-alpha.13"
authors = ["Chip Senkbeil <chip@senkbeil.org>"]
edition = "2021"
homepage = "https://github.com/chipsenkbeil/distant"
@ -21,7 +21,7 @@ macos-kqueue = ["notify/macos_kqueue"]
[dependencies]
async-trait = "0.1.68"
distant-core = { version = "=0.20.0", path = "../distant-core" }
distant-core = { version = "=0.20.0-alpha.13", path = "../distant-core" }
grep = "0.2.12"
ignore = "0.4.20"
log = "0.4.18"

@ -451,11 +451,9 @@ impl DistantApi for Api {
})?
.permissions();
// Apply the readonly flag for all platforms but junix
if !cfg!(unix) {
if let Some(readonly) = permissions.is_readonly() {
std_permissions.set_readonly(readonly);
}
// Apply the readonly flag for all platforms
if let Some(readonly) = permissions.is_readonly() {
std_permissions.set_readonly(readonly);
}
// On Unix platforms, we can apply a bitset change
@ -464,9 +462,7 @@ impl DistantApi for Api {
use std::os::unix::prelude::*;
let mut current = Permissions::from(std_permissions.clone());
current.apply_from(permissions);
let mode = current.to_unix_mode();
std_permissions.set_mode(mode);
std_permissions.set_mode(current.to_unix_mode());
}
Ok(std_permissions)

@ -3,7 +3,7 @@ name = "distant-net"
description = "Network library for distant, providing implementations to support client/server architecture"
categories = ["network-programming"]
keywords = ["api", "async"]
version = "0.20.0"
version = "0.20.0-alpha.13"
authors = ["Chip Senkbeil <chip@senkbeil.org>"]
edition = "2021"
homepage = "https://github.com/chipsenkbeil/distant"
@ -17,7 +17,7 @@ bytes = "1.4.0"
chacha20poly1305 = "0.10.1"
const-str = "0.5.6"
derive_more = { version = "0.99.17", default-features = false, features = ["as_mut", "as_ref", "deref", "deref_mut", "display", "from", "error", "into", "into_iterator", "is_variant", "try_into"] }
distant-auth = { version = "=0.20.0", path = "../distant-auth" }
distant-auth = { version = "=0.20.0-alpha.13", path = "../distant-auth" }
dyn-clone = "1.0.11"
flate2 = "1.0.26"
hex = "0.4.3"
@ -37,7 +37,7 @@ strum = { version = "0.24.1", features = ["derive"] }
tokio = { version = "1.28.2", features = ["full"] }
[dev-dependencies]
distant-auth = { version = "=0.20.0", path = "../distant-auth", features = ["tests"] }
distant-auth = { version = "=0.20.0-alpha.13", path = "../distant-auth", features = ["tests"] }
env_logger = "0.10.0"
serde_json = "1.0.96"
tempfile = "3.5.0"

@ -175,25 +175,7 @@ impl ManagerServer {
/// Kills the connection to the server with the specified `id`
async fn kill(&self, id: ConnectionId) -> io::Result<()> {
match self.connections.write().await.remove(&id) {
Some(connection) => {
// Close any open channels
if let Ok(ids) = connection.channel_ids().await {
let mut channels_lock = self.channels.write().await;
for id in ids {
if let Some(channel) = channels_lock.remove(&id) {
if let Err(x) = channel.close() {
error!("[Conn {id}] {x}");
}
}
}
}
// Make sure the connection is aborted so nothing new can happen
debug!("[Conn {id}] Aborting");
connection.abort();
Ok(())
}
Some(_) => Ok(()),
None => Err(io::Error::new(
io::ErrorKind::NotConnected,
"No connection found",

@ -2,7 +2,7 @@ use std::collections::HashMap;
use std::{fmt, io};
use log::*;
use tokio::sync::{mpsc, oneshot};
use tokio::sync::mpsc;
use tokio::task::JoinHandle;
use crate::client::{Mailbox, UntypedClient};
@ -62,17 +62,11 @@ impl ManagerConnection {
pub async fn spawn(
spawn: Destination,
options: Map,
mut client: UntypedClient,
client: UntypedClient,
) -> io::Result<Self> {
let connection_id = rand::random();
let (tx, rx) = mpsc::unbounded_channel();
// NOTE: Ensure that the connection is severed when the client is dropped; otherwise, when
// the connection is terminated via aborting it or the connection being dropped, the
// connection will persist which can cause problems such as lonely shutdown of the server
// never triggering!
client.shutdown_on_drop(true);
let (request_tx, request_rx) = mpsc::unbounded_channel();
let action_task = tokio::spawn(action_task(connection_id, rx, request_tx));
let response_task = tokio::spawn(response_task(
@ -111,38 +105,13 @@ impl ManagerConnection {
tx: self.tx.clone(),
})
}
pub async fn channel_ids(&self) -> io::Result<Vec<ManagerChannelId>> {
let (tx, rx) = oneshot::channel();
self.tx
.send(Action::GetRegistered { cb: tx })
.map_err(|x| {
io::Error::new(
io::ErrorKind::BrokenPipe,
format!("channel_ids failed: {x}"),
)
})?;
let channel_ids = rx.await.map_err(|x| {
io::Error::new(
io::ErrorKind::BrokenPipe,
format!("channel_ids callback dropped: {x}"),
)
})?;
Ok(channel_ids)
}
/// Aborts the tasks used to engage with the connection.
pub fn abort(&self) {
self.action_task.abort();
self.request_task.abort();
self.response_task.abort();
}
}
impl Drop for ManagerConnection {
fn drop(&mut self) {
self.abort();
self.action_task.abort();
self.request_task.abort();
self.response_task.abort();
}
}
@ -156,10 +125,6 @@ enum Action {
id: ManagerChannelId,
},
GetRegistered {
cb: oneshot::Sender<Vec<ManagerChannelId>>,
},
Read {
res: UntypedResponse<'static>,
},
@ -175,7 +140,6 @@ impl fmt::Debug for Action {
match self {
Self::Register { id, .. } => write!(f, "Action::Register {{ id: {id}, .. }}"),
Self::Unregister { id } => write!(f, "Action::Unregister {{ id: {id} }}"),
Self::GetRegistered { .. } => write!(f, "Action::GetRegistered {{ .. }}"),
Self::Read { .. } => write!(f, "Action::Read {{ .. }}"),
Self::Write { id, .. } => write!(f, "Action::Write {{ id: {id}, .. }}"),
}
@ -240,9 +204,6 @@ async fn action_task(
Action::Unregister { id } => {
registered.remove(&id);
}
Action::GetRegistered { cb } => {
let _ = cb.send(registered.keys().copied().collect());
}
Action::Read { mut res } => {
// Split {channel id}_{request id} back into pieces and
// update the origin id to match the request id only

@ -3,7 +3,7 @@ name = "distant-protocol"
description = "Protocol library for distant, providing data structures used between the client and server"
categories = ["data-structures"]
keywords = ["protocol"]
version = "0.20.0"
version = "0.20.0-alpha.13"
authors = ["Chip Senkbeil <chip@senkbeil.org>"]
edition = "2021"
homepage = "https://github.com/chipsenkbeil/distant"

@ -296,7 +296,7 @@ impl Permissions {
/// Converts a Unix `mode` into the permission set.
pub fn from_unix_mode(mode: u32) -> Self {
let flags = UnixFilePermissionFlags::from_bits_truncate(mode & 0o777);
let flags = UnixFilePermissionFlags::from_bits_truncate(mode);
Self {
owner_read: Some(flags.contains(UnixFilePermissionFlags::OWNER_READ)),
owner_write: Some(flags.contains(UnixFilePermissionFlags::OWNER_WRITE)),
@ -426,15 +426,15 @@ impl From<Permissions> for std::fs::Permissions {
bitflags! {
struct UnixFilePermissionFlags: u32 {
const OWNER_READ = 0o400;
const OWNER_WRITE = 0o200;
const OWNER_EXEC = 0o100;
const GROUP_READ = 0o040;
const GROUP_WRITE = 0o020;
const GROUP_EXEC = 0o010;
const OTHER_READ = 0o004;
const OTHER_WRITE = 0o002;
const OTHER_EXEC = 0o001;
const OWNER_READ = 0o400;
const OWNER_WRITE = 0o200;
const OWNER_EXEC = 0o100;
const GROUP_READ = 0o40;
const GROUP_WRITE = 0o20;
const GROUP_EXEC = 0o10;
const OTHER_READ = 0o4;
const OTHER_WRITE = 0o2;
const OTHER_EXEC = 0o1;
}
}
@ -442,364 +442,6 @@ bitflags! {
mod tests {
use super::*;
#[test]
fn should_properly_parse_unix_mode_into_permissions() {
let permissions = Permissions::from_unix_mode(0o400);
assert_eq!(
permissions,
Permissions {
owner_read: Some(true),
owner_write: Some(false),
owner_exec: Some(false),
group_read: Some(false),
group_write: Some(false),
group_exec: Some(false),
other_read: Some(false),
other_write: Some(false),
other_exec: Some(false),
}
);
let permissions = Permissions::from_unix_mode(0o200);
assert_eq!(
permissions,
Permissions {
owner_read: Some(false),
owner_write: Some(true),
owner_exec: Some(false),
group_read: Some(false),
group_write: Some(false),
group_exec: Some(false),
other_read: Some(false),
other_write: Some(false),
other_exec: Some(false),
}
);
let permissions = Permissions::from_unix_mode(0o100);
assert_eq!(
permissions,
Permissions {
owner_read: Some(false),
owner_write: Some(false),
owner_exec: Some(true),
group_read: Some(false),
group_write: Some(false),
group_exec: Some(false),
other_read: Some(false),
other_write: Some(false),
other_exec: Some(false),
}
);
let permissions = Permissions::from_unix_mode(0o040);
assert_eq!(
permissions,
Permissions {
owner_read: Some(false),
owner_write: Some(false),
owner_exec: Some(false),
group_read: Some(true),
group_write: Some(false),
group_exec: Some(false),
other_read: Some(false),
other_write: Some(false),
other_exec: Some(false),
}
);
let permissions = Permissions::from_unix_mode(0o020);
assert_eq!(
permissions,
Permissions {
owner_read: Some(false),
owner_write: Some(false),
owner_exec: Some(false),
group_read: Some(false),
group_write: Some(true),
group_exec: Some(false),
other_read: Some(false),
other_write: Some(false),
other_exec: Some(false),
}
);
let permissions = Permissions::from_unix_mode(0o010);
assert_eq!(
permissions,
Permissions {
owner_read: Some(false),
owner_write: Some(false),
owner_exec: Some(false),
group_read: Some(false),
group_write: Some(false),
group_exec: Some(true),
other_read: Some(false),
other_write: Some(false),
other_exec: Some(false),
}
);
let permissions = Permissions::from_unix_mode(0o004);
assert_eq!(
permissions,
Permissions {
owner_read: Some(false),
owner_write: Some(false),
owner_exec: Some(false),
group_read: Some(false),
group_write: Some(false),
group_exec: Some(false),
other_read: Some(true),
other_write: Some(false),
other_exec: Some(false),
}
);
let permissions = Permissions::from_unix_mode(0o002);
assert_eq!(
permissions,
Permissions {
owner_read: Some(false),
owner_write: Some(false),
owner_exec: Some(false),
group_read: Some(false),
group_write: Some(false),
group_exec: Some(false),
other_read: Some(false),
other_write: Some(true),
other_exec: Some(false),
}
);
let permissions = Permissions::from_unix_mode(0o001);
assert_eq!(
permissions,
Permissions {
owner_read: Some(false),
owner_write: Some(false),
owner_exec: Some(false),
group_read: Some(false),
group_write: Some(false),
group_exec: Some(false),
other_read: Some(false),
other_write: Some(false),
other_exec: Some(true),
}
);
let permissions = Permissions::from_unix_mode(0o000);
assert_eq!(
permissions,
Permissions {
owner_read: Some(false),
owner_write: Some(false),
owner_exec: Some(false),
group_read: Some(false),
group_write: Some(false),
group_exec: Some(false),
other_read: Some(false),
other_write: Some(false),
other_exec: Some(false),
}
);
let permissions = Permissions::from_unix_mode(0o777);
assert_eq!(
permissions,
Permissions {
owner_read: Some(true),
owner_write: Some(true),
owner_exec: Some(true),
group_read: Some(true),
group_write: Some(true),
group_exec: Some(true),
other_read: Some(true),
other_write: Some(true),
other_exec: Some(true),
}
);
}
#[test]
fn should_properly_convert_into_unix_mode() {
assert_eq!(
Permissions {
owner_read: Some(true),
owner_write: Some(false),
owner_exec: Some(false),
group_read: Some(false),
group_write: Some(false),
group_exec: Some(false),
other_read: Some(false),
other_write: Some(false),
other_exec: Some(false),
}
.to_unix_mode(),
0o400
);
assert_eq!(
Permissions {
owner_read: Some(false),
owner_write: Some(true),
owner_exec: Some(false),
group_read: Some(false),
group_write: Some(false),
group_exec: Some(false),
other_read: Some(false),
other_write: Some(false),
other_exec: Some(false),
}
.to_unix_mode(),
0o200
);
assert_eq!(
Permissions {
owner_read: Some(false),
owner_write: Some(false),
owner_exec: Some(true),
group_read: Some(false),
group_write: Some(false),
group_exec: Some(false),
other_read: Some(false),
other_write: Some(false),
other_exec: Some(false),
}
.to_unix_mode(),
0o100
);
assert_eq!(
Permissions {
owner_read: Some(false),
owner_write: Some(false),
owner_exec: Some(false),
group_read: Some(true),
group_write: Some(false),
group_exec: Some(false),
other_read: Some(false),
other_write: Some(false),
other_exec: Some(false),
}
.to_unix_mode(),
0o040
);
assert_eq!(
Permissions {
owner_read: Some(false),
owner_write: Some(false),
owner_exec: Some(false),
group_read: Some(false),
group_write: Some(true),
group_exec: Some(false),
other_read: Some(false),
other_write: Some(false),
other_exec: Some(false),
}
.to_unix_mode(),
0o020
);
assert_eq!(
Permissions {
owner_read: Some(false),
owner_write: Some(false),
owner_exec: Some(false),
group_read: Some(false),
group_write: Some(false),
group_exec: Some(true),
other_read: Some(false),
other_write: Some(false),
other_exec: Some(false),
}
.to_unix_mode(),
0o010
);
assert_eq!(
Permissions {
owner_read: Some(false),
owner_write: Some(false),
owner_exec: Some(false),
group_read: Some(false),
group_write: Some(false),
group_exec: Some(false),
other_read: Some(true),
other_write: Some(false),
other_exec: Some(false),
}
.to_unix_mode(),
0o004
);
assert_eq!(
Permissions {
owner_read: Some(false),
owner_write: Some(false),
owner_exec: Some(false),
group_read: Some(false),
group_write: Some(false),
group_exec: Some(false),
other_read: Some(false),
other_write: Some(true),
other_exec: Some(false),
}
.to_unix_mode(),
0o002
);
assert_eq!(
Permissions {
owner_read: Some(false),
owner_write: Some(false),
owner_exec: Some(false),
group_read: Some(false),
group_write: Some(false),
group_exec: Some(false),
other_read: Some(false),
other_write: Some(false),
other_exec: Some(true),
}
.to_unix_mode(),
0o001
);
assert_eq!(
Permissions {
owner_read: Some(false),
owner_write: Some(false),
owner_exec: Some(false),
group_read: Some(false),
group_write: Some(false),
group_exec: Some(false),
other_read: Some(false),
other_write: Some(false),
other_exec: Some(false),
}
.to_unix_mode(),
0o000
);
assert_eq!(
Permissions {
owner_read: Some(true),
owner_write: Some(true),
owner_exec: Some(true),
group_read: Some(true),
group_write: Some(true),
group_exec: Some(true),
other_read: Some(true),
other_write: Some(true),
other_exec: Some(true),
}
.to_unix_mode(),
0o777
);
}
#[test]
fn should_be_able_to_serialize_minimal_permissions_to_json() {
let permissions = Permissions {

@ -2,7 +2,7 @@
name = "distant-ssh2"
description = "Library to enable native ssh-2 protocol for use with distant sessions"
categories = ["network-programming"]
version = "0.20.0"
version = "0.20.0-alpha.13"
authors = ["Chip Senkbeil <chip@senkbeil.org>"]
edition = "2021"
homepage = "https://github.com/chipsenkbeil/distant"
@ -20,7 +20,7 @@ async-compat = "0.2.1"
async-once-cell = "0.5.2"
async-trait = "0.1.68"
derive_more = { version = "0.99.17", default-features = false, features = ["display", "error"] }
distant-core = { version = "=0.20.0", path = "../distant-core" }
distant-core = { version = "=0.20.0-alpha.13", path = "../distant-core" }
futures = "0.3.28"
hex = "0.4.3"
log = "0.4.18"

@ -1187,25 +1187,6 @@ async fn async_run(cmd: ClientSubcommand) -> CliResult {
mode,
path,
}) => {
debug!("Connecting to manager");
let mut client = Client::new(network)
.using_prompt_auth_handler()
.connect()
.await
.context("Failed to connect to manager")?;
let mut cache = read_cache(&cache).await;
let connection_id =
use_or_lookup_connection_id(&mut cache, connection, &mut client).await?;
debug!("Opening channel to connection {}", connection_id);
let mut channel: DistantChannel = client
.open_raw_channel(connection_id)
.await
.with_context(|| format!("Failed to open channel to connection {connection_id}"))?
.into_client()
.into_channel();
debug!("Parsing {mode:?} into a proper set of permissions");
let permissions = {
if mode.trim().eq_ignore_ascii_case("readonly") {
@ -1215,61 +1196,37 @@ async fn async_run(cmd: ClientSubcommand) -> CliResult {
} else {
// Attempt to parse an octal number (chmod absolute), falling back to
// parsing the mode string similar to chmod's symbolic mode
match u32::from_str_radix(&mode, 8) {
Ok(absolute) => {
Permissions::from_unix_mode(file_mode::Mode::from(absolute).mode())
}
let mode = match u32::from_str_radix(&mode, 8) {
Ok(absolute) => file_mode::Mode::from(absolute),
Err(_) => {
// The way parsing works, we need to parse and apply to two different
// situations
//
// 1. A mode that is all 1s so we can see if the mask would remove
// permission to some of the bits
// 2. A mode that is all 0s so we can see if the mask would add
// permission to some of the bits
let mut removals = file_mode::Mode::from(0o777);
removals
.set_str(&mode)
.context("Failed to parse mode string")?;
let removals_mask = !removals.mode();
let mut additions = file_mode::Mode::empty();
additions
let mut new_mode = file_mode::Mode::empty();
new_mode
.set_str(&mode)
.context("Failed to parse mode string")?;
let additions_mask = additions.mode();
macro_rules! get_mode {
($mask:expr) => {{
let is_false = removals_mask & $mask > 0;
let is_true = additions_mask & $mask > 0;
match (is_true, is_false) {
(true, false) => Some(true),
(false, true) => Some(false),
(false, false) => None,
(true, true) => {
unreachable!("Mask cannot be adding and removing")
}
}
}};
}
Permissions {
owner_read: get_mode!(0o400),
owner_write: get_mode!(0o200),
owner_exec: get_mode!(0o100),
group_read: get_mode!(0o040),
group_write: get_mode!(0o020),
group_exec: get_mode!(0o010),
other_read: get_mode!(0o004),
other_write: get_mode!(0o002),
other_exec: get_mode!(0o001),
}
new_mode
}
}
};
Permissions::from_unix_mode(mode.mode())
}
};
debug!("Connecting to manager");
let mut client = Client::new(network)
.using_prompt_auth_handler()
.connect()
.await
.context("Failed to connect to manager")?;
let mut cache = read_cache(&cache).await;
let connection_id =
use_or_lookup_connection_id(&mut cache, connection, &mut client).await?;
debug!("Opening channel to connection {}", connection_id);
let channel = client
.open_raw_channel(connection_id)
.await
.with_context(|| format!("Failed to open channel to connection {connection_id}"))?;
let options = SetPermissionsOptions {
recursive,
follow_symlinks,
@ -1277,6 +1234,8 @@ async fn async_run(cmd: ClientSubcommand) -> CliResult {
};
debug!("Setting permissions for {path:?} as (permissions = {permissions:?}, options = {options:?})");
channel
.into_client()
.into_channel()
.set_permissions(path.as_path(), permissions, options)
.await
.with_context(|| {

@ -16,8 +16,8 @@ use distant_core::net::manager::{ConnectHandler, LaunchHandler};
use distant_core::protocol::PROTOCOL_VERSION;
use log::*;
use tokio::io::{AsyncBufReadExt, BufReader};
use tokio::process::Command;
use tokio::sync::{watch, Mutex};
use tokio::process::{Child, Command};
use tokio::sync::Mutex;
use crate::options::{BindAddress, ClientLaunchConfig};
@ -33,28 +33,15 @@ fn invalid(label: &str) -> io::Error {
/// Supports launching locally through the manager as defined by `manager://...`
pub struct ManagerLaunchHandler {
shutdown: watch::Sender<bool>,
servers: Mutex<Vec<Child>>,
}
impl ManagerLaunchHandler {
pub fn new() -> Self {
Self {
shutdown: watch::channel(false).0,
servers: Mutex::new(Vec::new()),
}
}
/// Triggers shutdown of any tasks still checking that spawned servers have terminated.
pub fn shutdown(&self) {
let _ = self.shutdown.send(true);
}
}
impl Drop for ManagerLaunchHandler {
/// Terminates waiting for any servers spawned by this handler, which in turn should
/// shut them down.
fn drop(&mut self) {
self.shutdown();
}
}
#[async_trait]
@ -151,34 +138,9 @@ impl LaunchHandler for ManagerLaunchHandler {
match stdout.read_line(&mut line).await {
Ok(n) if n > 0 => {
if let Ok(destination) = line[..n].trim().parse::<Destination>() {
let mut rx = self.shutdown.subscribe();
// Wait for the process to complete in a task. We have to do this
// to properly check the exit status, otherwise if the server
// self-terminates then we get a ZOMBIE process! Oh no!
//
// This also replaces the need to store the children within the
// handler itself and instead uses a watch update to kill the
// task in advance in the case where the child hasn't terminated.
tokio::spawn(async move {
// We don't actually care about the result, just that we're done
loop {
tokio::select! {
result = rx.changed() => {
if result.is_err() {
break;
}
if *rx.borrow_and_update() {
break;
}
}
_ = child.wait() => {
break;
}
}
}
});
// Store a reference to the server so we can terminate them
// when this handler is dropped
self.servers.lock().await.push(child);
break Ok(destination);
} else {

@ -31,7 +31,6 @@ impl Manager {
global_paths::UNIX_SOCKET_PATH.as_path()
}
});
debug!("Manager wants to use unix socket @ {:?}", socket_path);
// Ensure that the path to the socket exists
if let Some(parent) = socket_path.parent() {
@ -61,7 +60,6 @@ impl Manager {
} else {
global_paths::WINDOWS_PIPE_NAME.as_str()
});
debug!("Manager wants to use windows pipe @ {:?}", pipe_name);
let server = ManagerServer::new(self.config)
.verifier(Verifier::none())

Loading…
Cancel
Save