2 * CertViewer - tee for Windows
3 * Copyright (c) 2023 "dEajL3kA" <Cumpoing79@web.de>
5 * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
6 * associated documentation files (the "Software"), to deal in the Software without restriction,
7 * including without limitation the rights to use, copy, modify, merge, publish, distribute,
8 * sub license, and/or sell copies of the Software, and to permit persons to whom the Software is
9 * furnished to do so, subject to the following conditions: The above copyright notice and this
10 * permission notice shall be included in all copies or substantial portions of the Software.
12 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
13 * NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
14 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
15 * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT
16 * OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
18 #define WIN32_LEAN_AND_MEAN 1
23 #pragma warning(disable: 4706)
24 #define BUFFSIZE 8192U
26 // --------------------------------------------------------------------------
28 // --------------------------------------------------------------------------
30 static wchar_t to_lower(const wchar_t c
)
32 return ((c
>= L
'A') && (c
<= L
'Z')) ? (L
'a' + (c
- L
'A')) : c
;
35 static BOOL
is_terminal(const HANDLE handle
)
38 return GetConsoleMode(handle
, &mode
);
41 static const wchar_t *get_filename(const wchar_t *filePath
)
43 for (const wchar_t *ptr
= filePath
; *ptr
!= L
'\0'; ++ptr
)
45 if ((*ptr
== L
'\\') || (*ptr
== L
'/'))
53 static BOOL
is_null_device(const wchar_t *filePath
)
55 filePath
= get_filename(filePath
);
56 if ((to_lower(filePath
[0U]) == L
'n') && (to_lower(filePath
[1U]) == L
'u') || (to_lower(filePath
[2U]) == L
'l'))
58 return ((filePath
[3U] == L
'\0') || (filePath
[3U] == L
'.'));
63 static wchar_t *concat_va(const wchar_t *const first
, ...)
70 for (ptr
= first
; ptr
!= NULL
; ptr
= va_arg(ap
, const wchar_t*))
76 wchar_t *const buffer
= (wchar_t*)LocalAlloc(LPTR
, sizeof(wchar_t) * (len
+ 1U));
80 for (ptr
= first
; ptr
!= NULL
; ptr
= va_arg(ap
, const wchar_t*))
82 lstrcatW(buffer
, ptr
);
90 #define CLOSE_HANDLE(HANDLE) do \
92 if (((HANDLE) != NULL) && ((HANDLE) != INVALID_HANDLE_VALUE)) \
94 CloseHandle((HANDLE)); \
100 #define CONCAT(...) concat_va(__VA_ARGS__, NULL)
102 // --------------------------------------------------------------------------
103 // Console CTRL+C handler
104 // --------------------------------------------------------------------------
106 static volatile BOOL g_stop
= FALSE
;
108 static BOOL WINAPI
console_handler(const DWORD ctrlType
)
113 case CTRL_BREAK_EVENT
:
114 case CTRL_CLOSE_EVENT
:
122 // --------------------------------------------------------------------------
124 // --------------------------------------------------------------------------
126 static ULONGLONG
get_version(void)
128 const HRSRC hVersion
= FindResourceW(NULL
, MAKEINTRESOURCE(VS_VERSION_INFO
), RT_VERSION
);
131 const HGLOBAL hResource
= LoadResource(NULL
, hVersion
);
134 const DWORD sizeOfResource
= SizeofResource(NULL
, hResource
);
135 if (sizeOfResource
>= sizeof(VS_FIXEDFILEINFO
))
137 const PVOID addrResourceBlock
= LockResource(hResource
);
138 if (addrResourceBlock
)
140 VS_FIXEDFILEINFO
*fileInfoData
;
142 if (VerQueryValueW(addrResourceBlock
, L
"\\", &fileInfoData
, &fileInfoSize
))
144 ULARGE_INTEGER fileVersion
;
145 fileVersion
.LowPart
= fileInfoData
->dwFileVersionLS
;
146 fileVersion
.HighPart
= fileInfoData
->dwFileVersionMS
;
147 return fileVersion
.QuadPart
;
157 static const wchar_t *get_version_string(void)
159 static wchar_t text
[64U] = { '\0' };
160 lstrcpyW(text
, L
"tee for Windows v#.#.# [" TEXT(__DATE__
) L
"]\n");
162 const ULONGLONG version
= get_version();
165 text
[17U] = L
'0' + ((version
>> 48) & 0xFFFF);
166 text
[19U] = L
'0' + ((version
>> 32) & 0xFFFF);
167 text
[21U] = L
'0' + ((version
>> 16) & 0xFFFF);
173 // --------------------------------------------------------------------------
175 // --------------------------------------------------------------------------
177 static char *utf16_to_utf8(const wchar_t *const input
)
179 const int buff_size
= WideCharToMultiByte(CP_UTF8
, 0, input
, -1, NULL
, 0, NULL
, NULL
);
182 char *const buffer
= (char*)LocalAlloc(LPTR
, buff_size
);
185 const int result
= WideCharToMultiByte(CP_UTF8
, 0, input
, -1, buffer
, buff_size
, NULL
, NULL
);
186 if ((result
> 0) && (result
<= buff_size
))
196 static BOOL
write_text(const HANDLE handle
, const wchar_t *const text
)
200 if (GetConsoleMode(handle
, &written
))
202 result
= WriteConsoleW(handle
, text
, lstrlenW(text
), &written
, NULL
);
206 char *const utf8_text
= utf16_to_utf8(text
);
209 result
= WriteFile(handle
, utf8_text
, lstrlenA(utf8_text
), &written
, NULL
);
210 LocalFree(utf8_text
);
216 #define WRITE_TEXT(...) do \
218 wchar_t* const _message = CONCAT(__VA_ARGS__); \
221 write_text(hStdErr, _message); \
222 LocalFree(_message); \
227 // --------------------------------------------------------------------------
229 // --------------------------------------------------------------------------
231 static BYTE buffer
[2U][BUFFSIZE
];
232 static DWORD bytesTotal
[2U] = { 0U, 0U };
233 static volatile ULONG_PTR index
= 0U;
237 HANDLE hOutput
,hError
;
239 HANDLE hEventReady
[2U], hEventCompleted
;
243 static DWORD WINAPI
writer_thread_start_routine(const LPVOID lpThreadParameter
)
246 const thread_t
*const param
= (thread_t
*) lpThreadParameter
;
250 switch (WaitForMultipleObjects(2U, param
->hEventReady
, FALSE
, INFINITE
))
254 case WAIT_OBJECT_0
+ 1U:
255 SetEvent(param
->hEventCompleted
);
258 write_text(param
->hError
, L
"[tee] System error: Failed to wait for event!\n");
262 const ULONG_PTR myIndex
= index
;
264 for (DWORD offset
= 0U; offset
< bytesTotal
[myIndex
]; offset
+= bytesWritten
)
266 const BOOL result
= WriteFile(param
->hOutput
, buffer
[myIndex
] + offset
, bytesTotal
[myIndex
] - offset
, &bytesWritten
, NULL
);
267 if ((!result
) || (!bytesWritten
))
269 write_text(param
->hError
, L
"[tee] Error: Not all data could be written!\n");
274 SetEvent(param
->hEventCompleted
);
278 FlushFileBuffers(param
->hOutput
);
283 // --------------------------------------------------------------------------
285 // --------------------------------------------------------------------------
289 BOOL append
, flush
, ignore
, help
, version
;
293 #define PARSE_OPTION(SHRT, NAME) do \
295 if ((lc == L##SHRT) || (name && (lstrcmpiW(name, L#NAME) == 0))) \
297 options->NAME = TRUE; \
303 static BOOL
parse_option(options_t
*const options
, const wchar_t c
, const wchar_t *const name
)
305 const wchar_t lc
= to_lower(c
);
307 PARSE_OPTION('a', append
);
308 PARSE_OPTION('f', flush
);
309 PARSE_OPTION('i', ignore
);
310 PARSE_OPTION('h', help
);
311 PARSE_OPTION('v', version
);
316 static BOOL
parse_argument(options_t
*const options
, const wchar_t *const argument
)
318 if ((argument
[0U] != L
'-') || (argument
[1U] == L
'\0'))
323 if (argument
[1U] == L
'-')
325 return (argument
[2U] != L
'\0') && parse_option(options
, L
'\0', argument
+ 2U);
329 for (const wchar_t* ptr
= argument
+ 1U; *ptr
!= L
'\0'; ++ptr
)
331 if (!parse_option(options
, *ptr
, NULL
))
340 // --------------------------------------------------------------------------
342 // --------------------------------------------------------------------------
344 int wmain(const int argc
, const wchar_t *const argv
[])
346 HANDLE hThreads
[2U] = { NULL
, NULL
};
347 HANDLE hEventStop
= NULL
, hEventThrdReady
[2U] = { NULL
, NULL
}, hEventCompleted
[2U] = { NULL
, NULL
};
348 HANDLE hMyFile
= INVALID_HANDLE_VALUE
;
349 int exitCode
= 1, argOff
= 1;
351 thread_t threadData
[2U];
354 SecureZeroMemory(&options
, sizeof(options_t
));
356 /* Initialize standard streams */
357 const HANDLE hStdIn
= GetStdHandle(STD_INPUT_HANDLE
), hStdOut
= GetStdHandle(STD_OUTPUT_HANDLE
), hStdErr
= GetStdHandle(STD_ERROR_HANDLE
);
358 if ((hStdIn
== INVALID_HANDLE_VALUE
) || (hStdOut
== INVALID_HANDLE_VALUE
) || (hStdErr
== INVALID_HANDLE_VALUE
))
363 /* Set up CRTL+C handler */
364 SetConsoleCtrlHandler(console_handler
, TRUE
);
366 /* Parse command-line options */
367 while ((argOff
< argc
) && (argv
[argOff
][0U] == L
'-') && (argv
[argOff
][1U] != L
'\0'))
369 const wchar_t *const argValue
= argv
[argOff
++];
370 if ((argValue
[1U] == L
'-') && (argValue
[2U] == L
'\0'))
374 else if (!parse_argument(&options
, argValue
))
376 WRITE_TEXT(L
"[tee] Error: Invalid option \"", argValue
, L
"\" encountered!\n");
381 /* Print version information */
384 write_text(hStdErr
, get_version_string());
388 /* Print manual page */
391 write_text(hStdErr
, get_version_string());
392 write_text(hStdErr
, L
"\n"
393 L
"Copy standard input to output file, and also to standard output.\n\n"
395 L
" gizmo.exe [...] | tee.exe [options] <output_file>\n\n"
397 L
" -a --append Append to the existing file, instead of truncating\n"
398 L
" -f --flush Flush output file after each write operation\n"
399 L
" -i --ignore Ignore the interrupt signal (SIGINT), e.g. CTRL+C\n\n");
403 /* Check output file name */
406 write_text(hStdErr
, L
"[tee] Error: Output file name is missing. Type \"tee --help\" for details!\n");
410 /* Check for excess arguments */
411 if (argOff
+ 1 < argc
)
413 write_text(hStdErr
, L
"[tee] Warning: Excess command line argument(s) ignored!\n");
416 /* Open output file */
417 if (!is_null_device(argv
[argOff
]))
419 if ((hMyFile
= CreateFileW(argv
[argOff
], GENERIC_WRITE
, FILE_SHARE_READ
, NULL
, options
.append
? OPEN_ALWAYS
: CREATE_ALWAYS
, 0U, NULL
)) == INVALID_HANDLE_VALUE
)
421 WRITE_TEXT(L
"[tee] Error: Failed to open the output file \"", argv
[argOff
], L
"\" for writing!\n");
425 /* Seek to the end of the file */
428 LARGE_INTEGER offset
= { .QuadPart
= 0LL };
429 if (!SetFilePointerEx(hMyFile
, offset
, NULL
, FILE_END
))
431 write_text(hStdErr
, L
"[tee] Error: Failed to move the file pointer to the end of the file!\n");
437 /* Determine thread count */
438 const DWORD threadCount
= (hMyFile
!= INVALID_HANDLE_VALUE
) ? 2U : 1U;
441 if (!(hEventStop
= CreateEventW(NULL
, TRUE
, FALSE
, NULL
)))
443 write_text(hStdErr
, L
"[tee] System error: Failed to create event object!\n\n");
446 for (size_t threadId
= 0U; threadId
< threadCount
; ++threadId
)
448 if (!(hEventThrdReady
[threadId
] = CreateEventW(NULL
, FALSE
, FALSE
, NULL
)))
450 write_text(hStdErr
, L
"[tee] System error: Failed to create event object!\n\n");
453 if (!(hEventCompleted
[threadId
] = CreateEventW(NULL
, FALSE
, FALSE
, NULL
)))
455 write_text(hStdErr
, L
"[tee] System error: Failed to create event object!\n\n");
460 /* Set up thread data */
461 for (size_t threadId
= 0; threadId
< threadCount
; ++threadId
)
463 threadData
[threadId
].hOutput
= (threadId
> 0U) ? hMyFile
: hStdOut
;
464 threadData
[threadId
].hError
= hStdErr
;
465 threadData
[threadId
].flush
= options
.flush
&& (!is_terminal(threadData
[threadId
].hOutput
));
466 threadData
[threadId
].hEventReady
[0U] = hEventThrdReady
[threadId
];
467 threadData
[threadId
].hEventReady
[1U] = hEventStop
;
468 threadData
[threadId
].hEventCompleted
= hEventCompleted
[threadId
];
472 for (DWORD threadId
= 0; threadId
< threadCount
; ++threadId
)
474 if (!(hThreads
[threadId
] = CreateThread(NULL
, 0U, writer_thread_start_routine
, &threadData
[threadId
], 0U, NULL
)))
476 write_text(hStdErr
, L
"[tee] System error: Failed to create thread!\n");
481 /* Are we reading from a pipe? */
482 const BOOL isPipeInput
= (GetFileType(hStdIn
) == FILE_TYPE_PIPE
);
484 /* Initialize index */
485 ULONG_PTR myIndex
= 1U - index
;
487 /* Process all input from STDIN stream */
490 for (DWORD threadId
= 0U; threadId
< threadCount
; ++threadId
)
492 if (!SetEvent(hEventThrdReady
[threadId
]))
494 write_text(hStdErr
, L
"[tee] System error: Failed to signal event!\n");
499 if (!ReadFile(hStdIn
, buffer
[myIndex
], BUFFSIZE
, &bytesTotal
[myIndex
], NULL
))
501 if (GetLastError() != ERROR_BROKEN_PIPE
)
503 write_text(hStdErr
, L
"[tee] Error: Failed to read input data!\n");
509 if ((!bytesTotal
[myIndex
]) && (!isPipeInput
)) /*pipes may return zero bytes, even when more data can become available later!*/
514 const DWORD waitResult
= WaitForMultipleObjects(threadCount
, hEventCompleted
, TRUE
, INFINITE
);
515 if ((waitResult
!= WAIT_OBJECT_0
) && (waitResult
!= WAIT_OBJECT_0
+ 1U))
517 write_text(hStdErr
, L
"[tee] System error: Failed to wait for events!\n");
521 myIndex
= (ULONG_PTR
) InterlockedExchangePointer((PVOID
*)&index
, (PVOID
)myIndex
);
523 while ((!g_stop
) || options
.ignore
);
529 /* Stop the worker threads */
532 SetEvent(hEventStop
);
535 /* Wait for worker threads to exit */
536 const DWORD _threadCount
= hThreads
[0U] ? (hThreads
[1U] ? 2U : 1U) : 0U;
539 const DWORD waitResult
= WaitForMultipleObjects(_threadCount
, hThreads
, TRUE
, 12000U);
540 if ((waitResult
!= WAIT_OBJECT_0
) && (waitResult
!= WAIT_OBJECT_0
+ 1U))
542 for (DWORD threadId
= 0U; threadId
< _threadCount
; ++threadId
)
544 if (WaitForSingleObject(hThreads
[threadId
], 1U) != WAIT_OBJECT_0
)
546 write_text(hStdErr
, L
"[tee] Error: Worker thread did not exit cleanly!\n");
547 TerminateThread(hThreads
[threadId
], 1U);
553 /* Flush the output file */
554 if ((hMyFile
!= INVALID_HANDLE_VALUE
) && options
.flush
)
556 FlushFileBuffers(hMyFile
);
559 /* Close worker threads */
560 for (DWORD threadId
= 0U; threadId
< 2U; ++threadId
)
562 CLOSE_HANDLE(hThreads
[threadId
]);
565 /* Close the output file */
566 CLOSE_HANDLE(hMyFile
);
569 for (DWORD threadId
= 0U; threadId
< 2U; ++threadId
)
571 CLOSE_HANDLE(hEventThrdReady
[threadId
]);
572 CLOSE_HANDLE(hEventCompleted
[threadId
]);
574 CLOSE_HANDLE(hEventStop
);
580 // --------------------------------------------------------------------------
582 // --------------------------------------------------------------------------
584 #pragma warning(disable: 4702)
586 int wmainCRTStartup(void)
588 SetErrorMode(SEM_FAILCRITICALERRORS
);
591 LPWSTR
*const szArglist
= CommandLineToArgvW(GetCommandLineW(), &nArgs
);
594 ExitProcess((UINT
)-1);
597 const int retval
= wmain(nArgs
, szArglist
);
598 LocalFree(szArglist
);
599 ExitProcess((UINT
)retval
);