//  Tee for NT (tnt)
//
//  Compile with:
//
//  Microsoft: cl /WX -o tnt.exe tnt.cpp
//  cygwin: gcc -Wall -o tnt.exe -mno-cygwin tnt.cpp
//
//  Usage: tnt filename command line to execute
//  Creates file "filename" and execues the command line, with output
//  going both to the screen and that file.
//
//  Based on samples on MSDN.
//
//  Copyright (C) 2000, Louis W. Erickson
//  Released under the GNU Public License, version 2 or greater.
//
//  This program is free software; you can redistribute it and/or modify
//  it under the terms of the GNU General Public License as published by
//  the Free Software Foundation; either version 2 of the License, or
//  (at your option) any later version.
//
//  This program is distributed in the hope that it will be useful,
//  but WITHOUT ANY WARRANTY; without even the implied warranty of
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//  GNU General Public License for more details.
//
//  You should have received a copy of the GNU General Public License
//  along with this program; if not, write to the Free Software
//  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
//  Or http://www.fsf.org/licenses/gpl.html for the full text.
//

// Headers
#include <windows.h>

// Globals.
HANDLE hStdinR, hStdoutW, hStderrW;
HANDLE hPipeinR, hPipeinW, hPipeoutR, hPipeoutW, hPipeerrR, hPipeerrW;
HANDLE hDupinW, hDupoutW, hDuperrW;

HANDLE hOutput;

HANDLE hErrorThread;
HANDLE hOutThread;
DWORD dwErrorID;

HANDLE hChild;

static char pBuff[8192];

void ErrorMsg(char *msg);
DWORD WINAPI ListenOutFn( LPVOID );
DWORD WINAPI ListenErrFn( LPVOID );
void Drain(void);

