Can you convert a mut i8 into an i32? - unix

I am attempting to build a small terminal emulator and am running into some interesting type conflicts with libc. When I am attempting to set up the slave portion of the pty connection I need to create the slave with a system call to ptsname() in order to get the name for the pts so I can access it. However, I get a type error saying that libc::ptsname() requires an i32 for the input. This is in direct conflict with the man page that says it should be passed a file descriptor. I'm just wondering if I can convert the libc::c_int that I have for a file descriptor into a i32 to pass into ptsname.
The code is as follows :
use libc::{self, c_int, grantpt, posix_openpt, ptsname, unlockpt, O_RDWR};
use std::os::unix::io::FromRawFd;
use std::process::{Child, Command, Stdio};
#[derive(Debug)]
pub struct Pty {
process: Child,
fd: i32,
}
fn create_pty(process: &str) -> Pty {
let master: c_int;
unsafe {
// create master/slave pair of fd
master = posix_openpt(O_RDWR);
if master == -1 {
panic!("Failed to posix_openpt");
}
// set slave ownership and mode as master
let mut result = grantpt(master);
if result == -1 {
panic!("Failed to grantpt");
}
// unlock slave
result = unlockpt(master);
if result == -1 {
panic!("Failed to unlockpt");
}
}
let slave: c_int = ptsname(master as i32);
slave = libc::open(slave);
let mut builder = Command::new(process);
match builder.spawn() {
Ok(process) => {
let pty = Pty {
process,
fd: master,
};
pty
}
Err(e) => {
panic!("Failed to create pty: {}", e);
}
}
}
fn main() {
let shell = "/bin/bish";
let pty = create_pty(shell);
println!("{:?}", pty);
}
and the console output(The second error can be ignored for now):
error[E0308]: mismatched types
--> src/main.rs:42:24
|
42 | let slave: c_int = ptsname(master as i32);
| ^^^^^^^^^^^^^^^^^^^^^^ expected i32, found *-ptr
|
= note: expected type `i32`
found type `*mut i8`
error[E0060]: this function takes at least 2 parameters but 1 parameter was supplied
--> src/main.rs:43:13
|
43 | slave = libc::open(slave);
| ^^^^^^^^^^^^^^^^^ expected at least 2 parameters
error: aborting due to 2 previous errors
Some errors have detailed explanations: E0060, E0308.
For more information about an error, try `rustc --explain E0060`.
error: could not compile `experiment`.

It's not saying that it requires an input of i32, but rather that you're asking that ptsname(master as i32); has the type i32. This might be a bit confusing because c_int is an alias for i32, so it sounds like it's asking for an unrelated type.
The problem is that you're giving slave the type c_int, when ptsname returns *mut c_char (c_char is also an alias, this time for i8).

Related

cannot move out of a captured variable in an async `Fn` closure

