1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087 |
- #define PROCESS_WIN32_PRIVATE
- #include "lib/intmath/cmp.h"
- #include "lib/buf/buffers.h"
- #include "lib/net/buffers_net.h"
- #include "lib/container/smartlist.h"
- #include "lib/log/log.h"
- #include "lib/log/util_bug.h"
- #include "lib/log/win32err.h"
- #include "lib/process/process.h"
- #include "lib/process/process_win32.h"
- #include "lib/process/env.h"
- #ifdef HAVE_SYS_TIME_H
- #include <sys/time.h>
- #endif
- #ifdef HAVE_STRING_H
- #include <string.h>
- #endif
- #ifdef _WIN32
- #define BUFFER_SIZE (1024)
- static periodic_timer_t *periodic_timer;
- struct process_win32_handle_t {
-
- HANDLE pipe;
-
- bool reached_eof;
-
- size_t data_available;
-
- char buffer[BUFFER_SIZE];
-
- OVERLAPPED overlapped;
-
- bool busy;
- };
- struct process_win32_t {
-
- process_win32_handle_t stdin_handle;
-
- process_win32_handle_t stdout_handle;
-
- process_win32_handle_t stderr_handle;
-
- PROCESS_INFORMATION process_information;
- };
- process_win32_t *
- process_win32_new(void)
- {
- process_win32_t *win32_process;
- win32_process = tor_malloc_zero(sizeof(process_win32_t));
- win32_process->stdin_handle.pipe = INVALID_HANDLE_VALUE;
- win32_process->stdout_handle.pipe = INVALID_HANDLE_VALUE;
- win32_process->stderr_handle.pipe = INVALID_HANDLE_VALUE;
- return win32_process;
- }
- void
- process_win32_free_(process_win32_t *win32_process)
- {
- if (! win32_process)
- return;
-
- process_win32_cleanup_handle(&win32_process->stdin_handle);
- process_win32_cleanup_handle(&win32_process->stdout_handle);
- process_win32_cleanup_handle(&win32_process->stderr_handle);
- tor_free(win32_process);
- }
- void
- process_win32_init(void)
- {
-
- }
- void
- process_win32_deinit(void)
- {
-
- if (process_win32_timer_running())
- process_win32_timer_stop();
- }
- process_status_t
- process_win32_exec(process_t *process)
- {
- tor_assert(process);
- process_win32_t *win32_process = process_get_win32_process(process);
- HANDLE stdout_pipe_read = NULL;
- HANDLE stdout_pipe_write = NULL;
- HANDLE stderr_pipe_read = NULL;
- HANDLE stderr_pipe_write = NULL;
- HANDLE stdin_pipe_read = NULL;
- HANDLE stdin_pipe_write = NULL;
- BOOL ret = FALSE;
-
- SECURITY_ATTRIBUTES security_attributes;
- security_attributes.nLength = sizeof(security_attributes);
- security_attributes.bInheritHandle = TRUE;
-
- security_attributes.lpSecurityDescriptor = NULL;
-
- if (! process_win32_create_pipe(&stdout_pipe_read,
- &stdout_pipe_write,
- &security_attributes,
- PROCESS_WIN32_PIPE_TYPE_READER)) {
- return PROCESS_STATUS_ERROR;
- }
-
- if (! process_win32_create_pipe(&stderr_pipe_read,
- &stderr_pipe_write,
- &security_attributes,
- PROCESS_WIN32_PIPE_TYPE_READER)) {
- return PROCESS_STATUS_ERROR;
- }
-
- if (! process_win32_create_pipe(&stdin_pipe_read,
- &stdin_pipe_write,
- &security_attributes,
- PROCESS_WIN32_PIPE_TYPE_WRITER)) {
- return PROCESS_STATUS_ERROR;
- }
-
- STARTUPINFOA startup_info;
- memset(&startup_info, 0, sizeof(startup_info));
- startup_info.cb = sizeof(startup_info);
- startup_info.hStdError = stderr_pipe_write;
- startup_info.hStdOutput = stdout_pipe_write;
- startup_info.hStdInput = stdin_pipe_read;
- startup_info.dwFlags |= STARTF_USESTDHANDLES;
-
- process_environment_t *env = process_get_environment(process);
-
- char **argv = process_get_argv(process);
-
- char *joined_argv = tor_join_win_cmdline((const char **)argv);
-
- ret = CreateProcessA(NULL,
- joined_argv,
- NULL,
- NULL,
- TRUE,
- CREATE_NO_WINDOW,
- env->windows_environment_block[0] == '\0' ?
- NULL : env->windows_environment_block,
- NULL,
- &startup_info,
- &win32_process->process_information);
- tor_free(argv);
- tor_free(joined_argv);
- process_environment_free(env);
- if (! ret) {
- log_warn(LD_PROCESS, "CreateProcessA() failed: %s",
- format_win32_error(GetLastError()));
-
- CloseHandle(stdout_pipe_read);
- CloseHandle(stdout_pipe_write);
- CloseHandle(stderr_pipe_read);
- CloseHandle(stderr_pipe_write);
- CloseHandle(stdin_pipe_read);
- CloseHandle(stdin_pipe_write);
- return PROCESS_STATUS_ERROR;
- }
-
- win32_process->stdout_handle.pipe = stdout_pipe_read;
- win32_process->stderr_handle.pipe = stderr_pipe_read;
- win32_process->stdin_handle.pipe = stdin_pipe_write;
-
- CloseHandle(stdout_pipe_write);
- CloseHandle(stderr_pipe_write);
- CloseHandle(stdin_pipe_read);
-
- win32_process->stdout_handle.overlapped.hEvent = (HANDLE)process;
- win32_process->stderr_handle.overlapped.hEvent = (HANDLE)process;
- win32_process->stdin_handle.overlapped.hEvent = (HANDLE)process;
-
- if (! process_win32_timer_running())
- process_win32_timer_start();
-
- process_notify_event_stdout(process);
- process_notify_event_stderr(process);
- return PROCESS_STATUS_RUNNING;
- }
- bool
- process_win32_terminate(process_t *process)
- {
- tor_assert(process);
- process_win32_t *win32_process = process_get_win32_process(process);
-
- BOOL ret;
- ret = TerminateProcess(win32_process->process_information.hProcess, 0);
- if (! ret) {
- log_warn(LD_PROCESS, "TerminateProcess() failed: %s",
- format_win32_error(GetLastError()));
- return false;
- }
-
- process_win32_cleanup_handle(&win32_process->stdin_handle);
- process_win32_cleanup_handle(&win32_process->stdout_handle);
- process_win32_cleanup_handle(&win32_process->stderr_handle);
- return true;
- }
- process_pid_t
- process_win32_get_pid(process_t *process)
- {
- tor_assert(process);
- process_win32_t *win32_process = process_get_win32_process(process);
- return (process_pid_t)win32_process->process_information.dwProcessId;
- }
- int
- process_win32_write(struct process_t *process, buf_t *buffer)
- {
- tor_assert(process);
- tor_assert(buffer);
- process_win32_t *win32_process = process_get_win32_process(process);
- BOOL ret = FALSE;
- DWORD error_code = 0;
- const size_t buffer_size = buf_datalen(buffer);
-
- if (win32_process->stdin_handle.busy)
- return 0;
-
- if (buffer_size == 0)
- return 0;
-
- if (BUG(win32_process->stdin_handle.reached_eof))
- return 0;
-
- const size_t write_size = MIN(buffer_size,
- sizeof(win32_process->stdin_handle.buffer));
-
- buf_get_bytes(buffer, win32_process->stdin_handle.buffer, write_size);
-
- SetLastError(0);
-
- ret = WriteFileEx(win32_process->stdin_handle.pipe,
- win32_process->stdin_handle.buffer,
- write_size,
- &win32_process->stdin_handle.overlapped,
- process_win32_stdin_write_done);
- if (! ret) {
- error_code = GetLastError();
-
- if (error_code == ERROR_HANDLE_EOF || error_code == ERROR_BROKEN_PIPE) {
- log_debug(LD_PROCESS, "WriteFileEx() returned EOF from pipe: %s",
- format_win32_error(error_code));
- } else {
- log_warn(LD_PROCESS, "WriteFileEx() failed: %s",
- format_win32_error(error_code));
- }
- win32_process->stdin_handle.reached_eof = true;
- return 0;
- }
-
- error_code = GetLastError();
- if (error_code != ERROR_SUCCESS) {
-
- log_warn(LD_PROCESS, "WriteFileEx() failed after returning success: %s",
- format_win32_error(error_code));
- win32_process->stdin_handle.reached_eof = true;
- return 0;
-
- }
-
- return (int)write_size;
- }
- int
- process_win32_read_stdout(struct process_t *process, buf_t *buffer)
- {
- tor_assert(process);
- tor_assert(buffer);
- process_win32_t *win32_process = process_get_win32_process(process);
- return process_win32_read_from_handle(&win32_process->stdout_handle,
- buffer,
- process_win32_stdout_read_done);
- }
- int
- process_win32_read_stderr(struct process_t *process, buf_t *buffer)
- {
- tor_assert(process);
- tor_assert(buffer);
- process_win32_t *win32_process = process_get_win32_process(process);
- return process_win32_read_from_handle(&win32_process->stderr_handle,
- buffer,
- process_win32_stderr_read_done);
- }
- void
- process_win32_trigger_completion_callbacks(void)
- {
- DWORD ret;
-
- ret = SleepEx(0, TRUE);
-
- if (ret != 0 && ret != WAIT_IO_COMPLETION) {
- log_warn(LD_PROCESS, "SleepEx() returned %lu", ret);
- }
- }
- void
- process_win32_timer_start(void)
- {
-
- if (BUG(process_win32_timer_running()))
- return;
-
- static const struct timeval interval = {1, 0};
- log_info(LD_PROCESS, "Starting Windows Process I/O timer");
- periodic_timer = periodic_timer_new(tor_libevent_get_base(),
- &interval,
- process_win32_timer_callback,
- NULL);
- }
- void
- process_win32_timer_stop(void)
- {
- if (BUG(periodic_timer == NULL))
- return;
- log_info(LD_PROCESS, "Stopping Windows Process I/O timer");
- periodic_timer_free(periodic_timer);
- }
- bool
- process_win32_timer_running(void)
- {
- return periodic_timer != NULL;
- }
- STATIC void
- process_win32_timer_callback(periodic_timer_t *timer, void *data)
- {
- tor_assert(timer == periodic_timer);
- tor_assert(data == NULL);
-
- process_win32_trigger_completion_callbacks();
-
-
- bool done;
- do {
- const smartlist_t *processes = process_get_all_processes();
- done = true;
- SMARTLIST_FOREACH_BEGIN(processes, process_t *, process) {
-
- if (process_win32_timer_test_process(process)) {
- done = false;
- break;
- }
- } SMARTLIST_FOREACH_END(process);
- } while (! done);
- }
- STATIC bool
- process_win32_timer_test_process(process_t *process)
- {
- tor_assert(process);
-
- if (process_get_status(process) != PROCESS_STATUS_RUNNING)
- return false;
- process_win32_t *win32_process = process_get_win32_process(process);
- BOOL ret = FALSE;
- DWORD exit_code = 0;
-
- if (! win32_process->stdout_handle.reached_eof)
- return false;
- if (! win32_process->stderr_handle.reached_eof)
- return false;
-
- ret = GetExitCodeProcess(win32_process->process_information.hProcess,
- &exit_code);
- if (! ret) {
- log_warn(LD_PROCESS, "GetExitCodeProcess() failed: %s",
- format_win32_error(GetLastError()));
- return false;
- }
-
- if (exit_code != STILL_ACTIVE) {
- process_notify_event_exit(process, exit_code);
- return true;
- }
- return false;
- }
- STATIC bool
- process_win32_create_pipe(HANDLE *read_pipe,
- HANDLE *write_pipe,
- SECURITY_ATTRIBUTES *attributes,
- process_win32_pipe_type_t pipe_type)
- {
- tor_assert(read_pipe);
- tor_assert(write_pipe);
- tor_assert(attributes);
- BOOL ret = FALSE;
-
- const size_t size = 4096;
-
- DWORD read_mode = 0;
- DWORD write_mode = 0;
-
- char pipe_name[MAX_PATH];
- static DWORD process_id = 0;
- static DWORD counter = 0;
- if (process_id == 0)
- process_id = GetCurrentProcessId();
- tor_snprintf(pipe_name, sizeof(pipe_name),
- "\\\\.\\Pipe\\Tor-Process-Pipe-%lu-%lu",
- process_id, counter++);
-
- switch (pipe_type) {
- case PROCESS_WIN32_PIPE_TYPE_READER:
- read_mode = FILE_FLAG_OVERLAPPED;
- break;
- case PROCESS_WIN32_PIPE_TYPE_WRITER:
- write_mode = FILE_FLAG_OVERLAPPED;
- break;
- default:
-
- tor_assert_nonfatal_unreached_once();
-
- }
-
- HANDLE read_handle;
- HANDLE write_handle;
-
- read_handle = CreateNamedPipeA(pipe_name,
- (PIPE_ACCESS_INBOUND|read_mode),
- (PIPE_TYPE_BYTE|PIPE_WAIT),
- 1,
- size,
- size,
- 1000,
- attributes);
- if (read_handle == INVALID_HANDLE_VALUE) {
- log_warn(LD_PROCESS, "CreateNamedPipeA() failed: %s",
- format_win32_error(GetLastError()));
- return false;
- }
-
- write_handle = CreateFileA(pipe_name,
- GENERIC_WRITE,
- 0,
- attributes,
- OPEN_EXISTING,
- (FILE_ATTRIBUTE_NORMAL|write_mode),
- NULL);
- if (write_handle == INVALID_HANDLE_VALUE) {
- log_warn(LD_PROCESS, "CreateFileA() failed: %s",
- format_win32_error(GetLastError()));
- CloseHandle(read_handle);
- return false;
- }
-
- switch (pipe_type) {
- case PROCESS_WIN32_PIPE_TYPE_READER:
- ret = SetHandleInformation(read_handle, HANDLE_FLAG_INHERIT, 0);
- break;
- case PROCESS_WIN32_PIPE_TYPE_WRITER:
- ret = SetHandleInformation(write_handle, HANDLE_FLAG_INHERIT, 0);
- break;
- default:
-
- tor_assert_nonfatal_unreached_once();
-
- }
- if (! ret) {
- log_warn(LD_PROCESS, "SetHandleInformation() failed: %s",
- format_win32_error(GetLastError()));
- CloseHandle(read_handle);
- CloseHandle(write_handle);
- return false;
- }
-
- *read_pipe = read_handle;
- *write_pipe = write_handle;
- return true;
- }
- STATIC void
- process_win32_cleanup_handle(process_win32_handle_t *handle)
- {
- tor_assert(handle);
- #if 0
- BOOL ret;
- DWORD error_code;
-
- ret = CancelIo(handle->pipe);
- if (! ret) {
- error_code = GetLastError();
-
- if (error_code != ERROR_NOT_FOUND) {
- log_warn(LD_PROCESS, "CancelIo() failed: %s",
- format_win32_error(error_code));
- }
- }
- #endif
-
- if (handle->pipe != INVALID_HANDLE_VALUE) {
- CloseHandle(handle->pipe);
- handle->pipe = INVALID_HANDLE_VALUE;
- handle->reached_eof = true;
- }
- }
- STATIC VOID WINAPI
- process_win32_stdout_read_done(DWORD error_code,
- DWORD byte_count,
- LPOVERLAPPED overlapped)
- {
- tor_assert(overlapped);
- tor_assert(overlapped->hEvent);
-
- process_t *process = (process_t *)overlapped->hEvent;
- process_win32_t *win32_process = process_get_win32_process(process);
- if (process_win32_handle_read_completion(&win32_process->stdout_handle,
- error_code,
- byte_count)) {
-
- process_notify_event_stdout(process);
- }
- }
- STATIC VOID WINAPI
- process_win32_stderr_read_done(DWORD error_code,
- DWORD byte_count,
- LPOVERLAPPED overlapped)
- {
- tor_assert(overlapped);
- tor_assert(overlapped->hEvent);
-
- process_t *process = (process_t *)overlapped->hEvent;
- process_win32_t *win32_process = process_get_win32_process(process);
- if (process_win32_handle_read_completion(&win32_process->stderr_handle,
- error_code,
- byte_count)) {
-
- process_notify_event_stderr(process);
- }
- }
- STATIC VOID WINAPI
- process_win32_stdin_write_done(DWORD error_code,
- DWORD byte_count,
- LPOVERLAPPED overlapped)
- {
- tor_assert(overlapped);
- tor_assert(overlapped->hEvent);
- (void)byte_count;
- process_t *process = (process_t *)overlapped->hEvent;
- process_win32_t *win32_process = process_get_win32_process(process);
-
- win32_process->stdin_handle.busy = false;
-
- if (BUG(win32_process->stdin_handle.reached_eof))
- return;
- if (error_code == 0) {
-
- win32_process->stdin_handle.data_available = 0;
- memset(win32_process->stdin_handle.buffer, 0,
- sizeof(win32_process->stdin_handle.buffer));
-
- process_notify_event_stdin(process);
- } else if (error_code == ERROR_HANDLE_EOF ||
- error_code == ERROR_BROKEN_PIPE) {
-
- tor_assert(byte_count == 0);
- win32_process->stdin_handle.reached_eof = true;
- } else {
-
- log_warn(LD_PROCESS,
- "Error in I/O completion routine from WriteFileEx(): %s",
- format_win32_error(error_code));
- win32_process->stdin_handle.reached_eof = true;
- }
- }
- STATIC int
- process_win32_read_from_handle(process_win32_handle_t *handle,
- buf_t *buffer,
- LPOVERLAPPED_COMPLETION_ROUTINE callback)
- {
- tor_assert(handle);
- tor_assert(buffer);
- tor_assert(callback);
- BOOL ret = FALSE;
- int bytes_available = 0;
- DWORD error_code = 0;
-
- if (BUG(handle->busy))
- return 0;
-
- if (BUG(handle->reached_eof))
- return 0;
-
- bytes_available = (int)handle->data_available;
- if (handle->data_available > 0) {
-
- buf_add(buffer, handle->buffer, handle->data_available);
-
- handle->data_available = 0;
- memset(handle->buffer, 0, sizeof(handle->buffer));
- }
-
- SetLastError(0);
-
- ret = ReadFileEx(handle->pipe,
- handle->buffer,
- sizeof(handle->buffer),
- &handle->overlapped,
- callback);
- if (! ret) {
- error_code = GetLastError();
-
- if (error_code == ERROR_HANDLE_EOF || error_code == ERROR_BROKEN_PIPE) {
- log_debug(LD_PROCESS, "ReadFileEx() returned EOF from pipe: %s",
- format_win32_error(error_code));
- } else {
- log_warn(LD_PROCESS, "ReadFileEx() failed: %s",
- format_win32_error(error_code));
- }
- handle->reached_eof = true;
- return bytes_available;
- }
-
- error_code = GetLastError();
- if (error_code != ERROR_SUCCESS) {
-
- log_warn(LD_PROCESS, "ReadFileEx() failed after returning success: %s",
- format_win32_error(error_code));
- handle->reached_eof = true;
- return bytes_available;
-
- }
-
- handle->busy = true;
- return bytes_available;
- }
- STATIC bool
- process_win32_handle_read_completion(process_win32_handle_t *handle,
- DWORD error_code,
- DWORD byte_count)
- {
- tor_assert(handle);
-
- handle->busy = false;
- if (error_code == 0) {
-
-
- tor_assert(byte_count <= BUFFER_SIZE);
- handle->data_available = (size_t)byte_count;
-
- return true;
- } else if (error_code == ERROR_HANDLE_EOF ||
- error_code == ERROR_BROKEN_PIPE) {
-
- tor_assert(byte_count == 0);
- handle->reached_eof = true;
- } else {
-
- log_warn(LD_PROCESS,
- "Error in I/O completion routine from ReadFileEx(): %s",
- format_win32_error(error_code));
- handle->reached_eof = true;
- }
-
- return false;
- }
- STATIC char *
- format_win_cmdline_argument(const char *arg)
- {
- char *formatted_arg;
- char need_quotes;
- const char *c;
- int i;
- int bs_counter = 0;
-
- const char backslash = '\\';
-
- smartlist_t *arg_chars;
- arg_chars = smartlist_new();
-
- need_quotes = (strchr(arg, ' ') || strchr(arg, '\t') || '\0' == arg[0]);
-
- for (c=arg; *c != '\0'; c++) {
- if ('"' == *c) {
-
- for (i=0; i<(bs_counter*2); i++)
- smartlist_add(arg_chars, (void*)&backslash);
- bs_counter = 0;
-
- smartlist_add(arg_chars, (void*)&backslash);
- smartlist_add(arg_chars, (void*)c);
- } else if ('\\' == *c) {
-
- bs_counter++;
- } else {
-
- for (i=0; i<bs_counter; i++)
- smartlist_add(arg_chars, (void*)&backslash);
- bs_counter = 0;
- smartlist_add(arg_chars, (void*)c);
- }
- }
-
- for (i=0; i<bs_counter; i++)
- smartlist_add(arg_chars, (void*)&backslash);
-
- const size_t formatted_arg_len = smartlist_len(arg_chars) +
- (need_quotes ? 2 : 0) + 1;
- formatted_arg = tor_malloc_zero(formatted_arg_len);
-
- i=0;
- if (need_quotes)
- formatted_arg[i++] = '"';
-
- SMARTLIST_FOREACH(arg_chars, char*, ch,
- {
- formatted_arg[i++] = *ch;
- });
-
- if (need_quotes)
- formatted_arg[i++] = '"';
- formatted_arg[i] = '\0';
- smartlist_free(arg_chars);
- return formatted_arg;
- }
- STATIC char *
- tor_join_win_cmdline(const char *argv[])
- {
- smartlist_t *argv_list;
- char *joined_argv;
- int i;
-
- argv_list = smartlist_new();
- for (i=0; argv[i] != NULL; i++) {
- smartlist_add(argv_list, (void *)format_win_cmdline_argument(argv[i]));
- }
-
- joined_argv = smartlist_join_strings(argv_list, " ", 0, NULL);
-
- SMARTLIST_FOREACH(argv_list, char *, arg,
- {
- tor_free(arg);
- });
- smartlist_free(argv_list);
- return joined_argv;
- }
- #endif
|