From 8db74f734f194cc57ddcbaf8d183924305f083ac Mon Sep 17 00:00:00 2001 From: Joe Sadusk Date: Thu, 28 Nov 2024 23:01:34 -0500 Subject: [PATCH] Work so far, compiles and includes benchmarks --- CMakeLists.txt | 3 + Cargo.toml | 3 + src/emacs-libssh.c | 20 +++- src/lib.rs | 231 ++++++++++++++++++++++++++++++++++++++------- test.el | 100 ++++++++++++++++++-- 5 files changed, 309 insertions(+), 48 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 28ee191..39adb0e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -10,6 +10,7 @@ project(TrampLibSSH VERSION 0.1 set(CMAKE_EXPORT_COMPILE_COMMANDS ON) add_library(emacs-libssh SHARED src/emacs-libssh.c hashtable/hashtable.c emacs-modules/emacs-module-helpers.c) +add_executable(test_stat src/test_stat.c) find_program(emacs_executable emacs) get_filename_component(emacs_bin_dir ${emacs_executable} DIRECTORY) @@ -24,6 +25,8 @@ find_package(LIBSSH) if (LIBSSH_FOUND) target_include_directories(emacs-libssh SYSTEM AFTER PUBLIC ${LIBSSH_INCLUDE_DIR}) target_link_libraries(emacs-libssh PRIVATE ssh) + target_include_directories(test_stat SYSTEM AFTER PUBLIC ${LIBSSH_INCLUDE_DIR}) + target_link_libraries(test_stat PRIVATE ssh) else () message(FATAL_ERROR "Unable to find libssh") endif () diff --git a/Cargo.toml b/Cargo.toml index 2c77969..e999a99 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,6 +15,9 @@ libc = "0.2.153" libssh-rs = { git = "https://github.com/jsadusk/libssh-rs" } thread_local = "1.1.7" thiserror = "1.0" +pipe = "0.4" +crossbeam = "0.8" +crossbeam-channel = "0.5.13" [dev-dependencies] emacs-rs-module = {version = "0.18.0"} diff --git a/src/emacs-libssh.c b/src/emacs-libssh.c index a19a66d..a312bcd 100644 --- a/src/emacs-libssh.c +++ b/src/emacs-libssh.c @@ -606,15 +606,31 @@ static emacs_value emacs_libssh_marker (emacs_env *env, ptrdiff_t nargs, emacs_v return 0; } +static emacs_value test_bare (emacs_env *env, ptrdiff_t nargs, emacs_value args[], void *data) { + emacs_value get_t = env->intern(env, "get-t"); + for (int i = 0; i < 10000; ++i) { + ptrdiff_t size; + env->copy_string_contents(env, args[0], NULL, &size); + char* message = calloc(sizeof(char), size); + env->copy_string_contents(env, args[0], message, &size); + + emacs_value message_str = env->make_string (env, message, strlen(message)); + env->funcall(env, get_t, 1, &message_str); + free(message); + } + return nilval(env); +} + int emacs_module_init(struct emacs_runtime *ert) { emacs_env *env = ert->get_environment(ert); - DEFUN("emacs-libssh-sftp-write-region", emacs_libssh_sftp_write_region, 6, 6, "Write a region from the current buffer into an sftp file", NULL); + /*DEFUN("emacs-libssh-sftp-write-region", emacs_libssh_sftp_write_region, 6, 6, "Write a region from the current buffer into an sftp file", NULL); DEFUN("emacs-libssh-get-ssh-session", emacs_libssh_get_ssh_session, 2, 2, "Get an ssh session", NULL); DEFUN("emacs-libssh-get-sftp-session", emacs_libssh_get_sftp_session, 1, 1, "Get an sftp session", NULL); - DEFUN("emacs-libssh-sftp-insert", emacs_libssh_sftp_insert, 5, 5, "Insert a file from sftp into the current buffer", NULL); + DEFUN("emacs-libssh-sftp-insert", emacs_libssh_sftp_insert, 5, 5, "Insert a file from sftp into the current buffer", NULL);*/ + DEFUN("emacs-libssh-test-bare", test_bare, 1, 1, "Bare test", NULL); provide(env, "emacs-libssh"); return 0; diff --git a/src/lib.rs b/src/lib.rs index 4619c6c..9b1e4c6 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -2,8 +2,10 @@ use emacs::Error as EmacsError; use emacs::Result as EmacsResult; use emacs::{defun, Env, FromLisp, IntoLisp, Value}; use libssh_rs::Error as SshError; +use std::cell::BorrowMutError; use std::cell::RefCell; use std::collections::HashMap; +use std::fs::File; use std::hash::{DefaultHasher, Hash, Hasher}; use std::io; use std::io::Error as IOError; @@ -11,8 +13,10 @@ use std::io::{Read, Seek, SeekFrom, Write}; use std::path::Path; use std::rc::Rc; use std::str::Utf8Error; -use std::time::{Instant, SystemTime}; +use std::time::SystemTime; use thiserror::Error; +//use pipe::{pipe, PipeReader, PipeWriter}; +//use crossbeam_channel::{unbounded as channel}; emacs::plugin_is_GPL_compatible!(); @@ -60,9 +64,16 @@ impl ScopedStatic { } } +/*struct AsyncSession { + signal_pipe: PipeWriter, + control: crossbeam_channel::Sender, + response: crossbeam_channel::Rec +}*/ + thread_local! { static SESSIONS: RefCell>> = RefCell::new(HashMap::new()); static SFTPS: RefCell>> = RefCell::new(HashMap::new()); + //static ASYNC_SESSIONS: RefCell>> = RefCell::new(HashMap::new()); static CURRENT_ENV: ScopedStatic = ScopedStatic::default(); } @@ -75,11 +86,14 @@ emacs::use_symbols! { tramp_dissect_file_name read_passwd read_string y_or_n_p insert replace_buffer_contents - set_buffer current_buffer generate_new_buffer kill_buffer + set_buffer current_buffer generate_new_buffer kill_buffer get_buffer_create buffer_substring_no_properties buffer_size string_match_p - string integer + string integer + eval + expand_file_name + get_t } #[emacs::module(name = "tramp-libssh")] @@ -113,12 +127,15 @@ trait LocalEnv<'a> { fn set_buffer(&self, buffer: Value<'a>) -> EmacsResult<()>; fn current_buffer(&self) -> EmacsResult>; fn generate_new_buffer(&self, name: &str) -> EmacsResult>; + fn get_buffer_create(&self, buffer_or_name: Value<'a>) -> EmacsResult>; fn kill_buffer(&self, buffer: Value<'a>) -> EmacsResult<()>; fn buffer_substring_no_properties(&self, start: usize, end: usize) -> EmacsResult; fn buffer_size(&self) -> EmacsResult; fn default_directory(&self) -> EmacsResult; fn string_match_p(&self, regexp: Value<'a>, match_string: &str) -> EmacsResult; fn build_list(&self, values: &[Value<'a>]) -> EmacsResult>; + fn eval(&self, text: &str) -> EmacsResult>; + fn expand_file_name(&self, filename: &str) -> EmacsResult>; } impl<'a> LocalEnv<'a> for &'a Env { @@ -221,8 +238,12 @@ impl<'a> LocalEnv<'a> for &'a Env { usize::from_lisp(self.call(buffer_size, &[])?) } + fn get_buffer_create(&self, buffer_or_name: Value<'a>) -> EmacsResult> { + self.call(get_buffer_create, &[buffer_or_name]) + } + fn default_directory(&self) -> EmacsResult { - self.tramp_dissect_file_name(self.intern("default-directory")?) + self.tramp_dissect_file_name(self.expand_file_name(".")?) } fn string_match_p(&self, regexp: Value<'a>, match_string: &str) -> EmacsResult { @@ -237,6 +258,14 @@ impl<'a> LocalEnv<'a> for &'a Env { } Ok(result) } + + fn eval(&self, text: &str) -> EmacsResult> { + self.call(eval, &[text.into_lisp(self)?]) + } + + fn expand_file_name(&self, filename: &str) -> EmacsResult> { + self.call(expand_file_name, &[filename.into_lisp(self)?]) + } } // Get around foreign type rule @@ -262,14 +291,41 @@ impl<'e> MyIntoLisp<'e> for SystemTime { } } +struct ValueIterator<'e>(Value<'e>); + +impl<'e> Iterator for ValueIterator<'e> { + type Item = EmacsResult>; + fn next(&mut self) -> Option { + let car_v: Value<'e> = match self.0.car() { + Ok(car_v) => car_v, + Err(e) => return Some(Err(e)), + }; + if car_v.is_not_nil() { + self.0 = match self.0.cdr() { + Ok(cdr_v) => cdr_v, + Err(e) => return Some(Err(e)), + }; + + return Some(Ok(car_v)); + } else { + return None; + } + } +} + trait ValueExt<'e> { fn cons(self, cdr_v: impl IntoLisp<'e>) -> EmacsResult>; + fn iter(self) -> ValueIterator<'e>; } impl<'e> ValueExt<'e> for Value<'e> { fn cons(self, cdr_v: impl IntoLisp<'e>) -> EmacsResult> { self.env.cons(cdr_v.into_lisp(self.env)?, self) } + + fn iter(self) -> ValueIterator<'e> { + ValueIterator(self) + } } fn ssh_auth_callback( @@ -302,7 +358,7 @@ fn ssh_auth_callback( .map_err(|e: anyhow::Error| SshError::Fatal(e.to_string())) } -fn get_connection(user: &str, host: &str, env: &Env) -> EmacsResult> { +fn get_connection(user: &str, host: &str, env: &Env) -> HandlerResult> { let connection_str = format!("{}@{}", user, host); SESSIONS.with(|sessions| { let mut sessions = sessions.try_borrow_mut()?; @@ -346,8 +402,8 @@ fn init_connection(user: &str, host: &str, env: &Env) -> EmacsResult { CURRENT_ENV.with(|current_env| { current_env.scope(env, || { session.connect()?; - let srv_pubkey = session.get_server_public_key()?; - let hash = srv_pubkey.get_public_key_hash(PublicKeyHashType::Sha1)?; + //let srv_pubkey = session.get_server_public_key()?; + //let hash = srv_pubkey.get_public_key_hash(PublicKeyHashType::Sha1)?; match session.is_known_server()? { KnownHosts::Changed => { bail!(format!( @@ -395,9 +451,12 @@ enum HandlerError { Io(#[from] IOError), #[error("{0}")] Utf8(#[from] Utf8Error), + #[error("{0}")] + BorrowMut(#[from] BorrowMutError), } -type HandlerResult<'a> = Result, HandlerError>; +type HandlerResult = Result; +type HandlerValue<'a> = Result, HandlerError>; trait Handle { fn handle(self) -> Result; @@ -409,13 +468,42 @@ impl> Handle for Result { } } +fn with_channel<'a>( + env: &'a Env, + handler: impl Fn(&DissectedFilename, &Session, Channel) -> HandlerValue<'a>, +) -> EmacsResult> { + let dissected = env.default_directory()?; + for retry in 0..RETRIES { + let result: HandlerResult<(Rc, Channel)> = (|| { + let session = get_connection(&dissected.user, &dissected.host, &env)?; + let channel = session.new_channel()?; + Ok((session, channel)) + })(); + match result { + Ok((session, channel)) => return Ok(handler(&dissected, &*session, channel)?), + Err(e) => { + if retry == RETRIES + || !env.y_or_n_p(&format!( + "ssh error [{}] would you like to reconnect? ", + e.to_string() + ))? + { + return Err(e.into()); + } + } + } + } + + bail!("Should not get here"); +} + fn with_sftp<'a>( env: &'a Env, tramp_path: Value<'a>, - handler: impl Fn(&DissectedFilename, &Session, &Sftp) -> HandlerResult<'a>, + handler: impl Fn(&DissectedFilename, &Session, &Sftp) -> HandlerValue<'a>, ) -> EmacsResult> { let mut error: Option = None; - for retry in 0..RETRIES { + for _retry in 0..RETRIES { let dissected = env.tramp_dissect_file_name(tramp_path)?; let session = get_connection(&dissected.user, &dissected.host, &env)?; let sftp = get_sftp(&dissected.user, &dissected.host, &*session)?; @@ -447,11 +535,11 @@ fn write_region<'a>( end: Option, filename: Value<'a>, append: Option, - visit: Option, - lockname: Option, - mustbenew: Option, + _visit: Option, + _lockname: Option, + _mustbenew: Option, ) -> EmacsResult> { - with_sftp(env, filename, |dissected, session, sftp| { + with_sftp(env, filename, |dissected, _session, sftp| { let (str_contents, begin) = if let Some(start) = start { ( String::from_lisp(start).ok(), @@ -527,12 +615,12 @@ fn write_region<'a>( fn insert_file_contents<'a>( env: &'a Env, filename: Value<'a>, - visit: Option, + _visit: Option, begin: Option, end: Option, replace: Option, ) -> EmacsResult> { - with_sftp(env, filename, |dissected, session, sftp| { + with_sftp(env, filename, |dissected, _session, sftp| { let mut rfile = sftp.open(&dissected.filename, OpenFlags::READ_ONLY, 0)?; if let Some(off) = begin { rfile.seek(SeekFrom::Start(off as u64))?; @@ -587,10 +675,8 @@ fn insert_file_contents<'a>( #[defun] fn file_exists_p<'a>(env: &'a Env, filename: Value<'a>) -> EmacsResult> { - with_sftp(env, filename, |dissected, session, sftp| { - let now = Instant::now(); + with_sftp(env, filename, |dissected, _session, sftp| { let res = sftp.open(&dissected.filename, OpenFlags::READ_ONLY, 0); - env.message(format!("time to open sftp file {:?}", now.elapsed()))?; match res { Ok(_) => Ok(t.bind(env)), Err(libssh_rs::Error::Sftp(e)) => { @@ -658,7 +744,7 @@ fn directory_files_impl<'a>( id_format: Option>, with_attributes: bool, ) -> EmacsResult> { - with_sftp(env, directory, |dissected, session, sftp| { + with_sftp(env, directory, |dissected, _session, sftp| { let dir = sftp.open_dir(&dissected.filename)?; let mut dirlist: Value<'a> = nil.bind(env); let fs_metadata = sftp.vfs_metadata(&dissected.filename)?; @@ -716,7 +802,6 @@ fn directory_files_impl<'a>( } } - env.message("changed again")?; if nosort.is_some() { Ok(dirlist) } else { @@ -726,8 +811,8 @@ fn directory_files_impl<'a>( } #[defun] -fn delete_file<'a>(env: &'a Env, filename: Value<'a>, trash: Value<'a>) -> EmacsResult> { - with_sftp(env, filename, |dissected, session, sftp| { +fn delete_file<'a>(env: &'a Env, filename: Value<'a>, _trash: Value<'a>) -> EmacsResult> { + with_sftp(env, filename, |dissected, _session, sftp| { sftp.remove_file(&dissected.filename)?; Ok(env.nil()) }) @@ -840,7 +925,7 @@ fn file_attributes<'a>( filename: Value<'a>, id_format: Value<'a>, ) -> EmacsResult> { - with_sftp(env, filename, |dissected, session, sftp| { + with_sftp(env, filename, |dissected, _session, sftp| { let path = Path::new(&dissected.filename); let dirname = path.parent().unwrap().to_str().unwrap().to_string(); let dir = sftp.open_dir(&dirname)?; @@ -875,21 +960,85 @@ fn file_attributes<'a>( }) } -/*#[defun] -fn make_process<'a>( +#[defun] +fn process_file<'a>( env: &'a Env, - name: &String, - buffer: &Option, - command: Value<'a>, - coding: Value<'a>, - connection_type: Value<'a>, - query_flag: Value<'a>, - filter: Value<'a>, - sentinel: Value<'a>, - stderr: Value<'a>, + program: Value<'a>, + infile: Value<'a>, + destination: Value<'a>, + _display: Value<'a>, + args: Value<'a>, ) -> EmacsResult> { + with_channel(env, |dissected, _session, channel| { + let program = String::from_lisp(program)?; + let arg_string = args + .iter() + .collect::>>>()? + .iter() + .map(|arg| String::from_lisp(*arg)) + .collect::>>()? + .iter() + .map(|arg| format!("'{}'", arg)) + .collect::>() + .join(" "); + channel.open_session()?; + let command = format!("cd {} && {} {}", &dissected.filename, &program, &arg_string); + channel.request_exec(&command)?; + let mut buf = [0; BLOCKSIZE]; + if infile.is_not_nil() { + let infile = String::from_lisp(infile)?; + let mut infile = File::open(&infile)?; + let mut stdin = channel.stdin(); + loop { + let bytes = infile.read(&mut buf)?; + if bytes == 0 { + break; + } -}*/ + stdin.write(&buf[0..bytes])?; + } + }; + + channel.send_eof()?; + + if destination.is_not_nil() { + let destination = env.get_buffer_create(destination)?; + let orig_buf = env.current_buffer()?; + env.set_buffer(destination)?; + + let mut maybe_stdout = Some(channel.stdout()); + let mut maybe_stderr = Some(channel.stderr()); + while maybe_stdout.is_some() || maybe_stderr.is_some() { + if let Some(mut stderr) = maybe_stderr { + let bytes = stderr.read(&mut buf)?; + maybe_stderr = if bytes == 0 { + None + } else { + env.insert(&std::str::from_utf8(&buf[0..bytes])?)?; + Some(stderr) + }; + }; + if let Some(mut stdout) = maybe_stdout { + let bytes = stdout.read(&mut buf)?; + maybe_stdout = if bytes == 0 { + None + } else { + env.insert(&std::str::from_utf8(&buf[0..bytes])?)?; + Some(stdout) + } + }; + } + + env.set_buffer(orig_buf)?; + } + + channel + .get_exit_status() + .context("Unable to get exit status")? + .into_lisp(env) + .handle() + }) +} fn octal_permissions_to_string(permissions: u32) -> String { let mut permissions: u32 = permissions; @@ -918,6 +1067,16 @@ fn octal_permissions_to_string(permissions: u32) -> String { ls.iter().collect() } +#[defun] +fn bare<'a>(env: &'a Env, filename1: Value<'a>) -> EmacsResult> { + for _i in 0..10000 { + let message = String::from_lisp(filename1)?; + let message = message.into_lisp(env)?; + env.call(get_t, &[message])?; + } + Ok(env.nil()) +} + #[cfg(test)] mod tests { use super::*; diff --git a/test.el b/test.el index d9fd8e4..fba55ad 100644 --- a/test.el +++ b/test.el @@ -36,6 +36,11 @@ ;; (let ((dissected (tramp-dissect-file-name "/ssh:joe@sadusk.com:/home/joe/hello.txt"))) ;; (message (prin1-to-string dissected)) ;; ) +(defun get-t (arg) + ;(message "hello") + t + ) +(message (concat "here i am again " (elt argv 0))) (add-to-list 'load-path "~/work/tramp-libssh") @@ -44,36 +49,44 @@ (setq testpath (concat "/ssh:" testhost ":" testdir)) (setq testfilename "missing.yaml") (setq testfilepath (concat testpath testfilename)) -(setq module-path "/Users/jsadusk/work/tramp-libssh/target/debug/libtramp_libssh.dylib") +(setq module-path "/Users/jsadusk/work/tramp-libssh/target/release/libtramp_libssh.dylib") (setq module-mtime -1) (message testfilepath) -(require 'rs-module) - +;(require 'rs-module) +(load module-path) +(load "/Users/jsadusk/work/tramp-libssh/build/emacs-libssh.so") (defun load-if-changed() "load if the module has changed" ;(if (file-has-changed-p module-path) - (rs-module/load module-path) - ; (message "not changed") - ; ) + ;(rs-module/load module-path) + ;(message "not changed") + ;) ) +(require 'benchmark) (defun test-libssh-insert-from-file () (interactive) (load-if-changed) (message (prin1-to-string (tramp-dissect-file-name "/ssh:joe@sadusk.com:/home/joe/data.txt"))) - (tramp-libssh-insert-file-contents1 "/ssh:joe@sadusk.com:/home/joe/data.txt" nil 4 15 nil) + (benchmark-run 1 + (tramp-libssh-insert-file-contents "/ssh:joe@sadusk.com:/home/joe/data.txt" nil 4 15 nil) + ) ) (defun test-libssh-replace-from-file () (interactive) (load-if-changed) (message (prin1-to-string (tramp-dissect-file-name "/ssh:joe@sadusk.com:/home/joe/data.txt"))) - (tramp-libssh-insert-file-contents1 "/ssh:joe@sadusk.com:/home/joe/data.txt" nil 4 15 t) + (tramp-libssh-insert-file-contents "/ssh:joe@sadusk.com:/home/joe/data.txt" nil 4 15 t) ) (defun test-libssh-write-buffer () (interactive) (load-if-changed) - (tramp-libssh-write-region nil nil testfilepath nil nil nil nil) + (message (prin1-to-string + (benchmark-elapse + (tramp-libssh-write-region nil nil testfilepath nil nil nil nil) + ) + )) ) (defun test-libssh-write-buffer-append () @@ -85,10 +98,25 @@ (defun test-libssh-file-exists () (interactive) (load-if-changed) - (message (prin1-to-string (tramp-libssh-file-exists-p testfilepath))) + (message (prin1-to-string + (benchmark-elapse + (message (prin1-to-string (tramp-libssh-file-exists-p testfilepath))) + ))) + (message (prin1-to-string + (benchmark-elapse (message (prin1-to-string (tramp-libssh-file-exists-p "/ssh:joe@sadusk.com:/home/joe/blarh.txt"))) + ))) + (message (prin1-to-string + (benchmark-elapse + (message (prin1-to-string (file-exists-p testfilepath))) + ))) + (message (prin1-to-string + (benchmark-elapse + (message (prin1-to-string (file-exists-p "/ssh:joe@sadusk.com:/home/joe/blarh.txt"))) + ))) ) + (defun test-directory-files () (interactive) (load-if-changed) @@ -137,6 +165,58 @@ (message (prin1-to-string (tramp-libssh-file-attributes testfilepath 'string))) ) +(defun test-call-process() + (interactive) + (load-if-changed) + (message testpath) + (let ((default-directory "/ssh:joe@sadusk.com:/home/joe")) + (message default-directory) + (message "libssh") + (message (prin1-to-string + (benchmark-elapse + (tramp-libssh-process-file "ls" nil "output" nil '("-l" "/usr")) + ))) + (message (prin1-to-string + (benchmark-elapse + (tramp-libssh-process-file "ls" nil "output" nil '("-l" "/usr")) + ))) + (message (prin1-to-string + (benchmark-elapse + (tramp-libssh-process-file "ls" nil "output" nil '("-l" "/usr")) + ))) + (message "tramp") + (message default-directory) + (message (prin1-to-string + (benchmark-elapse + (process-file "ls" nil "output" nil "-l" "/usr") + ))) + ) + ) + +(defun bare-lisp (arg) + (dotimes (i 10000) + (get-t arg) + ) + ) + + +(defun test-bare () + (interactive) + (load-if-changed) + (message "rust") + (message (prin1-to-string + (benchmark-elapse + (tramp-libssh-bare "hello")))) + (message "lisp") + (message (prin1-to-string + (benchmark-elapse + (bare-lisp "hello")))) + (message "C") + (message (prin1-to-string + (benchmark-elapse + (emacs-libssh-test-bare "hello")))) + ) + ;(message (read-string "hello: " nil nil nil nil))\ ;(message (read-passwd "asdf: " 't))