/*
* gensio.cpp -- IO-specific methods
* by
[email protected] at Tue Feb 26 13:28:12 CET 2002
*/
#ifdef __GNUC__
#ifndef __clang__
#pragma implementation
#endif
#endif
#if 0
extern "C" int errno;
/* OK: autodetect with autoconf */
extern "C" int _l_stat(const char *file_name, struct stat *buf);
/* OK: Imp: not in ANSI C, but we cannot emulate it! */
extern "C" int _v_s_n_printf ( char *str, size_t n, const char *format, va_list ap );
#else
#undef __STRICT_ANSI__ /* for __MINGW32__ */
#define _BSD_SOURCE 1 /* vsnprintf(); may be emulated with fixup_vsnprintf() */
#ifndef __APPLE__ /* SUXX: Max OS X has #ifndef _POSIX_SOURCE around lstat() :-( */
#define _POSIX_SOURCE 1 /* also popen() */
#endif
#ifdef USE_GNU_SOURCE_INSTEAD_OF_POSIX_SOURCE
#define _GNU_SOURCE 1 /* Implies _POSIX_C_SOURCE >= 2. */
#else
#define _POSIX_C_SOURCE 2 /* also popen() */
#endif
#define _XOPEN_SOURCE_EXTENDED 1 /* Digital UNIX lstat */
#ifndef _XPG4_2
#define _XPG4_2 1 /* SunOS 5.7 lstat() */
#endif
#undef _XOPEN_SOURCE /* pacify gcc-3.1 */
#define _XOPEN_SOURCE 1 /* popen() on Digital UNIX */
#endif
#include "gensio.hpp"
#include "error.hpp"
#include <string.h> /* strlen() */
#include <stdarg.h> /* va_list */
#if _MSC_VER > 1000
// extern "C" int getpid(void);
# include <process.h>
#else
# include <unistd.h> /* getpid() */
#endif
#include <sys/stat.h> /* struct stat */
#include <stdlib.h> /* getenv() */
#include <errno.h>
#include <signal.h> /* signal() */ /* Imp: use sigaction */
#if HAVE_DOS_BINARY
#undef __STRICT_ANSI__
#include <fcntl.h> /* O_BINARY */
#include <io.h> /* setmode() */
#endif
#define USGE(a,b) ((unsigned char)(a))>=((unsigned char)(b))
#if HAVE_PTS_VSNPRINTF /* Both old and c99 work OK */
# define VSNPRINTF vsnprintf
#else
# if OBJDEP
# warning REQUIRES: snprintf.o
# endif
# include "snprintf.h"
# define VSNPRINTF fixup_vsnprintf /* Tested, C99. */
#endif
static void cleanup(int) {
Error::cexit(Error::runCleanups(126));
}
void Files::doSignalCleanup() {
signal(SIGINT, cleanup);
signal(SIGTERM, cleanup);
#ifdef SIGHUP
signal(SIGHUP, SIG_IGN);
#endif
/* Dat: don't do cleanup for SIGQUIT */
}
GenBuffer::Writable& SimBuffer::B::vformat(slen_t n, char const *fmt, va_list ap) {
/* Imp: test this code in various systems and architectures. */
/* Dat: vsnprintf semantics are verified in configure AC_PTS_HAVE_VSNPRINTF,
* and a replacement vsnprintf is provided in case of problems. We don't
* depend on HAVE_PTS_VSNPRINTF_C99, because C99-vsnprintf is a run-time
* property.
*
* C99 vsnprintf semantics: vsnprintf() always returns a non-negative value:
* the number of characters (trailing \0 not included) that would have been
* printed if there were enough space. Only the first maxlen (@param)
* characters may be modified. The output is terminated by \0 iff maxlen!=0.
* NULL @param dststr is OK iff maxlen==0. Since glibc 2.1.
* old vsnprintf semantics: vsnprintf() returns a non-negative value or -1:
* 0 if maxlen==0, otherwise: -1 if maxlen is shorter than all the characters
* plus the trailing \0, otherwise: the number of characters (trailing \0 not
* included) that were printed. Only the first maxlen (@param)
* characters may be modified. The output is terminated by \0 iff maxlen!=0.
* NULL @param dststr is OK iff maxlen==0.
*/
if (n>0) { /* avoid problems with old-style vsnprintf */
char *s; vi_grow2(0, n+1, 0, &s); len-=n+1; /* +1: sprintf appends '\0' */
const_cast<char*>(beg)[len]='\0'; /* failsafe sentinel */
slen_t did=VSNPRINTF(s, n+1, fmt, ap);
if (did>n) did=n;
/* ^^^ Dat: always true: (unsigned)-1>n, so this works with both old and c99 */
/* Now: did contains the # chars to append, without trailing '\0' */
/* Dat: we cannot check for -1, because `did' is unsigned */
/* Dat: trailer '\0' doesn't count into `did' */
len+=did;
}
return *this;
}
GenBuffer::Writable& SimBuffer::B::vformat(char const *fmt, va_list ap) {
char dummy, *s;
slen_t did=VSNPRINTF(&dummy, 1, fmt, ap), n;
if (did>0) { /* skip if nothing to be appended */
/* vvv Dat: we cannot check for -1, because `did' is unsigned */
if ((did+1)!=(slen_t)0) { /* C99 semantics; quick shortcut */
vi_grow2(0, (n=did)+1, 0, &s); len-=n+1;
ASSERT_SIDE2(VSNPRINTF(s, n+1, fmt, ap), * 1U==did);
} else { /* old semantics: grow the buffer incrementally */
if ((n=strlen(fmt))<16) n=16; /* initial guess */
while (1) {
vi_grow2(0, n+1, 0, &s); len-=n+1; /* +1: sprintf appends '\0' */
const_cast<char*>(beg)[len]='\0'; /* failsafe sentinel */
did=VSNPRINTF(s, n+1, fmt, ap);
if ((did+1)!=(slen_t)0) {
assert(did!=0); /* 0 is caught early in this function */
assert(did<=n); /* non-C99 semantics */
break;
}
n<<=1;
}
}
len+=did;
}
return *this;
}
GenBuffer::Writable& SimBuffer::B::format(slen_t n, char const *fmt, ...) {
va_list ap;
PTS_va_start(ap, fmt);
vformat(n, fmt, ap);
va_end(ap);
return *this;
}
GenBuffer::Writable& SimBuffer::B::format(char const *fmt, ...) {
va_list ap;
PTS_va_start(ap, fmt);
vformat(fmt, ap);
va_end(ap);
return *this;
}
/* --- */
GenBuffer::Writable& Files::FILEW::vformat(slen_t n, char const *fmt, va_list ap) {
/* Dat: no vfnprintf :-( */
SimBuffer::B buf;
buf.vformat(n, fmt, ap);
fwrite(buf(), 1, buf.getLength(), f);
return*this;
}
GenBuffer::Writable& Files::FILEW::vformat(char const *fmt, va_list ap) {
vfprintf(f, fmt, ap);
return*this;
}
/* --- */
/** Must be <=32767. Should be a power of two. */
static const slen_t GENSIO_BUFLEN=4096;
void Encoder::vi_putcc(char c) { vi_write(&c, 1); }
int Decoder::vi_getcc() { char ret; return vi_read(&ret, 1)==1 ? (unsigned char)ret : -1; }
void Encoder::writeFrom(GenBuffer::Writable& out, FILE *f) {
char *buf=new char[GENSIO_BUFLEN];
int wr;
while (1) {
if ((wr=fread(buf, 1, GENSIO_BUFLEN, f))<1) break;
out.vi_write(buf, wr);
}
delete [] buf;
}
void Encoder::writeFrom(GenBuffer::Writable& out, GenBuffer::Readable& in) {
char *buf=new char[GENSIO_BUFLEN];
int wr;
while (1) {
if ((wr=in.vi_read(buf, GENSIO_BUFLEN))<1) break;
out.vi_write(buf, wr);
}
delete [] buf;
}
/* --- */
Filter::FILEE::FILEE(char const* filename) {
if (NULLP==(f=fopen(filename,"wb"))) Error::sev(Error::EERROR) << "Filter::FILEE: error open4write: " << FNQ2(filename,strlen(filename)) << (Error*)0;
closep=true;
}
void Filter::FILEE::vi_write(char const*buf, slen_t len) {
if (len==0) close(); else fwrite(buf, 1, len, f);
}
void Filter::FILEE::close() {
if (closep) { fclose(f); f=(FILE*)NULLP; closep=false; }
}
/* --- */
static FILE* fopenErr(char const* filename, char const* errhead) {
FILE *f;
if (NULLP==(f=fopen(filename,"rb")))
Error::sev(Error::EERROR) << errhead << ": error open4read: " << FNQ2(filename,strlen(filename)) << (Error*)0;
return f;
}
Filter::FILED::FILED(char const* filename) {
f=fopenErr(filename, "Filter::FileD");
closep=true;
}
slen_t Filter::FILED::vi_read(char *buf, slen_t len) {
if (len==0) { close(); return 0; }
return fread(buf, 1, len, f);
}
void Filter::FILED::close() {
if (closep) { fclose(f); f=(FILE*)NULLP; closep=false; }
}
/* --- */
Filter::UngetFILED::UngetFILED(char const* filename_, FILE *stdin_f, closeMode_t closeMode_) {
if (stdin_f!=NULLP && (filename_==NULLP || (filename_[0]=='-' && filename_[1]=='\0'))) {
f=stdin_f;
Files::set_binary_mode(fileno(f), true);
closeMode_&=~CM_unlinkp;
if (0!=(closeMode_&CM_keep_stdinp)) closeMode_&=~CM_closep;
filename_=(char const*)NULLP; /* BUGFIX at Tue Jan 4 23:45:31 CET 2005 */
} else {
f=fopenErr(filename_, "Filter::UngetFileD");
}
if (filename_!=NULLP) strcpy(const_cast<char*>(filename=new char[strlen(filename_)+1]), filename_);
else filename=(char*)NULLP;
/* fprintf(stderr,"filename:%s\n",filename); */
closeMode=closeMode_;
ftell_at=0;
ofs=0;
}
// Filter::UngetFILED::checkFILE() { }
slen_t Filter::UngetFILED::vi_read(char *buf, slen_t len) {
slen_t delta;
if (len==0) {
close(); return 0;
} else if (unget.getLength()==0) { delta=0; do_read_f:
delta+=(f==NULLP ? 0 : fread(buf, 1, len, f));
ftell_at+=delta;
return delta;
// delta+=fread(buf, 1, len, f); // write(1, buf, delta); // return delta;
} else if (ofs+len<=unget.getLength()) {
// printf("\nul=%d ft=%ld\n", unget.getLength(), ftell(f)); fflush(stdout);
memcpy(buf, unget()+ofs, len); /* Dat: don't remove from unget yet */
ftell_at+=len;
// write(1, buf, len);
if ((ofs+=len)==unget.getLength()) { unget.forgetAll(); ofs=0; }
return len;
} else {
// printf("\num=%d ft=%d\n", unget.getLength(), ftell(f)); fflush(stdout);
delta=unget.getLength()-ofs; /* BUGFIX at Sat Apr 19 17:15:50 CEST 2003 */
memcpy(buf, unget()+ofs, delta);
// write(1, buf, delta);
unget.forgetAll(); ofs=0;
buf+=delta;
len-=delta;
goto do_read_f;
}
}
void Filter::UngetFILED::close() {
/* Since close() may be closed twice (e.g. once manually, and once from the
* the destructor), everything below must be idempotent.
*/
if (0!=(closeMode&CM_closep)) {
fclose(f); f=(FILE*)NULLP;
closeMode&=~CM_closep; /* Make it idempotent. */
}
unget.forgetAll(); /* Idempotent. */
ofs=0; /* Idempotent. */
if (filename!=NULLP) {
if (0!=(closeMode&CM_unlinkp)) { remove(filename); }
delete [] filename;
filename=(const char*)NULLP; /* Make it idempotent. */
}
}
int Filter::UngetFILED::vi_getcc() {
if (unget.getLength()==0) { do_getc:
int i=-1;
if (f!=NULLP && (i=MACRO_GETC(f))!=-1) ftell_at++;
return i;
}
if (ofs==unget.getLength()) { ofs=0; unget.forgetAll(); goto do_getc; }
ftell_at++;
return unget[ofs++];
}
int Filter::UngetFILED::getc_seekable() {
/* Glibc stdio doesn't allow fseek() even if the seek would go into the
* read buffer, and fseek(f, 0, SEEK_CUR) fails for unseekable files. Fine.
*/
int c=MACRO_GETC(f);
return 0==fseek(f, -1L, SEEK_CUR) ? -2 : c;
}
bool Filter::UngetFILED::isSeekable() {
long pos, posend;
if (f==NULLP || 0!=(closeMode&CM_seekablep)) return true;
// return false;
clearerr(f); /* clears both the EOF and error indicators */
if (-1L==(pos=ftell(f)) /* sanity checks on ftell() and fseek() */
|| 0!=fseek(f, 0L, SEEK_CUR)
|| pos!=ftell(f)
|| 0!=fseek(f, 0L, SEEK_END)
|| (posend=ftell(f))==0 || posend<pos
|| 0!=fseek(f, 0L, SEEK_SET)
|| 0!=ftell(f)
|| 0!=fseek(f, pos, SEEK_SET)
|| pos!=ftell(f)
) return false;
int c;
if ((c=getc_seekable())==-2 && pos==ftell(f)) {
// if (0!=fseek(f, -1L, SEEK_CUR) || pos!=ftell(f))
// Error::sev(Error::EERROR) << "Filter::UngetFILED: cannot seek back" << (Error*)0;
return true;
}
unget << (char)c; /* not seekable, must unget the test character just read */
return false;
}
FILE* Filter::UngetFILED::getFILE(bool seekable_p) {
FILE *tf;
if (!unget.isEmpty() || (seekable_p && !isSeekable())) { do_temp:
/* must create a temporary file */
SimBuffer::B tmpnam;
if (filename==NULLP) Error::sev(Error::NOTICE) << "Filter::UngetFILED" << ": using temp for" << " `-' (stdin)" << (Error*)0;
else Error::sev(Error::NOTICE) << "Filter::UngetFILED" << ": using temp for" << ": " << FNQ2(filename,strlen(filename)) << (Error*)0;
if (NULLP==(tf=Files::open_tmpnam(tmpnam, "wb+"))) {
if (filename==NULLP) Error::sev(Error::EERROR) << "Filter::UngetFILED" << ": cannot open temp file for" << " `-' (stdin)" << (Error*)0;
else Error::sev(Error::EERROR) << "Filter::UngetFILED" << ": cannot open temp file for" << ": " << FNQ2(filename,strlen(filename)) << (Error*)0;
}
tmpnam.term0();
/* vvv change filename, so CM_unlinkp can work */
if (filename!=NULLP) {
if (0!=(closeMode&CM_unlinkp)) { remove(filename); }
delete [] filename;
}
/* This is the object data member char const* filename' in
* Filter::UngetFILED, owned by `this', deleted in the close() method
* called from the Filter::UngetFILED destructor.
*/
strcpy(const_cast<char*>(filename=new char[tmpnam.getLength()+1]), tmpnam());
/* Also makes a copy of the filename array, good */
Files::tmpRemoveCleanup(filename);
if (unget.getLength()-ofs==fwrite(unget()+ofs, 1, unget.getLength()-ofs, tf)) {
static const slen_t BUFSIZE=4096; /* BUGFIX at Sat Apr 19 15:43:59 CEST 2003 */
char *buf=new char[BUFSIZE];
unsigned got;
// fprintf(stderr, "ftf=%ld ofs=%d\n", ftell(f), ofs);
while ((0<(got=fread(buf, 1, BUFSIZE, f)))
&& got==fwrite(buf, 1, got, tf)) {}
// fprintf(stderr,"got=%d ftell=%d\n", got, ftell(tf));
delete [] buf;
}
unget.forgetAll(); ofs=0;
fflush(tf); rewind(tf);
if (ferror(tf) || ferror(f))
Error::sev(Error::EERROR) << "Filter::UngetFILED" << ": cannot write temp file" << (Error*)0;
if (0!=(closeMode&CM_closep)) fclose(f);
closeMode|=CM_closep|CM_unlinkp; /* close and unlink the temporary file */
/* ^^^ Imp: verify VC++ compilation, +others */
f=tf;
} else if (f==NULLP) { /* no real file open */
#if OS_COTY==COTY_UNIX
tf=fopen("/dev/null","rb");
#else
#if OS_COPTY==COTY_WIN9X || OS_COTY==COTY_WINNT
tf=fopen("nul","rb");
#else
tf=(FILE*)NULLP;
#endif
#endif
if (tf==NULLP) goto do_temp; /* perhaps inside chroot() */
close();
closeMode|=CM_closep|CM_unlinkp; /* close and unlink the temporary file */
f=tf;
}
closeMode|=CM_seekablep;
return f;
}
void Filter::UngetFILED::seek(long abs_ofs) {
if (abs_ofs==vi_tell()) return;
(void) getFILE(true); /* ensure seekability */
if (0!=fseek(f, abs_ofs, SEEK_SET))
Error::sev(Error::EERROR) << "Filter::UngetFILED" << ": cannot seek" << (Error*)0;
assert(unget.isEmpty());
assert(ofs==0);
ftell_at=abs_ofs;
}
void Filter::UngetFILED::unread(char const *s, slen_t slen) {
ftell_at-=slen;
if (slen==0) {
} else if (slen<=ofs) {
memcpy(const_cast<char*>(unget()+(ofs-=slen)), s, slen);
} else {
slen-=ofs;
ofs=0;
if (!unget.isEmpty() || 0!=fseek(f, -slen, SEEK_CUR)) {
assert(unget.isEmpty()); // !!
unget.vi_write(s, slen); /* complete garbage unless unget was empty */
assert(unget.getLength());
}
}
// fprintf(stderr, "%d..\n", unget.getLength());
}
void Filter::UngetFILED::appendLine(GenBuffer::Writable &buf, int delimiter) {
// fprintf(stderr, "this=%p %d (%p)\n", this, unget.getLength(), unget());
unget.term0();
if (delimiter<0) {
char rbuf[4096];
slen_t got;
/* vvv Imp: less memory copying, less stack usage? */
while (0!=(got=vi_read(rbuf, sizeof(rbuf)))) buf.vi_write(rbuf, got);
} else if (unget.getLength()==0) { do_getc:
int i;
if (f!=NULLP) {
while ((i=MACRO_GETC(f))>=0 && i!=delimiter) { buf.vi_putcc(i); ftell_at++; }
if (i>=0) buf.vi_putcc(i);
}
} else {
char const *p=unget()+ofs, *p0=p, *pend=unget.end_();
assert(ofs<=unget.getLength());
while (p!=pend && *p!=delimiter) p++;
ftell_at+=p-p0;
if (p==pend) { buf.vi_write(p0, p-p0); ofs=0; unget.forgetAll(); goto do_getc; }
ftell_at++; p++; /* found delimiter in `unget' */
buf.vi_write(p0, p-p0);
ofs+=p-p0;
}
}
/* --- */
Filter::PipeE::PipeE(GenBuffer::Writable &out_, char const*pipe_tmpl, slendiff_t i): tmpname(), out(out_), tmpename() {
/* <code similarity: Filter::PipeE::PipeE and Filter::PipeD::PipeD> */
param_assert(pipe_tmpl!=(char const*)NULLP);
SimBuffer::B *pp;
char const*s=pipe_tmpl;
lex: while (s[0]!='\0') { /* Interate throuh the template, substitute temporary filenames */
if (*s++=='%') switch (*s++) {
case '\0': case '%':
redir_cmd << '%';
break;
case 'i': /* the optional integer passed in param `i' */
redir_cmd << i;
break;
case '*': /* the optional unsafe string passed in param `i' */
redir_cmd << (char const*)i;
break;
case 'd': case 'D': /* temporary file for encoded data output */
pp=&tmpname;
put:
// if (*pp) Error::sev(Error::EERROR) << "Filter::PipeE" << ": multiple %escape" << (Error*)0;
/* ^^^ multiple %escape is now a supported feature */
// fprintf(stderr, "tmpl=(%s) s=(%s)\n", pipe_tmpl, s);
if (!*pp && !Files::find_tmpnam(*pp)) Error::sev(Error::EERROR) << "Filter::PipeE" << ": tmpnam() failed" << (Error*)0;
assert(! !*pp); /* pacify VC6.0 */
// *pp << ext;
pp->term0();
if ((unsigned char)(s[-1]-'A')<(unsigned char)('Z'-'A'))
redir_cmd.appendFnq(*pp, /*preminus:*/ true); /* Capital letter: quote from the shell */
else redir_cmd << *pp;
break;
case 'e': case 'E': /* temporary file for error messages */
pp=&tmpename;
goto put;
case 's': case 'S': /* temporary source file */
/* !! if the input is a regular, seekable file, don't copy to a temporary file */
pp=&tmpsname;
goto put;
default:
Error::sev(Error::EERROR) << "Filter::PipeE" << ": invalid %escape in pipe_tmpl" << (Error*)0;
} else redir_cmd << s[-1];
}
#if 0
if (!tmpname) Error::sev(Error::EERROR) << "Filter::PipeE" << ": no outname (%D) in cmd: " << (SimBuffer::B().appendDumpC(redir_cmd)) << (Error*)0;
#else
/* Append quoted file redirect to command, if missing */
if (!tmpname) { s=" >%D"; goto lex; }
#endif
#if !HAVE_PTS_POPEN
if (!tmpsname) { s=" <%S"; goto lex; }
#endif
// tmpname="tmp.name";
redir_cmd.term0();
if (tmpname) { Files::tmpRemoveCleanup(tmpname()); remove(tmpname()); } /* already term0() */
/* ^^^ Dat: remove() here introduces a race condition, but helps early error detection */
if (tmpename) Files::tmpRemoveCleanup(tmpename()); /* already term0() */
if (tmpsname) Files::tmpRemoveCleanup(tmpsname()); /* already term0() */
/* </code similarity: Filter::PipeE::PipeE and Filter::PipeD::PipeD> */
// fprintf(stderr, "rc: (%s)\n", redir_cmd());
#if HAVE_PTS_POPEN
if (!tmpsname) {
if (NULLP==(p=popen(redir_cmd(), "w" CFG_PTS_POPEN_B))) Error::sev(Error::EERROR) << "Filter::PipeE" << ": popen() failed: " << (SimBuffer::B().appendDumpC(redir_cmd)) << (Error*)0;
signal(SIGPIPE, SIG_IGN); /* Don't abort process with SIGPIPE signals if child cannot read our data */
} else {
#else
if (1) {
#endif
#if !HAVE_system_in_stdlib
Error::sev(Error::EERROR) << "Filter::PipeE" << ": no system() on this system" << (Error*)0;
#else
if (NULLP==(p=fopen(tmpsname(), "wb"))) Error::sev(Error::EERROR) << "Filter::PipeD" << ": fopen(w) failed: " << (SimBuffer::B().appendDumpC(redir_cmd)) << (Error*)0;
#endif
}
}
void Filter::PipeE::vi_copy(FILE *f) {
writeFrom(out, f);
if (ferror(f)) Error::sev(Error::EERROR) << "Filter::PipeE: vi_copy() failed" << (Error*)0;
fclose(f);
}
void Filter::PipeE::vi_write(char const*buf, slen_t len) {
assert(p!=NULLP);
int wr;
if (len==0) { /* EOF */
if (tmpsname) {
#if HAVE_system_in_stdlib
fclose(p);
if (0!=(Files::system3(redir_cmd()))) Error::sev(Error::EERROR) << "Filter::PipeE" << ": system() failed: " << (SimBuffer::B().appendDumpC(redir_cmd)) << (Error*)0;
remove(tmpsname());
#endif /* Dat: else is not required; would be unreachable code. */
} else {
#if HAVE_PTS_POPEN
if (0!=pclose(p)) Error::sev(Error::EERROR) << "Filter::PipeE" << ": pclose() failed; error in external prg" << (Error*)0;
#endif
}
vi_check();
p=(FILE*)NULLP;
FILE *f=fopen(tmpname(),"rb");
if (NULLP==f) Error::sev(Error::EERROR) << "Filter::PipeE" <<": fopen() after pclose() failed: "
<< redir_cmd << ": " << tmpname << (Error*)0;
vi_copy(f);
// if (ferror(f)) Error::sev(Error::EERROR) << "Filter::Pipe: fread() tmpfile failed" << (Error*)0;
// fclose(f);
/* ^^^ interacts badly when Image::load() is called inside vi_copy(),
* Image::load() calls fclose()
*/
if (tmpname ) remove(tmpname ());
if (tmpename) remove(tmpename());
if (tmpsname) remove(tmpsname());
out.vi_write(0,0); /* Signal EOF to subsequent filters. */
} else {
while (len!=0) {
wr=fwrite(buf, 1, len>0x4000?0x4000:len, p);
// assert(!ferror(p));
if (ferror(p)) {
vi_check(); /* Give a chance to report a better error message when Broken File. */
Error::sev(Error::EERROR) << "Filter::PipeE" << ": pipe write failed" << (Error*)0;
}
buf+=wr; len-=wr;
}
}
}
Filter::PipeE::~PipeE() {}
void Filter::PipeE::vi_check() {}
/* --- */
Filter::PipeD::PipeD(GenBuffer::Readable &in_, char const*pipe_tmpl, slendiff_t i): state(0), in(in_) {
/* <code similarity: Filter::PipeE::PipeE and Filter::PipeD::PipeD> */
param_assert(pipe_tmpl!=(char const*)NULLP);
SimBuffer::B *pp=(SimBuffer::B*)NULLP;
char const*s=pipe_tmpl;
lex: while (s[0]!='\0') { /* Interate throuh the template, substitute temporary filenames */
if (*s++=='%') switch (*s++) {
case '\0': case '%':
redir_cmd << '%';
break;
case 'i': /* the optional integer passed in param `i' */
redir_cmd << i;
break;
case '*': /* the optional unsafe string passed in param `i' */
redir_cmd << (char const*)i;
break;
case 'd': case 'D': /* temporary file for encoded data output */
pp=&tmpname;
put:
// if (*pp) Error::sev(Error::EERROR) << "Filter::PipeD: multiple %escape" << (Error*)0;
/* ^^^ multiple %escape is now a supported feature */
if (!*pp && !Files::find_tmpnam(*pp)) Error::sev(Error::EERROR) << "Filter::PipeD" << ": tmpnam() failed" << (Error*)0;
assert(*pp);
pp->term0();
if ((unsigned char)(s[-1]-'A')<(unsigned char)('Z'-'A'))
redir_cmd.appendFnq(*pp); /* Capital letter: quote from the shell */
else redir_cmd << *pp;
break;
case 'e': case 'E': /* temporary file for error messages */
pp=&tmpename;
goto put;
case 's': case 'S': /* temporary source file */
pp=&tmpsname;
goto put;
/* OK: implement temporary file for input, option to suppress popen() */
default:
Error::sev(Error::EERROR) << "Filter::PipeD: invalid %escape in pipe_tmpl" << (Error*)0;
} else redir_cmd << s[-1];
}
#if 0
if (!tmpname) Error::sev(Error::EERROR) << "Filter::PipeD" << ": no outname (%D) in cmd: " << (SimBuffer::B().appendDumpC(redir_cmd)) << (Error*)0;
#else
/* Append quoted file redirect to command, if missing */
if (!tmpname) { s=" >%D"; goto lex; }
#endif
#if !HAVE_PTS_POPEN
if (!tmpsname) { s=" <%S"; goto lex; }
#endif
// tmpname="tmp.name";
redir_cmd.term0();
if (tmpname) Files::tmpRemoveCleanup(tmpname ()); /* already term0() */
if (tmpename) Files::tmpRemoveCleanup(tmpename()); /* already term0() */
if (tmpsname) { Files::tmpRemoveCleanup(tmpsname()); remove(tmpsname()); } /* already term0() */
/* ^^^ Dat: remove() here introduces a race condition, but helps early error detection */
/* </code similarity: Filter::PipeE::PipeE and Filter::PipeD::PipeD> */
}
slen_t Filter::PipeD::vi_read(char *tobuf, slen_t tolen) {
assert(!(tolen!=0 && state==2));
if (state==2) return 0; /* Should really never happen. */
/* Normal read operation with tolen>0; OR tolen==0 */
if (state==0) { /* Read the whole stream from `in', write it to `tmpsname' */
#if HAVE_PTS_POPEN
if (!tmpsname) {
if (NULLP==(p=popen(redir_cmd(), "w" CFG_PTS_POPEN_B))) Error::sev(Error::EERROR) << "Filter::PipeD" << ": popen() failed: " << (SimBuffer::B().appendDumpC(redir_cmd)) << (Error*)0;
signal(SIGPIPE, SIG_IGN); /* Don't abort process with SIGPIPE signals if child cannot read our data */
vi_precopy();
in.vi_read(0,0);
if (0!=pclose(p)) Error::sev(Error::EERROR) << "Filter::PipeD" << ": pclose() failed; error in external prg" << (Error*)0;
} else {
#else
if (1) {
#endif
#if !HAVE_system_in_stdlib
Error::sev(Error::EERROR) << "Filter::PipeD" << ": no system() on this system" << (Error*)0;
#else
if (NULLP==(p=fopen(tmpsname(), "wb"))) Error::sev(Error::EERROR) << "Filter::PipeD" << ": fopen(w) failed: " << (SimBuffer::B().appendDumpC(redir_cmd)) << (Error*)0;
vi_precopy();
in.vi_read(0,0);
fclose(p);
if (0!=(Files::system3(redir_cmd()))) Error::sev(Error::EERROR) << "Filter::PipeD" << ": system() failed: " << (SimBuffer::B().appendDumpC(redir_cmd)) << (Error*)0;
remove(tmpsname());
#endif
}
vi_check();
if (NULLP==(p=fopen(tmpname(),"rb"))) Error::sev(Error::EERROR) << "Filter::PipeD" << ": fopen() after pclose() failed: " << tmpname << (Error*)0;
state=1;
} /* IF state==0 */
assert(state==1);
if (tolen==0 || 0==(tolen=fread(tobuf, 1, tolen, p))) do_close();
// putchar('{'); fwrite(tobuf, 1, tolen, stdout); putchar('}');
return tolen;
}
void Filter::PipeD::do_close() {
fclose(p); p=(FILE*)NULLP;
if (tmpname ) remove(tmpname ());
if (tmpename) remove(tmpename());
if (tmpsname) remove(tmpsname());
state=2;
}
void Filter::PipeD::vi_precopy() {
char *buf0=new char[GENSIO_BUFLEN], *buf;
slen_t len, wr;
while (0!=(len=in.vi_read(buf0, GENSIO_BUFLEN))) {
// printf("[%s]\n", buf0);
for (buf=buf0; len!=0; buf+=wr, len-=wr) {
wr=fwrite(buf, 1, len>0x4000?0x4000:len, p);
if (ferror(p)) {
vi_check(); /* Give a chance to report a better error message when Broken File. */
Error::sev(Error::EERROR) << "Filter::PipeD" << ": pipe write failed" << (Error*)0;
}
}
}
delete [] buf0;
}
int Filter::PipeD::vi_getcc() {
char ret; int i;
// fprintf(stderr,"state=%u\n", state);
switch (state) {
case 0: return vi_read(&ret, 1)==1 ? (unsigned char)ret : -1;
case 1: if (-1==(i=MACRO_GETC(p))) do_close(); return i;
/* case: 2: fall-through */
}
return -1;
}
void Filter::PipeD::vi_check() {}
Filter::PipeD::~PipeD() { if (state!=2) vi_read(0,0); }
Filter::BufR::BufR(GenBuffer const& buf_): bufp(&buf_) {
buf_.first_sub(sub);
}
int Filter::BufR::vi_getcc() {
if (bufp==(GenBuffer const*)NULLP) return -1; /* cast: pacify VC6.0 */
if (sub.len==0) {
bufp->next_sub(sub);
if (sub.len==0) { bufp=(GenBuffer const*)NULLP; return -1; }
}
sub.len--; return *sub.beg++;
}
slen_t Filter::BufR::vi_read(char *to_buf, slen_t max) {
if (max==0 || bufp==(GenBuffer const*)NULLP) return 0;
if (sub.len==0) {
bufp->next_sub(sub);
if (sub.len==0) { bufp=(GenBuffer const*)NULLP; return 0; }
}
if (max<sub.len) {
memcpy(to_buf, sub.beg, max);
sub.len-=max; sub.beg+=max;
return max;
}
max=sub.len; sub.len=0;
memcpy(to_buf, sub.beg, max);
return max;
}
void Filter::BufR::vi_rewind() { bufp->first_sub(sub); }
Filter::FlatD::FlatD(char const* s_, slen_t slen_): s(s_), sbeg(s_), slen(slen_) {}
Filter::FlatD::FlatD(char const* s_): s(s_), sbeg(s_), slen(strlen(s_)) {}
void Filter::FlatD::vi_rewind() { s=sbeg; }
int Filter::FlatD::vi_getcc() {
if (slen==0) return -1;
slen--; return *(unsigned char const*)s++;
}
slen_t Filter::FlatD::vi_read(char *to_buf, slen_t max) {
if (max>slen) max=slen;
memcpy(to_buf, s, max);
s+=max; slen-=max;
return max;
}
/* --- */
#if HAVE_lstat_in_sys_stat
# define PTS_lstat lstat
#else
# define PTS_lstat stat
#endif
/** @param fname must start with '/' (dir separator)
* @return true if file successfully created
*/
FILE *Files::try_dir(SimBuffer::B &dir, SimBuffer::B const&fname, char const*s1, char const*s2, char const*open_mode) {
if (dir.isEmpty() && s1==(char const*)NULLP) return (FILE*)NULLP;
SimBuffer::B full(s1!=(char const*)NULLP?s1:dir(),
s1!=(char const*)NULLP?strlen(s1):dir.getLength(),
s2!=(char const*)NULLP?s2:"",
s2!=(char const*)NULLP?strlen(s2):0, fname(), fname.getLength());
full.term0();
struct stat st;
FILE *f;
/* Imp: avoid race conditions with other processes pretending to be us... */
if (-1!=PTS_lstat(full(), &st)
|| (0==(f=fopen(full(), open_mode)))
|| ferror(f)
) return (FILE*)NULLP;
dir=full;
return f;
}
#if OS_COTY==COTY_WIN9X || OS_COTY==COTY_WINNT
# define DIR_SEP "\\"
#else
# define DIR_SEP "/"
#endif
FILE *Files::open_tmpnam(SimBuffer::B &dir, char const*open_mode, char const*extension) {
/* Imp: verify / on Win32... */
/* Imp: ensure uniqueness on NFS */
/* Imp: short file names */
static unsigned PTS_INT32_T counter=0;
assert(Error::tmpargv0!=(char const*)NULLP);
SimBuffer::B fname(DIR_SEP "tmp_", 5, Error::tmpargv0,strlen(Error::tmpargv0));
/* ^^^ Dat: we need DIR_SEP here, because the name of the tmp file may be
* passed to Win32 COMMAND.COM, which interprets "/" as a switch
*/
long pid=getpid();
if (pid<0 && pid>-(1<<24)) pid=-pid;
fname << '_' << pid << '_' << counter++;
if (extension) fname << extension;
fname.term0();
FILE *f=(FILE*)NULLP;
// char const* open_mode=binary_p ? "wb" : "w"; /* Dat: "bw" is bad */
(void)( ((FILE*)NULLP!=(f=try_dir(dir, fname, 0, 0, open_mode))) ||
((FILE*)NULLP!=(f=try_dir(dir, fname, getenv("TMPDIR"), 0, open_mode))) ||
((FILE*)NULLP!=(f=try_dir(dir, fname, getenv("TMP"), 0, open_mode))) ||
((FILE*)NULLP!=(f=try_dir(dir, fname, getenv("TEMP"), 0, open_mode))) ||
((FILE*)NULLP!=(f=try_dir(dir, fname, PTS_CFG_P_TMPDIR, 0, open_mode))) ||
((FILE*)NULLP!=(f=try_dir(dir, fname, "/tmp", 0, open_mode))) ||
((FILE*)NULLP!=(f=try_dir(dir, fname, getenv("WINBOOTDIR"), "//temp", open_mode))) ||
((FILE*)NULLP!=(f=try_dir(dir, fname, getenv("WINDIR"), "//temp", open_mode))) ||
((FILE*)NULLP!=(f=try_dir(dir, fname, "c:/temp", 0, open_mode))) ||
((FILE*)NULLP!=(f=try_dir(dir, fname, "c:/windows/temp", 0, open_mode))) ||
((FILE*)NULLP!=(f=try_dir(dir, fname, "c:/winnt/temp", 0, open_mode))) ||
((FILE*)NULLP!=(f=try_dir(dir, fname, "c:/tmp", 0, open_mode))) ||
((FILE*)NULLP!=(f=try_dir(dir, fname, ".", 0, open_mode))) ||
((FILE*)NULLP!=(f=try_dir(dir, fname, "..", 0, open_mode))) ||
((FILE*)NULLP!=(f=try_dir(dir, fname, "../..", 0, open_mode))) );
return f;
}
bool Files::find_tmpnam(SimBuffer::B &dir) {
FILE *f=open_tmpnam(dir);
if (f!=NULL) { fclose(f); return true; }
return false;
}
bool Files::tmpRemove=true;
static int cleanup_remove(Error::Cleanup *cleanup) {
if (Files::tmpRemove) {
int err = Files::removeIf(cleanup->getBuf());
if (err)
Error::sev(Error::ERROR_CONT) << "could not remove tmp file: " << cleanup->getBuf() << (Error*)0;
return err;
}
Error::sev(Error::WARNING) << "keeping tmp file: " << cleanup->getBuf() << (Error*)0;
return 0;
}
void Files::tmpRemoveCleanup(char const* filename) {
/* Copies the filename array. */
Error::newCleanup(cleanup_remove, 0, filename);
}
static int cleanup_remove_cond(Error::Cleanup *cleanup) {
if (*(FILE**)cleanup->data!=NULLP) {
fclose(*(FILE**)cleanup->data);
return cleanup_remove(cleanup);
}
return 0;
}
void Files::tmpRemoveCleanup(char const* filename, FILE**p) {
param_assert(p!=NULLP);
Error::newCleanup(cleanup_remove_cond, (void*)p, filename);
}
int Files::removeIf(char const* filename) {
if (0==remove(filename) || errno==ENOENT) return 0;
return 1;
}
slen_t Files::statSize(char const* filename) {
struct stat st;
if (-1==PTS_lstat(filename, &st)) return (slen_t)-1;
return st.st_size;
}
/* Tue Jul 2 10:57:21 CEST 2002 */
char const* Files::only_fext(char const*filename) {
char const *ret;
if (OS_COTY==COTY_WINNT || OS_COTY==COTY_WIN9X) {
if ((USGE('z'-'a',filename[0]-'a') || USGE('Z'-'A',filename[0]-'A'))
&& filename[1]==':'
) filename+=2; /* strip drive letter */
ret=filename;
while (*filename!='\0') {
if (*filename=='/' || *filename=='\\') ret=++filename;
else filename++;
}
} else { /* Everything else is treated as UNIX */
ret=filename;
while (filename[0]!='\0') if (*filename++=='/') ret=filename;
}
return ret;
}
#if HAVE_DOS_BINARY
void Files::set_binary_mode(int fd, bool binary) {
/* Wed Dec 11 18:17:30 CET 2002 */
setmode(fd, binary ? O_BINARY : O_TEXT);
}
#endif
int Files::system3(char const *commands) {
#if OS_COTY==COTY_WIN9X || OS_COTY==COTY_WINNT
char const *p;
p=commands; while (*p!='\0' && *p!='\n') p++;
if (*p=='\0') return system(commands); /* no newline -- simple run */
SimBuffer::B tmpnam;
FILE *f=Files::open_tmpnam(tmpnam, "w", ".bat");
tmpnam.term0();
Files::tmpRemoveCleanup(tmpnam());
fprintf(f, "@echo off\n%s\n", commands);
if (ferror(f)) Error::sev(Error::EERROR) << "system3: write to tmp .bat file: " << tmpnam << (Error*)NULLP;
fclose(f);
// printf("(%s)\n", tmpnam()); system("bash");
// int ret=system(("sh "+tmpnam)());
int ret=system(tmpnam());
remove(tmpnam());
return ret;
#else
return system(commands);
#endif
}
/* __END__ */