. "$BST_LIB" # enscript converts text to PostScript. Six programs ship in the package and all # six are covered here, because the interesting half of enscript is not the # converter: it is that HIGHLIGHTING IS NOT BUILT IN. enscript implements # --highlight by piping each input file through states(1) as an input filter, so # the feature the ticket is named for is a popen() away from the rest of it. # # ONE PLATFORM GAP had to be patched before any of that worked, and a regression # in it is near-silent, so the guard cases are called out: # # * glibc implements popen() and system() over posix_spawn(), which starts the # child with clone(CLONE_VM|CLONE_VFORK) -- the child runs on a borrowed # stack in the PARENT'S address space until it execs. On wasm32-hwjs a child # is a separate Worker with its own wasm instance, so CLONE_VM is not # declined, it is inexpressible: clone3 is unwired (ENOSYS) and the legacy # __clone fallback cannot honour it either. Unpatched, `enscript # --highlight=c` printed "couldn't open input filter ...: Function not # implemented" and "no output generated" -- and the OTHER three call sites # failed with no message at all, because none of them checks a return value. # `enscript/highlight`, `enscript/help-highlight`, `enscript/filter` and # `enscript/states-system` are the four guards, one per call site. # Plain fork() works here, which is why the fix is fork+pipe+exec and why # `enscript/plain` below passes with or without it. # # * `enscript/filter-reuse` guards the replacement's own bookkeeping rather # than the platform: it holds pipes in a fixed 8-slot table, so a leak shows # up as the 9th filter failing and nothing before it does. Hence 10 runs. # # `enscript/diffpp` guards a second, unrelated fix: diffpp(1) assigned the # array-base variable $[, fatal in perl since 5.30, so it died before reading # its input on any current perl -- nothing to do with this target. # # Every assertion below reads a DSC comment or a PostScript operator out of the # generated file rather than eyeballing a page, and no case needs a printer: # -p/-o keep the spooler (the fourth popen site, and the one that cannot be # exercised here since no lpr ships) out of the way. require enscript enscript/present D=${TMPDIR:-/tmp}/bst-enscript.$$ rm -rf "$D"; mkdir -p "$D" printf 'alpha beta gamma\ndelta epsilon\n' > "$D/in.txt" printf '#include \nint main(void){ return 0; }\n' > "$D/in.c" # --- identity and inventory --------------------------------------------------- check_out enscript/version "GNU Enscript 1.6.6" enscript --version # The six programs the package installs, plus the config file. over(1) and # sliceprint(1)/diffpp(1) are scripts; the other three are binaries. inv="" for f in /usr/bin/enscript /usr/bin/states /usr/bin/mkafmmap \ /usr/bin/over /usr/bin/sliceprint /usr/bin/diffpp /etc/enscript.cfg; do [ -e "$f" ] || inv="$inv $f" done check_eq enscript/inventory "" "$inv" # The library data enscript resolves at RUNTIME: without the .st rules there is # no highlighting to drive, and without the AFM metrics no text can be placed. st=$(ls /usr/share/enscript/hl/*.st 2>/dev/null | wc -l) afm=$(ls /usr/share/enscript/afm/*.afm 2>/dev/null | wc -l) check_eq enscript/hl-rules "yes" "$([ "$st" -ge 50 ] && echo yes || echo "$st")" check_eq enscript/afm-fonts "yes" "$([ "$afm" -ge 30 ] && echo yes || echo "$afm")" # --- the converter, with no filter in the picture ----------------------------- enscript -B -p "$D/plain.ps" "$D/in.txt" >/dev/null 2>&1 check enscript/plain test -s "$D/plain.ps" check_eq enscript/ps-header "%!PS-Adobe-3.0" "$(head -1 "$D/plain.ps")" check_out enscript/text "alpha beta gamma" cat "$D/plain.ps" check_out enscript/creator "%%Creator: GNU Enscript 1.6.6" cat "$D/plain.ps" # %%For is filled from getpwuid(getuid()), which enscript calls before it parses # a single option and treats as FATAL on NULL. It is the one case here that # asserts the image's NSS 'files' path rather than anything about enscript. check_out enscript/for-user "%%For: root" cat "$D/plain.ps" check_out enscript/media-default "%%DocumentMedia: A4 595 842" cat "$D/plain.ps" check_out enscript/portrait "%%Orientation: Portrait" cat "$D/plain.ps" enscript -B -r --media=Letter -p "$D/land.ps" "$D/in.txt" >/dev/null 2>&1 check_out enscript/media-letter "%%DocumentMedia: Letter 612 792" cat "$D/land.ps" check_out enscript/landscape "%%Orientation: Landscape" cat "$D/land.ps" # -2 reaches the generated PostScript as the /cols definition the prolog reads. enscript -B -2 -p "$D/two.ps" "$D/in.txt" >/dev/null 2>&1 check_out enscript/columns "/cols 2 def" cat "$D/two.ps" # -X selects one of the .enc files above; it names the procset it emits. enscript -B -X 88592 -p "$D/enc.ps" "$D/in.txt" >/dev/null 2>&1 check_out enscript/encoding "Enscript-Encoding-88592" cat "$D/enc.ps" check_out enscript/stdout "%!PS-Adobe-3.0" sh -c "enscript -B -o - '$D/in.txt' 2>/dev/null | head -1" check_out enscript/stdin "alpha beta gamma" \ sh -c "printf 'alpha beta gamma\n' | enscript -B -o '$D/stdin.ps' >/dev/null 2>&1; cat '$D/stdin.ps'" # --- the input-filter path: every case below forks a second program ----------- # THE guard. Unpatched this produced no file at all, so "the output exists and # carries the source text" is the whole assertion. enscript --highlight=c --color -p "$D/hl.ps" "$D/in.c" >/dev/null 2>&1 check enscript/highlight test -s "$D/hl.ps" check_out enscript/highlight-text "stdio.h" cat "$D/hl.ps" # ...and that it went through states rather than straight to the page: colouring # a token emits a setrgbcolor the same file converted plainly does not carry. enscript -B -p "$D/plainc.ps" "$D/in.c" >/dev/null 2>&1 hlc=$(grep -c setrgbcolor "$D/hl.ps" 2>/dev/null) plc=$(grep -c setrgbcolor "$D/plainc.ps" 2>/dev/null) check_eq enscript/highlight-colour "yes" "$([ "$hlc" -gt "$plc" ] && echo yes || echo "hl=$hlc plain=$plc")" # -E is the same path reached by autodetecting the language from the suffix. enscript -E -p "$D/auto.ps" "$D/in.c" >/dev/null 2>&1 check enscript/highlight-autodetect test -s "$D/auto.ps" # --help-highlight is the system() call site, not the popen() one: enscript # hands states the whole hl/ directory and lets it write the listing itself. check_out enscript/help-highlight "Name: c" enscript --help-highlight # --filter is the input-filter path with a user-supplied command. enscript --filter="cat %s" -B -p "$D/filt.ps" "$D/in.txt" >/dev/null 2>&1 check_out enscript/filter "alpha beta gamma" cat "$D/filt.ps" # 10 filters in a row: more than the replacement's 8 pipe slots, so a slot that # is not returned at pclose shows up here and only here. n=0; bad=0 while [ "$n" -lt 10 ]; do enscript --highlight=c -p "$D/loop.ps" "$D/in.c" >/dev/null 2>&1 || bad=$((bad + 1)) [ -s "$D/loop.ps" ] || bad=$((bad + 1)) n=$((n + 1)) done check_eq enscript/filter-reuse "0" "$bad" # --- states(1), the highlighting engine, on its own --------------------------- check_out enscript/states-version "states for GNU Enscript 1.6.6" states -V # The same job enscript delegates, driven directly: states emits its own # font{}/color{} markup, which enscript then turns into PostScript. check_out enscript/states-highlight "color{" \ states -f /usr/share/enscript/hl/enscript.st -s c -Dcolor=1 -Dstyle=emacs "$D/in.c" # The states language's own system() primitive — the fourth call site, and the # only one whose failure was entirely invisible (its result is a return value # the caller may ignore). printf 'state sysprobe { BEGIN { system("echo STATES_SYSTEM_OK"); } }\n' > "$D/sys.st" check_out enscript/states-system "STATES_SYSTEM_OK" \ states -f "$D/sys.st" -s sysprobe "$D/in.txt" # --- the remaining programs --------------------------------------------------- # mkafmmap reads AFM metric files and writes the font.map that enscript consults # to turn a font name into a metrics file. ( cd "$D" && mkafmmap /usr/share/enscript/afm/com.afm >/dev/null 2>&1 ) check_out enscript/mkafmmap "Courier" cat "$D/font.map" # The overstrike output language: plain text with backspace overstrikes rather # than PostScript. over(1) wraps exactly this, but pipes into less(1) and so is # not runnable unattended; this is its payload. check_out enscript/overstrike "alpha beta gamma" \ sh -c "enscript -p- --quiet --language=overstrike '$D/in.txt' 2>/dev/null" # sliceprint cuts over-wide input into vertical slices, one enscript run each. # -o is what makes it usable here: without it every slice goes to the spooler. printf 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaZZZ\n' > "$D/wide.txt" sliceprint -o "$D/slice.ps" "$D/wide.txt" >/dev/null 2>&1 check enscript/sliceprint test -s "$D/slice.ps.1" # diffpp annotates a file with the changes a diff describes: a deleted line is # shaded and marked (-), an added one shaded and marked (+). Asserting the # MARKED TEXT (not just that it ran) is what pins the substr() offset in the # perl fix — an off-by-one there silently truncates every changed line. printf 'one\ntwo\nthree\n' > "$D/a.txt" printf 'one\nTWO\nthree\n' > "$D/b.txt" diff "$D/a.txt" "$D/b.txt" > "$D/d.diff" 2>/dev/null diffpp "$D/a.txt" < "$D/d.diff" > "$D/pp.out" 2>"$D/pp.err" check_eq enscript/diffpp-clean "" "$(_bst_1line "$(cat "$D/pp.err")")" check_out enscript/diffpp-del "(-) show grestore}two" cat "$D/pp.out" check_out enscript/diffpp-add "(+) show grestore}TWO" cat "$D/pp.out" rm -rf "$D"