//
// Parse command line, create connections, run program.
//
int main(int argc, char *argv[])
{
	char *outputfl;
	PROCESS_INFORMATION pInfo;
	STARTUPINFO si;
	SECURITY_ATTRIBUTES saAttr; 
	int iLoop;

	// Try and get our real stdin stdout and stderr handles.
	hStdinR = GetStdHandle(STD_INPUT_HANDLE);
	hStdoutW = GetStdHandle(STD_OUTPUT_HANDLE);
	hStderrW = GetStdHandle(STD_ERROR_HANDLE);

	// If we can't then escape now.
	if(hStdinR == INVALID_HANDLE_VALUE || hStdoutW == INVALID_HANDLE_VALUE
		|| hStderrW == INVALID_HANDLE_VALUE)
	{
		return -1;
	}

	// Parse command line
	if(argc < 3)
	{
		ErrorMsg("Usage: tnt filename command line to run\n");
		return -1;
	}

	outputfl = argv[1];

	pBuff[0] = '\0';
	lstrcpy(pBuff, "cmd.exe /c ");
	for(iLoop = 2; argv[iLoop] != NULL; iLoop++)
	{
		lstrcat(pBuff, argv[iLoop]);
		lstrcat(pBuff, " ");
	}

	// Create our pipes.  If any fail, crash, writing an error to stdout.

	// Set the bInheritHandle flag so pipe handles are inherited.  
	saAttr.nLength = sizeof(SECURITY_ATTRIBUTES); 
	saAttr.bInheritHandle = TRUE;
	saAttr.lpSecurityDescriptor = NULL; 
   
	if(!CreatePipe(&hPipeinR, &hPipeinW, &saAttr, 0))
	{
		ErrorMsg("Cannot open pipe for stdin.\n");
		return -1;
	}
	if(!CreatePipe(&hPipeoutR, &hPipeoutW, &saAttr, 0))
	{
		ErrorMsg("Cannot open pipe for stdout.\n");
		return -1;
	}
	if(!CreatePipe(&hPipeerrR, &hPipeerrW, &saAttr, 0))
	{
		ErrorMsg("Cannot open pipe for stderr.\n");
		return -1;
	}

	// Duplicate the handles, setting no-inherit on our copies.
	if(!DuplicateHandle(GetCurrentProcess(), hPipeinW, GetCurrentProcess(), &hDupinW,
		0, FALSE, DUPLICATE_SAME_ACCESS))
	{
		ErrorMsg("Can't open copy of stdin.\n");
	}
	if(!DuplicateHandle(GetCurrentProcess(), hPipeoutW, GetCurrentProcess(), &hDupoutW,
		0, FALSE, DUPLICATE_SAME_ACCESS))
	{
		ErrorMsg("Can't open copy of stdout.\n");
	}
	if(!DuplicateHandle(GetCurrentProcess(), hPipeerrW, GetCurrentProcess(), &hDuperrW,
		0, FALSE, DUPLICATE_SAME_ACCESS))
	{
		ErrorMsg("Can't open copy of stderr.\n");
	}

	// Open output file
	hOutput = CreateFile(outputfl, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
	if(hOutput == INVALID_HANDLE_VALUE)
	{
		ErrorMsg("Can't open output file.\n");
		return -1;
	}


	// Tie console to pipes
	SetStdHandle(STD_OUTPUT_HANDLE, hPipeoutW);
	SetStdHandle(STD_ERROR_HANDLE, hPipeoutW);

	// Start listener threads.
	hOutThread = CreateThread(NULL, 0, ListenOutFn, 0, 0, &dwErrorID);
	if(hOutThread == INVALID_HANDLE_VALUE)
	{
		ErrorMsg("Can't start listener thread.\n");
		return -1;
	}

	// Run the external program!
	si.cb = sizeof(STARTUPINFO);
	si.lpReserved = NULL;
	si.lpDesktop = NULL;
	si.lpTitle = NULL;
	si.dwX = 0;
	si.dwY = 0;
	si.dwXSize = 0;
	si.dwYSize = 0;
	si.dwXCountChars = 0;
	si.dwYCountChars = 0;
	si.dwFillAttribute = 0;
	si.dwFlags = STARTF_FORCEOFFFEEDBACK|STARTF_USESTDHANDLES;
	si.wShowWindow = 0;
	si.cbReserved2 = 0;
	si.lpReserved2 = 0;
	si.hStdInput = hStdinR;
	si.hStdOutput = hPipeoutW;
	si.hStdError = hPipeoutW;

	if(!CreateProcess(NULL, pBuff, NULL, NULL, TRUE, 0, NULL, NULL, &si, &pInfo))
	{
		ErrorMsg("Can't create process.\n");
		return -1;
	}

	hChild = pInfo.hProcess;

	// Restore real handles
	SetStdHandle(STD_OUTPUT_HANDLE, hStdoutW);
	SetStdHandle(STD_ERROR_HANDLE, hStderrW);

	WaitForSingleObject(hChild, INFINITE);

	CloseHandle(hPipeoutW);
	CloseHandle(hPipeerrW);
	CloseHandle(hDupoutW);
	CloseHandle(hDuperrW);

	WaitForSingleObject(hOutThread, INFINITE);

	CloseHandle(hOutput);

	return 0;
}

// Very simple error output fn.
void ErrorMsg(char *msg)
{
	DWORD dwBytesOut;

	WriteFile(hStderrW, (LPCVOID)msg, strlen(msg), &dwBytesOut, NULL);
}

// Stdout listener
DWORD WINAPI ListenOutFn( LPVOID )
{
	DWORD dwCount;
	DWORD dwRead;

	while(1)
	{
		if(!ReadFile(hPipeoutR, pBuff, 1, &dwRead, NULL))
		{
			return 0;
		}
		if(dwRead == 0)
		{
			return 0;
		}
		if(*pBuff != 12)
		{
			WriteFile(hStdoutW, pBuff, dwRead, &dwCount, NULL);
			WriteFile(hOutput, pBuff, dwRead, &dwCount, NULL);
		}
	}
}

