PLUGIN shell lang: "C++" version: "1.0" date: "2022-01-31" author: "Julien BRUGUIER" maintainer: "Julien BRUGUIER " synopsis: "A basic shell handling plugin." description: %{ This plugin forks the SVM and launch a shell command. .P To communicate with the command standard input, output and error, this plugin defines all suitable objects to create a device of the official plugin com. %} example: "A stupid example" %{ .nf #!===SVMBIN=== LOG PLUGIN "svmcom.so" PLUGIN "===PLUGINLIB===" PROCESS "stupid" CODE "main" INLINE :memory com.device/cmd, STR/s :com.open shell.command <> "cat" "-" -> &cmd :com.write @&cmd "bla\(rsnbla\(rsn" :com.command @&cmd CLOSE :label loop :com.read @&cmd com.line -> &s :shutdown :unless &s INITIALISED :com.write STDOUT "> " @&s :goto loop END END .fi %} includes: %{ #include #include #include #include #include #include %} code: %{ struct RAII { RAII() { _in[0]=-1; _in[1]=-1; _out[0]=-1; _out[1]=-1; _err[0]=-1; _err[1]=-1; } void commit() { _in[0]=-1; _in[1]=-1; _out[0]=-1; _out[1]=-1; _err[0]=-1; _err[1]=-1; } int _in[2]; int _out[2]; int _err[2]; ~RAII() { if(_in[0]>=0) { ::close(_in[0]); } if(_in[1]>=0) { ::close(_in[1]); } if(_out[0]>=0) { ::close(_out[0]); } if(_out[1]>=0) { ::close(_out[1]); } if(_err[0]>=0) { ::close(_err[0]); } if(_err[1]>=0) { ::close(_err[1]); } } }; %} USE TYPE com.device help: %{ This plugin has be designed to work this the official plugin com. %} INTERRUPTION com.interrupted help: %{ This plugin use this interruption when a read has been interrupted. %} DEFINE STRUCT shell.command %{ struct_command(const int in, const int out, const int error, const pid_t child, const std::vector& command) :_in(in),_out(out),_err(error),_child(child), _command(command) {} int _in; int _out; int _err; pid_t _child; std::vector _command; %} delete default: %{} help: "Device to control command through anonymous pipes." FUNCTION shell.device_command_open [ < > <> ] [ > >> ] ? 'FREE' ? ( STR + | PTR ) -> $shell.command %{ int begin = 1; std::string s = ARGV_MARKER(0); bool enable_in = ((s==">") or (s=="<>")); bool enable_out = ((s=="<") or (s=="<>")); bool enable_err = false; bool enable_lock = true; bool merge_err = false; if(::svm_parameter_type_is_marker(svm,argv[begin])) { enable_err = true; merge_err = (ARGV_MARKER(begin)==">>"); ++begin; } if(::svm_parameter_type_is_keyword(svm,argv[begin])) { enable_lock = false; ++begin; } std::vector cmd; SVM_Value pp = ::svm_parameter_value_get(svm,argv[begin]); if(::svm_value_type_is_pointer(svm,pp)) { SVM_Address b = ::svm_value_pointer_get_address(svm,pp); SVM_Address e = b+::svm_value_pointer_get_size(svm,pp); for(SVM_Address a=b ; a: connect only to command stdin, - <: connect only to command stdout, - <>: connect to both. .P The second optional parameter indicates what to do with stderr: - nothing: do not connect to command stderr, - >: connect command stderr in a separate channel (to be read with :com.command ERROR), - >>: connect command stderr merged with stdout. .P The third optional parameter allows to disable locking all other processes while launching the command. Use with caution. .P The remaining parameters are the command name (with its path) and its command line arguments. %} FUNCTION shell.device_command_print $shell.command -> STR %{ const struct_command *command = ARGV_STRUCT(0,shell,command); std::ostringstream oss; oss << "$" ; for(const auto& c: command->_command) { oss << " " << c; } return ::svm_value_string_new__buffer(svm,oss.str().c_str(),oss.str().size()); %} help: "Function to print the shell command" FUNCTION shell.device_command_read $shell.command -> STR ? %{ struct_command *command = ARGV_STRUCT(0,shell,command); if(command->_out<0) { ERROR_INTERNAL(DEVICE,"Unable to read from shell command."); } char buffer[1025]; ::svm_process_pause(svm,CURRENT(process)); ::svm_process_interruptionnotification_enable(svm,CURRENT(process)); int r = ::read(command->_out,buffer,1024); ::svm_process_interruptionnotification_disable(svm,CURRENT(process)); ::svm_process_resume(svm,CURRENT(process)); if(r<0) { if(errno==EINTR) { ERROR_EXTERNAL(com,interrupted,"Read interrupted."); } ERROR_INTERNAL(DEVICE,"Failed to read from shell command."); } if(r==0) { return ::svm_value_string_new_null(svm); } return ::svm_value_string_new__buffer(svm,buffer,r); %} help: "Function to read from the shell command" FUNCTION shell.device_command_write $shell.command STR %{ struct_command *command = ARGV_STRUCT(0,shell,command); if(command->_in<0) { ERROR_INTERNAL(DEVICE,"Unable to write to shell command."); } SVM_String buffer = ARGV_VALUE(1,string); ssize_t w = ::write(command->_in,buffer.string,buffer.size); if(w<0) { ERROR_INTERNAL(DEVICE,"Failed to write to shell command."); } if(static_cast(w)!=buffer.size) { ERROR_INTERNAL(DEVICE,"Failed to write everything to shell command."); } %} help: "Function to write to the shell command" FUNCTION shell.device_command_idle $shell.command MUTABLE INT 3 %{ struct_command *command = ARGV_STRUCT(0,shell,command); if(command->_out>=0) { SVM_Value_Integer i = ::svm_parameter_value_get(svm,argv[1]); ::svm_value_integer_set(svm,i,command->_out); } if(command->_in>=0) { SVM_Value_Integer i = ::svm_parameter_value_get(svm,argv[2]); ::svm_value_integer_set(svm,i,command->_in); } %} help: "Function to idle from and to the shell command" FUNCTION shell.device_command_command $shell.command . * -> VALUE ? %{ struct_command *command = ARGV_STRUCT(0,shell,command); if(argc==1) { ERROR_INTERNAL(DEVICE,"Missing command"); } if(not ::svm_parameter_type_is_keyword(svm,argv[1])) { ERROR_INTERNAL(DEVICE,"Invalid command"); } std::string k = ARGV_KEYWORD(1); if(k=="ERROR") { std::ostringstream oss; char buffer[1025]; ssize_t r=0; while((r=::read(command->_err,buffer,1024))>0) { oss << std::string(buffer,r); } if(r<0) { ERROR_INTERNAL(DEVICE,"Failed to read error from shell command."); } return ::svm_value_string_new__buffer(svm,oss.str().c_str(),oss.str().size()); } if(k=="CLOSE") { if(command->_in<0) { ERROR_INTERNAL(DEVICE,"stdin already closed."); } ::close(command->_in); command->_in = -1; return ::svm_value_string_new_null(svm); } ERROR_INTERNAL(DEVICE,"Invalid command"); return ::svm_value_string_new_null(svm); %} help: %{ This function allows several operations: .TP Standard error read The keyword ERROR dumps all the standard error and returns it as a string. .TP Standard input close The keyword CLOSE closes the channel to the standard input of the command. %} FUNCTION shell.device_command_close $shell.command -> BLN %{ return NEW_VALUE(boolean,TRUE); %} help: "Function to close the shell command"