Shell.java

package jasper.component.vm;

import io.micrometer.core.annotation.Timed;
import jasper.config.Props;
import jasper.errors.ScriptException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import java.io.IOException;
import java.io.OutputStreamWriter;

import static jasper.component.vm.RunProcess.runProcess;

@Component
public class Shell {
	private static final Logger logger = LoggerFactory.getLogger(Shell.class);

	@Autowired
	Props props;

	@Value("http://localhost:${server.port}")
	String api;

	// language=Sh
	private final String wrapperScript = """
set -e
CURRENT_SHELL=$(basename "$0")
IFS= read -r -d '' TARGET_SCRIPT
IFS= read -r -d '' INPUT_STRING
SCRIPT_DIR=$(mktemp -d)
trap 'rm -rf "$SCRIPT_DIR"' EXIT
SCRIPT_FILE="$SCRIPT_DIR/script.sh"
echo "$TARGET_SCRIPT" > "$SCRIPT_FILE"
chmod +x "$SCRIPT_FILE"
timeout "$1" "$CURRENT_SHELL" -c "JASPER_API='$2' '$SCRIPT_FILE'" << EOF
$INPUT_STRING
EOF
    """;

	@Timed("jasper.vm")
	public String runShellScript(String targetScript, String inputString, int timeoutMs) throws ScriptException, IOException {
		var process = new ProcessBuilder(props.getShell(), "-c", wrapperScript, props.getShell(), String.valueOf(timeoutMs), api).start();
		try (var writer = new OutputStreamWriter(process.getOutputStream())) {
			writer.write(targetScript);
			writer.write("\0"); // null character as delimiter
			writer.write(inputString);
			writer.write("\0"); // both inputs must be null terminated
			writer.flush();
		} catch (IOException e) {
			logger.warn("Script terminated before receiving input.");
		}
		return runProcess(process, timeoutMs);
	}
}