Instrument Neutral Distributed Interface INDI 2.2.5
LocalDvrInfo.cpp
Go to the documentation of this file.
1/* INDI Server for protocol version 1.7.
2 * Copyright (C) 2007 Elwood C. Downey <[email protected]>
3 2013 Jasem Mutlaq <[email protected]>
4 2022 Ludovic Pollet
5 This library is free software; you can redistribute it and/or
6 modify it under the terms of the GNU Lesser General Public
7 License as published by the Free Software Foundation; either
8 version 2.1 of the License, or (at your option) any later version.
9
10 This library is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 Lesser General Public License for more details.
14
15 You should have received a copy of the GNU Lesser General Public
16 License along with this library; if not, write to the Free Software
17 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
18*/
19
20#include "LocalDrvInfo.hpp"
21#include "Utils.hpp"
22#include "Msg.hpp"
23#include "Constants.hpp"
24#include "CommandLineArgs.hpp"
25
26#include "Fifo.hpp"
27#include <sys/socket.h>
28#include <sys/wait.h>
29#include <fcntl.h>
30#include <libgen.h>
31#include <unistd.h>
32
33/* start the given local INDI driver process.
34 * exit if trouble.
35 */
37{
38 Msg *mp;
39 int rp[2], wp[2], ep[2];
40 int ux[2];
41 int pid;
42
43#ifdef OSX_EMBEDED_MODE
44 fprintf(stderr, "STARTING \"%s\"\n", name.c_str());
45 fflush(stderr);
46#endif
47
48 /* build three pipes: r, w and error*/
50 {
51 // FIXME: lots of FD are opened by indiserver. FD_CLOEXEC is a must + check other fds
52 if (socketpair(AF_UNIX, SOCK_STREAM, 0, ux) == -1)
53 {
54 log(fmt("socketpair: %s\n", strerror(errno)));
55 Bye();
56 }
57 }
58 else
59 {
60 if (pipe(rp) < 0)
61 {
62 log(fmt("read pipe: %s\n", strerror(errno)));
63 Bye();
64 }
65 if (pipe(wp) < 0)
66 {
67 log(fmt("write pipe: %s\n", strerror(errno)));
68 Bye();
69 }
70 }
71 if (pipe(ep) < 0)
72 {
73 log(fmt("stderr pipe: %s\n", strerror(errno)));
74 Bye();
75 }
76
77 /* fork&exec new process */
78 pid = fork();
79 if (pid < 0)
80 {
81 log(fmt("fork: %s\n", strerror(errno)));
82 Bye();
83 }
84 if (pid == 0)
85 {
86 /* child: exec name */
87 int fd;
88
89 /* rig up pipes */
91 {
92 // For unix socket, the same socket end can be used for both read & write
93 dup2(ux[0], 0); /* driver stdin reads from ux[0] */
94 dup2(ux[0], 1); /* driver stdout writes to ux[0] */
95 ::close(ux[0]);
96 ::close(ux[1]);
97 }
98 else
99 {
100 dup2(wp[0], 0); /* driver stdin reads from wp[0] */
101 dup2(rp[1], 1); /* driver stdout writes to rp[1] */
102 }
103 dup2(ep[1], 2); /* driver stderr writes to e[]1] */
104 for (fd = 3; fd < 100; fd++)
105 {
106 (void)::close(fd);
107 }
108 if (!envDev.empty())
109 {
110 setenv("INDIDEV", envDev.c_str(), 1);
111 }
112 /* Only reset environment variable in case of FIFO */
113 else if (fifoHandle)
114 {
115 unsetenv("INDIDEV");
116 }
117 if (!envConfig.empty())
118 {
119 setenv("INDICONFIG", envConfig.c_str(), 1);
120 }
121 else if (fifoHandle)
122 {
123 unsetenv("INDICONFIG");
124 }
125 if (!envSkel.empty())
126 {
127 setenv("INDISKEL", envSkel.c_str(), 1);
128 }
129 else if (fifoHandle)
130 {
131 unsetenv("INDISKEL");
132 }
133 std::string executable;
134 if (!envPrefix.empty())
135 {
136 setenv("INDIPREFIX", envPrefix.c_str(), 1);
137#if defined(OSX_EMBEDED_MODE)
138 executable = envPrefix + "/Contents/MacOS/" + name;
139#elif defined(__APPLE__)
140 executable = envPrefix + "/" + name;
141#else
142 executable = envPrefix + "/bin/" + name;
143#endif
144
145 fprintf(stderr, "%s\n", executable.c_str());
146
147 execlp(executable.c_str(), name.c_str(), NULL);
148 }
149 else
150 {
151 execlp(name.c_str(), name.c_str(), NULL);
152 }
153
154#ifdef OSX_EMBEDED_MODE
155 fprintf(stderr, "FAILED \"%s\"\n", name.c_str());
156 fflush(stderr);
157#endif
158 log(fmt("execlp %s: %s\n", executable.c_str(), strerror(errno)));
159 _exit(1); /* parent will notice EOF shortly */
160 }
161
162 if (useSharedBuffer)
163 {
164 /* don't need child's other socket end */
165 ::close(ux[0]);
166
167 /* record pid, io channels, init lp and snoop list */
168 setFds(ux[1], ux[1]);
169 rp[0] = ux[1];
170 wp[1] = ux[1];
171 }
172 else
173 {
174 /* don't need child's side of pipes */
175 ::close(wp[0]);
176 ::close(rp[1]);
177
178 /* record pid, io channels, init lp and snoop list */
179 setFds(rp[0], wp[1]);
180 }
181
182 ::close(ep[1]);
183
184 // Watch pid
185 this->pid = pid;
186 this->pidwatcher.set(pid);
187 this->pidwatcher.start();
188
189 // Watch input on efd
190 this->efd = ep[0];
191 fcntl(this->efd, F_SETFL, fcntl(this->efd, F_GETFL, 0) | O_NONBLOCK);
192 this->eio.start(this->efd, ev::READ);
193
194 /* first message primes driver to report its properties -- dev known
195 * if restarting
196 */
198 log(fmt("pid=%d rfd=%d wfd=%d efd=%d\n", pid, rp[0], wp[1], ep[0]));
199
200 XMLEle *root = addXMLEle(NULL, "getProperties");
201 addXMLAtt(root, "version", TO_STRING(INDIV));
202 mp = new Msg(nullptr, root);
203
204 // pushmsg can kill mp. do at end
205 pushMsg(mp);
206}
207
209{
210 eio.set<LocalDvrInfo, &LocalDvrInfo::onEfdEvent>(this);
211 pidwatcher.set<LocalDvrInfo, &LocalDvrInfo::onPidEvent>(this);
212}
213
215 DvrInfo(model),
216 envDev(model.envDev),
217 envConfig(model.envConfig),
218 envSkel(model.envSkel),
219 envPrefix(model.envPrefix)
220{
221 eio.set<LocalDvrInfo, &LocalDvrInfo::onEfdEvent>(this);
222 pidwatcher.set<LocalDvrInfo, &LocalDvrInfo::onPidEvent>(this);
223}
224
226{
227 closeEfd();
228 if (pid != 0)
229 {
230 kill(pid, SIGKILL); /* libev insures there will be no zombies */
231 pid = 0;
232 }
233 closePid();
234}
235
237{
238 return new LocalDvrInfo(*this);
239}
240
241void LocalDvrInfo::closeEfd()
242{
243 ::close(efd);
244 efd = -1;
245 eio.stop();
246}
247
248void LocalDvrInfo::closePid()
249{
250 pid = 0;
251 pidwatcher.stop();
252}
253
254void LocalDvrInfo::onEfdEvent(ev::io &, int revents)
255{
256 if (EV_ERROR & revents)
257 {
258 int sockErrno = readFdError(this->efd);
259 if (sockErrno)
260 {
261 log(fmt("Error on stderr: %s\n", strerror(sockErrno)));
262 closeEfd();
263 }
264 return;
265 }
266
267 if (revents & EV_READ)
268 {
269 ssize_t nr;
270
271 /* read more */
272 nr = read(efd, errbuff + errbuffpos, sizeof(errbuff) - errbuffpos);
273 if (nr <= 0)
274 {
275 if (nr < 0)
276 {
277 if (errno == EAGAIN || errno == EWOULDBLOCK) return;
278
279 log(fmt("stderr %s\n", strerror(errno)));
280 }
281 else
282 log("stderr EOF\n");
283 closeEfd();
284 return;
285 }
286 errbuffpos += nr;
287
288 for(int i = 0; i < errbuffpos; ++i)
289 {
290 if (errbuff[i] == '\n')
291 {
292 log(fmt("%.*s\n", (int)i, errbuff));
293 i++; /* count including nl */
294 errbuffpos -= i; /* remove from nexbuf */
295 memmove(errbuff, errbuff + i, errbuffpos); /* slide remaining to front */
296 i = -1; /* restart for loop scan */
297 }
298 }
299 }
300}
301
302void LocalDvrInfo::onPidEvent(ev::child &, int revents)
303{
304 if (revents & EV_CHILD)
305 {
306 if (WIFEXITED(pidwatcher.rstatus))
307 {
308 log(fmt("process %d exited with status %d\n", pid, WEXITSTATUS(pidwatcher.rstatus)));
309 }
310 else if (WIFSIGNALED(pidwatcher.rstatus))
311 {
312 int signum = WTERMSIG(pidwatcher.rstatus);
313 log(fmt("process %d killed with signal %d - %s\n", pid, signum, strsignal(signum)));
314 }
315 pid = 0;
316 this->pidwatcher.stop();
317 }
318}
CommandLineArgs * userConfigurableArguments
Fifo * fifoHandle
std::string fmt(const char *fmt,...)
Definition Utils.cpp:241
void Bye()
Definition Utils.cpp:108
int readFdError(int fd)
Definition Utils.cpp:167
#define TO_STRING(x)
Definition Utils.hpp:46
void log(const std::string &log) const override
Definition DvrInfo.cpp:320
std::string name
Definition DvrInfo.hpp:57
void close() override
Definition DvrInfo.cpp:124
std::string envPrefix
std::string envConfig
std::string envSkel
void start() override
virtual ~LocalDvrInfo()
std::string envDev
LocalDvrInfo * clone() const override
void pushMsg(Msg *msg)
Definition MsgQueue.cpp:336
bool useSharedBuffer
Definition MsgQueue.hpp:66
void setFds(int rFd, int wFd)
Definition MsgQueue.cpp:284
Definition Msg.hpp:33
int errno
#define INDIV
Definition indiapi.h:134
int fd
XMLEle * addXMLEle(XMLEle *parent, const char *tag)
add an element with the given tag to the given element. parent can be NULL to make a new root.
Definition lilxml.cpp:670
XMLAtt * addXMLAtt(XMLEle *ep, const char *name, const char *valu)
Add an XML attribute to an existing XML element.
Definition lilxml.cpp:706