1 /** @file runfilter.cc
2 * @brief Run an external filter and capture its output in a std::string.
3 *
4 * Copyright (C) 2003,2006,2007,2009,2010,2011 Olly Betts
5 *
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.
10 *
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.
15 *
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
19 */
20
21 #include <config.h>
22
23 #include "runfilter.h"
24
25 #include <iostream>
26 #include <string>
27
28 #include <sys/types.h>
29 #include "safeerrno.h"
30 #include "safefcntl.h"
31 #include <cstdio>
32 #ifdef HAVE_SYS_TIME_H
33 # include <sys/time.h>
34 #endif
35 #ifdef HAVE_SYS_RESOURCE_H
36 # include <sys/resource.h>
37 #endif
38 #include "safesysselect.h"
39 #ifdef HAVE_SYS_SOCKET_H
40 # include <sys/socket.h>
41 #endif
42 #include "safesyswait.h"
43 #include "safeunistd.h"
44
45 #if defined HAVE_FORK && defined HAVE_SOCKETPAIR && defined HAVE_SETRLIMIT
46 # include <csignal>
47 #endif
48
49 #include "freemem.h"
50
51 #ifdef _MSC_VER
52 # define popen _popen
53 # define pclose _pclose
54 #endif
55
56 using namespace std;
57
58 string
stdout_to_string(const string & cmd)59 stdout_to_string(const string &cmd)
60 {
61 string out;
62 #if defined HAVE_FORK && defined HAVE_SOCKETPAIR && defined HAVE_SETRLIMIT
63 // We want to be able to get the exit status of the child process.
64 signal(SIGCHLD, SIG_DFL);
65
66 int fds[2];
67 if (socketpair(AF_UNIX, SOCK_STREAM, PF_UNSPEC, fds) < 0)
68 throw ReadError();
69
70 pid_t child = fork();
71 if (child == 0) {
72 // We're the child process.
73
74 // Close the parent's side of the socket pair.
75 close(fds[0]);
76
77 // Connect stdout to our side of the socket pair.
78 dup2(fds[1], 1);
79
80 // Impose some pretty generous resource limits to prevent run-away
81 // filter programs from causing problems.
82
83 // Limit CPU time to 300 seconds (5 minutes).
84 struct rlimit cpu_limit = { 300, RLIM_INFINITY } ;
85 setrlimit(RLIMIT_CPU, &cpu_limit);
86
87 #if defined RLIMIT_AS || defined RLIMIT_VMEM || defined RLIMIT_DATA
88 // Limit process data to free physical memory.
89 long mem = get_free_physical_memory();
90 if (mem > 0) {
91 struct rlimit ram_limit = {
92 static_cast<rlim_t>(mem),
93 RLIM_INFINITY
94 };
95 #ifdef RLIMIT_AS
96 setrlimit(RLIMIT_AS, &ram_limit);
97 #elif defined RLIMIT_VMEM
98 setrlimit(RLIMIT_VMEM, &ram_limit);
99 #else
100 // Only limits the data segment rather than the total address
101 // space, but that's better than nothing.
102 setrlimit(RLIMIT_DATA, &ram_limit);
103 #endif
104 }
105 #endif
106
107 execl("/bin/sh", "/bin/sh", "-c", cmd.c_str(), (void*)NULL);
108 _exit(-1);
109 }
110
111 // We're the parent process.
112
113 // Close the child's side of the socket pair.
114 close(fds[1]);
115 if (child == -1) {
116 // fork() failed.
117 close(fds[0]);
118 throw ReadError();
119 }
120
121 int fd = fds[0];
122
123 fd_set readfds;
124 FD_ZERO(&readfds);
125 while (true) {
126 // If we wait 300 seconds (5 minutes) without getting data from the
127 // filter, then give up to avoid waiting forever for a filter which
128 // has ended up blocked waiting for something which will never happen.
129 struct timeval tv;
130 tv.tv_sec = 300;
131 tv.tv_usec = 0;
132 FD_SET(fd, &readfds);
133 int r = select(fd + 1, &readfds, NULL, NULL, &tv);
134 if (r <= 0) {
135 if (r < 0) {
136 if (errno == EINTR) {
137 // select() interrupted by a signal, so retry.
138 continue;
139 }
140 cerr << "Reading from filter failed (" << strerror(errno) << ")"
141 << endl;
142 } else {
143 cerr << "Filter inactive for too long" << endl;
144 }
145 kill(child, SIGKILL);
146 close(fd);
147 int status;
148 (void)waitpid(child, &status, 0);
149 throw ReadError();
150 }
151
152 char buf[4096];
153 ssize_t res = read(fd, buf, sizeof(buf));
154 if (res == 0) break;
155 if (res == -1) {
156 if (errno == EINTR) {
157 // read() interrupted by a signal, so retry.
158 continue;
159 }
160 close(fd);
161 int status;
162 (void)waitpid(child, &status, 0);
163 throw ReadError();
164 }
165 out.append(buf, res);
166 }
167
168 close(fd);
169 int status;
170 if (waitpid(child, &status, 0) == -1) {
171 throw ReadError();
172 }
173 #else
174 FILE * fh = popen(cmd.c_str(), "r");
175 if (fh == NULL) throw ReadError();
176 while (!feof(fh)) {
177 char buf[4096];
178 size_t len = fread(buf, 1, 4096, fh);
179 if (ferror(fh)) {
180 (void)pclose(fh);
181 throw ReadError();
182 }
183 out.append(buf, len);
184 }
185 int status = pclose(fh);
186 #endif
187
188 if (status != 0) {
189 if (WIFEXITED(status) && WEXITSTATUS(status) == 127) {
190 throw NoSuchFilter();
191 }
192 #ifdef SIGXCPU
193 if (WIFSIGNALED(status) && WTERMSIG(status) == SIGXCPU) {
194 cerr << "Filter process consumed too much CPU time" << endl;
195 }
196 #endif
197 throw ReadError();
198 }
199 return out;
200 }
201