Here is my code. In this program, I want to create a simple websocket server. When user sends a request to the ws://{url}/, the browser will establish a websocket connection with the server.
use std::{collections::HashMap, sync::Arc};
use async_std::{prelude::*, sync::Mutex};
use tide_websockets::WebSocket;
use uuid::Uuid;
#[async_std::main]
async fn main() {
let connections = Arc::new(Mutex::new(HashMap::new()));
let mut app = tide::new();
app.at("/").get(WebSocket::new(move |_, mut stream| async move {
let uuid = Uuid::new_v4();
// Add the connection to clients when opening a new connection
connections.lock().await.insert(uuid, stream.clone());
// Waiting for the connection to be closed
while let Some(Ok(_)) = stream.next().await {}
// Remove the connection from clients when it is closed
connections.lock().await.remove(&uuid);
Ok(())
}));
// app.bind(url).await
}
When I tried to compile this program, the rustc said:
error[E0507]: cannot move out of `connections`, a captured variable in an `Fn` closure
--> src/main.rs:11:57
|
9 | let connections = Arc::new(Mutex::new(HashMap::new()));
| ----------- captured outer variable
10 | let mut app = tide::new();
11 | app.at("/").get(WebSocket::new(move |_, mut stream| async move {
| ____________________________________--------------------_^
| | |
| | captured by this `Fn` closure
12 | | let uuid = Uuid::new_v4();
13 | |
14 | | // Add the connection to clients when opening a new connection
15 | | connections.lock().await.insert(uuid, stream.clone());
| | -----------
| | |
| | variable moved due to use in generator
| | move occurs because `connections` has type `Arc<async_std::sync::Mutex<HashMap<Uuid, WebSocketConnection>>>`, which does not implement the `Copy` trait
... |
23 | | Ok(())
24 | | }));
| |_____^ move out of `connections` occurs here
For more information about this error, try `rustc --explain E0507`.
error: could not compile `mre` due to previous error
And this is the definition of the Websocket::new method (no sure if it's useful):
impl<S, H, Fut> WebSocket<S, H>
where
S: Send + Sync + Clone + 'static,
H: Fn(Request<S>, WebSocketConnection) -> Fut + Sync + Send + 'static,
Fut: Future<Output = Result<()>> + Send + 'static,
{
/// Build a new WebSocket with a handler function that
pub fn new(handler: H) -> Self {
Self {
handler: Arc::new(handler),
ghostly_apparition: PhantomData,
protocols: Default::default(),
}
}
// ...
}
I tried searching this problem before posting this question. Most of the answers are either irrelevant, or need to modify the source code of the method (Websocket::new method here). But this method is not written by me but is from a third-party crate. Is there still any way to resolve this problem?
The argument of WebSocket::new() has to be an Fn closure, meaning, it must be callable repeatedly.
In your code, however, it internally uses the connections variable inside of an async move, meaning it moves the variable into the async block. This can for obvious reasons only be done once.
It's easy to fix, though. Instead of moving the entire connections variable in, you need to create a new Arc reference of the connections variable and move that one into the async move. So every invocation gets its own copy of it, making it compatible with Fn.
Here is a compiling version:
use std::{collections::HashMap, sync::Arc};
use async_std::{prelude::*, sync::Mutex};
use tide_websockets::WebSocket;
use uuid::Uuid;
#[async_std::main]
async fn main() {
let connections = Arc::new(Mutex::new(HashMap::new()));
let mut app = tide::new();
app.at("/").get(WebSocket::new(move |_, mut stream| {
let connections = Arc::clone(&connections);
async move {
let uuid = Uuid::new_v4();
// Add the connection to clients when opening a new connection
connections.lock().await.insert(uuid, stream.clone());
// Waiting for the connection to be closed
while let Some(Ok(_)) = stream.next().await {}
// Remove the connection from clients when it is closed
connections.lock().await.remove(&uuid);
Ok(())
}
}));
// app.bind(url).await
}

Error: Could not find main or io in tokio, invalid return type `impl Future`

I'm on my way of converting to Rust from the ML family, but I'm finding it hard at some strange places I'm not used to having problems.
I'm trying to use hyper for http handling but can't seem to get tokio to work.
I have tried to copy paste this example:
use hyper::{body::HttpBody as _, Client};
use tokio::io::{self, AsyncWriteExt as _};
type Result<T> = std::result::Result<T, Box<dyn std::error::Error + Send + Sync>>;
#[tokio::main]
async fn main() -> Result<()> {
// ...
fetch_url(url).await
}
async fn fetch_url(url: hyper::Uri) -> Result<()> {
// ...
Ok(())
}
Here is my Cargo.toml:
[package]
name = "projectname"
version = "0.1.0"
authors = ["username"]
edition = "2018"
[dependencies]
hyper = "0.14.4"
tokio = "1.2.0"
It is complaining that it can't find the io crate, and that main has an invalid type impl Future, and that it can't find main in tokio:
error[E0433]: failed to resolve: could not find `main` in `tokio`
--> src/main.rs:9:10
|
9 | #[tokio::main]
| ^^^^ could not find `main` in `tokio`
error[E0277]: `main` has invalid return type `impl Future`
--> src/main.rs:10:20
|
10 | async fn main() -> Result<()> {
| ^^^^^^^^^^ `main` can only return types that implement `Termination`
error[E0432]: unresolved import `hyper::Client`
--> src/main.rs:3:34
|
3 | use hyper::{body::HttpBody as _, Client};
| ^^^^^^ no `Client` in the root
error[E0425]: cannot find function `stdout` in module `io`
--> src/main.rs:45:13
|
45 | io::stdout().write_all(&chunk).await?;
| ^^^^^^ not found in `io`
|
error[E0432]: unresolved import `tokio::io::AsyncWriteExt`
--> src/main.rs:4:23
|
4 | use tokio::io::{self, AsyncWriteExt as _};
| -------------^^^^^
| |
| no `AsyncWriteExt` in `io`
| help: a similar name exists in the module: `AsyncWrite`
Is #[tokio::main] and client not in hyper?
The tokio::main macro converts an async main to a regular main that spawns a runtime. However, because the macro is not found is scope, it cannot transform your main function, and the compiler is complaining that your main has an invalid return type of impl Future. To fix this, you have to enable the required features to import the main macro:
tokio = { version = "1.2.0", features = ["rt", "macros"] }
You also have to enable the io-util feature to access io::AsyncWriteExt, and the io-std feature to access io::stdout. To simplify this, tokio provides the full feature flag, which will enable all optional features:
tokio = { version = "1.2.0", features = ["full"] }
You also need hyper's client and http feature flags to resolve the Client import:
hyper = { version = "0.14.4", features = ["client", "http1", "http2"] }

No output from non standard File Descriptor

I am having issues receiving data from the child process.
To send a string to the child process you write to FD3 and it will output the result to FD4.
The child process runs fine, and if it couldn't write to FD4, the process would not start correctly, so FD4 has to be available but I just don't know why no output is given.
My initial thoughts is when sending the string (to FD3) it wasn't null byte terminating therefore not receiving the string correctly (and then not sending anything back on FD4) but I am sure I am doing it correctly.
I have tested writing to FD4 within the child process manually and the parent receives the output.
use nix::fcntl::FcntlArg::{F_SETFD};
use nix::fcntl::{fcntl, open, FdFlag, OFlag};
use nix::sys::socket::{socketpair, AddressFamily, SockFlag, SockType};
use nix::sys::stat::Mode;
use nix::unistd::{close, dup2, execvp, fork, pipe, read, write, ForkResult};
use std::ffi::CString;
use std::os::unix::io::RawFd;
use std::process::abort;
fn main() {
let input_socket: (RawFd, RawFd) = create_socket();
let output_socket: (RawFd, RawFd) = create_socket();
match fork() {
Ok(ForkResult::Parent { child, .. }) => {
println!("Child PID: {}", child);
close(input_socket.1).unwrap();
close(output_socket.1).unwrap();
let test = r#"{"id":0,"method":"Target.getTargets"}\0"#;
write(input_socket.0, test.as_bytes()).expect("unable to write");
let mut buf = [0; 64];
read(output_socket.0, &mut buf).unwrap();
println!("BUFFER: {:#?}", std::str::from_utf8(&buf).unwrap());
}
Ok(ForkResult::Child) => {
setup_child(input_socket.1, output_socket.1);
abort()
}
Err(err) => { println!("{}", err); abort()},
}
}
#[cfg(any(
target_os = "android",
target_os = "dragonfly",
target_os = "emscripten",
target_os = "freebsd",
target_os = "linux",
target_os = "netbsd",
target_os = "openbsd"
))]
fn create_socket() -> (RawFd, RawFd) {
socketpair(
AddressFamily::Unix,
SockType::Stream,
None,
SockFlag::SOCK_CLOEXEC,
)
.unwrap()
}
#[cfg(any(target_os = "ios", target_os = "macos"))]
fn create_socket() -> (RawFd, RawFd) {
let socket = socketpair(
AddressFamily::Unix,
SockType::Stream,
None,
SockFlag::empty(),
)
.unwrap();
fcntl(socket.0, F_SETFD(FdFlag::FD_CLOEXEC)).unwrap();
fcntl(socket.1, F_SETFD(FdFlag::FD_CLOEXEC)).unwrap();
socket
}
fn setup_child(input: RawFd, output: RawFd) {
let _input: RawFd = dup2(input, 3).unwrap();
let _output: RawFd = dup2(output, 4).unwrap();
let file = CString::new("/Applications/Google Chrome.app/Contents/MacOS/Google Chrome").unwrap();
let arg1 = CString::new("--remote-debugging-pipe").unwrap();
let arg2 = CString::new("--enable-logging=stderr").unwrap();
let args = vec![arg1.as_c_str(),arg2.as_c_str()];
let _res = execvp(&file, &args).unwrap();
}
This is the open FDs for the child process: lsof -p 14620
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
Chrome 14620 tom 0 PIPE 0xcafab653bf79be56 16384 ->0xfc41206e43b6bb9d
Chrome 14620 tom 1 PIPE 0x71c3c6a8bffbf5ad 16384 ->0x587f975b5bfd4499
Chrome 14620 tom 2 PIPE 0x71c3c6a8bffbf5ad 16384 ->0x587f975b5bfd4499
Chrome 14620 tom 3u unix 0x36cd3ac44c49d68d 0t0 ->0x36cd3ac44c49f2ad
Chrome 14620 tom 4u unix 0x36cd3ac44c49dc05 0t0 ->0x36cd3ac44c49f9b5
UPDATE
I found the issue, for some reason the first argument when running the chrome process was being ignored?? So when I place any other argument as the first in the list chrome actually starts up the remote debugging. Weird issue!
When I run the chrome process in terminal with --remote-debugging-pipe as the first argument it works fine too, so why does it happen when i use: execvp(&file, &args)
The problem is how I am running the process.
execvp(&file, &args) requires the first arg to be the file location. Thanks to: source
Changing this code makes everything run as expected:
let args = vec![file.as_c_str(), arg1.as_c_str(),arg2.as_c_str()];

