diff --git a/.gitignore b/.gitignore index a6e760d..c538562 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,4 @@ /run-tests.sh /Zmakefile /TODO +/lxsh diff --git a/include/options.hpp b/include/options.hpp index 45f724d..012207a 100644 --- a/include/options.hpp +++ b/include/options.hpp @@ -6,6 +6,8 @@ extern ztd::option_set options; extern bool opt_minimize; +extern bool piped; // for cd in substitutions + ztd::option_set gen_options(); void print_help(const char* arg0); diff --git a/include/struc.hpp b/include/struc.hpp index f9bc0ef..46ac262 100644 --- a/include/struc.hpp +++ b/include/struc.hpp @@ -11,13 +11,16 @@ /* structure: +list_t : condlist[] + block: - group - - brace: condlist[] - - subsh: condlist[] + - brace: list_t + - subsh: list_t - cmd: arglist[] -- if - - pair[] +- case + - arg (input) + - pair[] (cases) condlist: pipeline[] @@ -30,14 +33,11 @@ arglist: arg: - raw -- subarg[] +- subarg[] split into subarguments in case of subshells subarg: - raw -- variable -- block: subshell - - +- block: subshell (substitution) */ #define AND_OP false @@ -49,6 +49,7 @@ class pipeline; class arg; class subarg; +// type pack of condlist typedef std::vector list_t; block make_cmd(std::vector args); @@ -63,11 +64,14 @@ public: void setstring(std::string const& str); + // has to be set manually std::string raw; std::vector sa; + // return if is a string and only one subarg std::string string(); + std::string generate(int ind); }; @@ -99,12 +103,17 @@ public: class block { public: + // type enum blocktype { none, subshell, brace, main, cmd, function, case_block, for_block, if_block, while_block}; + blocktype type; + + // ctor block() { type=none; } block(blocktype in) { type=in; } - blocktype type; + // subshell/brace/main list_t cls; + // cmd arglist args; @@ -143,12 +152,13 @@ public: condlist() { parallel=false; } condlist(block const& pl) { parallel=false; this->add(pl);} condlist(pipeline const& pl) { parallel=false; this->add(pl);} - void add(pipeline const& pl, bool or_op=false); - bool parallel; - // don't push_back here - std::vector or_ops; + bool parallel; // & at the end + + void add(pipeline const& pl, bool or_op=false); + // don't push_back here, use add() instead std::vector pls; + std::vector or_ops; // size of 1 less than pls, defines separator between pipelines std::string generate(int ind); }; @@ -157,12 +167,15 @@ public: class subarg { public: + // type enum argtype { string, subshell }; + argtype type; + + // ctor subarg(argtype in) { this->type=in; } subarg(std::string const& in="") { type=string; val=in; } subarg(block const& in) { type=subshell; sbsh=in; } - argtype type; // raw string std::string val; // subshell diff --git a/include/util.hpp b/include/util.hpp index 9c1ae99..e86e45a 100644 --- a/include/util.hpp +++ b/include/util.hpp @@ -21,7 +21,8 @@ template std::string strf( const std::string& format, Args ... args ) { size_t size = snprintf( nullptr, 0, format.c_str(), args ... ) + 1; // Extra space for '\0' - if( size <= 0 ){ throw std::runtime_error( "Error during formatting." ); } + if( size <= 0 ) + throw std::runtime_error( "Error during formatting." ); std::unique_ptr buf( new char[ size ] ); snprintf( buf.get(), size, format.c_str(), args ... ); return std::string( buf.get(), buf.get() + size - 1 ); // We don't want the '\0' inside @@ -29,4 +30,10 @@ std::string strf( const std::string& format, Args ... args ) std::string delete_brackets(std::string const& in); +std::string pwd(); + +void _exec(std::string const& bin, std::vector const& args); + +std::string stringReplace(std::string subject, const std::string& search, const std::string& replace); + #endif //UTIL_HPP diff --git a/lxsh b/lxsh deleted file mode 100755 index 65272ee..0000000 Binary files a/lxsh and /dev/null differ diff --git a/src/generate.cpp b/src/generate.cpp index 25848bb..be56a6f 100644 --- a/src/generate.cpp +++ b/src/generate.cpp @@ -6,6 +6,8 @@ #include "options.hpp" #include "parse.hpp" +#include + std::vector included; bool is_sub_special_cmd(std::string in) @@ -99,10 +101,24 @@ std::string concatargs(std::vector args) std::string generate_resolve(std::vector args, int ind) { + std::string ret; + auto opts=create_resolve_opts(); auto rargs = opts.process(args, false, true, false); std::string cmd=concatargs(rargs); + std::string dir; + + if(!opts['C'] && !piped) + { + dir=pwd(); + std::string cddir=ztd::exec("dirname", g_origin).first; + cddir.pop_back(); + chdir(cddir.c_str()); + } + + + // exec call auto p=ztd::shp("exec "+cmd); if(!opts['f'] && p.second!=0) @@ -114,17 +130,29 @@ std::string generate_resolve(std::vector args, int ind) if(opts['p']) { - block bl = parse(p.first); - std::string ret = bl.generate(ind, false); + block bl; + try + { + bl = parse(p.first); + } + catch(ztd::format_error& e) + { + throw ztd::format_error(e.what(), "command `"+cmd+'`', e.data(), e.where()); + } + ret = bl.generate(ind, false); std::string tmpind=INDENT; ret = ret.substr(tmpind.size()); ret.pop_back(); // remove \n - return ret; } else { - return p.first; + ret = p.first; } + + if(!opts['C'] && !piped) + chdir(dir.c_str()); + + return ret; } std::string generate_include(std::vector args, int ind) @@ -140,6 +168,17 @@ std::string generate_include(std::vector args, int ind) else if(opts['d']) quote = '"'; + std::string curfile=g_origin; + std::string dir; + + if(!opts['C'] && !piped) + { + dir=pwd(); + std::string cddir=ztd::exec("dirname", curfile).first; + cddir.pop_back(); + chdir(cddir.c_str()); + } + // do shell resolution std::string command="for I in "; for(auto it: rargs) @@ -149,6 +188,7 @@ std::string generate_include(std::vector args, int ind) auto v = split(inc, '\n'); + std::string file; block bl; bool indent_remove=true; @@ -159,10 +199,13 @@ std::string generate_include(std::vector args, int ind) add_include(it) ) // not already included { file=import_file(it); + if(opts['e']) + file = stringReplace(file, "\"", "\\\""); if(opts['r']) ret += file; else { + g_origin=it; try { bl = parse(quote + file + quote); @@ -182,6 +225,9 @@ std::string generate_include(std::vector args, int ind) } } } + if(!opts['C'] && !piped) + chdir(dir.c_str()); + g_origin=curfile; if(!opts['r']) ret.pop_back(); @@ -215,13 +261,17 @@ std::string block::generate_case(int ind) ind++; for(auto cs: this->cases) { + // case definition : foo) if(!opt_minimize) ret += INDENT; ret += cs.first.generate(ind) + ')'; if(!opt_minimize) ret += '\n'; + // commands for(auto it: cs.second) ret += it.generate(ind+1); + // end of case: ;; if(opt_minimize) { + // ;; can be right after command if(ret[ret.size()-1] == '\n') ret.pop_back(); } @@ -233,6 +283,7 @@ std::string block::generate_case(int ind) } ret += ";;\n"; } + // close case ind--; if(!opt_minimize) ret += INDENT; ret += "esac"; @@ -251,25 +302,30 @@ std::string block::generate(int ind, bool print_shebang) { if(type==function) { + // function definition ret += shebang + "()"; if(!opt_minimize) ret += '\n' + INDENT; - ret += "{\n"; + ret += "{\n"; + // commands for(auto it: cls) ret += it.generate(ind+1); - if(!opt_minimize) - ret += INDENT; + if(!opt_minimize) ret += INDENT; + // end function ret += '}'; } else if(type==subshell) { + // open subshell ret += '('; if(!opt_minimize) ret += '\n'; + // commands for(auto it: cls) ret += it.generate(ind+1); if(opt_minimize) - ret.pop_back(); + ret.pop_back(); // ) can be right after command else ret += INDENT; + // close subshell ret += ')'; } else if(type==brace) diff --git a/src/main.cpp b/src/main.cpp index 7d4deef..2c940e3 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -7,18 +7,19 @@ #include +#include "util.hpp" #include "struc.hpp" #include "parse.hpp" #include "options.hpp" int main(int argc, char* argv[]) { - auto args=options.process(argc, argv); + auto args=options.process(argc, argv, false, true); if(options['m']) opt_minimize=true; - bool piped=false; + piped=false; if(options['h']) { @@ -58,34 +59,48 @@ int main(int argc, char* argv[]) } } - if(!piped && !options['C']) - { - std::string dir=ztd::exec("dirname", file).first; - dir.pop_back(); - file=ztd::exec("basename", file).first; - file.pop_back(); - chdir(dir.c_str()); - } - g_origin=file; add_include(file); - if(args.size()>0) + try { - try + block sh(parse(import_file(file))); + if(options['E']) + { + std::string data=sh.generate(); + // generate path + std::string tmpdir = (getenv("TMPDIR") != NULL) ? getenv("TMPDIR") : "/tmp" ; + std::string filepath = tmpdir + "/lxsh_exec_" + ztd::sh("tr -dc '[:alnum:]' < /dev/urandom | head -c10"); + // create stream + std::ofstream stream(filepath); + if(!stream) + throw std::runtime_error("Failed to write to file '"+filepath+'\''); + + // output + stream << data; + stream.close(); + auto p = ztd::exec("chmod", "+x", filepath); + if(p.second != 0) + return p.second; + + args.erase(args.begin()); + _exec(filepath, args); + + } + else { - block sh(parse(import_file(file))); std::cout << sh.generate(); } - catch(ztd::format_error& e) - { - printFormatException(e); - } - catch(std::exception& e) - { - std::cerr << e.what() << std::endl; - } } + catch(ztd::format_error& e) + { + printFormatException(e); + } + catch(std::exception& e) + { + std::cerr << e.what() << std::endl; + } + return 0; } diff --git a/src/options.cpp b/src/options.cpp index fa0c6e0..a3a6ce2 100644 --- a/src/options.cpp +++ b/src/options.cpp @@ -2,13 +2,15 @@ ztd::option_set options = gen_options(); bool opt_minimize; +bool piped=false; + ztd::option_set gen_options() { ztd::option_set ret; ret.add(ztd::option('h', "help", false, "Display this help message")); ret.add(ztd::option('m', "minimize", false, "Minimize code")); - ret.add(ztd::option('C', "no-cd", false, "Don't change directories")); + ret.add(ztd::option('E', "exec", false, "Directly exec instead of outputting")); ret.add(ztd::option("help-commands", false, "Print help for linker commands")); return ret; } @@ -19,8 +21,9 @@ ztd::option_set create_include_opts() opts.add( ztd::option('s', false, "Single quote contents"), ztd::option('d', false, "Double quote contents"), - // ztd::option('e', false, "Escape for double quotes"), + ztd::option('e', false, "Escape double quotes"), ztd::option('r', false, "Include raw contents, don't parse"), + ztd::option('C', false, "Don't cd to folder the file is in"), ztd::option('f', false, "Force include even if already included. Don't count as included") ); return opts; @@ -30,8 +33,8 @@ ztd::option_set create_resolve_opts() { ztd::option_set opts; opts.add( - // ztd::option('e', false, "Escape for double quotes"), ztd::option('p', false, "Parse contents as shell code"), + ztd::option('C', false, "Don't cd to folder this file is in"), ztd::option('f', false, "Ignore non-zero return values") ); return opts; @@ -39,7 +42,7 @@ ztd::option_set create_resolve_opts() void print_help(const char* arg0) { - printf("%s [options] [file]\n", arg0); + printf("%s [options] [arg...]\n", arg0); printf("Link extended shell, allows file including and command resolving\n"); printf("See --help-commands for help on linker commands\n"); printf("\n"); @@ -50,7 +53,7 @@ void print_help(const char* arg0) void print_include_help() { printf("%%include [options] \n"); - printf("Include the targeted files. Paths are relative to folder of current file\n"); + printf("Include the targeted files, from folder of current file\n"); printf(" - Regular shell processing applies to the file arguments\n"); printf(" - Only includes not already included files\n"); printf(" - `%%include` in command substitutions replaces the substitution\n"); @@ -64,7 +67,7 @@ void print_include_help() void print_resolve_help() { printf("%%resolve [options] \n"); - printf("Execute shell command and substitute output. Paths is from folder of current file\n"); + printf("Execute shell command and substitute output, from folder of current file\n"); printf(" - Fails if return value is not 0. Can be ignored with -f\n"); printf(" - `%%resolve` in command substitutions replaces the substitution\n"); printf(" =>`%%resolve_s` can be used inside a substitution to prevent this\n"); diff --git a/src/parse.cpp b/src/parse.cpp index 39e4351..8af1df0 100644 --- a/src/parse.cpp +++ b/src/parse.cpp @@ -241,7 +241,9 @@ std::pair parse_condlist(const char* in, uint32_t size, uint return std::make_pair(ret, i); } -// parse condlists until ) +// parse a subshell +// must start right after the opening ( +// ends at ) and nothing else std::pair parse_subshell(const char* in, uint32_t size, uint32_t start) { uint32_t i = skip_unread(in, size, start); @@ -258,7 +260,9 @@ std::pair parse_subshell(const char* in, uint32_t size, uint32_ return std::make_pair(ret,i); } -// parse condlists until } +// parse a brace block +// must start right after the opening { +// ends at } and nothing else std::pair parse_brace(const char* in, uint32_t size, uint32_t start) { uint32_t i = skip_unread(in, size, start); @@ -279,6 +283,9 @@ std::pair parse_brace(const char* in, uint32_t size, uint32_t s return std::make_pair(ret,i); } +// parse a functions +// must start right after the () +// then parses a brace block std::pair parse_function(const char* in, uint32_t size, uint32_t start) { block ret(block::function); @@ -301,16 +308,18 @@ std::pair parse_cmd(const char* in, uint32_t size, uint32_t sta block ret(block::cmd); uint32_t i=start; + // parse first arg and keep it auto tp=parse_arg(in, size, i); i=skip_unread(in, size, tp.second); - if(word_eq("()", in, size, i)) + if(word_eq("()", in, size, i)) // is a function { i += 2; auto pp = parse_function(in, size, i); + // first arg is function name pp.first.shebang = tp.first.raw; return pp; } - else + else // is a command { auto pp=parse_arglist(in, size, start); ret.args = pp.first; @@ -320,16 +329,20 @@ std::pair parse_cmd(const char* in, uint32_t size, uint32_t sta return std::make_pair(ret, i); } +// parse a case block +// must start right after the case +// ends at } and nothing else std::pair parse_case(const char* in, uint32_t size, uint32_t start) { block ret(block::case_block); - uint32_t i=start; + uint32_t i=skip_unread(in, size, start);; - auto pa = parse_arg(in, size, i); // case arg + // get the treated argument + auto pa = parse_arg(in, size, i); ret.carg = pa.first; i=skip_unread(in, size, pa.second); - + // must be an 'in' if(!word_eq("in", in, size, i)) { auto pp=parse_arg(in, size, i); @@ -338,6 +351,7 @@ std::pair parse_case(const char* in, uint32_t size, uint32_t st i=skip_unread(in, size, i+2); + // parse all cases while(i parse_case(const char* in, uint32_t size, uint32_t st if(in[i] == ')') throw ztd::format_error( strf("Unexpected token '%c', expecting ';;'", in[i]), g_origin, in, i ); + // end of case: on same line if(in[i-1] == ';' && in[i] == ';') { i++; break; } + // end of case: on new line i=skip_unread(in, size, i); if(word_eq(";;", in, size, i)) { i+=2; break; } + // end of block: ignore missing ;; if(word_eq("esac", in, size, i)) break; @@ -381,6 +398,7 @@ std::pair parse_case(const char* in, uint32_t size, uint32_t st ret.cases.push_back(cc); } + // ended before finding esac if(i>=size) throw ztd::format_error("Expecting 'esac'", g_origin, in, i); i+=4; @@ -405,8 +423,7 @@ std::pair parse_block(const char* in, uint32_t size, uint32_t s } else if(word_eq("case", in, size, i)) { - i = skip_unread(in, size, i+4); - ret = parse_case(in, size, i); + ret = parse_case(in, size, i+4); } else // command { @@ -424,15 +441,16 @@ std::pair parse_block(const char* in, uint32_t size, uint32_t s // parse main block parse(const char* in, uint32_t size) { - block ret(block::main); uint32_t i=0; + // get shebang if(word_eq("#!", in, size, 0)) { i=skip_until(in, size, 0, "\n"); ret.shebang=std::string(in, i); } i = skip_unread(in, size, i); + // parse all commands while(i + block make_cmd(std::vector args) { block cmd(block::cmd); @@ -24,6 +26,7 @@ void arg::setstring(std::string const& str) { sa.resize(0); sa.push_back(subarg(str)); + raw = str; } void condlist::add(pipeline const& pl, bool or_op) diff --git a/src/util.cpp b/src/util.cpp index 85e3cef..5560a44 100644 --- a/src/util.cpp +++ b/src/util.cpp @@ -1,5 +1,9 @@ #include "util.hpp" +#include + +#include + std::string indenting_string="\t"; std::string indent(int n) @@ -52,3 +56,36 @@ std::string delete_brackets(std::string const& in) } return ret; } + +std::string pwd() +{ + char buf[2048]; + if(getcwd(buf, 2048) != NULL) + { + std::string ret=ztd::exec("pwd").first; // getcwd failed: call pwd + ret.pop_back(); + return ret; + } + return std::string(buf); +} + +void _exec(std::string const& bin, std::vector const& args) +{ + std::vector rargs; + rargs.push_back((char*) bin.c_str()); + for(auto it=args.begin(); it!=args.end(); it++) + rargs.push_back((char*) it->c_str()); + rargs.push_back(NULL); + execvp(bin.c_str(), rargs.data()); +} + +std::string stringReplace(std::string subject, const std::string& search, const std::string& replace) +{ + size_t pos = 0; + while ((pos = subject.find(search, pos)) != std::string::npos) + { + subject.replace(pos, search.length(), replace); + pos += replace.length(); + } + return subject; +}