/*
This file is part of c++script.
c++script is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
c++script is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with c++script. If not, see .
*/
/*
Execution of sub-program. Replacement for system() with more control
on the output of the sub-program (handle 1 and 2).
This is very old code used by the COMMAND object..
*/
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "c++script.h"
using namespace std;
static bool debug = false;
namespace cpps{
// Use to split input buffer into lines
struct INPUT_BUF{
string buf;
size_t pos=0;
unsigned nblines=0; // Number of complete line (with \n) in the buffer
void clear(){
buf.clear();
pos = 0;
nblines = 0;
}
void clear_start(){
if (pos > 0){
buf.erase(0,pos);
pos = 0;
}
}
// Find the number of complete line in the buffer
void set_nblines(){
nblines = 0;
for (auto c:buf|views::drop(pos)){ //size_t i = pos; i 0 || (eof && pos < buf.size());
}
};
class POPENFD{
friend class POPENWAITS;
protected:
struct {
int in; // stdin of the process
FILE *fin; // Used to send buffered output
int out; // stdout of the process
int err; // error
int ctl; // Special channel used to send a message
// indicating the end of the process (avoid some races)
}fds;
INPUT_BUF outbuf;
INPUT_BUF errbuf;
bool bytesent; // Flag telling us if at least one message
// was sent to the application, so we must give
// it some time to process it at close time.
bool eofout;
bool eoferr;
public:
POPENFD (int handlein, int handleout);
protected:
POPENFD (void);
public:
void flush (void);
FILE *getfout (void);
void grabhandles (int &in, int &out, int &err);
bool iseof (void);
virtual bool isok (void);
void loadout (const char *lines);
int process (int ret_select,
fd_set&in,
int otherfd,
bool&ctlmsg);
int readerr (string &line);
private:
void readif (fd_set *in,
int fd,
INPUT_BUF &buf,
bool &ctlmsg,
bool &eof);
int readline (string &line, INPUT_BUF &buf, bool eof);
public:
int readout (string &line);
int readoutraw (char *data, int size);
void send (string_view msg);
void send (const void *msg, int len);
void sendf (const char *ctl, ...);
void seteof (void);
protected:
void setfds (int fdin,
int fdout,
int fderr,
int fdctl);
public:
int setup (fd_set&in, int maxfd, int otherfd);
int wait (int timeout);
int wait (int timeout, int otherfd);
protected:
int wait (int timeout, int otherfd, bool&ctlmsg);
public:
virtual ~POPENFD (void);
bool has_lines(){
if (debug){
cerr << format ("has_lines eofout={} eoferr={} output.nb={} outbuf.pos={} output.size={} errbuf.nb={} errbuf.pos={} errbuf.size={}\n"
,eofout,eoferr,outbuf.nblines,outbuf.pos,outbuf.buf.size(),errbuf.nblines,errbuf.pos,errbuf.buf.size());
cerr << format ("has_lines {}\n",outbuf.has_lines(eofout) || errbuf.has_lines(eoferr));
}
return outbuf.has_lines(eofout) || errbuf.has_lines(eoferr);
}
};
class POPEN: public POPENFD{
int pid = -1;
int status = -1;
int cur_dead; // Current level of child_counter
public:
POPEN (std::string_view command);
POPEN (std::string_view command,
int uid,
bool keepenv,
bool keepcwd);
POPEN (std::string_view command, std::string_view args);
POPEN (std::string_view command, std::string_view args, int uid);
POPEN (std::string_view command, int uid);
private:
void checksignal (void);
public:
int close (void);
void closepipe (void);
void forget (void);
int getstatus (void);
private:
void init (std::string_view command,
int uid,
bool keepenv,
bool keepcwd);
void initarg (std::string_view command,
std::string_view args,
int uid);
public:
bool isok (void);
void kill (void);
int wait (int timeout);
int wait (int timeout, int otherfd);
private:
void waitend (void);
void waitone (void);
public:
~POPEN (void);
};
class POPENUSER: public POPEN{
public:
POPENUSER (std::string_view cmd);
};
static int popen_fdset (int fd, fd_set &in, int maxfd)
{
if (fd != -1){
FD_SET (fd,&in);
if (fd > maxfd) maxfd = fd;
}
return maxfd;
}
static bool popen_fd_isset (int fd, fd_set &in)
{
bool ret = false;
if (fd != -1){
ret = FD_ISSET (fd,&in);
}
return ret;
}
void POPENFD::setfds (
int fdin,
int fdout,
int fderr,
int fdctl)
{
fds.in = fdin;
fds.fin = fdopen (fds.in,"w");
fds.out = fdout;
fds.err = fderr;
fds.ctl = fdctl;
}
POPENFD::POPENFD()
{
bytesent = false;
eofout = eoferr = false;
fds.in = fds.out = fds.err = fds.ctl = -1;
fds.fin = NULL;
}
POPENFD::POPENFD(int handlein, int handleout)
{
bytesent = false;
setfds (handlein,handleout,-1,-1);
eofout = eoferr = false;
}
POPENFD::~POPENFD()
{
if (fds.in != -1){
if (fds.fin != NULL){
fclose (fds.fin);
}else{
::close (fds.in);
}
}
::close (fds.out);
::close (fds.err);
::close (fds.ctl);
}
/*
Return != 0 if the pipe is corretly opened
*/
bool POPENFD::isok()
{
return fds.in != -1;
}
bool POPENFD::iseof()
{
return eofout && eoferr;
}
void POPENFD::readif (
fd_set *in,
int fd,
INPUT_BUF &buf,
bool &ctlmsg,
bool &eof)
{
if (popen_fd_isset(fd,*in)){
// Clear the start of the input buffer
buf.clear_start();
char bufread[10000];
int len = read (fd,bufread,sizeof(bufread)-1);
if (len > 0){
buf.append (bufread,len);
}else{
// end of process
eof = true;
ctlmsg = true;
}
}
}
int POPENFD::setup (fd_set &in, int maxfd, int otherfd)
{
if (!iseof()){
maxfd = popen_fdset (fds.out,in,maxfd);
maxfd = popen_fdset (fds.err,in,maxfd);
maxfd = popen_fdset (fds.ctl,in,maxfd);
maxfd = popen_fdset (otherfd,in,maxfd);
if (fds.fin != NULL) fflush (fds.fin);
}
return maxfd;
}
int POPENFD::process (
int ret_select,
fd_set &in,
int otherfd,
bool &ctlmsg)
{
int ret = -1;
if (!iseof()){
#if 0
printf ("ret=%d errno=%d otherfd=%d %d %d %d %d\n"
,ret,errno,otherfd
,popen_fd_isset(fds.err,in)
,popen_fd_isset(fds.out,in)
,popen_fd_isset(otherfd,in)
,popen_fd_isset(fds.ctl,in));
#endif
ret = 0;
if (ret_select > 0){
if (popen_fd_isset(fds.err,in)
|| popen_fd_isset(fds.out,in)){
ret = 1;
readif (&in,fds.out,outbuf,ctlmsg,eofout);
readif (&in,fds.err,errbuf,ctlmsg,eoferr);
}
if (popen_fd_isset(otherfd,in)) ret |= 2;
if (popen_fd_isset(fds.ctl,in)){
char buf[10];
read (fds.ctl,buf,10); // Just remove the message
// fprintf (stderr,"ctlmsg ret=%d\n",ret);
ctlmsg = true;
::close (fds.ctl);
fds.ctl = -1;
}
}else if (ret_select == -1 && errno == EINTR){
ret = 0;
}
}
return ret;
}
/*
Wait for anything to be available from the child process
Return -1 if any error.
Return 0 if the timeout has elapsed or a signal has been received.
Return 1 if there is some data to read
Return 2 if there is some data on otherfd
Return 3 if there is some data on otherfd and on POPEN file handle
*/
int POPENFD::wait(int timeout, int otherfd, bool &ctlmsg)
{
int ret = -1;
if (debug) cerr << format ("wait iseof()={}\n",iseof());
if (!iseof()){
fd_set in;
FD_ZERO(&in);
int maxfd = setup (in,0,otherfd);
struct timeval tim;
tim.tv_usec = 0;
tim.tv_sec = timeout;
while (1){
ret = select (maxfd+1,&in,NULL,NULL,timeout != -1 ? &tim: nullptr);
if (ret != -1 || errno != EINTR) break;
}
ret = process (ret,in,otherfd,ctlmsg);
if (debug) cerr << format ("POPENFD::wait ret={} errno={} {} eofout={} eoferr={}\n",ret,errno,strerror(errno),eofout,eoferr);
}
return ret;
}
/*
Wait for anything to be available from the child process
Return -1 if any error.
Return 0 if the timeout has elapsed.
Return 1 if there is some data to read
Return 2 if there is some data on otherfd
Return 3 if there is some data on otherfd and on POPEN file handle
*/
int POPENFD::wait(int timeout, int otherfd)
{
bool dummy=false;
int ret = wait (timeout,otherfd,dummy);
// fprintf (stderr,"wait %d %d\n",ret,dummy);
if (ret == 0 && dummy) ret = -1;
return ret;
}
/*
Wait for anything to be available from the child process
Return -1 if any error.
Return 0 if the timeout has elapsed.
Return 1 if there is some data to read
*/
int POPENFD::wait(int timeout)
{
return wait (timeout,-1);
}
void POPENFD::seteof()
{
eofout = eoferr = true;
}
/*
Read one complete line or up to size byte in "line".
If there is no complete line, nothing is read
*/
int POPENFD::readline (string &line, INPUT_BUF &buf, bool eof)
{
line.clear();
bool eol = false;
for (size_t i=buf.pos; i 0 ? 0 : -1;
}
/*
Read one line of stderr if available (won't block).
Return -1 if no more to read.
*/
int POPENFD::readerr (string &line)
{
return readline (line,errbuf,eoferr);
}
/*
Read one line of stdout if available (won't block).
Return -1 if no more to read.
*/
int POPENFD::readout (string &line)
{
return readline (line,outbuf,eofout);
}
#if 0
/*
Read some bytes of stdout if available (won't block).
Return the number of bytes written in buf
Return -1 if no more to read.
*/
int POPENFD::readoutraw (char *data, int size)
{
int ret = -1;
int len = outbuf.getlen();
if (len < size){
outbuf.copy (data);
outbuf.setfrom ("");
ret = len;
}else{
ret = size - 1;
strncpy (data,outbuf.get(),ret);
data[ret] = '\0';
outbuf.setfrom (outbuf.get()+ret);
}
return ret;
}
/*
get the FILE handle used to send strings to the process.
The application does not have to close it.
*/
FILE *POPENFD::getfout ()
{
// We assume the application will send commands or message to the
// process
bytesent = true;
return fds.fin;
}
#endif
/*
Send a string to the standard input of the process
*/
void POPENFD::send (string_view msg)
{
if (fds.fin != NULL){
bytesent = true;
for (auto c:msg) fputc (c,fds.fin);
}
}
#if 0
/*
Send a buffer to the standard input of the process
*/
void POPENFD::send (const void *msg, int len)
{
if (fds.fin != NULL){
bytesent = true;
fwrite (msg,1,len,fds.fin);
}
}
#endif
/*
Flush the output buffer
*/
void POPENFD::flush ()
{
if (fds.fin != NULL) fflush (fds.fin);
}
static int child_counter = 0;
// Pid of a process we are currently waiting for
struct RETCOD {
int pid;
int status;
bool done;
};
// We wait for at most 20 process concurently
static RETCOD tbcod[20];
static void fchild(int )
{
/*
A long time ago, they changed something in the linux schedular.
A newly forked process was scheduled before its parent. A fix
was done in this function to cope with this. Not much later
they returned to the original behavior.
The new behavior could create something like this
A parent fork
the child executes and dies
the parent, immediatly after the fork tries to install its pid
in tbcod[] (using popen_reservpid below). But it is too late
because the child is already dead and the code below
has already been executed.
To solve this, a second loop (adding in tbcod[]) was created.
So when the parent finally execute, it will find the pid
in tbcod and know the child is already done.
Now, so far so good. But what happen if your application fork()
on its own (or use libc popen()). Then the pid of the ending
process will be added here (unless you installed another signal
handler). And it will never be removed.
Now, the kernel seems to behave to old way, but you never know
if the new way will prevail. This is why we have this cleanup
in removepid. In removepid, we know that no POPEN object has
fork() recently.
So in popen_forgetpid, we have a special cleanup of the tbcod[]
array.
Using debug_printf in fchild may create a deadlock because
it tries to allocate memory or something.
*/
signal (SIGCHLD,fchild);
child_counter++;
int status,pid;
// Several process may be dying at the same time
while ((pid=waitpid(-1,&status,WNOHANG)) > 0){
unsigned i;
for (i=0; ipw_dir;
}else{
home = getenv ("HOME");
}
homestr = format("HOME={}",home);
tb[2] = homestr.c_str();
int nbenv = 3;
popen_add2env ("DISPLAY",displaystr,tb,nbenv);
popen_add2env ("LANG",langstr,tb,nbenv);
popen_add2env ("XAUTHORITY",authstr,tb,nbenv);
tb[nbenv] = NULL;
environ = (char**)tb;
}
::close (fderr[0]);
::close (fdctl[0]);
string newcommand(command);
if (pos == string_view::npos){
dup2 (fdin[0],0);
dup2 (fdout[1],1);
::close (fdin[0]);
::close (fdin[1]);
::close (fdout[0]);
::close (fdout[1]);
}else{
newcommand = newcommand.substr(0,pos)+format("{}",fdinout[1])+newcommand.substr(pos+8);
::close (fdinout[0]);
}
dup2 (fderr[1],2);
::close (fderr[1]);
setuid (uid);
seteuid(uid);
// Closing unnecessary file handle
for (int i=3; i<100; i++){
if (i != fdctl[1] && i != fdinout[1]) ::close (i);
}
if (!keepcwd) chdir ("/");
int ret = system (newcommand.c_str());
write (fdctl[1],"done\n",5);
#if 0
FILE *fout = fopen ("/tmp/popen.log","a");
fprintf (fout,"%s -> %d\n",command,ret);
fclose (fout);
#endif
if ((ret & 0xff)==0) ret >>= 8;
_exit (ret);
}else if (pid == -1){
::close (fderr[0]);
::close (fderr[1]);
::close (fdctl[0]);
::close (fdctl[1]);
::close (fdin[0]);
::close (fdin[1]);
::close (fdout[0]);
::close (fdout[1]);
::close (fdinout[0]);
::close (fdinout[1]);
}else{
popen_reservpid (pid);
::close (fderr[1]);
::close (fdctl[1]);
if (pos != string_view::npos){
::close (fdinout[1]);
setfds (fdinout[0],fdinout[0],fderr[0],fdctl[0]);
}else{
::close (fdin[0]);
::close (fdout[1]);
setfds (fdin[1],fdout[0],fderr[0],fdctl[0]);
}
}
}
}
POPEN::POPEN(
string_view command,
int uid) // Effective and real UID of the new process
{
init (command,uid,false,false);
}
POPEN::POPEN(
string_view command,
int uid, // Effective and real UID of the new process
bool keepenv,
bool keepcwd)
{
init (command,uid,keepenv,keepcwd);
}
POPEN::POPEN(string_view command)
{
init (command,geteuid(),false,false);
}
void POPEN::initarg(
string_view command,
string_view args,
int uid)
{
string cmd = format ("{} {}",command,args);
init (cmd,uid,false,false);
}
/*
Get the UID of the user who is login on this terminal.
Even if this user has done a "su" command, we can find out who he
really is.
*/
int popen_getloginuid()
{
int uid = getuid();
const char *tname = ttyname(0);
if (tname != NULL){
struct utmp ut;
strcpy (ut.ut_line,tname+5);
struct utmp *u = getutline (&ut);
if (u != NULL){
struct passwd *p = getpwnam (u->ut_user);
if (p != NULL) uid = p->pw_uid;
}
}
return uid;
}
POPEN::POPEN(string_view command, string_view args)
{
initarg(command,args,geteuid());
}
POPEN::POPEN(string_view command, string_view args, int uid)
{
initarg(command,args,uid);
}
/*
Execute a command for a user.
Unlike POPEN which make sure tne environment is secure, POPENUSER
is really trusting the user. This is never called by Linuxconf
*/
POPENUSER::POPENUSER(string_view cmd)
: POPEN (cmd,getuid(),true,true)
{
}
POPEN::~POPEN()
{
if (bytesent){
// We have sent some message to the process.
// we must close the link and let the process complete by itself
close();
}
kill();
}
static int process_findchild(int pid)
{
return -1;
}
void POPEN::kill()
{
if (pid != -1){
int child_pid = process_findchild(pid);
if (child_pid != -1) ::kill (child_pid,SIGTERM);
::kill (pid,SIGTERM);
waitend();
}
}
/*
Forget about the child process.
It won't be killed by the destructor of POPEN
*/
void POPEN::forget()
{
popen_forgetpid (pid);
pid = -1;
}
void POPEN::waitone()
{
if (pid != -1){
for (unsigned i=0; i>= 8;
popen_forgetpid (pid);
pid = -1;
}
break;
}
}
}
}
/*
Wait until the process is really dead and recover its end status
*/
void POPEN::waitend ()
{
while (1){
waitone();
if (pid == -1) break;
if (::kill(pid,0)==-1){
fprintf (stderr,"POPEN::waitend waiting for a gone process, odd: %d (%s)\n",pid,strerror(errno));
//pid = -1;
//break;
}
//sleep(10); // Should be interrupted when the child exited
if(waitpid (pid,&status,0)==-1){
waitone();
}else{
if ((status & 0xff)==0) status >>= 8;
}
popen_forgetpid (pid);
pid = -1;
break;
}
}
/*
Return != 0 if the pipe is corretly opened
*/
bool POPEN::isok()
{
if (debug) cerr << format ("isok pid={}\n",pid);
return pid != -1;
}
/*
Check if some signal was received about child death
*/
void POPEN::checksignal()
{
if (child_counter > cur_dead){
cur_dead = child_counter;
waitone();
}
}
/*
Wait for anything to be available from the child process
Return -1 if any error (or end of session).
Return 0 if the timeout has elapsed.
Return 1 if there is some data to read
Return 2 if there is some data on otherfd
Return 3 if there is some data on otherfd and on POPEN file handle
*/
int POPEN::wait(int timeout, int otherfd)
{
int ret = -1;
#if 0
FILE *fout = fopen ("/tmp/popen.log","a");
fprintf (fout,"wait pid=%d fds=[%d %d %d %d] eofout=%d eoferr=%d\n",pid
,fds.in,fds.out,fds.err,fds.ctl,eofout,eoferr);
fclose (fout);
#endif
if (pid != -1){
checksignal();
bool ctlmsg = false;
ret = POPENFD::wait (timeout,otherfd,ctlmsg);
checksignal();
if (ctlmsg){
waitend();
if (ret == 0) ret = -1;
}
}else if(!iseof()){
ret = POPENFD::wait (timeout,-1);
}
return ret;
}
/*
Wait for anything to be available from the child process
Return -1 if any error.
Return 0 if the timeout has elapsed.
Return 1 if there is some data to read
*/
int POPEN::wait(int timeout)
{
return wait (timeout,-1);
}
/*
Return the status code of the ending process
*/
int POPEN::getstatus()
{
return status;
}
/*
Closing the PIPE to standard input of the command.
*/
void POPEN::closepipe()
{
if (fds.fin != NULL){
fclose (fds.fin);
fds.fin = NULL;
}
if (fds.in != -1){
::close (fds.in);
fds.in = -1;
}
}
/*
Closing the PIPE to standard input of the command.
Expect the command to terminate soon (20 seconds timeout).
Return the exit code
*/
int POPEN::close()
{
closepipe();
time_t end = time(NULL) + 20;
//printf ("outbuf size %d eof %d pid %d\n",outbuf.getlen(),eof,pid);
while (time(NULL) < end && !iseof() && pid != -1){
wait(20);
//printf ("loop outbuf size %d eof %d pid %d code = %d\n",outbuf.getlen(),eof,pid,code);
}
//printf ("fin outbuf size %d eof %d pid %d\n",outbuf.getlen(),eof,pid);
return getstatus();
}
void COMMANDEXEC_iterator::fill_line()
{
while (!pop->has_lines() && !pop->iseof()){
int ret = readnext();
if (debug) cerr << format ("opeation ++ readnext={}\n",ret);
//if (ret == -1) break;
}
if (pop->iseof()){
if (debug) cerr << format ("status={}\n",pop->getstatus());
}
if (pop->readerr(content.line)!=-1){
content.is_err = true;
}else if (pop->readout(content.line)!=-1){
content.is_err = false;
}
}
int COMMANDEXEC_iterator::readnext() const
{
int ret = pop->wait(-1);
if (debug) cerr << format("readnext ret={}\n",ret);
return ret;
}
COMMANDEXEC_iterator::COMMANDEXEC_iterator(POPENUSER *_pop)
:pop(_pop)
{
fill_line();
}
COMMANDEXEC_iterator::COMMANDEXEC_iterator(const COMMANDEXEC_iterator &n)
: pop(n.pop),content(n.content)
{
}
COMMANDEXEC_iterator & COMMANDEXEC_iterator::operator = (const COMMANDEXEC_iterator &n)
{
content = n.content;
pop = n.pop;
return *this;
}
COMMANDEXEC_iterator::~COMMANDEXEC_iterator()
{
}
COMMANDEXEC_iterator & COMMANDEXEC_iterator::operator ++()
{
if (debug) cerr << "operator ++\n";
fill_line();
return *this;
}
COMMANDEXEC_iterator COMMANDEXEC_iterator::operator ++(int)
{
if (debug) cerr << "operator ++(int)\n";
return *this;
}
COMMANDEXEC_line &COMMANDEXEC_iterator::operator *() const
{
if (debug) cerr << format ("operator * {} {}\n",content.is_err,content.line.size());
return (COMMANDEXEC_line&)content; //(COMMANDEXEC_iterator&)*this;
}
bool COMMANDEXEC_iterator::operator == (const COMMANDEXEC_iterator &d) const
{
if (debug) cerr << "operator ==\n";
return true;
}
bool COMMANDEXEC_iterator::operator == (const COMMANDEXEC_end &d) const
{
if (debug) cerr << format("operator == end isok()={} iseof={}\n",pop->isok(),pop->iseof());
return pop->iseof();
}
COMMANDEXEC::COMMANDEXEC(std::string_view _command)
:pop(make_shared(_command)),command(_command)
{
}
COMMANDEXEC::COMMANDEXEC(const COMMANDEXEC &n)
:pop(n.pop),command(n.command)
{
}
COMMANDEXEC::~COMMANDEXEC()
{
}
COMMANDEXEC_iterator COMMANDEXEC::begin() noexcept
{
return COMMANDEXEC_iterator (pop.get());
}
COMMANDEXEC_iterator COMMANDEXEC::begin() const noexcept
{
return COMMANDEXEC_iterator (pop.get());
}
COMMANDEXEC_end COMMANDEXEC::end()
{
return COMMANDEXEC_end();
}
}
#ifdef TEST
using namespace cpps;
int main (int argc, char *argv[])
{
if (argc > 1){
cpps::option optdebug ('d',"debug","Debug messages",false,false);
cpps::option arg ('m',"match","match parameter","something",false);
cpps::option sequence ('s',"sequence","Do many tests",false,false);
cpps::endoptions(argc,argv);
debug = optdebug.val;
string buf;
bool first = true;
for (auto &a:cpps::args){
if (!first) buf += ' ';
first = false;
buf += a;
}
cout << format ("exec command :{}:\n",buf);
#if 0
POPENUSER p(buf);
while (p.isok()){
if (p.wait(10)>0){
string line;
while (p.readout(line)!=-1){
cout << line << endl;
}
}else{
break;
}
}
#else
cpps::COMMAND cmd (buf);
cout << "==================== cmd.run()\n";
for (auto &&l:cmd.run()){
cout << format ("{}{}-{}\n",l.is_error() ? "* " : "+ ",l.line.size(),l.line);
}
if (sequence.val){
cout << "==================== dir.ls|take(2)\n";
cpps::DIRNAME dir("/tmp");
for (auto &&l: dir.ls()|views::take(2)){
cout << format ("file {}\n",l.name);
}
cout << "==================== cmd.run()|take(2)\n";
for (auto &&l:cmd.run() | views::take(2)){
cout << format ("{}{}-{}\n",l.is_error() ? "* " : "+ ",l.line.size(),l.line);
}
#if 1
cout << "==================== cmd.run()|match(arg)\n";
for (auto &&l:cmd.run() | cpps::match (arg.val)){
cout << format ("{}{}-{}\n",l.is_error() ? "* " : "+ ",l.line.size(),l.line);
}
#endif
}
#endif
}
return 0;
}
#endif