/***********************************************************************\ * rederr.d * * * * Redirector for stdout/stderr * * * * by Stewart Gordon * * September 2005 * \***********************************************************************/ import std.cstream; import std.string; import std.c.windows.windows; import smjg.libs.sdwf.except; import smjg.libs.util.commandline; extern (Windows) { HANDLE GetStdHandle(DWORD nStdHandle); BOOL CreatePipe(HANDLE* hReadPipe, HANDLE* hWritePipe, LPSECURITY_ATTRIBUTES lpPipeAttributes, DWORD nSize); BOOL SetStdHandle(DWORD nStdHandle, HANDLE hHandle); BOOL CreateProcessA(LPCSTR lpApplicationName, LPSTR lpCommandLine, LPSECURITY_ATTRIBUTES lpProcessAttributes, LPSECURITY_ATTRIBUTES lpThreadAttributes, BOOL bInheritHandles, DWORD dwCreationFlags, LPVOID lpEnvironment, LPCSTR lpCurrentDirectory, STARTUPINFOA* lpStartupInfo, PROCESS_INFORMATION* lpProcessInformation); BOOL GetExitCodeProcess(HANDLE hProcess, LPDWORD lpExitCode); } enum : uint { STD_INPUT_HANDLE = -10, STD_OUTPUT_HANDLE = -11, STD_ERROR_HANDLE = -12 } struct PROCESS_INFORMATION { HANDLE hProcess; HANDLE hThread; DWORD dwProcessId; DWORD dwThreadId; } struct STARTUPINFOA { DWORD cb; LPSTR lpReserved; LPSTR lpDesktop; LPSTR lpTitle; DWORD dwX; DWORD dwY; DWORD dwXSize; DWORD dwYSize; DWORD dwXCountChars; DWORD dwYCountChars; DWORD dwFillAttribute; DWORD dwFlags; WORD wShowWindow; WORD cbReserved2; LPBYTE lpReserved2; HANDLE hStdInput; HANDLE hStdOutput; HANDLE hStdError; } HANDLE originalStdout, originalStderr; struct OutFile { enum : uint { UNKNOWN, FILE, STDOUT, STDERR } enum : uint { NEW = 1, OVERWRITE = 2, APPEND = 4 } static const SECURITY_ATTRIBUTES sa = { nLength: SECURITY_ATTRIBUTES.sizeof, bInheritHandle: true }; uint streamType = UNKNOWN; uint mode = NEW; char[] name; HANDLE stream; int opEquals(OutFile of) { return streamType == of.streamType && tolower(name) == tolower(of.name); } void ensureUnknown(bit isError) { if (streamType != UNKNOWN) { throw isError ? InvalidSyntax.ERROR_TWICE : InvalidSyntax.OUTPUT_TWICE; } } void open() { switch (streamType) { case FILE: stream = CreateFileA(toStringz(name), GENERIC_WRITE, 0, &sa, mode, FILE_FLAG_WRITE_THROUGH, null); DefinedAPIException.validate(stream != cast(HANDLE) -1, "CreateFileA", "OutFile.open " ~ name); if (mode == APPEND) SetFilePointer(stream, 0, null, 2); break; case STDOUT: stream = originalStdout; break; case STDERR: stream = originalStderr; } } void close() { if (stream != null && stream != cast(HANDLE) -1) { FlushFileBuffers(stream); if (streamType == FILE) { DefinedAPIException.validate(CloseHandle(stream), "CloseHandle", "OutFile.close"); } } stream = null; } } int main(char[][] args) { OutFile outputFile; OutFile errorFile; try { // Get command line if (args.length < 3) { throw InvalidSyntax.TOO_FEW_ARGS; } char[][] commandLine = args[2..$]; if (args[1].length == 0) throw InvalidSyntax.EMPTY_ARG; if (find(args[1], '=') == -1) { outputFile.streamType = OutFile.FILE; outputFile.name = args[1]; errorFile = outputFile; } else { uint iArg; argLoop: for (iArg = 1; iArg < args.length; iArg++) { if (args[iArg].length == 0) throw InvalidSyntax.EMPTY_ARG; int eqSign = find(args[iArg], '='); if (eqSign == -1) break argLoop; char[] fileName = args[iArg][eqSign+1..$]; switch (tolower(args[iArg][0..eqSign])) { case "": if (fileName.length == 0) { iArg++; break argLoop; } outputFile.ensureUnknown(0); errorFile.ensureUnknown(1); outputFile.streamType = OutFile.FILE; outputFile.name = fileName; errorFile = outputFile; break; case "o": outputFile.ensureUnknown(0); if (fileName.length == 0) { outputFile.streamType = OutFile.STDERR; } else { outputFile.streamType = OutFile.FILE; outputFile.name = fileName; } break; case "e": errorFile.ensureUnknown(1); if (fileName.length == 0) { errorFile.streamType = OutFile.STDOUT; } else { errorFile.streamType = OutFile.FILE; errorFile.name = fileName; } break; case "!": outputFile.ensureUnknown(0); errorFile.ensureUnknown(1); outputFile.streamType = OutFile.FILE; outputFile.mode = OutFile.OVERWRITE; outputFile.name = fileName; errorFile = outputFile; break; case "o!": outputFile.ensureUnknown(0); outputFile.streamType = OutFile.FILE; outputFile.mode = OutFile.OVERWRITE; outputFile.name = fileName; break; case "e!": errorFile.ensureUnknown(1); errorFile.streamType = OutFile.FILE; errorFile.mode = OutFile.OVERWRITE; errorFile.name = fileName; break; case "+": outputFile.ensureUnknown(0); errorFile.ensureUnknown(1); outputFile.streamType = OutFile.FILE; outputFile.mode = OutFile.APPEND; outputFile.name = fileName; errorFile = outputFile; break; case "o+": outputFile.ensureUnknown(0); outputFile.streamType = OutFile.FILE; outputFile.mode = OutFile.APPEND; outputFile.name = fileName; break; case "e+": errorFile.ensureUnknown(1); errorFile.streamType = OutFile.FILE; errorFile.mode = OutFile.APPEND; errorFile.name = fileName; break; default: throw InvalidSyntax.INVALID_PREFIX; } } commandLine = args[iArg..$]; } if (commandLine.length == 0) { throw InvalidSyntax.MISSING_EXE; } // fill in the blanks and check for missing filename switch (outputFile.streamType) { case OutFile.UNKNOWN: outputFile.streamType = OutFile.STDOUT; break; case OutFile.FILE: if (outputFile.name.length == 0) { throw InvalidSyntax.NO_NAME; } break; default: } switch (errorFile.streamType) { case OutFile.UNKNOWN: errorFile.streamType = OutFile.STDERR; break; case OutFile.FILE: if (errorFile.name.length == 0) { throw InvalidSyntax.NO_NAME; } break; default: } // get console I/O handles originalStdout = GetStdHandle(STD_OUTPUT_HANDLE); originalStderr = GetStdHandle(STD_ERROR_HANDLE); DefinedAPIException.validate(originalStdout != cast(HANDLE) -1, "GetStdHandle(STD_OUTPUT_HANDLE)", "main"); DefinedAPIException.validate(originalStderr != cast(HANDLE) -1, "GetStdHandle(STD_ERROR_HANDLE)", "main"); // now open the files if (outputFile == errorFile) { if (outputFile.mode != errorFile.mode) { throw InvalidSyntax.MODE_MISMATCH; } outputFile.open(); errorFile = outputFile; } else { outputFile.open(); errorFile.open(); } DefinedAPIException.validate( SetStdHandle(STD_OUTPUT_HANDLE, outputFile.stream), "SetStdHandle(stdout)", "main"); DefinedAPIException.validate( SetStdHandle(STD_ERROR_HANDLE, errorFile.stream), "SetStdHandle(stderr)", "main"); // get the process started PROCESS_INFORMATION processInfo; STARTUPINFOA startupInfo; startupInfo.cb = STARTUPINFOA.sizeof; DefinedAPIException.validate(CreateProcessA(null, toStringz(buildCommandLine(commandLine)), null, null, true, 0, null, null, &startupInfo, &processInfo), "CreateProcessA", "main"); // wait for exit uint exitCode; while (true) { DefinedAPIException.validate( GetExitCodeProcess(processInfo.hProcess, &exitCode), "GetExitCodeProcess", "main"); if (exitCode == 0x103) { Sleep(100); } else { return exitCode; } } } catch (InvalidSyntax e) { derr.writefln("Invalid arguments: %s", e.toString); derr.writefln("Usage: rederr outerr commandline rederr {o|e}=[filename] commandline rederr {!|!o|!e|+|+o|+e}=filename commandline where options are: o stdout e stderr ! overwrite file + append to file"); return 255; } finally { SetStdHandle(STD_OUTPUT_HANDLE, originalStdout); SetStdHandle(STD_ERROR_HANDLE, originalStderr); outputFile.close(); if (errorFile != outputFile) errorFile.close(); } } class InvalidSyntax : Exception { private this(char[] s) { super(s); } static { InvalidSyntax TOO_FEW_ARGS() { return new InvalidSyntax("Too few arguments"); } InvalidSyntax INVALID_PREFIX() { return new InvalidSyntax("Invalid redirect specifier"); } InvalidSyntax OUTPUT_TWICE() { return new InvalidSyntax( "stdout redirect specified multiple times"); } InvalidSyntax ERROR_TWICE() { return new InvalidSyntax( "stderr redirect specified multiple times"); } InvalidSyntax NO_NAME() { return new InvalidSyntax( "! or + valid only for specified filename"); } InvalidSyntax MODE_MISMATCH() { return new InvalidSyntax( "stdout and stderr to same file, but modes don't match"); } InvalidSyntax MISSING_EXE() { return new InvalidSyntax("Program to execute not specified"); } InvalidSyntax EMPTY_ARG() { return new InvalidSyntax("Empty argument encountered"); } } }