commit 418059acde4e38dd4b23cb8073a13da5972c172c Author: Venelin Date: Sat Oct 7 19:38:27 2023 +0300 init diff --git a/basicTypes.h b/basicTypes.h new file mode 100644 index 0000000..b905f81 --- /dev/null +++ b/basicTypes.h @@ -0,0 +1,15 @@ +#pragma once + +#include +#include + +typedef int8_t I8; +typedef uint8_t U8; +typedef int16_t I16; +typedef uint16_t U16; +typedef int32_t I32; +typedef uint32_t U32; +typedef int64_t I64; +typedef uint64_t U64; + +typedef int fd_t; // file descriptor diff --git a/childProcess.cpp b/childProcess.cpp new file mode 100644 index 0000000..1092833 --- /dev/null +++ b/childProcess.cpp @@ -0,0 +1,189 @@ +#include +#include +#include +#include +#include +#include "childProcess.hpp" + + +RetProc::RetProc(Proc origin, int status) + : Proc(origin) + , normal_exit(WIFEXITED(status)) + , returned(WEXITSTATUS(status)) +{} + +RetProc::operator bool() +{ + return normal_exit && returned == 0; +} + +/* create a process taking input from pipe childIn */ +static Proc createProc(const char* const argv[], const fd_t childIn[2]) +{ + Proc childproc; + childproc.in = childIn[1]; + childproc.out = STDOUT_FILENO; + + childproc.pid = fork(); + if(childproc.pid == 0) /* child */ + { + close(childIn[1]); + dup2(childIn[0], STDIN_FILENO); + + exec_or_die(argv); + } + + return childproc; +} + +/* create a process and redirect it's output to a pipe */ +static Proc createRedirProc(const char* const argv[]) +{ + fd_t childOut[2]; + pipe(childOut); + + Proc childproc; + childproc.in = STDIN_FILENO; + childproc.out = childOut[0]; + + childproc.pid = fork(); + /* close(STDIN_FILENO); flush?*/ + if(childproc.pid != 0) /* parent */ + { + close(childOut[1]); + } + else + { + dup2(childOut[1], STDOUT_FILENO); + + exec_or_die(argv); + } + + return childproc; +} + +/* create a process redirecting both in and out */ +static Proc createRedirProc(const char* const argv[], const fd_t childIn[2]) +{ + fd_t childOut[2]; /* todo: add error #include "processTypes.hpp"*/ + pipe(childOut); + + Proc childproc; + childproc.in = childIn[1]; + childproc.out = childOut[0]; + + childproc.pid = fork(); + /* close(STDIN_FILENO); flush?*/ + if(childproc.pid != 0) /* parent */ + { + close(childOut[1]); + } + else + { + close(childIn[1]); + dup2(childIn[0], STDIN_FILENO); + + dup2(childOut[1], STDOUT_FILENO); + + exec_or_die(argv); + } + + return childproc; +} + + +Proc createRedirProcess(const char* const argv[], U32 flags) /* todo rename */ +{ + fd_t childIn[2]; + Proc proc; + + if(flags & INPUT) + { + pipe(childIn); + if(flags & OUTPUT) + proc = createRedirProc(argv, childIn); + else + proc = createProc(argv, childIn); + } + else if(flags & OUTPUT) + proc = createRedirProc(argv); + else + proc = createProcess(argv); + + return proc; +} + +Proc createCapProcess(const char* const argv[], fd_t in, fd_t err) +{ + fd_t childOut[2]; + pipe(childOut); + + Proc proc; + proc.in = in; + proc.out = childOut[0]; + proc.err = err; + + proc.pid = fork(); + if(proc.pid == 0) /* child */ + { + if(in != STDIN_FILENO) + dup2(in, STDIN_FILENO); + + dup2(childOut[1], STDOUT_FILENO); + + if(err != STDERR_FILENO) + dup2(err, STDERR_FILENO); + + exec_or_die(argv); + } + else + { + close(childOut[1]); + } + + return proc; +} + +Proc createProcess(const char* const argv[], fd_t in, fd_t out, fd_t err) +{ + Proc proc; + proc.in = in; + proc.out = out; + proc.err = err; + + proc.pid = fork(); + if(proc.pid == 0) /* child */ + { + if(in != STDIN_FILENO) + dup2(in, STDIN_FILENO); + + if(out != STDOUT_FILENO) + dup2(out, STDOUT_FILENO); + + if(err != STDERR_FILENO) + dup2(err, STDERR_FILENO); + + exec_or_die(argv); + } + + return proc; +} + +RetProc runProcess(const char* const argv[], fd_t in, fd_t out, fd_t err) +{ + Proc p = createProcess(argv, in, out, err); + + // Wait to end + int status; + waitpid(p.pid, &status, 0); + + return RetProc(p, status); +} + +void exec_or_die(const char* const argv[]) +{ + execvp(argv[0], (char* const *)argv); + + std::cerr << "Can't execute: " << argv[0] << ' ' << strerror(errno) << std::endl; + _exit(1); // _exit since we are a child +} diff --git a/childProcess.hpp b/childProcess.hpp new file mode 100644 index 0000000..6ae42fc --- /dev/null +++ b/childProcess.hpp @@ -0,0 +1,52 @@ +#pragma once + +#include "basicTypes.h" + +struct Proc +{ + pid_t pid; + fd_t in; + fd_t out; + fd_t err; +}; + +struct RetProc: public Proc /* returned process */ +{ + RetProc(Proc, int status); /* status as returned by waitpid */ + bool normal_exit; + U8 returned; /* return code */ + + /* A returned process evaluates to true if it exited normaly and + * returned 0 */ + explicit operator bool(); + +}; + +enum Redirect: U32 +{ + NOTHING =0, + INPUT = 1 << 0, + OUTPUT = 1 << 1, + ERR = 1 << 2 /* todo */ +}; + + +/* For all funcions argv is an array of command parameters + * argv[0] is the command itself, argv needs to end with nullptr */ + +/* Create a proccess and redirect anyting flaged to a new pipe + flags - INPUT, OUTPUT, ERR + return pipe file descriptors*/ +Proc createRedirProcess(const char* const argv[], U32 flags); + +/* Capture ouput to a pipe, can take alternative input and err FDs */ +Proc createCapProcess(const char* const argv[], fd_t in=0, fd_t err=2); + +/* Can take FDs as arguments which will be used instead of std */ +Proc createProcess(const char* const argv[], fd_t in=0, fd_t out=1, fd_t err=2); + +/* Same but waits for the process to finish */ +RetProc runProcess(const char* const argv[], fd_t in=0, fd_t out=1, fd_t err=2); + +/* execvp the command or exit */ +void exec_or_die(const char* const argv[]); diff --git a/commands.cpp b/commands.cpp new file mode 100644 index 0000000..5d6ba7c --- /dev/null +++ b/commands.cpp @@ -0,0 +1,264 @@ +#include +#include +#include +#include +#include +#include +#include +#include "commands.hpp" +#include "childProcess.hpp" + +enum constants: I32 +{ + FILE_PERMISIONS = S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH +}; + +static fd_t open_or_die(const char* file, I32 flags) +{ + fd_t fd = open(file, flags, FILE_PERMISIONS); + if(fd == -1) + { + std::cerr << "Can't open: " << file << ' ' << strerror(errno) << std::endl; + exit(1); + } + return fd; +} + +RetProc Cmd::operator()(fd_t in, fd_t out, fd_t err) +{ + RetProc p = runProcess(argv.data(), in, out, err); + + // Close files if such were used + // if(in > 2) + // close(in); + // if(out > 2) + // close(out); + // if(err > 2) + // close(err); + + return p; +} + +Proc Cmd::detach(fd_t in, fd_t out, fd_t err) +{ + return createProcess(argv.data(), in, out, err); +} + +void Cmd::append_args(std::initializer_list args) +{ + argv.reserve(argv.size() + args.size()); + argv.back() = *args.begin(); + for(auto it = args.begin() + 1; it < args.end(); ++it) + argv.push_back(*it); + argv.push_back(nullptr); +} + +Cmd Cmd::operator+(const char* arg) +{ + Cmd result(*this); + result.argv.back() = arg; + result.argv.push_back(nullptr); + return result; +} + +Cmd& Cmd::operator+=(const char* arg) +{ + argv.back() = arg; + argv.push_back(nullptr); + return *this; +} + + +PendingCmd::PendingCmd(const Cmd& origin, fd_t in, fd_t out, fd_t err) + : cmd(origin) + , in(in) + , out(out) + , err(err) + , execed_(false) +{} + +PendingCmd::~PendingCmd() +{ + if(!execed_) + operator()(); + /* if(in != 0) */ + /* close(in); */ + /* if(out != 1) */ + /* close(out); */ + /* if(err != 2) */ + /* close(err); */ +} + +RetProc PendingCmd::operator()() +{ + assert(!execed_ && "Executed command twice"); + execed_ = true; + return cmd(in, out, err); +} + +RetProc PendingCmd::runRedir() +{ + assert(!execed_ && "executed command twice"); assert(out==1 && "already redirected"); + execed_ = true; + + RetProc p = runProcess(cmd.argv.data(), in, err); + return p; +} + +Proc PendingCmd::detach() +{ + assert(!execed_ && "Executed command twice"); + execed_ = true; + return cmd.detach(in, out, err); +} + +Proc PendingCmd::detachRedirOut() +{ + assert(!execed_ && "Executed command twice"); assert(out==1 && "already redirected"); + execed_ = true; + return createCapProcess(cmd.argv.data(), in, err); +} + +void PendingCmd::cancel() +{ + execed_ = true; +} + +str $(const PendingCmd& cmd) +{ + RetProc p = const_cast(cmd).runRedir(); + + // check output size (not portable?) + int pipe_size; + int rc = ioctl(p.out, FIONREAD, &pipe_size); assert(rc==0); + + // write to the string directly, todo: find a better way + str output; + output.resize(pipe_size); + read(p.out, (char*)output.c_str(), output.size()); + return output; +} + +void exec(const Cmd& cmd) +{ + exec_or_die(cmd.argv.data()); +} + +PendingCmd operator,(const PendingCmd& cfirst, const Cmd& second) +{ + auto& first = const_cast(cfirst); + first(); + return PendingCmd(second); +} + +PendingCmd operator,(RetProc, const Cmd& second) +{ + return PendingCmd(second); +} + +PendingCmd operator|(const PendingCmd& cfirst, const Cmd& second) +{ + auto& first = const_cast(cfirst); + fd_t firstOut = first.detachRedirOut().out; + return PendingCmd(second, firstOut); +} + + +RetProc operator&&(const PendingCmd& cfirst, const Cmd& csecond) +{ + auto& first = const_cast(cfirst); + auto& second = const_cast(csecond); + RetProc firstProc = first(); + + if(firstProc) + return second(); + + return firstProc; +} + +RetProc operator&&(RetProc p, const Cmd& ccmd) +{ + auto& cmd = const_cast(ccmd); + if(p) + return cmd(); + + return p; +} + +RetProc operator||(const PendingCmd& cfirst, const Cmd& csecond) +{ + auto& first = const_cast(cfirst); + auto& second = const_cast(csecond); + RetProc firstProc = first(); + + if(!firstProc) + return second(); + + return firstProc; +} + +RetProc operator||(RetProc p, const Cmd& ccmd) +{ + auto& cmd = const_cast(ccmd); + if(!p) + return cmd(); + + return p; +} + +PendingCmd& operator>(const PendingCmd& cmd, const char* file) +{ + fd_t fd = open_or_die(file, O_WRONLY | O_CREAT); + return cmd > fd; +} +PendingCmd& operator>(const PendingCmd& ccmd, fd_t fd) +{ + auto& cmd = const_cast(ccmd); + assert(cmd.out == 1 || !"ERROR: Output is already redirected!"); + + cmd.out = fd; + return cmd; +} + +PendingCmd& operator>>(const PendingCmd& cmd, const char* file) +{ + fd_t fd = open_or_die(file, O_WRONLY | O_CREAT | O_APPEND); + return cmd > fd; +} +PendingCmd& operator>>(const PendingCmd& cmd, fd_t fd) +{ + return cmd > fd; +} + +PendingCmd& operator>=(const PendingCmd& cmd, const char* file) +{ + fd_t fd = open_or_die(file, O_WRONLY | O_CREAT); + return cmd >= fd; +} +PendingCmd& operator>=(const PendingCmd& ccmd, fd_t fd) +{ + auto& cmd = const_cast(ccmd); + assert(cmd.err == 2 || !"ERROR: Error output is already redirected!"); + + cmd.err = fd; + return cmd; +} + +PendingCmd& operator<(const PendingCmd& cmd, const char* file) +{ + fd_t fd = open_or_die(file, O_RDONLY); + return cmd < fd; +} +PendingCmd& operator<(const PendingCmd& ccmd, fd_t fd) +{ + auto& cmd = const_cast(ccmd); + assert(cmd.in == 0 || !"ERROR: Input is already redirected!"); + cmd.in = fd; + return cmd; +} + +PendingCmd operator&(const PendingCmd& cfirst, const Cmd& second) +{ + const_cast(cfirst).detach(); + return PendingCmd(second); +} diff --git a/commands.hpp b/commands.hpp new file mode 100644 index 0000000..652a135 --- /dev/null +++ b/commands.hpp @@ -0,0 +1,160 @@ +#pragma once + +#include +#include +#include "childProcess.hpp" + +/* All CONST references are used and cast away to allow for taking + both l and r values without using templates or making copies + + implicitly convert to it but not to const char* and we want the + implicit conversion for quick scripting + arg pointers are kept and used not copied */ + +/* A shell command */ +class Cmd +{ +public: + /* all args must be const char* */ + template + explicit Cmd(Args... args) + : argv({args...}) + { + argv.push_back(nullptr); + } + + /* Execute the command, if arguments are not given use stdin,out,err + else use the given file desciptors. this can also be used + to redirect err to out by giving err=1 like in shell */ + RetProc operator()(fd_t in=0, fd_t out=1, fd_t err=2); + + /* Run the command, don't wait to return like shell's & */ + Proc detach(fd_t in=0, fd_t out=1, fd_t err=2); + + /* Append arguments */ + void append_args(std::initializer_list); + /* Append an argument and return the new command */ + Cmd operator+(const char* arg); + /* Append an argument */ + Cmd& operator+=(const char* arg); + + std::vector argv; /* null terminateded arg list */ +}; + +/* An instance of a shell comand that is pending execution + if the command is not executed during the life of the obj + its executed on destruction */ +class PendingCmd +{ +public: + /* Can be implicitly created from a command */ + PendingCmd(const Cmd&, fd_t in=0, fd_t out=1, fd_t err=2); + /* Execute the command on destruction */ + ~PendingCmd(); + + /* No copy we use unnamed return value optimization to return PendingCmd without destruction */ + PendingCmd(const PendingCmd&) = delete; + PendingCmd& operator=(const PendingCmd&) = delete; + + /* Run the command */ + RetProc operator()(); + /* Run the command, capturing the output in a new pipe */ + RetProc runRedir(); + + /* Run the command async + shell: cmd & + becomes: cmd.detach() */ + Proc detach(); + /* Detach but redirect output to a pipe */ + Proc detachRedirOut(); + + /* Prevent a pending command from being executed on destruction */ + void cancel(); + + Cmd cmd; + fd_t in, out, err; +private: + bool execed_; +}; + + +/* Anything that takes PendingCmd can take Cmd aswell */ + +/* Like shell's exec */ +void exec(const Cmd&); /* todo: take Pending? */ + +/* Run commands in sequence + shell: + ls + cd + becomes: + ls, + cd; +*/ +PendingCmd operator,(const PendingCmd&, const Cmd&); +PendingCmd operator,(RetProc, const Cmd&); +/* Forbiden funcion to prevent wrong sequencing such as: + echo, echo && echo + here the 2nd and 3rd echos would be ran before 1st + due to the C++ operator precedence*/ +PendingCmd operator,(const PendingCmd&, RetProc) = delete; + + +/* Shell pipe operator | - execute two commands, second takes input + from first can be chained many times */ +PendingCmd operator|(const PendingCmd&, const Cmd&); + +/* Shell operator && - run the second command only if first returns 0 (no errors)*/ +RetProc operator&&(const PendingCmd&, const Cmd&); +RetProc operator&&(RetProc, const Cmd&); + +/* Shell operator || - run second only if first returns != 0 + Operator precedence is different from shell, C precendence is && > || + so mixing || and && may not compile, but shouldn't cause other issues, + use () to resolve these cases */ +RetProc operator||(const PendingCmd&, const Cmd&); +RetProc operator||(RetProc, const Cmd&); + +/* Shell operator > - redirect output to file + shell: cmd > file 2>&1 + becomes: cmd > "file" >=1 */ +PendingCmd& operator>(const PendingCmd&, const char* file); +/* A file descriptor can be given istead of a file path in which case + * no truncation occurs */ +PendingCmd& operator>(const PendingCmd&, fd_t); +/* Same but append rather then truncate the file */ +PendingCmd& operator>>(const PendingCmd&, const char* file); +PendingCmd& operator>>(const PendingCmd&, fd_t); +/* Redirect errors to file */ +PendingCmd& operator>=(const PendingCmd&, const char* file); +PendingCmd& operator>=(const PendingCmd&, fd_t); +/* todo >>= */ + +/* Shell oprator < use file as input */ +PendingCmd& operator<(const PendingCmd&, const char* file); +PendingCmd& operator<(const PendingCmd&, fd_t); + +/* Shell operator & but only for single processes + e.g.: echo a && echo b & echo c + in shell would detach "echo a && echo b" but here + this is not possible */ +PendingCmd operator&(const PendingCmd&, const Cmd&); + +/* string that is implicitly cast to const char* and doesn't deallocate + to allow for simpler synthax, speed and safety + e.g: echo + some_string + rather then: echo + some_string.c_str() */ +class str: public std::basic_string /* todo: allocator */ +{ +public: + using std::basic_string::basic_string; + operator const char*() { return c_str();} +}; + +/* Execute a command and capture the output + shell: + var=$(ls) + becomes: + string var = $(ls); +*/ +str $(const PendingCmd&); diff --git a/cppipe.cpp b/cppipe.cpp new file mode 100644 index 0000000..c3b90a2 --- /dev/null +++ b/cppipe.cpp @@ -0,0 +1,128 @@ +#include +#include +#include +#include +#include +#include "commands.hpp" + +using namespace std; +using namespace std::filesystem; + +static path get_cache_path(const char* file); +static bool compile_file(const char* file, path cache, bool debug); // return true on success + +int main(int argc, char* argv[]) +{ + if(argc < 2) + { + cout << "Usage: cppipe [-g] FILE [ARGUMENTS]...\n"; + return 1; + } + if(!strcmp(argv[1], "--help")) + { + cout << "Usage: cppipe [-g] FILE [ARGUMENTS]...\n" + << "Compile and run C++ source FILE that uses the cppipe library.\n" + "Pass the ARGUMENTS to he compiled binary.\n" + "The binaries are cached and recompiled only if the source is newer.\n" + "-g debug the binary, asserts are also enabled\n"; + return 0; + + } + + bool debug = false; + int file_arg = 1; + if(!strcmp(argv[1], "-g")) + { + if(argc < 3) + { + cout << "Usage: cppipe [-g] FILE [ARGUMENTS]...\n"; + return 1; + } + + debug = true; + file_arg = 2; + } + + const char* file = argv[file_arg]; + if( !exists(file) ) + { + cerr << "File: " << file << " doesn't exist\n"; + exit(1); + } + + path cache( get_cache_path(file) ); // cache bins to avoid recompiles + + if(debug) + cache += "_dbg"; + + // Compile the passed file + if( !compile_file(file, cache, debug) ) + return 1; + + // Run the file without forking + Cmd run; + if(debug) // debug it with gdb + { + run += "gdb"; + run += "--args"; + } + + run += cache.c_str(); + for(int i = file_arg; i < argc; ++i) + run += argv[i]; + exec(run); +} + +static path get_cache_path(const char* file) +{ + path cache; + if(char* xdg_cache = getenv("XDG_CACHE_HOME")) + { + cache = xdg_cache; + cache /= "cppipe"; + } + else + { + char* home( getenv("HOME") ); + cache = home; + cache /= ".cache/cppipe"; + } + cache += absolute(file); + create_directories(cache.parent_path()); + return cache; +} + +static bool compile_file(const char* file, path cache, bool debug) +{ + // Only compile if the source is newer then the bin or we are in debug + error_code ec; + if(last_write_time(file) > last_write_time(cache, ec)) + { + Cmd compile( + "g++", + "-o", cache.c_str(), + "-pipe", "-std=c++17", "-march=native", + // "-Wall", "-Wextra", "-Wno-parentheses", + file, + "-lcppipe" + ); + + if(debug) + { + compile.append_args({ + "-g", + "-Wall", "-Wextra", "-Wno-parentheses" + }); + } + else + { + compile.append_args({ + "-Ofast", "-flto", "-DNDEBUG", "-s" + }); + } + + if( !compile() ) // if failed to compile + return false; + } + return true; +} diff --git a/install.sh b/install.sh new file mode 100755 index 0000000..f784306 --- /dev/null +++ b/install.sh @@ -0,0 +1,20 @@ +#!/bin/sh +set -e + +# Compile lib +options="-std=c++17 -Ofast -flto -march=native -DNDEBUG -Wall -Wextra -Wno-parentheses -s -pipe" +g++ -c $options commands.cpp childProcess.cpp + +# Create lib +ar r libcppipe.a commands.o childProcess.o +mv libcppipe.a /usr/local/lib +rm commands.o childProcess.o + +# Compile cppipe +g++ -o cppipe $options cppipe.cpp -lcppipe +chmod 755 cppipe +mv cppipe /usr/local/bin + +# Add headers +mkdir -p /usr/local/include/cppipe +cp basicTypes.h commands.hpp childProcess.hpp /usr/local/include/cppipe diff --git a/test.cpp b/test.cpp new file mode 100644 index 0000000..2953579 --- /dev/null +++ b/test.cpp @@ -0,0 +1,62 @@ +#include + +#include +#include + +// tab vs space +// shaded obj? +// fix warning +/* close files on destruction ?*/ +/* -> for error ? */ +// check if processes normal_exit + +using namespace std; +int main() +{ + // f(var("ab")); + Cmd ls("ls"); + Cmd ll("ls", "-l"); + Cmd grep("grep"); + Cmd echo("echo"); + Cmd rm("rm"); + + str out1 = $(ll | grep + "cpp" | grep + "child"); + if(out1.find("childProcess.cpp") != str::npos) + cout << "OK 0/11" << endl; + + Cmd success("echo", "OK 1/11"); + Cmd fail("mkdir", "."); + Cmd unexpected("echo", "FAILURE"); + success && + fail && + unexpected; + + // shouldnt compile + // success || + // unexpected && + // unexpected; + + Cmd write_file("echo", "Existing ", " ", "file. OK 2/11"); + + write_file > "file.txt"; + grep + "Existing" < "file.txt"; + + echo + "Appended to file OK 3/11" >> "file.txt"; + grep + "Appended" < "file.txt" && + rm + "file.txt" && + fail || + Cmd("echo", "OK 4/11"); + + echo + "OK 5/11" && + echo + "OK 6/11", + Cmd("echo", "OK 7/11"); + + echo + "OK 8/11" & + echo + "OK 9/11" && + echo + "OK 10/11"; + + // wait for all detached + while(wait(nullptr) != -1); + + exec( echo + $(echo + "OK 11/11") ); +}