Avoid unused static function warning on non-mingw!
[xapian.git] / xapian-applications / omega / runfilter.cc
blob4b869f6226d3321c6fdcdde6171aa9790dd6033c
1 /** @file
2 * @brief Run an external filter and capture its output in a std::string.
3 */
4 /* Copyright (C) 2003-2024 Olly Betts
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
21 #include <config.h>
23 #include "runfilter.h"
25 #include <iostream>
26 #include <string>
27 #include <vector>
29 #include <sys/types.h>
30 #include "safefcntl.h"
31 #include <cerrno>
32 #include <cinttypes>
33 #include <cstdio>
34 #include <cstring>
35 #ifdef HAVE_SYS_TIME_H
36 # include <sys/time.h>
37 #endif
38 #ifdef HAVE_SYS_RESOURCE_H
39 # include <sys/resource.h>
40 #endif
41 #include "safesysselect.h"
42 #include "safesyssocket.h"
43 #include "safesyswait.h"
44 #include "safeunistd.h"
46 #if defined HAVE_FORK && defined HAVE_SOCKETPAIR
47 # include <signal.h>
48 #endif
50 #include "closefrom.h"
51 #include "freemem.h"
52 #include "setenv.h"
53 #include "stringutils.h"
55 #ifdef __WIN32__
56 # include "append_filename_arg.h"
57 #endif
59 using namespace std;
61 #ifndef __WIN32__
62 static int devnull = -1;
63 #endif
65 #if defined HAVE_FORK && defined HAVE_SOCKETPAIR
66 bool
67 command_needs_shell(const char * p)
69 for ( ; *p; ++p) {
70 // Probably overly conservative, but suitable for
71 // real-world cases.
72 if (strchr("!\"#$&()*;<>?[\\]^`{|}~", *p) != NULL) {
73 return true;
76 return false;
79 static bool
80 unquote(string & s, size_t & j)
82 bool quoted = false;
83 if (s[j] == '\'') {
84 single_quoted:
85 quoted = true;
86 s.erase(j, 1);
87 while (true) {
88 j = s.find('\'', j + 1);
89 if (j == s.npos) {
90 // Unmatched ' in command string.
91 // dash exits 2 in this case, bash exits 1.
92 throw ReadError(2 << 8);
94 // Replace four character sequence '\'' with ' - this is
95 // how a single quote inside single quotes gets escaped.
96 if (s[j + 1] != '\\' ||
97 s[j + 2] != '\'' ||
98 s[j + 3] != '\'') {
99 break;
101 s.erase(j + 1, 3);
103 if (j + 1 != s.size()) {
104 char ch = s[j + 1];
105 if (ch != ' ' && ch != '\t' && ch != '\n') {
106 // Handle the expansion of e.g.: --input=%f,html
107 s.erase(j, 1);
108 goto out_of_quotes;
111 } else {
112 out_of_quotes:
113 j = s.find_first_of(" \t\n'", j + 1);
114 // Handle the expansion of e.g.: --input=%f
115 if (j != s.npos && s[j] == '\'') goto single_quoted;
117 if (j != s.npos) {
118 s[j++] = '\0';
120 return quoted;
123 static pid_t pid_to_kill_on_signal;
125 #ifdef HAVE_SIGACTION
126 static struct sigaction old_hup_handler;
127 static struct sigaction old_int_handler;
128 static struct sigaction old_quit_handler;
129 static struct sigaction old_term_handler;
131 extern "C" {
133 static void
134 handle_signal(int signum)
136 if (pid_to_kill_on_signal) {
137 kill(pid_to_kill_on_signal, SIGKILL);
138 pid_to_kill_on_signal = 0;
140 switch (signum) {
141 case SIGHUP:
142 sigaction(signum, &old_hup_handler, NULL);
143 break;
144 case SIGINT:
145 sigaction(signum, &old_int_handler, NULL);
146 break;
147 case SIGQUIT:
148 sigaction(signum, &old_quit_handler, NULL);
149 break;
150 case SIGTERM:
151 sigaction(signum, &old_term_handler, NULL);
152 break;
153 default:
154 return;
156 raise(signum);
161 static inline void
162 runfilter_init_signal_handlers_()
164 struct sigaction sa;
165 sa.sa_handler = handle_signal;
166 sigemptyset(&sa.sa_mask);
167 sa.sa_flags = 0;
169 sigaction(SIGHUP, &sa, &old_hup_handler);
170 sigaction(SIGINT, &sa, &old_int_handler);
171 sigaction(SIGQUIT, &sa, &old_quit_handler);
172 sigaction(SIGTERM, &sa, &old_term_handler);
174 #else
175 static sighandler_t old_hup_handler;
176 static sighandler_t old_int_handler;
177 static sighandler_t old_quit_handler;
178 static sighandler_t old_term_handler;
180 extern "C" {
182 static void
183 handle_signal(int signum)
185 if (pid_to_kill_on_signal) {
186 kill(pid_to_kill_on_signal, SIGKILL);
187 pid_to_kill_on_signal = 0;
189 switch (signum) {
190 case SIGHUP:
191 signal(signum, old_hup_handler);
192 break;
193 case SIGINT:
194 signal(signum, old_int_handler);
195 break;
196 case SIGQUIT:
197 signal(signum, old_quit_handler);
198 break;
199 case SIGTERM:
200 signal(signum, old_term_handler);
201 break;
202 default:
203 return;
205 raise(signum);
210 static inline void
211 runfilter_init_signal_handlers_()
213 old_hup_handler = signal(SIGHUP, handle_signal);
214 old_int_handler = signal(SIGINT, handle_signal);
215 old_quit_handler = signal(SIGQUIT, handle_signal);
216 old_term_handler = signal(SIGTERM, handle_signal);
218 #endif
219 #else
220 bool
221 command_needs_shell(const char *)
223 // We don't try to avoid the shell on this platform, so don't waste time
224 // analysing commands to see if they could.
225 return true;
228 static inline void
229 runfilter_init_signal_handlers_()
232 #endif
234 void
235 runfilter_init()
237 runfilter_init_signal_handlers_();
238 #ifndef __WIN32__
239 devnull = open("/dev/null", O_WRONLY);
240 if (devnull < 0) {
241 cerr << "Failed to open /dev/null: " << strerror(errno) << endl;
242 exit(1);
244 // Ensure that devnull isn't fd 0, 1 or 2 (stdin, stdout or stderr) and
245 // that we have open fds for stdin, stdout and stderr. This simplifies the
246 // code after fork() because it doesn't need to worry about such corner
247 // cases.
248 while (devnull <= 2) {
249 devnull = dup(devnull);
251 #endif
254 void
255 run_filter(int fd_in, const char* const cmd[], string* out, int alt_status)
257 #if defined HAVE_FORK && defined HAVE_SOCKETPAIR
258 // We want to be able to get the exit status of the child process.
259 signal(SIGCHLD, SIG_DFL);
261 int fds[2];
262 if (socketpair(AF_UNIX, SOCK_STREAM, PF_UNSPEC, fds) < 0)
263 throw ReadError("socketpair failed");
264 // Ensure fds[1] != 0 to simplify handling in child process.
265 if (rare(fds[1] == 0)) swap(fds[0], fds[1]);
267 pid_t child = fork();
268 if (child == 0) {
269 // We're the child process.
271 #ifdef HAVE_SETPGID
272 // Put the child process into its own process group, so that we can
273 // easily kill it and any children it in turn forks if we need to.
274 setpgid(0, 0);
275 #endif
277 // Close the parent's side of the socket pair.
278 close(fds[0]);
280 if (fd_in > -1) {
281 // Connect piped input to stdin if it's not already fd 0.
282 if (fd_in != 0) {
283 dup2(fd_in, 0);
284 close(fd_in);
288 // Connect stdout to our side of the socket pair.
289 dup2(fds[1], 1);
291 // Close extraneous file descriptors (but leave stderr alone).
292 closefrom(3);
294 #ifdef HAVE_SETRLIMIT
295 // Impose some pretty generous resource limits to prevent run-away
296 // filter programs from causing problems.
298 // Limit CPU time to 300 seconds (5 minutes).
299 struct rlimit cpu_limit = { 300, RLIM_INFINITY };
300 setrlimit(RLIMIT_CPU, &cpu_limit);
302 #if defined RLIMIT_AS || defined RLIMIT_VMEM || defined RLIMIT_DATA
303 // Limit process data to free physical memory.
304 long mem = get_free_physical_memory();
305 if (mem > 0) {
306 struct rlimit ram_limit = {
307 static_cast<rlim_t>(mem),
308 RLIM_INFINITY
310 // FIXME: setrlimit() is not listed in signal-safety(7) as safe to
311 // call between fork() and exec...
312 #ifdef RLIMIT_AS
313 setrlimit(RLIMIT_AS, &ram_limit);
314 #elif defined RLIMIT_VMEM
315 setrlimit(RLIMIT_VMEM, &ram_limit);
316 #else
317 // Only limits the data segment rather than the total address
318 // space, but that's better than nothing.
319 setrlimit(RLIMIT_DATA, &ram_limit);
320 #endif
322 #endif
323 #endif
325 execvp(cmd[0], const_cast<char **>(cmd));
326 // Emulate shell behaviour and exit with status 127 if the command
327 // isn't found, and status 126 for other problems. In particular, we
328 // rely on 127 below to throw NoSuchFilter.
329 _exit(errno == ENOENT ? 127 : 126);
332 // We're the parent process.
333 #ifdef HAVE_SETPGID
334 pid_to_kill_on_signal = -child;
335 #else
336 pid_to_kill_on_signal = child;
337 #endif
339 // Close the child's side of the socket pair.
340 close(fds[1]);
341 if (child == -1) {
342 // fork() failed.
343 close(fds[0]);
344 throw ReadError("fork failed");
347 int fd = fds[0];
349 fd_set readfds;
350 FD_ZERO(&readfds);
351 while (true) {
352 // If we wait 300 seconds (5 minutes) without getting data from the
353 // filter, then give up to avoid waiting forever for a filter which
354 // has ended up blocked waiting for something which will never happen.
355 struct timeval tv;
356 tv.tv_sec = 300;
357 tv.tv_usec = 0;
358 FD_SET(fd, &readfds);
359 int r = select(fd + 1, &readfds, NULL, NULL, &tv);
360 if (r <= 0) {
361 if (r < 0) {
362 if (errno == EINTR || errno == EAGAIN) {
363 // select() interrupted by a signal, so retry.
364 continue;
366 cerr << "Reading from filter failed (" << strerror(errno) << ")"
367 << endl;
368 } else {
369 cerr << "Filter inactive for too long" << endl;
371 #ifdef HAVE_SETPGID
372 kill(-child, SIGKILL);
373 #else
374 kill(child, SIGKILL);
375 #endif
376 close(fd);
377 int status = 0;
378 while (waitpid(child, &status, 0) < 0 && errno == EINTR) { }
379 pid_to_kill_on_signal = 0;
380 throw ReadError(status);
383 char buf[4096];
384 ssize_t res = read(fd, buf, sizeof(buf));
385 if (res == 0) break;
386 if (res == -1) {
387 if (errno == EINTR) {
388 // read() interrupted by a signal, so retry.
389 continue;
391 close(fd);
392 #ifdef HAVE_SETPGID
393 kill(-child, SIGKILL);
394 #endif
395 int status = 0;
396 while (waitpid(child, &status, 0) < 0 && errno == EINTR) { }
397 pid_to_kill_on_signal = 0;
398 throw ReadError(status);
400 if (out) out->append(buf, res);
403 close(fd);
404 #ifdef HAVE_SETPGID
405 kill(-child, SIGKILL);
406 #endif
407 int status = 0;
408 while (waitpid(child, &status, 0) < 0) {
409 if (errno != EINTR)
410 throw ReadError("wait pid failed");
412 pid_to_kill_on_signal = 0;
414 if (WIFEXITED(status)) {
415 int exit_status = WEXITSTATUS(status);
416 if (exit_status == 0 || exit_status == alt_status)
417 return;
418 if (exit_status == 127)
419 throw NoSuchFilter();
421 # ifdef SIGXCPU
422 if (WIFSIGNALED(status) && WTERMSIG(status) == SIGXCPU) {
423 cerr << "Filter process consumed too much CPU time" << endl;
425 # endif
426 #else
427 LARGE_INTEGER counter;
428 // QueryPerformanceCounter() will always succeed on XP and later
429 // and gives us a counter which increments each CPU clock cycle
430 // on modern hardware (Pentium or newer).
431 QueryPerformanceCounter(&counter);
432 char pipename[256];
433 snprintf(pipename, sizeof(pipename),
434 "\\\\.\\pipe\\xapian-omega-filter-%lx-%lx_%" PRIx64,
435 static_cast<unsigned long>(GetCurrentProcessId()),
436 static_cast<unsigned long>(GetCurrentThreadId()),
437 static_cast<unsigned long long>(counter.QuadPart));
438 pipename[sizeof(pipename) - 1] = '\0';
439 // Create a pipe so we can read stdout from the child process.
440 HANDLE hPipe = CreateNamedPipe(pipename,
441 PIPE_ACCESS_DUPLEX|FILE_FLAG_OVERLAPPED,
443 1, 4096, 4096, NMPWAIT_USE_DEFAULT_WAIT,
444 NULL);
446 if (hPipe == INVALID_HANDLE_VALUE) {
447 throw ReadError("CreateNamedPipe failed");
450 HANDLE hClient = CreateFile(pipename,
451 GENERIC_READ|GENERIC_WRITE, 0, NULL,
452 OPEN_EXISTING,
453 FILE_FLAG_OVERLAPPED, NULL);
455 if (hClient == INVALID_HANDLE_VALUE) {
456 throw ReadError("CreateFile failed");
459 if (!ConnectNamedPipe(hPipe, NULL) &&
460 GetLastError() != ERROR_PIPE_CONNECTED) {
461 throw ReadError("ConnectNamedPipe failed");
464 // Set the appropriate handles to be inherited by the child process.
465 SetHandleInformation(hClient, HANDLE_FLAG_INHERIT, 1);
467 // Create the child process.
468 PROCESS_INFORMATION procinfo;
469 memset(&procinfo, 0, sizeof(PROCESS_INFORMATION));
471 STARTUPINFO startupinfo;
472 memset(&startupinfo, 0, sizeof(STARTUPINFO));
473 startupinfo.cb = sizeof(STARTUPINFO);
474 startupinfo.hStdError = GetStdHandle(STD_ERROR_HANDLE);
475 startupinfo.hStdOutput = hClient;
476 // FIXME: Is NULL the way to say "/dev/null"?
477 // It's what GetStdHandle() is documented to return if "an application does
478 // not have associated standard handles"...
479 startupinfo.hStdInput = fd_in >= 0 ? (HANDLE) _get_osfhandle(fd_in) : NULL;
480 startupinfo.dwFlags |= STARTF_USESTDHANDLES;
482 string cmdline;
483 for (auto i = cmd; *i; ++i) {
484 append_filename_argument(cmdline, *i, (i != cmd));
486 // For some reason Windows wants a modifiable command line so we
487 // pass `&cmdline[0]` rather than `cmdline.c_str()`.
488 if (!CreateProcess(NULL, &cmdline[0],
489 0, 0, TRUE, 0, 0, 0,
490 &startupinfo, &procinfo)) {
491 if (GetLastError() == ERROR_FILE_NOT_FOUND)
492 throw NoSuchFilter();
493 throw ReadError("CreateProcess failed");
496 CloseHandle(hClient);
497 CloseHandle(procinfo.hThread);
498 HANDLE child = procinfo.hProcess;
500 while (true) {
501 char buf[4096];
502 DWORD received;
503 if (!ReadFile(hPipe, buf, sizeof(buf), &received, NULL)) {
504 throw ReadError("ReadFile failed");
506 if (received == 0) break;
508 if (out) out->append(buf, received);
510 CloseHandle(hPipe);
512 WaitForSingleObject(child, INFINITE);
513 DWORD rc;
514 while (GetExitCodeProcess(child, &rc) && rc == STILL_ACTIVE) {
515 Sleep(100);
517 CloseHandle(child);
518 int status = int(rc);
519 if (status == 0 || status == alt_status)
520 return;
522 #endif
523 throw ReadError(status);
526 void
527 run_filter(int fd_in, const string& cmd, bool use_shell, string* out,
528 int alt_status)
530 #if defined HAVE_FORK && defined HAVE_SOCKETPAIR
531 // We want to be able to get the exit status of the child process.
532 signal(SIGCHLD, SIG_DFL);
534 int fds[2];
535 if (socketpair(AF_UNIX, SOCK_STREAM, PF_UNSPEC, fds) < 0)
536 throw ReadError("socketpair failed");
537 // Ensure fds[1] != 0 to simplify handling in child process.
538 if (rare(fds[1] == 0)) swap(fds[0], fds[1]);
540 string s;
541 vector<const char *> argv;
542 vector<pair<const char *, const char*>> env;
543 vector<pair<int, int>> dups;
544 if (!use_shell) {
545 // Parse the command line before we fork() it's not safe to call
546 // malloc() between fork() and exec and std::string and std::vector
547 // creation is likely to need to allocate memory.
549 // FIXME: Maybe we should do this once per command and cache the
550 // results.
551 s = cmd;
552 // Handle any environment variable assignments.
553 // Name must start with alpha or '_', contain only alphanumerics and
554 // '_', and there must be no quoting of either the name or the '='.
555 size_t j = 0;
556 while (true) {
557 j = s.find_first_not_of(" \t\n", j);
558 if (!(C_isalpha(s[j]) || s[j] == '_')) break;
559 size_t i = j;
560 do ++j; while (C_isalnum(s[j]) || s[j] == '_');
561 if (s[j] != '=') {
562 j = i;
563 break;
566 size_t eq = j;
567 unquote(s, j);
568 s[eq] = '\0';
569 env.emplace_back(&s[i], &s[eq + 1]);
570 j = s.find_first_not_of(" \t\n", j);
573 while (true) {
574 size_t i = s.find_first_not_of(" \t\n", j);
575 if (i == string::npos) break;
576 bool quoted = unquote(s, j);
577 const char * word = s.c_str() + i;
578 if (!quoted) {
579 // Handle simple cases of redirection.
580 if (strcmp(word, ">/dev/null") == 0) {
581 dups.emplace_back(devnull, 1);
582 continue;
584 if (strcmp(word, "2>/dev/null") == 0) {
585 dups.emplace_back(devnull, 2);
586 continue;
588 if (strcmp(word, "2>&1") == 0) {
589 dups.emplace_back(1, 2);
590 continue;
592 if (strcmp(word, "1>&2") == 0) {
593 dups.emplace_back(2, 1);
594 continue;
597 argv.push_back(word);
599 if (argv.empty()) return; // Empty command!
600 argv.push_back(NULL);
603 pid_t child = fork();
604 if (child == 0) {
605 // We're the child process.
607 #ifdef HAVE_SETPGID
608 // Put the child process into its own process group, so that we can
609 // easily kill it and any children it in turn forks if we need to.
610 setpgid(0, 0);
611 #endif
613 // Close the parent's side of the socket pair.
614 close(fds[0]);
616 if (fd_in > -1) {
617 // Connect piped input to stdin if it's not already fd 0.
618 if (fd_in != 0) {
619 dup2(fd_in, 0);
620 close(fd_in);
624 // Connect stdout to our side of the socket pair.
625 dup2(fds[1], 1);
627 // Close extraneous file descriptors (but leave stderr alone).
628 closefrom(3);
630 #ifdef HAVE_SETRLIMIT
631 // Impose some pretty generous resource limits to prevent run-away
632 // filter programs from causing problems.
634 // Limit CPU time to 300 seconds (5 minutes).
635 struct rlimit cpu_limit = { 300, RLIM_INFINITY };
636 setrlimit(RLIMIT_CPU, &cpu_limit);
638 #if defined RLIMIT_AS || defined RLIMIT_VMEM || defined RLIMIT_DATA
639 // Limit process data to free physical memory.
640 long mem = get_free_physical_memory();
641 if (mem > 0) {
642 struct rlimit ram_limit = {
643 static_cast<rlim_t>(mem),
644 RLIM_INFINITY
646 // FIXME: setrlimit() is not listed in signal-safety(7) as safe to
647 // call between fork() and exec...
648 #ifdef RLIMIT_AS
649 setrlimit(RLIMIT_AS, &ram_limit);
650 #elif defined RLIMIT_VMEM
651 setrlimit(RLIMIT_VMEM, &ram_limit);
652 #else
653 // Only limits the data segment rather than the total address
654 // space, but that's better than nothing.
655 setrlimit(RLIMIT_DATA, &ram_limit);
656 #endif
658 #endif
659 #endif
661 if (use_shell) {
662 execl("/bin/sh", "/bin/sh", "-c", cmd.c_str(), (void*)NULL);
663 _exit(-1);
666 // Process any environment variable assignments.
667 for (auto& e : env) {
668 setenv(e.first, e.second, 1);
671 // Process any redirections.
672 for (auto& d : dups) {
673 dup2(d.first, d.second);
676 execvp(argv[0], const_cast<char **>(&argv[0]));
677 // Emulate shell behaviour and exit with status 127 if the command
678 // isn't found, and status 126 for other problems. In particular, we
679 // rely on 127 below to throw NoSuchFilter.
680 _exit(errno == ENOENT ? 127 : 126);
683 // We're the parent process.
684 #ifdef HAVE_SETPGID
685 pid_to_kill_on_signal = -child;
686 #else
687 pid_to_kill_on_signal = child;
688 #endif
690 // Close the child's side of the socket pair.
691 close(fds[1]);
692 if (child == -1) {
693 // fork() failed.
694 close(fds[0]);
695 throw ReadError("fork failed");
698 int fd = fds[0];
700 fd_set readfds;
701 FD_ZERO(&readfds);
702 while (true) {
703 // If we wait 300 seconds (5 minutes) without getting data from the
704 // filter, then give up to avoid waiting forever for a filter which
705 // has ended up blocked waiting for something which will never happen.
706 struct timeval tv;
707 tv.tv_sec = 300;
708 tv.tv_usec = 0;
709 FD_SET(fd, &readfds);
710 int r = select(fd + 1, &readfds, NULL, NULL, &tv);
711 if (r <= 0) {
712 if (r < 0) {
713 if (errno == EINTR || errno == EAGAIN) {
714 // select() interrupted by a signal, so retry.
715 continue;
717 cerr << "Reading from filter failed (" << strerror(errno) << ")"
718 << endl;
719 } else {
720 cerr << "Filter inactive for too long" << endl;
722 #ifdef HAVE_SETPGID
723 kill(-child, SIGKILL);
724 #else
725 kill(child, SIGKILL);
726 #endif
727 close(fd);
728 int status = 0;
729 while (waitpid(child, &status, 0) < 0 && errno == EINTR) { }
730 pid_to_kill_on_signal = 0;
731 throw ReadError(status);
734 char buf[4096];
735 ssize_t res = read(fd, buf, sizeof(buf));
736 if (res == 0) break;
737 if (res == -1) {
738 if (errno == EINTR) {
739 // read() interrupted by a signal, so retry.
740 continue;
742 close(fd);
743 #ifdef HAVE_SETPGID
744 kill(-child, SIGKILL);
745 #endif
746 int status = 0;
747 while (waitpid(child, &status, 0) < 0 && errno == EINTR) { }
748 pid_to_kill_on_signal = 0;
749 throw ReadError(status);
751 if (out) out->append(buf, res);
754 close(fd);
755 #ifdef HAVE_SETPGID
756 kill(-child, SIGKILL);
757 #endif
758 int status = 0;
759 while (waitpid(child, &status, 0) < 0) {
760 if (errno != EINTR)
761 throw ReadError("wait pid failed");
763 pid_to_kill_on_signal = 0;
765 if (WIFEXITED(status)) {
766 int exit_status = WEXITSTATUS(status);
767 if (exit_status == 0 || exit_status == alt_status)
768 return;
769 if (exit_status == 127)
770 throw NoSuchFilter();
772 # ifdef SIGXCPU
773 if (WIFSIGNALED(status) && WTERMSIG(status) == SIGXCPU) {
774 cerr << "Filter process consumed too much CPU time" << endl;
776 # endif
777 #else
778 (void)use_shell;
779 LARGE_INTEGER counter;
780 // QueryPerformanceCounter() will always succeed on XP and later
781 // and gives us a counter which increments each CPU clock cycle
782 // on modern hardware (Pentium or newer).
783 QueryPerformanceCounter(&counter);
784 char pipename[256];
785 snprintf(pipename, sizeof(pipename),
786 "\\\\.\\pipe\\xapian-omega-filter-%lx-%lx_%" PRIx64,
787 static_cast<unsigned long>(GetCurrentProcessId()),
788 static_cast<unsigned long>(GetCurrentThreadId()),
789 static_cast<unsigned long long>(counter.QuadPart));
790 pipename[sizeof(pipename) - 1] = '\0';
791 // Create a pipe so we can read stdout from the child process.
792 HANDLE hPipe = CreateNamedPipe(pipename,
793 PIPE_ACCESS_DUPLEX|FILE_FLAG_OVERLAPPED,
795 1, 4096, 4096, NMPWAIT_USE_DEFAULT_WAIT,
796 NULL);
798 if (hPipe == INVALID_HANDLE_VALUE) {
799 throw ReadError("CreateNamedPipe failed");
802 HANDLE hClient = CreateFile(pipename,
803 GENERIC_READ|GENERIC_WRITE, 0, NULL,
804 OPEN_EXISTING,
805 FILE_FLAG_OVERLAPPED, NULL);
807 if (hClient == INVALID_HANDLE_VALUE) {
808 throw ReadError("CreateFile failed");
811 if (!ConnectNamedPipe(hPipe, NULL) &&
812 GetLastError() != ERROR_PIPE_CONNECTED) {
813 throw ReadError("ConnectNamedPipe failed");
816 // Set the appropriate handles to be inherited by the child process.
817 SetHandleInformation(hClient, HANDLE_FLAG_INHERIT, 1);
819 // Create the child process.
820 PROCESS_INFORMATION procinfo;
821 memset(&procinfo, 0, sizeof(PROCESS_INFORMATION));
823 STARTUPINFO startupinfo;
824 memset(&startupinfo, 0, sizeof(STARTUPINFO));
825 startupinfo.cb = sizeof(STARTUPINFO);
826 startupinfo.hStdError = GetStdHandle(STD_ERROR_HANDLE);
827 startupinfo.hStdOutput = hClient;
828 // FIXME: Is NULL the way to say "/dev/null"?
829 // It's what GetStdHandle() is documented to return if "an application does
830 // not have associated standard handles"...
831 startupinfo.hStdInput = fd_in >= 0 ? (HANDLE) _get_osfhandle(fd_in) : NULL;
832 startupinfo.dwFlags |= STARTF_USESTDHANDLES;
834 string cmdline{cmd};
835 // For some reason Windows wants a modifiable command line so we
836 // pass `&cmdline[0]` rather than `cmdline.c_str()`.
837 if (!CreateProcess(NULL, &cmdline[0],
838 0, 0, TRUE, 0, 0, 0,
839 &startupinfo, &procinfo)) {
840 if (GetLastError() == ERROR_FILE_NOT_FOUND)
841 throw NoSuchFilter();
842 throw ReadError("CreateProcess failed");
845 CloseHandle(hClient);
846 CloseHandle(procinfo.hThread);
847 HANDLE child = procinfo.hProcess;
849 while (true) {
850 char buf[4096];
851 DWORD received;
852 if (!ReadFile(hPipe, buf, sizeof(buf), &received, NULL)) {
853 throw ReadError("ReadFile failed");
855 if (received == 0) break;
857 if (out) out->append(buf, received);
859 CloseHandle(hPipe);
861 WaitForSingleObject(child, INFINITE);
862 DWORD rc;
863 while (GetExitCodeProcess(child, &rc) && rc == STILL_ACTIVE) {
864 Sleep(100);
866 CloseHandle(child);
867 int status = int(rc);
868 if (status == 0 || status == alt_status)
869 return;
871 #endif
872 throw ReadError(status);