2 * Copyright (c) 2004-2005 Sergey Lyubka <valenok@gmail.com>
5 * "THE BEER-WARE LICENSE" (Revision 42):
6 * Sergey Lyubka wrote this file. As long as you retain this notice you
7 * can do whatever you want with this stuff. If we meet some day, and you think
8 * this stuff is worth it, you can buy me a beer in return.
13 static const char *config_file = CONFIG;
17 static HICON hIcon; /* SHTTPD icon handle */
18 HWND hLog; /* Log window */
21 * Dialog box control IDs
27 #define ID_SETTINGS 104
29 #define ID_TRAYICON 106
32 #define ID_ADVANCED 109
33 #define ID_SHOWLOG 110
40 run_server(void *param)
42 struct shttpd_ctx *ctx = param;
44 open_listening_ports(ctx);
46 while (WaitForSingleObject(ctx->ev[0], 0) != WAIT_OBJECT_0)
47 shttpd_poll(ctx, 1000);
54 * Save the configuration back into config file
57 save_config(HWND hDlg, FILE *fp)
59 const struct opt *opt;
60 char text[FILENAME_MAX];
64 elog(E_FATAL, NULL, "save_config: cannot open %s", config_file);
66 for (opt = options; opt->name != NULL; opt++) {
67 id = ID_USER + (opt - options); /* Control ID */
69 /* Do not save if the text is the same as default */
71 if (opt->flags & OPT_BOOL)
72 (void) fprintf(fp, "%s\t%d\n",
73 opt->name, IsDlgButtonChecked(hDlg, id));
74 else if (GetDlgItemText(hDlg, id, text, sizeof(text)) != 0 &&
75 (opt->def == NULL || strcmp(text, opt->def) != 0))
76 (void) fprintf(fp, "%s\t%s\n", opt->name, text);
83 set_control_values(HWND hDlg, const struct shttpd_ctx *ctx)
85 const struct opt *opt;
86 const union variant *v;
87 char buf[FILENAME_MAX];
90 for (opt = options; opt->name != NULL; opt++) {
91 id = ID_USER + (opt - options);
92 v = (union variant *) ((char *) ctx + opt->ofs);
93 if (opt->flags & OPT_BOOL) {
94 CheckDlgButton(hDlg, id,
95 v->v_int ? BST_CHECKED : BST_UNCHECKED);
96 } else if (opt->flags & OPT_INT) {
97 my_snprintf(buf, sizeof(buf), "%d", v->v_int);
98 SetDlgItemText(hDlg, id, buf);
100 SetDlgItemText(hDlg, id, v->v_str);
107 DlgProc(HWND hDlg, UINT msg, WPARAM wParam, LPARAM lParam)
109 static struct shttpd_ctx *ctx, **pctx;
111 const struct opt *opt;
119 KillTimer(hDlg, ID_TIMER);
124 switch (LOWORD(wParam)) {
126 EnableWindow(GetDlgItem(hDlg, ID_SAVE), FALSE);
127 save_config(hDlg, fopen(config_file, "w+"));
129 SetEvent(ctx->ev[0]);
130 WaitForSingleObject(ev, INFINITE);
131 *pctx = ctx = init_from_argc_argv(config_file, 0, NULL);
132 open_listening_ports(ctx);
133 _beginthread(run_server, 0, ctx);
134 EnableWindow(GetDlgItem(hDlg, ID_SAVE), TRUE);
139 id = ID_USER + ID_DELTA;
140 for (opt = options; opt->name != NULL; opt++, id++)
141 if (LOWORD(wParam) == id) {
144 char path[FILENAME_MAX] = "";
146 memset(&of, 0, sizeof(of));
147 of.lStructSize = sizeof(of);
148 of.hwndOwner = (HWND) hDlg;
150 of.nMaxFile = sizeof(path);
151 of.lpstrInitialDir = ctx->document_root;
152 of.Flags = OFN_CREATEPROMPT | OFN_NOCHANGEDIR;
154 memset(&bi, 0, sizeof(bi));
155 bi.hwndOwner = (HWND) hDlg;
156 bi.lpszTitle = "Choose WWW root directory:";
157 bi.ulFlags = BIF_RETURNONLYFSDIRS;
159 if (opt->flags & OPT_DIR)
161 SHBrowseForFolder(&bi), path);
163 GetOpenFileName(&of);
166 SetWindowText(GetDlgItem(hDlg,
167 id - ID_DELTA), path);
173 pctx = (struct shttpd_ctx **) lParam;
175 SendMessage(hDlg,WM_SETICON,(WPARAM)ICON_SMALL,(LPARAM)hIcon);
176 SendMessage(hDlg,WM_SETICON,(WPARAM)ICON_BIG,(LPARAM)hIcon);
177 SetWindowText(hDlg, "SHTTPD settings");
178 SetFocus(GetDlgItem(hDlg, ID_SAVE));
179 set_control_values(hDlg, ctx);
189 align(void *ptr, DWORD alig)
191 ULONG ul = (ULONG) ptr;
196 return ((void *) ul);
201 add_control(unsigned char **mem, DLGTEMPLATE *dia, WORD type, DWORD id,
202 DWORD style, WORD x, WORD y, WORD cx, WORD cy, const char *caption)
209 *mem = align(*mem, 3);
210 tp = (DLGITEMTEMPLATE *) *mem;
214 tp->dwExtendedStyle = 0;
220 p = align(*mem + sizeof(*tp), 1);
224 while (*caption != '\0')
225 *p++ = (WCHAR) *caption++;
230 *mem = (unsigned char *) p;
234 show_settings_dialog(struct shttpd_ctx **ctxp)
238 #define LABEL_WIDTH 70
240 unsigned char mem[4096], *p;
242 DLGTEMPLATE *dia = (DLGTEMPLATE *) mem;
243 WORD cl, x, y, width, nelems = 0;
244 const struct opt *opt;
248 DLGTEMPLATE template; /* 18 bytes */
253 } dialog_header = {{WS_CAPTION | WS_POPUP | WS_SYSMENU | WS_VISIBLE |
254 DS_SETFONT | WS_DLGFRAME, WS_EX_TOOLWINDOW,
255 0, 200, 200, WIDTH, 0}, 0, 0, L"", 8, L"Tahoma"};
262 (void) memset(mem, 0, sizeof(mem));
263 (void) memcpy(mem, &dialog_header, sizeof(dialog_header));
264 p = mem + sizeof(dialog_header);
266 for (opt = options; opt->name != NULL; opt++) {
268 style = WS_CHILD | WS_VISIBLE | WS_TABSTOP;
269 x = 10 + (WIDTH / 2) * (nelems % 2);
270 y = (nelems/2 + 1) * HEIGHT + 5;
271 width = WIDTH / 2 - 20 - LABEL_WIDTH;
272 if (opt->flags & OPT_INT) {
275 style |= WS_BORDER | ES_AUTOHSCROLL;
276 } else if (opt->flags & OPT_BOOL) {
278 style |= BS_AUTOCHECKBOX;
279 } else if (opt->flags & (OPT_DIR | OPT_FILE)) {
280 style |= WS_BORDER | ES_AUTOHSCROLL;
283 add_control(&p, dia, 0x80,
284 ID_USER + ID_DELTA + (opt - options),
285 WS_VISIBLE | WS_CHILD | BS_PUSHBUTTON,
286 (WORD) (x + width + LABEL_WIDTH + 5),
290 style |= WS_BORDER | ES_AUTOHSCROLL;
292 add_control(&p, dia, 0x82, ID_STATIC, WS_VISIBLE | WS_CHILD,
293 x, y, LABEL_WIDTH, HEIGHT, opt->desc);
294 add_control(&p, dia, cl, ID_USER + (opt - options), style,
295 (WORD) (x + LABEL_WIDTH), y, width, 12, "");
299 y = (WORD) (((nelems + 1)/2 + 1) * HEIGHT + 5);
300 add_control(&p, dia, 0x80, ID_GROUP, WS_CHILD | WS_VISIBLE |
301 BS_GROUPBOX, 5, 5, WIDTH - 10, y, "Settings");
303 add_control(&p, dia, 0x80, ID_SAVE,
304 WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON | WS_TABSTOP,
305 WIDTH - 70, y, 65, 12, "Save Settings");
307 add_control(&p, dia, 0x80, ID_ADVANCED,
308 WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON | WS_TABSTOP,
309 WIDTH - 190, y, 110, 12, "Show Advanced Settings >>");
311 add_control(&p, dia, 0x82, ID_STATIC,
312 WS_CHILD | WS_VISIBLE | WS_DISABLED,
313 5, y, 180, 12,"SHTTPD v." VERSION
314 " (http://shttpd.sourceforge.net)");
316 dia->cy = ((nelems + 1)/2 + 1) * HEIGHT + 30;
317 DialogBoxIndirectParam(NULL, dia, NULL, DlgProc, (LPARAM) ctxp);
322 LogProc(HWND hDlg, UINT msg, WPARAM wParam, LPARAM lParam)
324 static struct shttpd_ctx *ctx;
327 RECT rect, rect2, rect3, rect4;
328 int len, up, widths[] = {120, 220, 330, 460, -1};
329 char text[256], buf[1024 * 64];
334 KillTimer(hDlg, ID_TIMER);
339 hEdit = GetDlgItem(hDlg, ID_LOG);
340 len = GetWindowText(hEdit, buf, sizeof(buf));
341 if (len > sizeof(buf) * 4 / 5)
342 len = sizeof(buf) * 4 / 5;
343 my_snprintf(buf + len, sizeof(buf) - len,
344 "%s\r\n", (char *) lParam);
345 SetWindowText(hEdit, buf);
346 SendMessage(hEdit, WM_VSCROLL, SB_BOTTOM, 0);
350 /* Print statistics on a status bar */
351 up = current_time - ctx->start_time;
352 (void) my_snprintf(text, sizeof(text),
353 " Up: %3d h %2d min %2d sec",
354 up / 3600, up / 60 % 60, up % 60);
355 SendMessage(hStatus, SB_SETTEXT, 0, (LPARAM) text);
356 (void) my_snprintf(text, sizeof(text),
357 " Requests: %u", ctx->nrequests);
358 SendMessage(hStatus, SB_SETTEXT, 1, (LPARAM) text);
359 (void) my_snprintf(text, sizeof(text),
360 " Sent: %4.2f Mb", (double) ctx->out / 1048576);
361 SendMessage(hStatus, SB_SETTEXT, 2, (LPARAM) text);
362 (void) my_snprintf(text, sizeof(text),
363 " Received: %4.2f Mb", (double) ctx->in / 1048576);
364 SendMessage(hStatus, SB_SETTEXT, 3, (LPARAM) text);
368 ctx = (struct shttpd_ctx *) lParam;
369 SendMessage(hDlg,WM_SETICON,(WPARAM)ICON_SMALL,(LPARAM)hIcon);
370 SendMessage(hDlg,WM_SETICON,(WPARAM)ICON_BIG,(LPARAM)hIcon);
371 hStatus = CreateStatusWindow(WS_CHILD | WS_VISIBLE,
372 "", hDlg, ID_STATUS);
373 SendMessage(hStatus, SB_SETPARTS, 5, (LPARAM) widths);
374 SendMessage(hStatus, SB_SETTEXT, 4, (LPARAM) " Running");
375 SetWindowText(hDlg, "SHTTPD web server log");
376 SetTimer(hDlg, ID_TIMER, 1000, NULL);
377 GetWindowRect(GetDesktopWindow(), &rect3);
378 GetWindowRect(hDlg, &rect4);
379 GetClientRect(hDlg, &rect);
380 GetClientRect(hStatus, &rect2);
381 SetWindowPos(GetDlgItem(hDlg, ID_LOG), 0,
382 0, 0, rect.right, rect.bottom - rect2.bottom, 0);
383 SetWindowPos(hDlg, HWND_TOPMOST,
384 rect3.right - (rect4.right - rect4.left),
385 rect3.bottom - (rect4.bottom - rect4.top) - 30,
388 SendMessage(hDlg, WM_TIMER, 0, 0);
400 show_log_window(struct shttpd_ctx *ctx)
402 unsigned char mem[4096], *p;
404 DLGTEMPLATE *dia = (DLGTEMPLATE *) mem;
405 WORD cl, x, y, width, nelems = 0;
408 DLGTEMPLATE template; /* 18 bytes */
413 } dialog_header = {{WS_CAPTION | WS_POPUP | WS_VISIBLE | WS_SYSMENU |
414 DS_SETFONT | WS_DLGFRAME, WS_EX_TOOLWINDOW,
415 0, 200, 200, 400, 100}, 0, 0, L"", 8, L"Tahoma"};
420 (void) memset(mem, 0, sizeof(mem));
421 (void) memcpy(mem, &dialog_header, sizeof(dialog_header));
422 p = mem + sizeof(dialog_header);
424 add_control(&p, dia, 0x81, ID_LOG, WS_CHILD | WS_VISIBLE |
425 WS_BORDER | WS_VSCROLL | ES_MULTILINE | ES_AUTOVSCROLL |
426 ES_READONLY, 5, 5, WIDTH - 10, 60, "");
428 DialogBoxIndirectParam(NULL, dia, NULL, LogProc, (LPARAM) ctx);
433 static LRESULT CALLBACK
434 WindowProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
436 static NOTIFYICONDATA ni;
437 static struct shttpd_ctx *ctx;
438 DWORD tid; /* Thread ID */
444 ctx = ((CREATESTRUCT *) lParam)->lpCreateParams;
445 memset(&ni, 0, sizeof(ni));
446 ni.cbSize = sizeof(ni);
447 ni.uID = ID_TRAYICON;
448 ni.uFlags = NIF_ICON | NIF_MESSAGE | NIF_TIP;
451 my_snprintf(ni.szTip, sizeof(ni.szTip), "SHTTPD web server");
452 ni.uCallbackMessage = WM_USER;
453 Shell_NotifyIcon(NIM_ADD, &ni);
454 ctx->ev[0] = CreateEvent(0, TRUE, FALSE, 0);
455 ctx->ev[1] = CreateEvent(0, TRUE, FALSE, 0);
456 _beginthread(run_server, 0, ctx);
459 Shell_NotifyIcon(NIM_DELETE, &ni);
463 switch (LOWORD(wParam)) {
465 show_settings_dialog(&ctx);
468 SendMessage(hWnd, WM_CLOSE, wParam, lParam);
472 show_log_window(ctx);
480 case WM_LBUTTONDBLCLK:
481 hMenu = CreatePopupMenu();
482 AppendMenu(hMenu, 0, ID_SETTINGS, "Settings");
483 AppendMenu(hMenu, 0, ID_SHOWLOG, "Show Log");
484 AppendMenu(hMenu, 0, ID_QUIT, "Exit SHTTPD");
486 TrackPopupMenu(hMenu, 0, pt.x, pt.y, 0, hWnd, NULL);
493 return (DefWindowProc(hWnd, msg, wParam, lParam));
497 WinMain(HINSTANCE h, HINSTANCE prev, char *cmdline, int show)
499 struct shttpd_ctx *ctx;
504 ctx = init_from_argc_argv(config_file, 0, NULL);
505 (void) memset(&cls, 0, sizeof(cls));
507 hIcon = LoadIcon(GetModuleHandle(NULL), MAKEINTRESOURCE(ID_ICON));
509 hIcon = LoadIcon(NULL, IDI_APPLICATION);
510 cls.lpfnWndProc = (WNDPROC) WindowProc;
512 cls.lpszClassName = "shttpd v." VERSION;
514 if (!RegisterClass(&cls))
515 elog(E_FATAL, NULL, "RegisterClass: %d", ERRNO);
516 else if ((hWnd = CreateWindow(cls.lpszClassName, "",WS_OVERLAPPEDWINDOW,
517 0, 0, 0, 0, NULL, NULL, NULL, ctx)) == NULL)
518 elog(E_FATAL, NULL, "CreateWindow: %d", ERRNO);
520 while (GetMessage(&msg, (HWND) NULL, 0, 0)) {
521 TranslateMessage(&msg);
522 DispatchMessage(&msg);
530 fix_directory_separators(char *path)
532 for (; *path != '\0'; path++) {
536 while (path[1] == '\\' || path[1] == '/')
537 (void) memmove(path + 1,
538 path + 2, strlen(path + 2) + 1);
543 my_open(const char *path, int flags, int mode)
545 char buf[FILENAME_MAX];
546 wchar_t wbuf[FILENAME_MAX];
548 my_strlcpy(buf, path, sizeof(buf));
549 fix_directory_separators(buf);
550 MultiByteToWideChar(CP_UTF8, 0, buf, -1, wbuf, sizeof(wbuf));
552 return (_wopen(wbuf, flags));
556 my_stat(const char *path, struct stat *stp)
558 char buf[FILENAME_MAX], *p;
559 wchar_t wbuf[FILENAME_MAX];
561 my_strlcpy(buf, path, sizeof(buf));
562 fix_directory_separators(buf);
564 p = buf + strlen(buf) - 1;
565 while (p > buf && *p == '\\' && p[-1] != ':')
568 MultiByteToWideChar(CP_UTF8, 0, buf, -1, wbuf, sizeof(wbuf));
570 return (_wstat(wbuf, (struct _stat *) stp));
574 my_remove(const char *path)
576 char buf[FILENAME_MAX];
577 wchar_t wbuf[FILENAME_MAX];
579 my_strlcpy(buf, path, sizeof(buf));
580 fix_directory_separators(buf);
582 MultiByteToWideChar(CP_UTF8, 0, buf, -1, wbuf, sizeof(wbuf));
584 return (_wremove(wbuf));
588 my_rename(const char *path1, const char *path2)
590 char buf1[FILENAME_MAX];
591 char buf2[FILENAME_MAX];
592 wchar_t wbuf1[FILENAME_MAX];
593 wchar_t wbuf2[FILENAME_MAX];
595 my_strlcpy(buf1, path1, sizeof(buf1));
596 my_strlcpy(buf2, path2, sizeof(buf2));
597 fix_directory_separators(buf1);
598 fix_directory_separators(buf2);
600 MultiByteToWideChar(CP_UTF8, 0, buf1, -1, wbuf1, sizeof(wbuf1));
601 MultiByteToWideChar(CP_UTF8, 0, buf2, -1, wbuf2, sizeof(wbuf2));
603 return (_wrename(wbuf1, wbuf2));
607 my_mkdir(const char *path, int mode)
609 char buf[FILENAME_MAX];
610 wchar_t wbuf[FILENAME_MAX];
612 my_strlcpy(buf, path, sizeof(buf));
613 fix_directory_separators(buf);
615 MultiByteToWideChar(CP_UTF8, 0, buf, -1, wbuf, sizeof(wbuf));
617 return (_wmkdir(wbuf));
621 wide_to_utf8(const wchar_t *str)
625 int nchar = WideCharToMultiByte(CP_UTF8, 0, str, -1, NULL, 0, NULL, NULL);
630 else if (!WideCharToMultiByte(CP_UTF8, 0, str, -1, buf, nchar, NULL, NULL)) {
643 my_getcwd(char *buffer, int maxlen)
646 wchar_t *wbuffer, *wresult;
649 /* User-supplied buffer */
650 wbuffer = malloc(maxlen * sizeof(wchar_t));
654 /* Dynamically allocated buffer */
656 wresult = _wgetcwd(wbuffer, maxlen);
660 /* User-supplied buffer */
661 int n = WideCharToMultiByte(CP_UTF8, 0, wresult, -1, buffer, maxlen, NULL, NULL);
667 /* Buffer allocated by _wgetcwd() */
668 result = wide_to_utf8(wresult);
678 opendir(const char *name)
681 char path[FILENAME_MAX];
682 wchar_t wpath[FILENAME_MAX];
684 if (name == NULL || name[0] == '\0') {
686 } else if ((dir = malloc(sizeof(*dir))) == NULL) {
689 my_snprintf(path, sizeof(path), "%s/*", name);
690 fix_directory_separators(path);
691 MultiByteToWideChar(CP_UTF8, 0, path, -1, wpath, sizeof(wpath));
692 dir->handle = FindFirstFileW(wpath, &dir->info);
694 if (dir->handle != INVALID_HANDLE_VALUE) {
695 dir->result.d_name[0] = '\0';
711 if (dir->handle != INVALID_HANDLE_VALUE)
712 result = FindClose(dir->handle) ? 0 : -1;
726 struct dirent *result = 0;
728 if (dir && dir->handle != INVALID_HANDLE_VALUE) {
729 if(!dir->result.d_name ||
730 FindNextFileW(dir->handle, &dir->info)) {
731 result = &dir->result;
733 WideCharToMultiByte(CP_UTF8, 0, dir->info.cFileName,
735 sizeof(result->d_name), NULL, NULL);
745 set_non_blocking_mode(int fd)
747 unsigned long on = 1;
749 return (ioctlsocket(fd, FIONBIO, &on));
753 set_close_on_exec(int fd)
755 fd = 0; /* Do nothing. There is no FD_CLOEXEC on Windows */
763 big_int_t content_len;
767 * Thread function that reads POST data from the socket pair
768 * and writes it to the CGI process.
770 static void//DWORD WINAPI
773 struct threadparam *tp = arg;
774 int n, sent, stop = 0;
780 max_recv = min(sizeof(buf), tp->content_len - total);
781 while (!stop && max_recv > 0 && (n = recv(tp->s, buf, max_recv, 0)) > 0) {
782 for (sent = 0; !stop && sent < n; sent += k)
783 if (!WriteFile(tp->hPipe, buf + sent, n - sent, &k, 0))
786 max_recv = min(sizeof(buf), tp->content_len - total);
789 CloseHandle(tp->hPipe); /* Suppose we have POSTed everything */
794 * Thread function that reads CGI output and pushes it to the socket pair.
799 struct threadparam *tp = arg;
805 while (!stop && ReadFile(tp->hPipe, buf, sizeof(buf), &n, NULL)) {
807 for (sent = 0; !stop && sent < n; sent += k)
808 if ((k = send(tp->s, buf + sent, n - sent, 0)) <= 0)
811 CloseHandle(tp->hPipe);
814 * Windows is a piece of crap. When this thread closes its end
815 * of the socket pair, the other end (get_cgi() function) may loose
816 * some data. I presume, this happens if get_cgi() is not fast enough,
817 * and the data written by this end does not "push-ed" to the other
818 * end socket buffer. So after closesocket() the remaining data is
819 * gone. If I put shutdown() before closesocket(), that seems to
820 * fix the problem, but I am not sure this is the right fix.
821 * XXX (submitted by James Marshall) we do not do shutdown() on UNIX.
822 * If fork() is called from user callback, shutdown() messes up things.
833 spawn_stdio_thread(int sock, HANDLE hPipe, void (*func)(void *),
834 big_int_t content_len)
836 struct threadparam *tp;
839 tp = malloc(sizeof(*tp));
844 tp->content_len = content_len;
845 _beginthread(func, 0, tp);
849 spawn_process(struct conn *c, const char *prog, char *envblk,
850 char *envp[], int sock, const char *dir)
852 HANDLE a[2], b[2], h[2], me;
854 char *p, cmdline[FILENAME_MAX], line[FILENAME_MAX];
857 PROCESS_INFORMATION pi;
859 me = GetCurrentProcess();
860 flags = DUPLICATE_CLOSE_SOURCE | DUPLICATE_SAME_ACCESS;
862 /* FIXME add error checking code here */
863 CreatePipe(&a[0], &a[1], NULL, 0);
864 CreatePipe(&b[0], &b[1], NULL, 0);
865 DuplicateHandle(me, a[0], me, &h[0], 0, TRUE, flags);
866 DuplicateHandle(me, b[1], me, &h[1], 0, TRUE, flags);
868 (void) memset(&si, 0, sizeof(si));
869 (void) memset(&pi, 0, sizeof(pi));
871 /* XXX redirect CGI errors to the error log file */
873 si.dwFlags = STARTF_USESTDHANDLES | STARTF_USESHOWWINDOW;
874 si.wShowWindow = SW_HIDE;
875 si.hStdOutput = si.hStdError = h[1];
878 /* If CGI file is a script, try to read the interpreter line */
879 if (c->ctx->cgi_interpreter == NULL) {
880 if ((fp = fopen(prog, "r")) != NULL) {
881 (void) fgets(line, sizeof(line), fp);
882 if (memcmp(line, "#!", 2) != 0)
884 /* Trim whitespaces from interpreter name */
885 for (p = &line[strlen(line) - 1]; p > line &&
890 (void) my_snprintf(cmdline, sizeof(cmdline), "%s%s%s",
891 line + 2, line[2] == '\0' ? "" : " ", prog);
893 (void) my_snprintf(cmdline, sizeof(cmdline), "%s %s",
894 c->ctx->cgi_interpreter, prog);
897 (void) my_snprintf(line, sizeof(line), "%s", dir);
898 fix_directory_separators(line);
899 fix_directory_separators(cmdline);
902 * Spawn reader & writer threads before we create CGI process.
903 * Otherwise CGI process may die too quickly, loosing the data
905 spawn_stdio_thread(sock, b[0], stdinput, 0);
906 spawn_stdio_thread(sock, a[1], stdoutput, c->rem.content_len);
908 if (CreateProcessA(NULL, cmdline, NULL, NULL, TRUE,
909 CREATE_NEW_PROCESS_GROUP, envblk, line, &si, &pi) == 0) {
910 elog(E_LOG, c,"redirect: CreateProcess(%s): %d",cmdline,ERRNO);
915 CloseHandle(pi.hThread);
916 CloseHandle(pi.hProcess);