diff --git a/src/lib.rs b/src/lib.rs index 2a57dcf..d11939f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,10 +1,9 @@ -use libc; use mlua::prelude::*; use nix; use std::collections::HashMap; use std::ffi::CString; use std::fmt::Arguments; -use std::io::{Error, ErrorKind, Read, Write}; +use std::io::{BufRead, BufReader, Error, ErrorKind, Read, Write}; use std::os::fd::RawFd; use std::os::unix::io::AsRawFd; @@ -17,6 +16,8 @@ pub const APP_VERSION: &str = "0.1.0"; /// Application name pub const DAEMON_NAME: &str = "luad"; +const LUA_SLICE_MAGIC: usize = 0x8AD73B9F; + fn is_debug_enable() -> bool { match std::env::var("debug") { Ok(value) => return value == "1" || value == "true", @@ -136,6 +137,16 @@ macro_rules! ERROR { }) } +/// Macro for info log debug +/// +#[macro_export] +macro_rules! DEBUG { + ($($args:tt)*) => ({ + let prefix = format!(":debug@[{}:{}]: ",file!(), line!()); + let _ = LOG::log(&prefix[..], &LogLevel::DEBUG, format_args!($($args)*)); + }) +} + /// Different Logging levels for `LOG` pub enum LogLevel { /// Error conditions @@ -144,6 +155,8 @@ pub enum LogLevel { INFO, /// Warning conditions WARN, + /// Debugs message + DEBUG, } /// Log struct wrapper @@ -188,11 +201,12 @@ impl LOG { let sysloglevel = match level { LogLevel::ERROR => libc::LOG_ERR, LogLevel::WARN => libc::LOG_WARNING, + LogLevel::INFO => libc::LOG_NOTICE, _ => { if !is_debug_enable() { return Ok(()); } - libc::LOG_NOTICE + libc::LOG_INFO } }; let mut output = String::new(); @@ -450,7 +464,7 @@ struct FGCIRequest { data: Option>, state: FCGIRequestState, } - +#[repr(C)] struct FCGIOStream { fd: RawFd, id: u16, @@ -468,7 +482,7 @@ impl FCGIOStream { let mut output: Vec = Vec::new(); for value in values { - match value { + match &value { LuaNil => {} LuaValue::Boolean(v) => output.extend(v.to_string().as_bytes()), LuaValue::Integer(v) => output.extend(v.to_string().as_bytes()), @@ -481,11 +495,18 @@ impl FCGIOStream { return Err(Box::new(ERR!("Unsupported data type"))); } LuaValue::UserData(v) => { - if !v.is::() { - return Err(Box::new(ERR!("Unsupported data type"))); + if v.is::() { + let arr = v.borrow::()?; + output.extend(&arr.0); + } else { + let st = value.to_pointer() as *const LuaSlice; + if unsafe { (*st).magic } != LUA_SLICE_MAGIC { + return Err(Box::new(ERR!("Unsupported data type"))); + } + let data_slice = + unsafe { std::slice::from_raw_parts((*st).data, (*st).len) }; + output.extend(data_slice); } - let arr = v.borrow::()?; - output.extend(arr.0.clone()); } LuaValue::Error(e) => { fcgi_send_stderr(self, self.id, Some(e.to_string().into()))?; @@ -528,6 +549,24 @@ impl mlua::UserData for FCGIOStream { .map_err(|e| mlua::Error::external(ERR!(e.to_string()))) }, ); + methods.add_method_mut("send_file", |_, this: &mut FCGIOStream, path: String| { + let file = std::fs::File::open(path)?; + let mut buf_reader = BufReader::with_capacity(2048, file); + loop { + let length = { + let buffer = buf_reader.fill_buf()?; + fcgi_send_stdout(this, this.id, Some(buffer.to_vec())) + .map_err(|e| mlua::Error::external(ERR!(e.to_string())))?; + buffer.len() + }; + if length == 0 { + break; + } + buf_reader.consume(length); + } + Ok(()) + }); + methods.add_method_mut( "print", |_, this: &mut FCGIOStream, values: mlua::Variadic<_>| { @@ -535,8 +574,24 @@ impl mlua::UserData for FCGIOStream { .map_err(|e| mlua::Error::external(ERR!(e.to_string()))) }, ); - methods.add_method("raw_fd", |_, this: &FCGIOStream, ()| Ok(this.fd)); + methods.add_method("fd", |_, this: &FCGIOStream, ()| Ok(this.fd)); methods.add_method("id", |_, this: &FCGIOStream, ()| Ok(this.id)); + methods.add_method("log_info", |_, _: &FCGIOStream, string: String| { + INFO!("{}", string); + Ok(()) + }); + methods.add_method("log_error", |_, _: &FCGIOStream, string: String| { + ERROR!("{}", string); + Ok(()) + }); + methods.add_method("log_debug", |_, _: &FCGIOStream, string: String| { + DEBUG!("{}", string); + Ok(()) + }); + methods.add_method("log_warn", |_, _: &FCGIOStream, string: String| { + WARN!("{}", string); + Ok(()) + }); } } @@ -569,7 +624,9 @@ fn fcgi_execute_request_handle(rq: &mut FGCIRequest) -> Result<(), Box( match header.kind { FCGIHeaderType::BeginRequest => { let body = FCGIBeginRequestBody::from_bytes(&fcgi_read_body(stream, &header)?); - INFO!("Begin Request: {:?}, with body {:?}", header, body); + DEBUG!("Begin Request: {:?}, with body {:?}", header, body); if body.role != FCGIRole::Responder { fcgi_send_end_request(stream, header.id, EndRequestStatus::UnknownRole)?; return Err(Box::new(ERR!("Only Responder role is supported"))); @@ -697,7 +754,7 @@ pub fn process_request( ); } else { if header.length == 0 { - INFO!( + DEBUG!( "All param records read, now wait for stdin data on request: {}", header.id ); @@ -719,7 +776,7 @@ pub fn process_request( ); } else { if header.length == 0 { - INFO!( + DEBUG!( "All stdin records read, now wait for stdout data on request: {}", header.id ); @@ -816,7 +873,7 @@ fn fcgi_decode_params( let key = String::from_utf8(data[index..index + key_len].to_vec())?; let value: String = String::from_utf8(data[index + key_len..index + key_len + value_len].to_vec())?; - INFO!("PARAM: [{}] -> [{}]", key, value); + DEBUG!("PARAM: [{}] -> [{}]", key, value); let _ = rq.params.insert(key, value); Ok(()) } @@ -826,6 +883,15 @@ fn lua_new_bytes(_: &mlua::Lua, size: usize) -> LuaResult { Ok(arr) } +fn lua_new_from_slice(_: &mlua::Lua, value: mlua::Value) -> LuaResult { + let st = value.to_pointer() as *const LuaSlice; + if unsafe { (*st).magic } != LUA_SLICE_MAGIC { + return Err(mlua::Error::external(ERR!("Unsupported data type"))); + } + let data_slice = unsafe { std::slice::from_raw_parts((*st).data, (*st).len) }; + Ok(LuabyteArray(data_slice.to_vec())) +} + fn lua_new_bytes_from_string(_: &mlua::Lua, string: String) -> LuaResult { let arr = LuabyteArray(string.as_bytes().to_vec()); Ok(arr) @@ -837,9 +903,11 @@ impl mlua::UserData for LuabyteArray { fn add_methods<'lua, M: mlua::UserDataMethods<'lua, Self>>(methods: &mut M) { methods.add_method("size", |_, this: &LuabyteArray, ()| Ok(this.0.len())); - methods.add_method("ptr", |_, this:&LuabyteArray, ()| Ok(this.0.as_ptr() as usize)); + methods.add_method("ptr", |_, this: &LuabyteArray, ()| { + Ok(this.0.as_ptr() as usize) + }); - methods.add_method("write", |_, this: &LuabyteArray, path: String| { + methods.add_method("fileout", |_, this: &LuabyteArray, path: String| { match std::fs::File::create(&path) { Ok(mut file) => { if let Err(error) = file.write_all(&this.0) { @@ -878,7 +946,6 @@ impl mlua::UserData for LuabyteArray { Ok(string) => Ok(Some(string)), }, ); - methods.add_meta_method_mut( mlua::MetaMethod::NewIndex, |_, this, (index, value): (isize, u8)| { @@ -898,3 +965,27 @@ impl mlua::UserData for LuabyteArray { methods.add_meta_method(mlua::MetaMethod::Len, |_, this, ()| Ok(this.0.len())); } } + +#[repr(C)] +pub struct LuaSlice { + magic: usize, + len: usize, + data: *const u8, +} + +#[no_mangle] +pub extern "C" fn fcgi_send_slice(fd: RawFd, id: u16, ptr: *const u8, size: usize) -> isize { + let data_slice = unsafe { std::slice::from_raw_parts(ptr, size) }.to_vec(); + let mut stream = FCGIOStream { fd, id }; + + if let Err(error) = fcgi_send_stdout(&mut stream, id, Some(data_slice)) { + ERROR!("Unable to send data slice: {}", error); + return -1; + } + return 0; +} + +#[no_mangle] +pub extern "C" fn lua_slice_magic() -> usize { + return LUA_SLICE_MAGIC; +} diff --git a/src/main.rs b/src/main.rs index bd70da0..5370e9d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -56,7 +56,7 @@ fn handle_request(stream: &mut T) { if let Err(error) = process_request(stream) { ERROR!("Unable to process request: {}", error); } - INFO!("Request on socket {} is processed", stream.as_raw_fd()); + DEBUG!("Request on socket {} is processed", stream.as_raw_fd()); } /// Start the `fastCGI` server @@ -94,11 +94,11 @@ fn serve(config: &Config) { } else { // if there is no socket configuration, assume that the stdio is already mapped // to a socket. This is usually done by by the parent process (e.g. webserver) that launches efcgi - INFO!("No socket specified! use stdin as listenning socket"); + INFO!("No socket specified! use stdin as listening socket"); let stdin = std::io::stdin(); let fd = stdin.as_raw_fd(); if is_unix_socket(fd).unwrap() { - INFO!("Stdin is used as Unix domain socket"); + DEBUG!("Stdin is used as Unix domain socket"); let listener = unsafe { UnixListener::from_raw_fd(stdin.as_raw_fd()) }; for client in listener.incoming() { let mut stream = client.unwrap(); @@ -108,7 +108,7 @@ fn serve(config: &Config) { }); } } else { - INFO!("Stdin is used as TCP Socket"); + DEBUG!("Stdin is used as TCP Socket"); let listener = unsafe { TcpListener::from_raw_fd(stdin.as_raw_fd()) }; for client in listener.incoming() { let mut stream = client.unwrap(); @@ -174,7 +174,7 @@ fn main() { Some(pidfile) => { let mut f = std::fs::File::create(&pidfile).unwrap(); write!(f, "{}", std::process::id()).unwrap(); - INFO!("PID file created at {}", pidfile); + DEBUG!("PID file created at {}", pidfile); } None => {} }