Sending commands to the shell, on Unix-like platforms, or simulating this on Windows.
§1. Operating system interface. Some of our programs have to issue commands to the host operating system,
to copy files, pass them through TeX, and so on. All of that is done using
the C standard library system function; the commands invoked are all
standard for POSIX, so will work on MacOS and Linux, but on a Windows system
they would need to be read in a POSIX-style environment like Cygwin or MSYS2.
void Shell::quote_path(OUTPUT_STREAM, pathname *P) { TEMPORARY_TEXT(FN) WRITE_TO(FN, "%p", P); Shell::quote_text(OUT, FN); DISCARD_TEXT(FN) } void Shell::quote_file(OUTPUT_STREAM, filename *F) { TEMPORARY_TEXT(FN) WRITE_TO(FN, "%f", F); Shell::quote_text(OUT, FN); DISCARD_TEXT(FN) } void Shell::plain(OUTPUT_STREAM, char *raw) { WRITE("%s", raw); } void Shell::plain_text(OUTPUT_STREAM, text_stream *raw) { WRITE("%S", raw); } void Shell::quote_text(OUTPUT_STREAM, text_stream *raw) { PUT(SHELL_QUOTE_CHARACTER); LOOP_THROUGH_TEXT(pos, raw) { inchar32_t c = Str::get(pos); if (c == SHELL_QUOTE_CHARACTER) PUT('\\'); PUT(c); } PUT(SHELL_QUOTE_CHARACTER); PUT(' '); }
void Shell::apply(char *command, filename *F) { TEMPORARY_TEXT(COMMAND) Shell::plain(COMMAND, command); Shell::plain(COMMAND, " "); Shell::quote_file(COMMAND, F); Shell::run(COMMAND); DISCARD_TEXT(COMMAND) } void Shell::apply_S(text_stream *command, filename *F) { TEMPORARY_TEXT(COMMAND) Shell::plain_text(COMMAND, command); Shell::plain(COMMAND, " "); Shell::quote_file(COMMAND, F); Shell::run(COMMAND); DISCARD_TEXT(COMMAND) }
void Shell::rm(filename *F) { Shell::apply("rm", F); } void Shell::copy(filename *F, pathname *T, char *options) { TEMPORARY_TEXT(COMMAND) Shell::plain(COMMAND, "cp "); Shell::plain(COMMAND, options); Shell::plain(COMMAND, " "); Shell::quote_file(COMMAND, F); Shell::quote_path(COMMAND, T); Shell::run(COMMAND); DISCARD_TEXT(COMMAND) }
§4. This writes the traditional Unix shell syntax for redirecting the output
from both stdout and stderr to the same named file.
void Shell::redirect(OUTPUT_STREAM, filename *F) { Shell::plain(OUT, ">"); Shell::quote_file(OUT, F); Shell::plain(OUT, "2>&1"); }
§5. Actual commands. The scheme is that commands are composed using the above functions, and then sent to this one.
We make the buffer here long enough for 8 filenames of worst-case length, all transcoded to UTF-8 in the most unlucky way imaginable.
define SPOOL_LENGTH 4*8*MAX_FILENAME_LENGTH
int shell_verbosity = FALSE; void Shell::verbose(void) { shell_verbosity = TRUE; } int Shell::run(OUTPUT_STREAM) { if (shell_verbosity) PRINT("shell: %S\n", OUT); LOGIF(SHELL_USAGE, "shell: %S\n", OUT); char spool[SPOOL_LENGTH]; Streams::write_as_locale_string(spool, OUT, SPOOL_LENGTH); if (debugger_mode) { WRITE_TO(STDOUT, "debugger mode suppressing shell command: %S\n", OUT); return 0; } int rv = Platform::system(spool); if (rv == -1) { WRITE_TO(STDERR, "shell: %S\n", OUT); internal_error("OS shell error"); } if (rv == 127) { WRITE_TO(STDERR, "shell: %S\n", OUT); internal_error("Execution of the shell failed"); } return rv; }