the trait bound `tokio::net::tcp::stream::TcpStream: tokio_io::async_read::AsyncRead` is not satisfied

I can't compile a simple application to test tokio-codec.
tokio::net::tcp::stream::TcpStream implements AsyncRead and -Write.
But when i try to compile the code below, i get the error below.
I am still new to Rust and Tokio, so no doubt im missing something obvious (i hope)...
main.rs:
use tokio::net::TcpListener;
use tokio::prelude::*;
use tokio_codec::{ Framed, LinesCodec };
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let mut listener = TcpListener::bind("127.0.0.1:12321").await?;
loop {
let (socket, _addr) = listener.accept().await?;
tokio::spawn(async move {
let (_sink, mut stream) = Framed::new(socket, LinesCodec::new()).split();
while let Some(Ok(line)) = stream.next().await {
println!("{:?}", line);
}
});
}
}
Cargo.toml:
[dependencies]
tokio = { version = "0.2.6", features = ["full"] }
tokio-codec = "0.1.1"
Output:
error[E0277]: the trait bound `tokio::net::tcp::stream::TcpStream: tokio_io::async_read::AsyncRead` is not satisfied
--> src\main.rs:14:36
|
14 | let (mut sink, mut stream) = Framed::new(socket, LinesCodec::new()).split();
| ^^^^^^^^^^^ the trait `tokio_io::async_read::AsyncRead` is not implemented for `tokio::net::tcp::stream::TcpStream`
|
= note: required by `tokio_io::_tokio_codec::framed::Framed::<T, U>::new`
error[E0277]: the trait bound `tokio::net::tcp::stream::TcpStream: tokio_io::async_write::AsyncWrite` is not satisfied
--> src\main.rs:14:36
|
14 | let (mut sink, mut stream) = Framed::new(socket, LinesCodec::new()).split();
| ^^^^^^^^^^^ the trait `tokio_io::async_write::AsyncWrite` is not implemented for `tokio::net::tcp::stream::TcpStream`
|
= note: required by `tokio_io::_tokio_codec::framed::Framed::<T, U>::new`
error[E0599]: no method named `split` found for type `tokio_io::_tokio_codec::framed::Framed<tokio::net::tcp::stream::TcpStream, tokio_codec::lines_codec::LinesCodec>` in the current scope
--> src\main.rs:14:75
|
14 | let (mut sink, mut stream) = Framed::new(socket, LinesCodec::new()).split();
| ^^^^^ method not found in `tokio_io::_tokio_codec::framed::Framed<tokio::net::tcp::stream::TcpStream, tokio_codec::lines_codec::LinesCodec>`
|
= note: the method `split` exists but the following trait bounds were not satisfied:
`&mut tokio_io::_tokio_codec::framed::Framed<tokio::net::tcp::stream::TcpStream, tokio_codec::lines_codec::LinesCodec> : tokio::io::util::async_buf_read_ext::AsyncBufReadExt`
`&tokio_io::_tokio_codec::framed::Framed<tokio::net::tcp::stream::TcpStream, tokio_codec::lines_codec::LinesCodec> : tokio::io::util::async_buf_read_ext::AsyncBufReadExt`
`tokio_io::_tokio_codec::framed::Framed<tokio::net::tcp::stream::TcpStream, tokio_codec::lines_codec::LinesCodec> : tokio::io::util::async_buf_read_ext::AsyncBufReadExt`
How can i solve this?
tokio-codec is an outdated crate that depends on a pre-async/await version of Tokio (0.1.7)
Codecs seem to have been moved to tokio-util that depends on Tokio 0.2, so you should have more luck with that.
Generally, when the compiler tells you that a type does not implement a trait, but in documentation you see it does, this means you have two different versions of the crate that defines the trait in your project (Tokio 0.1 and 0.2 in this case).

