1 /*MT*
2 
3     MediaTomb - http://www.mediatomb.cc/
4 
5     process_io_handler.cc - this file is part of MediaTomb.
6 
7     Copyright (C) 2005 Gena Batyan <bgeradz@mediatomb.cc>,
8                        Sergey 'Jin' Bostandzhyan <jin@mediatomb.cc>
9 
10     Copyright (C) 2006-2010 Gena Batyan <bgeradz@mediatomb.cc>,
11                             Sergey 'Jin' Bostandzhyan <jin@mediatomb.cc>,
12                             Leonhard Wimmer <leo@mediatomb.cc>
13 
14     MediaTomb is free software; you can redistribute it and/or modify
15     it under the terms of the GNU General Public License version 2
16     as published by the Free Software Foundation.
17 
18     MediaTomb is distributed in the hope that it will be useful,
19     but WITHOUT ANY WARRANTY; without even the implied warranty of
20     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
21     GNU General Public License for more details.
22 
23     You should have received a copy of the GNU General Public License
24     version 2 along with MediaTomb; if not, write to the Free Software
25     Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.
26 
27     $Id$
28 */
29 
30 /// \file process_io_handler.cc
31 
32 #include "process_io_handler.h" // API
33 
34 #include <csignal>
35 
36 #include <fcntl.h>
37 #include <sys/select.h>
38 #include <sys/stat.h>
39 
40 #include "content/content_manager.h"
41 #include "util/process.h"
42 
43 // after MAX_TIMEOUTS we will tell libupnp to check the socket,
44 // this will make sure that we do not block the read and allow libupnp to
45 // call our close() callback
46 
47 #define MAX_TIMEOUTS 2 // maximum allowe consecutive timeouts
48 
ProcListItem(std::shared_ptr<Executor> && exec,bool abortOnDeath)49 ProcListItem::ProcListItem(std::shared_ptr<Executor>&& exec, bool abortOnDeath)
50     : executor(std::move(exec))
51     , abort(abortOnDeath)
52 {
53 }
54 
getExecutor()55 std::shared_ptr<Executor> ProcListItem::getExecutor()
56 {
57     return executor;
58 }
59 
abortOnDeath() const60 bool ProcListItem::abortOnDeath() const
61 {
62     return abort;
63 }
64 
abort() const65 bool ProcessIOHandler::abort() const
66 {
67     return std::any_of(procList.begin(), procList.end(),
68         [=](auto&& proc) { auto exec = proc->getExecutor();
69             return exec && !exec->isAlive() && proc->abortOnDeath(); });
70 }
71 
killAll() const72 void ProcessIOHandler::killAll() const
73 {
74     for (auto&& i : procList) {
75         auto exec = i->getExecutor();
76         if (exec)
77             exec->kill();
78     }
79 }
80 
registerAll()81 void ProcessIOHandler::registerAll()
82 {
83     if (mainProc)
84         content->registerExecutor(mainProc);
85 
86     for (auto&& i : procList) {
87         auto exec = i->getExecutor();
88         if (exec)
89             content->registerExecutor(std::move(exec));
90     }
91 }
92 
unregisterAll()93 void ProcessIOHandler::unregisterAll()
94 {
95     if (mainProc)
96         content->unregisterExecutor(mainProc);
97 
98     for (auto&& i : procList) {
99         auto exec = i->getExecutor();
100         if (exec)
101             content->unregisterExecutor(exec);
102     }
103 }
104 
ProcessIOHandler(std::shared_ptr<ContentManager> content,fs::path filename,std::shared_ptr<Executor> mainProc,std::vector<std::shared_ptr<ProcListItem>> procList,bool ignoreSeek)105 ProcessIOHandler::ProcessIOHandler(std::shared_ptr<ContentManager> content,
106     fs::path filename,
107     std::shared_ptr<Executor> mainProc,
108     std::vector<std::shared_ptr<ProcListItem>> procList,
109     bool ignoreSeek)
110     : content(std::move(content))
111     , procList(std::move(procList))
112     , mainProc(std::move(mainProc))
113     , filename(std::move(filename))
114     , ignoreSeek(ignoreSeek)
115 {
116     if (this->mainProc && (!this->mainProc->isAlive() || abort())) {
117         killAll();
118         throw_std_runtime_error("process terminated early");
119     }
120     /*
121     if (mkfifo(filename.c_str(), O_RDWR) == -1)
122     {
123         log_error("Failed to create fifo: {}", std::strerror(errno));
124         killAll();
125         if (main_proc)
126             main_proc->kill();
127 
128         throw_std_runtime_error("Could not create reader fifo");
129     }
130 */
131     registerAll();
132 }
133 
open(enum UpnpOpenFileMode mode)134 void ProcessIOHandler::open(enum UpnpOpenFileMode mode)
135 {
136     if ((mainProc) && ((!mainProc->isAlive() || abort()))) {
137         killAll();
138         throw_std_runtime_error("process terminated early");
139     }
140 
141     if (mode == UPNP_READ)
142 #ifdef __linux__
143         fd = ::open(filename.c_str(), O_RDONLY | O_NONBLOCK | O_CLOEXEC);
144 #else
145         fd = ::open(filename.c_str(), O_RDONLY | O_NONBLOCK);
146 #endif
147     else if (mode == UPNP_WRITE)
148 #ifdef __linux__
149         fd = ::open(filename.c_str(), O_WRONLY | O_NONBLOCK | O_CLOEXEC);
150 #else
151         fd = ::open(filename.c_str(), O_WRONLY | O_NONBLOCK);
152 #endif
153     else
154         fd = -1;
155 
156     if (fd == -1) {
157         if (errno == ENXIO) {
158             throw TryAgainException(fmt::format("open failed: {}", std::strerror(errno)));
159         }
160 
161         killAll();
162         if (mainProc)
163             mainProc->kill();
164         fs::remove(filename);
165         throw_std_runtime_error("open: failed to open: {}", filename.c_str());
166     }
167 }
168 
read(char * buf,std::size_t length)169 std::size_t ProcessIOHandler::read(char* buf, std::size_t length)
170 {
171     fd_set readSet;
172     struct timespec timeout;
173     ssize_t bytes_read = 0;
174     std::size_t num_bytes = 0;
175     char* p_buffer = buf;
176     int exit_status = EXIT_SUCCESS;
177     int ret;
178     int timeout_count = 0;
179 
180     while (true) {
181         FD_ZERO(&readSet);
182         FD_SET(fd, &readSet);
183 
184         timeout.tv_sec = FIFO_READ_TIMEOUT;
185         timeout.tv_nsec = 0;
186 
187         ret = pselect(fd + 1, &readSet, nullptr, nullptr, &timeout, nullptr);
188         if (ret == -1) {
189             if (errno == EINTR)
190                 continue;
191         }
192 
193         // timeout
194         if (ret == 0) {
195             if (mainProc) {
196                 bool main_ok = mainProc->isAlive();
197                 if (!main_ok || abort()) {
198                     if (!main_ok) {
199                         exit_status = mainProc->getStatus();
200                         log_debug("process exited with status {}", exit_status);
201                         killAll();
202                         return (exit_status == EXIT_SUCCESS) ? 0 : -1;
203                     }
204                     mainProc->kill();
205                     killAll();
206                     return -1;
207                 }
208             } else {
209                 killAll();
210                 return 0;
211             }
212 
213             timeout_count++;
214             if (timeout_count > MAX_TIMEOUTS) {
215                 log_debug("max timeouts, checking socket!");
216                 return CHECK_SOCKET;
217             }
218         }
219 
220         if (FD_ISSET(fd, &readSet)) {
221             timeout_count = 0;
222             bytes_read = ::read(fd, p_buffer, length);
223             if (bytes_read == 0)
224                 break;
225 
226             if (bytes_read < 0) {
227                 log_debug("aborting read!!!");
228                 return -1;
229             }
230 
231             num_bytes = num_bytes + bytes_read;
232             length = length - bytes_read;
233 
234             if (length == 0)
235                 break;
236 
237             p_buffer = buf + num_bytes;
238         }
239     }
240 
241     if (num_bytes == 0) {
242         // not sure what we return here since no way of knowing about feof
243         // actually that will depend on the ret code of the process
244         ret = -1;
245 
246         if (mainProc) {
247             if (mainProc->isAlive())
248                 mainProc->kill();
249             if (mainProc->getStatus() == EXIT_SUCCESS)
250                 ret = 0;
251         } else
252             ret = 0;
253 
254         killAll();
255         return ret;
256     }
257 
258     return num_bytes;
259 }
260 
write(char * buf,std::size_t length)261 std::size_t ProcessIOHandler::write(char* buf, std::size_t length)
262 {
263     fd_set writeSet;
264     struct timespec timeout;
265     ssize_t bytes_written = 0;
266     std::size_t num_bytes = 0;
267     char* p_buffer = buf;
268     int exit_status = EXIT_SUCCESS;
269     int ret;
270 
271     while (true) {
272         FD_ZERO(&writeSet);
273         FD_SET(fd, &writeSet);
274 
275         timeout.tv_sec = FIFO_WRITE_TIMEOUT;
276         timeout.tv_nsec = 0;
277 
278         ret = pselect(fd + 1, nullptr, &writeSet, nullptr, &timeout, nullptr);
279         if (ret == -1) {
280             if (errno == EINTR) {
281                 continue;
282             }
283         }
284 
285         // timeout
286         if (ret == 0) {
287             if (mainProc) {
288                 bool main_ok = mainProc->isAlive();
289                 if (!main_ok || abort()) {
290                     if (!main_ok) {
291                         exit_status = mainProc->getStatus();
292                         log_debug("process exited with status {}", exit_status);
293                         killAll();
294                         return (exit_status == EXIT_SUCCESS) ? 0 : -1;
295                     }
296 
297                     mainProc->kill();
298                     killAll();
299                     return -1;
300                 }
301             } else {
302                 killAll();
303                 return 0;
304             }
305         }
306 
307         if (FD_ISSET(fd, &writeSet)) {
308             bytes_written = ::write(fd, p_buffer, length);
309             if (bytes_written == 0)
310                 break;
311 
312             if (bytes_written < 0) {
313                 log_debug("aborting write!!!");
314                 return -1;
315             }
316 
317             num_bytes = num_bytes + bytes_written;
318             length = length - bytes_written;
319             if (length == 0)
320                 break;
321 
322             p_buffer = buf + num_bytes;
323         }
324     }
325 
326     if (num_bytes == 0) {
327         // not sure what we return here since no way of knowing about feof
328         // actually that will depend on the ret code of the process
329         ret = -1;
330 
331         if (mainProc) {
332             if (!mainProc->isAlive()) {
333                 if (mainProc->getStatus() == EXIT_SUCCESS)
334                     ret = 0;
335 
336             } else {
337                 mainProc->kill();
338             }
339         } else
340             ret = 0;
341 
342         killAll();
343         return ret;
344     }
345 
346     return num_bytes;
347 }
348 
seek(off_t offset,int whence)349 void ProcessIOHandler::seek(off_t offset, int whence)
350 {
351     // we know we can not seek in a fifo, but the PS3 asks for a hack...
352     if (!ignoreSeek)
353         throw_std_runtime_error("fseek failed");
354 }
355 
close()356 void ProcessIOHandler::close()
357 {
358     bool ret;
359 
360     log_debug("terminating process, closing {}", this->filename.c_str());
361     unregisterAll();
362 
363     if (mainProc) {
364         ret = mainProc->kill();
365     } else
366         ret = true;
367 
368     killAll();
369 
370     ::close(fd);
371 
372     fs::remove(filename);
373 
374     if (!ret)
375         throw_std_runtime_error("failed to kill process");
376 }
377 
~ProcessIOHandler()378 ProcessIOHandler::~ProcessIOHandler()
379 {
380     try {
381         close();
382     } catch (const std::runtime_error& ex) {
383     }
384 }
385