/* * gensio.cpp -- IO-specific methods * by pts@fazekas.hu 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 /* strlen() */ #include /* va_list */ #if _MSC_VER > 1000 // extern "C" int getpid(void); # include #else # include /* getpid() */ #endif #include /* struct stat */ #include /* getenv() */ #include #include /* signal() */ /* Imp: use sigaction */ #if HAVE_DOS_BINARY #undef __STRICT_ANSI__ #include /* O_BINARY */ #include /* 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(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(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(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(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(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() { /* */ 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() */ /* */ // 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_) { /* */ 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 */ /* */ } 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 (maxfirst_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__ */