Send SIGINT to a process by sending ctrl-c to stdin

I'm looking for a way to mimick a terminal for some automated testing: i.e. start a process and then interact with it via sending data to stdin and reading from stdout. E.g. sending some lines of input to stdin including ctrl-c and ctrl-\ which should result in sending signals to the process.
Using std::process::Commannd I'm able to send input to e.g. cat and I'm also seeing its output on stdout, but sending ctrl-c (as I understand that is 3) does not cause SIGINT sent to the shell. E.g. this program should terminate:
use std::process::{Command, Stdio};
use std::io::Write;
fn main() {
let mut child = Command::new("sh")
.arg("-c").arg("-i").arg("cat")
.stdin(Stdio::piped())
.spawn().unwrap();
let mut stdin = child.stdin.take().unwrap();
stdin.write(&[3]).expect("cannot send ctrl-c");
child.wait();
}
I suspect the issue is that sending ctrl-c needs the some tty and via sh -i it's only in "interactive mode".
Do I need to go full fledged and use e.g. termion or ncurses?
Update: I confused shell and terminal in the original question. I cleared this up now. Also I mentioned ssh which should have been sh.
The simplest way is to directly send the SIGINT signal to the child process. This can be done easily using nix's signal::kill function:
// add `nix = "0.15.0"` to your Cargo.toml
use std::process::{Command, Stdio};
use std::io::Write;
fn main() {
// spawn child process
let mut child = Command::new("cat")
.stdin(Stdio::piped())
.spawn().unwrap();
// send "echo\n" to child's stdin
let mut stdin = child.stdin.take().unwrap();
writeln!(stdin, "echo");
// sleep a bit so that child can process the input
std::thread::sleep(std::time::Duration::from_millis(500));
// send SIGINT to the child
nix::sys::signal::kill(
nix::unistd::Pid::from_raw(child.id() as i32),
nix::sys::signal::Signal::SIGINT
).expect("cannot send ctrl-c");
// wait for child to terminate
child.wait().unwrap();
}
You should be able to send all kinds of signals using this method. For more advanced "interactivity" (e.g. child programs like vi that query terminal size) you'd need to create a pseudoterminal like #hansaplast did in his solution.
After a lot of research I figured out it's not too much work to do the pty fork myself. There's pty-rs, but it has bugs and seems unmaintained.
The following code needs pty module of nix which is not yet on crates.io, so Cargo.toml needs this for now:
[dependencies]
nix = {git = "https://github.com/nix-rust/nix.git"}
The following code runs cat in a tty and then writes/reads from it and sends Ctrl-C (3):
extern crate nix;
use std::path::Path;
use nix::pty::{posix_openpt, grantpt, unlockpt, ptsname};
use nix::fcntl::{O_RDWR, open};
use nix::sys::stat;
use nix::unistd::{fork, ForkResult, setsid, dup2};
use nix::libc::{STDIN_FILENO, STDOUT_FILENO, STDERR_FILENO};
use std::os::unix::io::{AsRawFd, FromRawFd};
use std::io::prelude::*;
use std::io::{BufReader, LineWriter};
fn run() -> std::io::Result<()> {
// Open a new PTY master
let master_fd = posix_openpt(O_RDWR)?;
// Allow a slave to be generated for it
grantpt(&master_fd)?;
unlockpt(&master_fd)?;
// Get the name of the slave
let slave_name = ptsname(&master_fd)?;
match fork() {
Ok(ForkResult::Child) => {
setsid()?; // create new session with child as session leader
let slave_fd = open(Path::new(&slave_name), O_RDWR, stat::Mode::empty())?;
// assign stdin, stdout, stderr to the tty, just like a terminal does
dup2(slave_fd, STDIN_FILENO)?;
dup2(slave_fd, STDOUT_FILENO)?;
dup2(slave_fd, STDERR_FILENO)?;
std::process::Command::new("cat").status()?;
}
Ok(ForkResult::Parent { child: _ }) => {
let f = unsafe { std::fs::File::from_raw_fd(master_fd.as_raw_fd()) };
let mut reader = BufReader::new(&f);
let mut writer = LineWriter::new(&f);
writer.write_all(b"hello world\n")?;
let mut s = String::new();
reader.read_line(&mut s)?; // what we just wrote in
reader.read_line(&mut s)?; // what cat wrote out
writer.write(&[3])?; // send ^C
writer.flush()?;
let mut buf = [0; 2]; // needs bytewise read as ^C has no newline
reader.read(&mut buf)?;
s += &String::from_utf8_lossy(&buf).to_string();
println!("{}", s);
println!("cat exit code: {:?}", wait::wait()?); // make sure cat really exited
}
Err(_) => println!("error"),
}
Ok(())
}
fn main() {
run().expect("could not execute command");
}
Output:
hello world
hello world
^C
cat exit code: Signaled(2906, SIGINT, false)
Try adding -t option TWICE to force pseudo-tty allocation. I.e.
klar (16:14) ~>echo foo | ssh user#host.ssh.com tty
not a tty
klar (16:14) ~>echo foo | ssh -t -t user#host.ssh.com tty
/dev/pts/0
When you have a pseudo-tty, I think it should convert that to SIGINT as you wanted to do.
In your simple example, you could also just close stdin after the write, in which case the server should exit. For this particular case it would be more elegant and probably more reliable.
Solution without using a crate
Now that you are spawning a command in Rust, you might as well spawn another to send SIGINT to it. That command is kill.
So, you can do this:
use std::process::{Command, Stdio};
use std::io::{Result, Write};
fn main() -> Result<()> {
let mut child = Command::new("sh")
.arg("-c").arg("-i").arg("cat")
.stdin(Stdio::piped())
.spawn()?;
let mut stdin = child.stdin.take().unwrap();
let mut kill = Command::new("kill")
.arg(child.id().to_string())
.spawn()?;
kill.wait()
}

Resources