fix: macOS and Windows test/lint failures; repo.dixon.cx packaging for nexusterminal
CI hadn't run on macOS or Windows for a while (billing); once it did: - Windows: the RDP shared-drive code and its symlink test are Unix-only but were compiled there (and the unused pieces failed clippy). The drive type, its impls, imports and test are now cfg(unix); refusing a drive request is a free function used everywhere. Other Windows-only warnings fixed (serial test import, unused mut, harden_process). - macOS: the local shell's directory was sampled on the first output after Enter, which is the shell echoing the command before a cd runs, so restored tabs opened one directory behind. It's now sampled shortly after Enter and again once the command has had time to finish. - packaging/repo.sh publishes the 'nexusterminal' .deb/.rpm/Arch packages to repo.dixon.cx from the release's own Linux binaries, verified against SHA256SUMS. - The desktop entry mentions RDP.
This commit is contained in:
@@ -171,6 +171,9 @@ impl LocalShell {
|
||||
}
|
||||
}
|
||||
|
||||
/// macOS has no cheap cwd lookup (it's an `lsof` run), so the directory is sampled after Enter:
|
||||
/// the first output after Enter is usually the shell echoing the command, before a `cd` has
|
||||
/// run, so sample shortly afterwards and again once the command has had time to finish.
|
||||
fn capture_cwd_in_background(self: &Arc<Self>) {
|
||||
let Some(pid) = self.state.lock().0.as_ref().and_then(|r| r.pid) else { return };
|
||||
if self.capturing.swap(true, Ordering::SeqCst) {
|
||||
@@ -178,9 +181,12 @@ impl LocalShell {
|
||||
}
|
||||
let me = self.clone();
|
||||
std::thread::spawn(move || {
|
||||
// A failed lookup (the shell already exited) keeps the last known directory.
|
||||
if let Some(dir) = process_cwd(pid).filter(|d| d.is_dir()) {
|
||||
me.state.lock().1 = dir;
|
||||
for delay in [150, 850] {
|
||||
std::thread::sleep(std::time::Duration::from_millis(delay));
|
||||
// A failed lookup (the shell already exited) keeps the last known directory.
|
||||
if let Some(dir) = process_cwd(pid).filter(|d| d.is_dir()) {
|
||||
me.state.lock().1 = dir;
|
||||
}
|
||||
}
|
||||
me.capturing.store(false, Ordering::SeqCst);
|
||||
});
|
||||
@@ -223,6 +229,11 @@ mod tests {
|
||||
std::thread::sleep(Duration::from_millis(20));
|
||||
}
|
||||
sh.resize(100, 30).unwrap();
|
||||
// macOS samples the directory shortly after a command runs (Linux reads it live).
|
||||
while sh.working_directory() != std::path::Path::new("/") {
|
||||
assert!(t.elapsed() < Duration::from_secs(5), "cwd still {:?}", sh.working_directory());
|
||||
std::thread::sleep(Duration::from_millis(20));
|
||||
}
|
||||
sh.write(b"exit\n").unwrap();
|
||||
while *closed.lock() == 0 {
|
||||
assert!(t.elapsed() < Duration::from_secs(5));
|
||||
|
||||
@@ -205,6 +205,7 @@ impl Client {
|
||||
}
|
||||
if self.cfg.shared_folder.is_some() || self.cfg.printer.is_some() {
|
||||
let mut devices = super::rdp_devices::Devices::default();
|
||||
#[cfg_attr(not(unix), allow(unused_mut))]
|
||||
let mut drives = vec![];
|
||||
#[cfg(unix)]
|
||||
if let Some((folder, name)) = &self.cfg.shared_folder {
|
||||
|
||||
@@ -14,9 +14,11 @@ use ironrdp::core::impl_as_any;
|
||||
use ironrdp::pdu::PduResult;
|
||||
use ironrdp::rdpdr::RdpdrBackend;
|
||||
use ironrdp::rdpdr::pdu::RdpdrPdu;
|
||||
#[cfg(unix)]
|
||||
use ironrdp::rdpdr::pdu::efs::FileInformationClass;
|
||||
use ironrdp::rdpdr::pdu::efs::{
|
||||
ClientDriveQueryDirectoryResponse, ClientDriveSetInformationResponse, DeviceCloseResponse, DeviceControlRequest, DeviceCreateResponse, DeviceIoResponse,
|
||||
DeviceWriteResponse, FileInformationClass, Information, NtStatus, PrinterIoRequest, ServerDeviceAnnounceResponse, ServerDriveIoRequest,
|
||||
DeviceWriteResponse, Information, NtStatus, PrinterIoRequest, ServerDeviceAnnounceResponse, ServerDriveIoRequest,
|
||||
};
|
||||
use ironrdp::rdpdr::pdu::esc::{ScardCall, ScardIoCtlCode};
|
||||
use ironrdp::svc::SvcMessage;
|
||||
@@ -24,7 +26,9 @@ use ironrdp::svc::SvcMessage;
|
||||
use ironrdp_rdpdr_native::backend::NixRdpdrBackend;
|
||||
use std::collections::HashMap;
|
||||
use std::io::Write;
|
||||
use std::path::{Path, PathBuf};
|
||||
#[cfg(unix)]
|
||||
use std::path::Path;
|
||||
use std::path::PathBuf;
|
||||
|
||||
/// A print job may not grow past this (a hostile server could otherwise fill the disk).
|
||||
const MAX_PRINT_JOB: u64 = 256 << 20;
|
||||
@@ -32,6 +36,7 @@ const MAX_PRINT_JOB: u64 = 256 << 20;
|
||||
const MAX_OPEN_PRINT_JOBS: usize = 4;
|
||||
/// Largest drive read served in one request. The server picks the length (a u32), and the
|
||||
/// native backend allocates it before reading; Windows clients never ask for more than ~1 MiB.
|
||||
#[cfg(unix)]
|
||||
const MAX_DRIVE_READ: u32 = 8 << 20;
|
||||
|
||||
/// Called with the file a finished print job was saved to.
|
||||
@@ -151,7 +156,7 @@ impl RdpdrBackend for Devices {
|
||||
if let Some(d) = &mut self.drive {
|
||||
return d.handle(req);
|
||||
}
|
||||
ConfinedDrive::refuse(req)
|
||||
refuse_drive_io(req)
|
||||
}
|
||||
|
||||
fn handle_printer_io_request(&mut self, req: PrinterIoRequest) -> PduResult<Vec<SvcMessage>> {
|
||||
@@ -164,14 +169,35 @@ impl RdpdrBackend for Devices {
|
||||
}
|
||||
}
|
||||
|
||||
/// Answers a drive request with a refusal (no shared folder, or a path outside it).
|
||||
fn refuse_drive_io(req: ServerDriveIoRequest) -> PduResult<Vec<SvcMessage>> {
|
||||
tracing::warn!("RDP server asked for a path outside the shared folder; refused");
|
||||
let pdu = match req {
|
||||
ServerDriveIoRequest::ServerCreateDriveRequest(r) => RdpdrPdu::DeviceCreateResponse(DeviceCreateResponse {
|
||||
device_io_reply: DeviceIoResponse::new(r.device_io_request, NtStatus::ACCESS_DENIED),
|
||||
file_id: 0,
|
||||
information: Information::empty(),
|
||||
}),
|
||||
ServerDriveIoRequest::ServerDriveQueryDirectoryRequest(r) => RdpdrPdu::ClientDriveQueryDirectoryResponse(ClientDriveQueryDirectoryResponse {
|
||||
device_io_reply: DeviceIoResponse::new(r.device_io_request, NtStatus::NO_MORE_FILES),
|
||||
buffer: None,
|
||||
}),
|
||||
ServerDriveIoRequest::ServerDriveSetInformationRequest(r) => RdpdrPdu::ClientDriveSetInformationResponse(
|
||||
ClientDriveSetInformationResponse::new(&r, NtStatus::ACCESS_DENIED).map_err(|e| ironrdp::pdu::encode_err!(e))?,
|
||||
),
|
||||
_ => return Ok(vec![]),
|
||||
};
|
||||
Ok(vec![SvcMessage::from(pdu)])
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
#[derive(Debug)]
|
||||
struct ConfinedDrive {
|
||||
#[cfg(unix)]
|
||||
inner: NixRdpdrBackend,
|
||||
#[cfg_attr(not(unix), allow(dead_code))]
|
||||
base: PathBuf,
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
impl ConfinedDrive {
|
||||
pub fn new(folder: &Path) -> std::io::Result<ConfinedDrive> {
|
||||
let base = folder.canonicalize()?;
|
||||
@@ -211,28 +237,9 @@ impl ConfinedDrive {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn refuse(req: ServerDriveIoRequest) -> PduResult<Vec<SvcMessage>> {
|
||||
tracing::warn!("RDP server asked for a path outside the shared folder; refused");
|
||||
let pdu = match req {
|
||||
ServerDriveIoRequest::ServerCreateDriveRequest(r) => RdpdrPdu::DeviceCreateResponse(DeviceCreateResponse {
|
||||
device_io_reply: DeviceIoResponse::new(r.device_io_request, NtStatus::ACCESS_DENIED),
|
||||
file_id: 0,
|
||||
information: Information::empty(),
|
||||
}),
|
||||
ServerDriveIoRequest::ServerDriveQueryDirectoryRequest(r) => RdpdrPdu::ClientDriveQueryDirectoryResponse(ClientDriveQueryDirectoryResponse {
|
||||
device_io_reply: DeviceIoResponse::new(r.device_io_request, NtStatus::NO_MORE_FILES),
|
||||
buffer: None,
|
||||
}),
|
||||
ServerDriveIoRequest::ServerDriveSetInformationRequest(r) => RdpdrPdu::ClientDriveSetInformationResponse(
|
||||
ClientDriveSetInformationResponse::new(&r, NtStatus::ACCESS_DENIED).map_err(|e| ironrdp::pdu::encode_err!(e))?,
|
||||
),
|
||||
_ => return Ok(vec![]),
|
||||
};
|
||||
Ok(vec![SvcMessage::from(pdu)])
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
impl ConfinedDrive {
|
||||
#[cfg(unix)]
|
||||
fn handle(&mut self, mut req: ServerDriveIoRequest) -> PduResult<Vec<SvcMessage>> {
|
||||
@@ -250,7 +257,7 @@ impl ConfinedDrive {
|
||||
// Everything else names a file by an id that a checked create handed out.
|
||||
_ => true,
|
||||
};
|
||||
if ok { self.inner.handle_drive_io_request(req) } else { Self::refuse(req) }
|
||||
if ok { self.inner.handle_drive_io_request(req) } else { refuse_drive_io(req) }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -314,6 +321,7 @@ mod tests {
|
||||
std::fs::remove_dir_all(dir).unwrap();
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
#[test]
|
||||
fn server_paths_stay_inside_the_share() {
|
||||
let root = std::env::temp_dir().join(format!("nexus-drive-{}", std::process::id()));
|
||||
|
||||
@@ -206,6 +206,7 @@ fn open_error(path: &str, e: &serialport::Error) -> crate::Error {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
#[cfg(unix)]
|
||||
use std::sync::mpsc;
|
||||
|
||||
#[test]
|
||||
|
||||
Reference in New Issue
Block a user