MythTV master
mythzmserver.cpp
Go to the documentation of this file.
1/* main.cpp
2 * ============================================================
3 * This program is free software; you can redistribute it
4 * and/or modify it under the terms of the GNU General
5 * Public License as published bythe Free Software Foundation;
6 * either version 2, or (at your option)
7 * any later version.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 *
14 * ============================================================ */
15
16#include <arpa/inet.h>
17#include <algorithm>
18#include <array>
19#include <csignal>
20#include <cstdio>
21#include <cstdlib>
22#include <cstring>
23#include <fcntl.h>
24#include <iostream>
25#include <map>
26#include <netinet/in.h>
27#include <string>
28#include <sys/socket.h>
29#include <sys/time.h>
30#include <sys/types.h>
31#include <unistd.h>
32
33#include "zmserver.h"
34
35// default port to listen on
36static constexpr uint16_t PORT { 6548 };
37
38// default location of zoneminders default config file
39static constexpr const char* ZM_CONFIG { "/etc/zm/zm.conf" };
40
41// default location of zoneminders override config file
42static constexpr const char* ZM_OVERRIDECONFIG { "/etc/zm/conf.d/01-system-paths.conf" };
43
44// Care should be taken to keep these in sync with the exit codes in
45// libmythbase/exitcodes.h (which is not included here to keep this code
46// separate from mythtv libraries).
47enum EXIT_CODES : std::uint8_t {
50 EXIT_OPENING_LOGFILE_ERROR = 136, // mapped to _PERMISSIONS_ERROR
54};
55
56int main(int argc, char **argv)
57{
58 fd_set master; // master file descriptor list
59 fd_set read_fds; // temp file descriptor list for select()
60 struct sockaddr_in myaddr {}; // server address
61 struct sockaddr_in remoteaddr {};// client address
62 int fdmax = -1; // maximum file descriptor number
63 int listener = -1; // listening socket descriptor
64 int newfd = -1; // newly accept()ed socket descriptor
65 std::array<char,4096> buf {}; // buffer for client data
66 int yes=1; // for setsockopt() SO_REUSEADDR, below
67 bool quit = false; // quit flag
68
69 bool debug = false; // debug mode enabled
70 bool daemon_mode = false; // is daemon mode enabled
71 int port = PORT; // port we're listening on
72 std::string logfile; // log file
73 std::string zmconfig = ZM_CONFIG; // location of zoneminders default config file
74 std::string zmoverideconfig = ZM_OVERRIDECONFIG; // location of zoneminders override config file
75
76 // Check command line arguments
77 for (int argpos = 1; argpos < argc; ++argpos)
78 {
79 if (strcmp(argv[argpos],"-d") == 0 ||
80 strcmp(argv[argpos],"--daemon") == 0)
81 {
82 daemon_mode = true;
83 }
84 else if (strcmp(argv[argpos],"-n") == 0 ||
85 strcmp(argv[argpos],"--nodaemon") == 0)
86 {
87 daemon_mode = false;
88 }
89 else if (strcmp(argv[argpos],"-p") == 0 ||
90 strcmp(argv[argpos],"--port") == 0)
91 {
92 if (argc > argpos)
93 {
94 port = atoi(argv[argpos+1]);
95
96 if (port < 1 || port > 65534)
97 {
98 std::cout << "Bad port number: " << port << std::endl;
100 }
101 ++argpos;
102 }
103 else
104 {
105 std::cout << "Missing argument to -p/--port option\n";
107 }
108 }
109 else if (strcmp(argv[argpos],"-l") == 0 ||
110 strcmp(argv[argpos],"--logfile") == 0)
111 {
112 if (argc > argpos)
113 {
114 logfile = argv[argpos+1];
115 if (logfile[0] == '-')
116 {
117 std::cerr << "Invalid or missing argument to -l/--logfile option\n";
119 }
120
121 ++argpos;
122 }
123 else
124 {
125 std::cerr << "Missing argument to -l/--logfile option\n";
127 }
128 }
129 else if (strcmp(argv[argpos],"-c") == 0 ||
130 strcmp(argv[argpos],"--zmconfig") == 0)
131 {
132 if (argc > argpos)
133 {
134 zmconfig = argv[argpos+1];
135 if (zmconfig[0] == '-')
136 {
137 std::cerr << "Invalid or missing argument to -c/--zmconfig option\n";
139 }
140
141 ++argpos;
142 }
143 else
144 {
145 std::cerr << "Missing argument to -c/--zmconfig option\n";
147 }
148 }
149 else if (strcmp(argv[argpos],"-o") == 0 ||
150 strcmp(argv[argpos],"--zmoverrideconfig") == 0)
151 {
152 if (argc > argpos)
153 {
154 zmconfig = argv[argpos+1];
155 if (zmconfig[0] == '-')
156 {
157 std::cerr << "Invalid or missing argument to -o/--zmoverrideconfig option\n";
159 }
160
161 ++argpos;
162 }
163 else
164 {
165 std::cerr << "Missing argument to -o/--zmoverrideconfig option\n";
167 }
168 }
169 else if (strcmp(argv[argpos],"-v") == 0 ||
170 strcmp(argv[argpos],"--verbose") == 0)
171 {
172 debug = true;
173 }
174 else
175 {
176 std::cerr << "Invalid argument: " << argv[argpos] << std::endl <<
177 "Valid options are: " << std::endl <<
178 "-p or --port number A port number to listen on (default is 6548) " << std::endl <<
179 "-d or --daemon Runs mythzmserver as a daemon " << std::endl <<
180 "-n or --nodaemon Does not run mythzmserver as a daemon (default)" << std::endl <<
181 "-c or --zmconfig Location of zoneminders default config file (default is " << ZM_CONFIG << ")" << std::endl <<
182 "-o or --zmoverrideconfig Location of zoneminders override config file (default is " << ZM_OVERRIDECONFIG << ")" << std::endl <<
183 "-l or --logfile filename Writes STDERR and STDOUT messages to filename" << std::endl <<
184 "-v or --verbose Prints more debug output" << std::endl;
186 }
187 }
188
189 // set up log file
190 int logfd = -1;
191
192 if (!logfile.empty())
193 {
194 logfd = open(logfile.c_str(), O_WRONLY|O_CREAT|O_APPEND, 0664);
195
196 if (logfd < 0)
197 {
198 perror("open(logfile)");
200 }
201 }
202
203 if (logfd != -1)
204 {
205 // Send stdout and stderr to the logfile
206 dup2(logfd, 1);
207 dup2(logfd, 2);
208
209 // Close the unduplicated logfd
210 if (logfd != 1 && logfd != 2)
211 close(logfd);
212 }
213
214 if (signal(SIGPIPE, SIG_IGN) == SIG_ERR)
215 std::cout << "Unable to ignore SIGPIPE\n";
216
217 // Switch to daemon mode?
218 if (daemon_mode)
219 {
220 if (daemon(0, 0) < 0)
221 {
222 std::cout << "Failed to run as a daemon. Bailing out.\n";
224 }
225 std::cout << std::endl;
226 }
227
228 std::map<int, ZMServer*> serverList; // list of ZMServers
229
230 // load the default config
231 loadZMConfig(zmconfig);
232
233 // load the override config
234 loadZMConfig(zmoverideconfig);
235
236 // check we have a version (default to 1.34.16 if not found)
237 if (g_zmversion.empty())
238 {
239 std::cout << "ZM version not found. Assuming at least v1.34.16 is installed" << std::endl;
240 g_majorVersion = 1;
241 g_minorVersion = 34;
243 }
244 else
245 {
246 sscanf(g_zmversion.c_str(), "%10d.%10d.%10d", &g_majorVersion, &g_minorVersion, &g_revisionVersion);
247
248 // we support version 1.24.0 or later
249 if (checkVersion(1, 24, 0))
250 {
251 std::cout << "ZM is version '" << g_zmversion << "'" << std::endl;
252 }
253 else
254 {
255 std::cout << "This version of ZM is to old you need 1.24.0 or later '" << g_zmversion << "'" << std::endl;
256 return EXIT_VERSION_ERROR;
257 }
258 }
259
260 // connect to the DB
262
263 // clear the master and temp sets
264 FD_ZERO(&master); // NOLINT(readability-isolate-declaration)
265 FD_ZERO(&read_fds); // NOLINT(readability-isolate-declaration)
266
267 // get the listener
268 listener = socket(AF_INET, SOCK_STREAM, 0);
269 if (listener == -1)
270 {
271 perror("socket");
272 return EXIT_SOCKET_ERROR;
273 }
274
275 // lose the pesky "address already in use" error message
276 if (setsockopt(listener, SOL_SOCKET, SO_REUSEADDR, &yes,
277 sizeof(int)) == -1)
278 {
279 perror("setsockopt");
280 return EXIT_SOCKET_ERROR;
281 }
282
283 // bind
284 myaddr.sin_family = AF_INET;
285 myaddr.sin_addr.s_addr = INADDR_ANY;
286 myaddr.sin_port = htons(port);
287 memset(&(myaddr.sin_zero), '\0', 8);
288 if (::bind(listener, (struct sockaddr *)&myaddr, sizeof(myaddr)) == -1)
289 {
290 perror("bind");
291 return EXIT_SOCKET_ERROR;
292 }
293
294 // listen
295 if (listen(listener, 10) == -1)
296 {
297 perror("listen");
298 return EXIT_SOCKET_ERROR;
299 }
300
301 std::cout << "Listening on port: " << port << std::endl;
302
303 // add the listener to the master set
304 FD_SET(listener, &master);
305
306 // keep track of the biggest file descriptor
307 fdmax = listener; // so far, it's this one
308
309 // main loop
310 while (!quit)
311 {
312 // the maximum time select() should wait
313 struct timeval timeout {DB_CHECK_TIME.count(), 0};
314
315 read_fds = master; // copy it
316 int res = select(fdmax+1, &read_fds, nullptr, nullptr, &timeout);
317
318 if (res == -1)
319 {
320 perror("select");
321 return EXIT_SOCKET_ERROR;
322 }
323 if (res == 0)
324 {
325 // select timed out
326 // just kick the DB connection to keep it alive
328 continue;
329 }
330
331 // run through the existing connections looking for data to read
332 for (int i = 0; i <= fdmax; i++)
333 {
334 if (FD_ISSET(i, &read_fds))
335 {
336 // we got one!!
337 if (i == listener)
338 {
339 // handle new connections
340 socklen_t addrlen = sizeof(remoteaddr);
341 newfd = accept(listener, (struct sockaddr *) &remoteaddr,
342 &addrlen);
343 if (newfd == -1)
344 {
345 perror("accept");
346 }
347 else
348 {
349 // add to master set
350 FD_SET(newfd, &master);
351 fdmax = std::max(newfd, fdmax); // keep track of the maximum
352
353 // create new ZMServer and add to map
354 auto *server = new ZMServer(newfd, debug);
355 serverList[newfd] = server;
356
357 printf("new connection from %s on socket %d\n",
358 inet_ntoa(remoteaddr.sin_addr), newfd);
359 }
360 }
361 else
362 {
363 // handle data from a client
364 int nbytes = recv(i, buf.data(), buf.size(), 0);
365 if (nbytes <= 0)
366 {
367 // got error or connection closed by client
368 if (nbytes == 0)
369 {
370 // connection closed
371 printf("socket %d hung up\n", i);
372 }
373 else
374 {
375 perror("recv");
376 }
377
378 // remove from master set
379 FD_CLR(i, &master);
380
381 close(i);
382
383 // remove from server list
384 ZMServer *server = serverList[i];
385 delete server;
386 serverList.erase(i);
387 }
388 else
389 {
390 ZMServer *server = serverList[i];
391 quit = server->processRequest(buf.data(), nbytes);
392 }
393 }
394 }
395 }
396 }
397
398 // cleanly remove all the ZMServer's
399 for (auto & server : serverList)
400 delete server.second;
401
402 mysql_close(&g_dbConn);
403
404 return EXIT_OK;
405}
406
bool processRequest(char *buf, int nbytes)
Definition: zmserver.cpp:634
#define daemon(x, y)
Definition: compat.h:189
#define SIGPIPE
Definition: compat.h:138
#define close
Definition: compat.h:39
unsigned short uint16_t
Definition: iso6937tables.h:3
@ quit
Definition: lirc_client.h:30
static constexpr const char * ZM_CONFIG
EXIT_CODES
@ EXIT_VERSION_ERROR
@ EXIT_OPENING_LOGFILE_ERROR
@ EXIT_DAEMONIZING_ERROR
@ EXIT_INVALID_CMDLINE
@ EXIT_SOCKET_ERROR
@ EXIT_OK
int main(int argc, char **argv)
static constexpr const char * ZM_OVERRIDECONFIG
static constexpr uint16_t PORT
VERBOSE_PREAMBLE Most debug(nodatabase, notimestamp, noextra)") VERBOSE_MAP(VB_GENERAL
void loadZMConfig(const std::string &configfile)
Definition: zmserver.cpp:109
bool checkVersion(int major, int minor, int revision)
Definition: zmserver.cpp:102
int g_revisionVersion
Definition: zmserver.cpp:97
int g_majorVersion
Definition: zmserver.cpp:95
void kickDatabase(bool debug)
Definition: zmserver.cpp:206
int g_minorVersion
Definition: zmserver.cpp:96
std::string g_zmversion
Definition: zmserver.cpp:85
MYSQL g_dbConn
Definition: zmserver.cpp:84
void connectToDatabase(void)
Definition: zmserver.cpp:181
static constexpr std::chrono::seconds DB_CHECK_TIME
Definition: zmserver.h:54