From 38446a955162a7958b4680c1954b1beb2df4ae9c Mon Sep 17 00:00:00 2001 From: Christian Ebner Date: Thu, 21 Nov 2019 12:47:50 +0100 Subject: [PATCH] src/backup/readline.rs: impl wrapper for GNU readline In order to provide the context needed for tab completion via the readline callback, the needed mut ref is passed via a thread local storage key. Signed-off-by: Christian Ebner --- src/backup/readline.rs | 147 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 147 insertions(+) create mode 100644 src/backup/readline.rs diff --git a/src/backup/readline.rs b/src/backup/readline.rs new file mode 100644 index 00000000..41e79876 --- /dev/null +++ b/src/backup/readline.rs @@ -0,0 +1,147 @@ +//! Wrapper for GNU readline +//! +//! Implements a wrapper around the GNU readline C interface. +use std::cell::RefCell; +use std::ffi::{CStr, CString}; +use std::fs::File; +use std::sync::Once; +use std::mem; + +use libc; + +use super::catalog::{CatalogReader, DirEntry}; + +#[link(name = "readline")] +extern "C" { + fn readline(prompt: *const libc::c_char) -> *mut libc::c_char; + fn add_history(line: *const libc::c_char); + static mut rl_attempted_completion_function: extern "C" fn( + text: *const libc::c_char, + start: libc::c_int, + end: libc::c_int, + ) -> *const *const libc::c_char; +} + +/// Context holding the catalog reader and stack of the current +/// working directory. +pub struct Context { + pub catalog: CatalogReader, + pub current: Vec, +} + +pub struct Readline { + prompt: CString, + completion_callback: Option Vec>>, + ctx: Option, +} + +std::thread_local! { + static CALLBACK: RefCell Vec>>> = RefCell::new(None); + static CONTEXT: RefCell> = RefCell::new(None); +} + +static mut INIT: Once = Once::new(); + +impl Readline { + /// Create a new readline instance. + /// + /// This will create a readline instance, showing the given prompt and setting the completion + /// callback to the provided function. + pub fn new( + prompt: CString, + root: Vec, + completion_callback: Box Vec>, + catalog: CatalogReader, + ) -> Self { + unsafe { INIT.call_once(|| + rl_attempted_completion_function = Self::attempted_completion + )}; + Self { + prompt, + completion_callback: Some(completion_callback), + ctx: Some(Context { + catalog, + current: root, + }) + } + } + + /// Wrapper function to provide the libc readline functionality. + /// + /// Prints the shell prompt and returns the line read from stdin. + /// None is returned on EOF. + pub fn readline(&mut self) -> Option> { + let pptr = self.prompt.as_ptr() as *const i8; + let lptr = CALLBACK.with(|cb| + CONTEXT.with(|ctx| { + // Swap context and callback into thread local to be used in + // readline rl_attempted_completion_function callback. + mem::swap(&mut *cb.borrow_mut(), &mut self.completion_callback); + mem::swap(&mut *ctx.borrow_mut(), &mut self.ctx); + let lptr = unsafe { readline(pptr) }; + // Swap context and callback back into self. + mem::swap(&mut *cb.borrow_mut(), &mut self.completion_callback); + mem::swap(&mut *ctx.borrow_mut(), &mut self.ctx); + lptr + }) + ); + if lptr.is_null() { + None + } else { + let slice = unsafe { CStr::from_ptr(lptr) }; + let line = slice.to_bytes().to_vec(); + unsafe { + add_history(lptr as *const libc::c_char); + libc::free(lptr as *mut libc::c_void); + } + Some(line) + } + } + + /// Sets the prompt to the provided string. + pub fn update_prompt(&mut self, prompt: CString) { + self.prompt = prompt; + } + + /// Access the context from outside. + pub fn context(&mut self) -> &mut Context { + self.ctx.as_mut().unwrap() + } + + /// Callback function for the readline C implementation. + /// + /// This will call the completion function registered on instance creation + /// and pass it the context. + /// It further converts the result returned by the callback to a FFI compatible + /// list of pointers to CStrings. + extern "C" fn attempted_completion( + text: *const libc::c_char, + start: libc::c_int, + end: libc::c_int, + ) -> *const *const libc::c_char { + let list = CALLBACK.with(|cb| { + CONTEXT.with(|ctx| { + unsafe { + (*cb.borrow_mut().as_ref().unwrap())( + &mut (*ctx.borrow_mut().as_mut().unwrap()), + CStr::from_ptr(text), + start as usize, + end as usize + ) + } + }) + }); + if list.is_empty() { + return std::ptr::null(); + } + // Create a list of pointers to the individual strings returnable via FFI + let mut ptr_list: Vec<_> = list.iter().map(|s| s.as_ptr()).collect(); + // Final pointer is null, end of list + ptr_list.push(std::ptr::null()); + let ptr = ptr_list.as_ptr(); + // Pass ownership to caller + std::mem::forget(list); + std::mem::forget(ptr_list); + ptr + } +}