summaryrefslogtreecommitdiff
path: root/graphics/sam2p/gensio.cpp
blob: 2d53748a724c4b77e80019f13e267a0d2cf94ee3 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
/*
 * 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 <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__ */