-
Notifications
You must be signed in to change notification settings - Fork 10
/
main.cpp
404 lines (369 loc) · 12.5 KB
/
main.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
/*
History:
01-MAR-2013, MTEE 2.1
Bug: In Windows 8, mtee resulted to "Incorrect function" if output was to pipe.
Fixed to not not rely on undocumented error codes from WriteConsole.
At the same time, got rid of separate functions for console and disk files,
and combined them to WriteBufferToConsoleAndFilesA and WriteBufferToConsoleAndFilesW and separating
the console case with fi->bIsConsole. Some re-org to the idea of args.fi : the first item
is always the std output, and the rest of the files are the given output files. The last item
in the linked list no longer a dummy item.
Bug: echo x x x x | mtee guessed that the input was Unicode.
Fixed to use IS_TEXT_UNICODE_NULL_BYTES instead of IS_TEXT_UNICODE_ASCII16 | IS_TEXT_UNICODE_STATISTICS.
Bug: echo t013|mtee /u con entered a forever loop.
Fixed the bug in WriteBufferToDiskW loop.
Bug: assumed that all files are less than 4 GB.
Fixed by using also dwFileSizeHigh in GetFileSize.
Bug: redir to console and con as output file was not supported.
Fixed by not trying to truncate the result file with SetEndOfFile if it is a console.
(redir to con may be wanted if std output is already redirected to file)
27-APR-2016, MTEE 2.2
Workaround: Using ExitProcess at end to workaround an issue in Windows 10
Ref: https://connect.microsoft.com/VisualStudio/feedback/details/2640071/30-s-delay-after-returning-from-main-program
*/
#include "header.h"
#include <stdio.h>
#define PEEK_BUF_SIZE (0x400) // 1024
#define PEEK_WAIT_INT (10)
DWORD dwCtrlEvent; // set by ctrl handler
int main(VOID)
{
PCHAR lpBuf = NULL; // pointer to main input buffer
PCHAR lpAnsiBuf = NULL; // pointer to buffer for converting unicode to ansi
PWCHAR lpUnicodeBuf = NULL; // pointer to buffer for converting ansi to unicode
DWORD dwBytesRead = 0L; // bytes read from input
HANDLE hOut = NULL; // handle to stdout
HANDLE hIn = NULL; // handle to stdin
DWORD dwStdInType = (DWORD)NULL; // stdin's filetype (file/pipe/console)
PFILEINFO fi = NULL; // pointer for indexing FILEINFO records
BOOL bBomFound = FALSE;// true if BOM found
BOOL bCtrlHandler = FALSE;
ARGS args; // holds commandline arguments/options
DWORD dwPeekBytesRead = 0L;
DWORD dwPeekBytesAvailable = 0L;
DWORD dwPeekBytesUnavailable= 0L;
DWORD cPeekTimeout = 0L;
BYTE byPeekBuf[PEEK_BUF_SIZE]; // holds peeked input for ansi/unicode test
DWORD dwInFormat = OP_ANSI_IN;
DWORD dwOperation;
int iFlags;
#ifdef _DEBUG
MessageBox(0,"start", "mtee", MB_OK);
#endif
/*
if(!GetWinVer())
{
Verbose(TEXT("This program requires Windows NT4, 2000, XP or 2003.\r\n"));
ExitProcess(1);
}
*/
//
// install ctrl handler to trap ctrl-c and ctrl-break
//
dwCtrlEvent = CTRL_CLEAR_EVENT;
bCtrlHandler = SetConsoleCtrlHandler(HandlerRoutine, TRUE);
//
// parse the commandline
//
if(!ParseCommandlineW(&args)) ExitProcess(1);
//
// did user want to display helpscreen?
//
if(args.bHelp) ExitProcess(ShowHelp());
//
// get handles to stdout/stdin
//
if((hIn = GetStdHandle(STD_INPUT_HANDLE)) == INVALID_HANDLE_VALUE) ExitProcess(Perror((DWORD)NULL));
if((hOut = GetStdHandle(STD_OUTPUT_HANDLE)) == INVALID_HANDLE_VALUE) ExitProcess(Perror((DWORD)NULL));
args.fi.hFile = hOut;
//
// determine whether the output is a console
//
args.fi.bIsConsole = IsAnOutputConsoleDevice(hOut);
//
// determine the type of input file then peek at content to ascertain if it's unicode
//
dwStdInType = GetFileType(hIn);
//
// if requested by user, get handle to piped process
//
HANDLE hPipedProcess = NULL;
if (args.bFwdExitCode) hPipedProcess = GetPipedProcessHandle();
switch(dwStdInType)
{
case FILE_TYPE_DISK: // stdin is from a file: mtee.exe < infile
{
DWORD dwFileSizeAtLeast;
DWORD dwFileSizeHigh = 0;
//
// try and determine if input file is unicode or ansi. first check the filesize
// if its zero bytes then don't use readfile as this will block
//
dwFileSizeAtLeast = GetFileSize(hIn, &dwFileSizeHigh);
if (dwFileSizeHigh != 0)
dwFileSizeAtLeast = 0xFFFFFFFE;
if(dwFileSizeAtLeast == 0xFFFFFFFF && GetLastError() != NO_ERROR)
{
if(!args.bContinue) ExitProcess(Perror((DWORD)NULL));
else dwFileSizeAtLeast = 0L;
}
//
// only try and peek if there's at least a wchar available otherwise a test for
// unicode is meaningless
//
if(dwFileSizeAtLeast >= sizeof(WCHAR))
{
if(!ReadFile(hIn,
byPeekBuf,
dwFileSizeAtLeast < sizeof(byPeekBuf) ? dwFileSizeAtLeast : sizeof(byPeekBuf),
&dwPeekBytesRead,
NULL))
{
//
// if failed and if i/o errors not being ignored then quit
//
if(!args.bContinue) ExitProcess(Perror((DWORD)NULL));
else break;
}
//
// reset the filepointer to beginning
//
if(SetFilePointer(hIn, (LONG)NULL, NULL, FILE_BEGIN) && (!args.bContinue))
ExitProcess(Perror((DWORD)NULL));
}
}
break;
case FILE_TYPE_CHAR:
// stdin from NUL, CON, CONIN$, AUX or COMx
// if AUX or COMx, then quit without creating any files (not even zero byte files)
args.dwBufSize = 1;
{
DWORD dwInMode;
if(!GetConsoleMode(hIn, &dwInMode)) // fails (err 6) if NUL, COMx, AUX
{
COMMTIMEOUTS CommTimeouts;
// suceeds if AUX or COMx so quit (allow NUL)
if(GetCommTimeouts(hIn, &CommTimeouts)) ExitProcess(ERROR_SUCCESS);
}
}
break;
case FILE_TYPE_PIPE: // stdin is from pipe, prn or lpt1
while((!dwPeekBytesRead) && (cPeekTimeout < args.dwPeekTimeout) && (dwCtrlEvent == CTRL_CLEAR_EVENT))
{
if(!PeekNamedPipe(
hIn, // handle to pipe to copy from
byPeekBuf, // pointer to data buffer
PEEK_BUF_SIZE, // size, in bytes, of data buffer
&dwPeekBytesRead, // pointer to number of bytes read
&dwPeekBytesAvailable, // pointer to total number of bytes available
&dwPeekBytesUnavailable)) // pointer to unread bytes in this message
{
if(GetLastError() != ERROR_BROKEN_PIPE) ExitProcess(Perror((DWORD)NULL));
}
Sleep(PEEK_WAIT_INT);
cPeekTimeout += PEEK_WAIT_INT;
}
break;
}
//
// open/create all the files after checking stdin, that way if there was an error then
// zero byte files are not created
//
fi = args.fi.fiNext;
while(fi)
{
fi->hFile = CreateFileW
(
args.bIntermediate ? CreateFullPathW(fi->lpFileName) : fi->lpFileName,
GENERIC_WRITE, // we definitely need write access
FILE_SHARE_READ, // allow others to open file for read
NULL, // security attr - no thanks
OPEN_ALWAYS, // creation disposition - we always want to open or append
0, // flags & attributes - gulp! have you seen the documentation?
NULL // handle to a template? yer right
);
if((fi->hFile == INVALID_HANDLE_VALUE) && !args.bContinue) ExitProcess(Perror((DWORD)NULL));
//
// if appending set filepointer to eof
//
if(fi->bAppend)
{
if((SetFilePointer(fi->hFile, (LONG)NULL, NULL, FILE_END) == INVALID_SET_FILE_POINTER)
&& !args.bContinue)
{
ExitProcess(Perror((DWORD)NULL));
}
}
//
// Check if it happens to be CON or CONOUT$
//
fi->bIsConsole = IsAnOutputConsoleDevice(fi->hFile);
//
// Truncate the possibly existing file to zero size
//
if(!fi->bIsConsole && !SetEndOfFile(fi->hFile))
{
switch(GetLastError())
{
case ERROR_INVALID_HANDLE: // CON, CONOUT$, CONIN$ device, so close the record
//Yes, this is OK also. fi->hFile = INVALID_HANDLE_VALUE;
break;
case ERROR_INVALID_FUNCTION: // NUL device
case ERROR_INVALID_PARAMETER: // PRN device
break;
default:
if(!args.bContinue) ExitProcess(Perror((DWORD)NULL));
}
}
fi = fi->fiNext;
}
//
// if enough bytes read for a meaningful unicode test...
//
if(dwPeekBytesRead >= sizeof(WCHAR) * 2)
{
//Verbose(TEXT("dwPeekBytesRead >= 4\r\n"));
//
// first look for BOM
// TO DO. if BOM found then do not write it to the console
// maybe write to files and then advance input pointer two bytes
//
if((byPeekBuf[0] == 0xFF) && (byPeekBuf[1] == 0xFE)) // notepad and wordpad's unicode format
{
bBomFound = TRUE;
dwInFormat = OP_UNICODE_IN;
}
else
{
iFlags = IS_TEXT_UNICODE_NULL_BYTES;
IsTextUnicode(byPeekBuf, dwPeekBytesRead, &iFlags);
if(iFlags & IS_TEXT_UNICODE_NULL_BYTES)
{
dwInFormat = OP_UNICODE_IN;
}
}
}
// if(dwInFormat & OP_UNICODE_IN) Verbose("Unicode in...\r\n");
// else Verbose("ANSI in...\r\n");
//
// allocate the main I/O buffer
//
lpBuf = (PCHAR) HeapAlloc(GetProcessHeap(), 0, args.dwBufSize * sizeof(CHAR));
if(!lpBuf) ExitProcess(Perror((DWORD)NULL));
if(args.bAnsi) dwOperation = (dwInFormat | OP_ANSI_OUT);
else if(args.bUnicode) dwOperation = (dwInFormat | OP_UNICODE_OUT);
else dwOperation = dwInFormat | (dwInFormat << OP_IN_OUT_SHIFT);
//
// if input starts with a BOM and output is unicode skip over BOM
//
if(bBomFound)
{
if(!ReadFile(hIn, lpBuf, sizeof(WCHAR), &dwBytesRead, NULL))
{
if(GetLastError() != ERROR_BROKEN_PIPE) ExitProcess(Perror((DWORD)NULL));
}
}
//
// if output is unicode and user specified unicode conversion, write BOM to files (but not to the std output)
//
if((dwOperation & OP_UNICODE_OUT) && args.bUnicode)
{
if(!WriteBom(args.fi.fiNext, args.bContinue))
{
if(!args.bContinue) ExitProcess(Perror((DWORD)NULL));
}
}
LARGE_INTEGER startTimestamp, endTimestamp, elapsedTime;
LARGE_INTEGER frequency;
if( args.bElapsedTime )
{
(void)QueryPerformanceFrequency(&frequency);
(void)QueryPerformanceCounter(&startTimestamp);
}
for(;;)
{
if(!ReadFile(hIn, lpBuf, args.dwBufSize * sizeof(CHAR), &dwBytesRead, NULL))
{
if(GetLastError() != ERROR_BROKEN_PIPE)
{
Perror((DWORD)NULL);
break;
}
}
//
// if nothing read or user entered EOF then break (ctrl event also causes nothing to be read )
//
if( (!dwBytesRead) || ((dwStdInType == FILE_TYPE_CHAR) && (*lpBuf == '\x1A')) ) break;
if(dwOperation == OP_ANSI_IN_ANSI_OUT)
{
if(!WriteBufferToConsoleAndFilesA(&args, lpBuf, dwBytesRead, args.bAddDate, args.bAddTime))
{
Perror((DWORD)NULL);
break;
}
}
else if(dwOperation == OP_UNICODE_IN_UNICODE_OUT)
{
if(!WriteBufferToConsoleAndFilesW(&args, (PWCHAR) lpBuf, dwBytesRead / sizeof(WCHAR), args.bAddDate, args.bAddTime))
{
Perror((DWORD)NULL);
break;
}
}
else if(dwOperation == OP_ANSI_IN_UNICODE_OUT)
{
AnsiToUnicode(&lpUnicodeBuf, lpBuf, &dwBytesRead);
if(!WriteBufferToConsoleAndFilesW(&args, (PWCHAR) lpUnicodeBuf, dwBytesRead, args.bAddDate, args.bAddTime))
{
Perror((DWORD)NULL);
break;
}
}
else if(dwOperation == OP_UNICODE_IN_ANSI_OUT)
{
UnicodeToAnsi(&lpAnsiBuf, (PWCHAR) lpBuf, &dwBytesRead);
if(!WriteBufferToConsoleAndFilesA(&args, lpAnsiBuf, dwBytesRead / sizeof(WCHAR), args.bAddDate, args.bAddTime))
{
Perror((DWORD)NULL);
break;
}
}
}
if( args.bElapsedTime )
{
char strElapsedTime[128];
int strLen = 0;
memset( strElapsedTime, 0x00, sizeof(strElapsedTime) );
(void)QueryPerformanceCounter(&endTimestamp);
elapsedTime.QuadPart = endTimestamp.QuadPart - startTimestamp.QuadPart;
elapsedTime.QuadPart *= 1000000L;
elapsedTime.QuadPart /= frequency.QuadPart;
strLen = FormatElapsedTime( &elapsedTime, strElapsedTime,
sizeof(strElapsedTime) );
WriteBufferToConsoleAndFilesA(&args, strElapsedTime, strLen, FALSE,
FALSE);
}
//
// close all open files (not the first entry that contains the std output)
//
fi = args.fi.fiNext;
while(fi)
{
if(fi->hFile != INVALID_HANDLE_VALUE) CloseHandle(fi->hFile);
fi = fi->fiNext;
}
if(bCtrlHandler) SetConsoleCtrlHandler(HandlerRoutine, FALSE);
if(dwStdInType == FILE_TYPE_CHAR) FlushFileBuffers(hIn);
DWORD dwExitCode = 0;
//
// if requested by user, get exit code of piped process
//
if(args.bFwdExitCode) {
GetExitCodeProcess(hPipedProcess, &dwExitCode);
CloseHandle(hPipedProcess);
}
//
// Use ExitProcess (instead of return) to workaround an issue in Windows 10
//
ExitProcess(dwExitCode);
}