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