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 using namespace std;
53 
54 int main(int argc, char **argv)
55 {
56  fd_set master; // master file descriptor list
57  fd_set read_fds; // temp file descriptor list for select()
58  struct sockaddr_in myaddr {}; // server address
59  struct sockaddr_in remoteaddr {};// client address
60  int fdmax = -1; // maximum file descriptor number
61  int listener = -1; // listening socket descriptor
62  int newfd = -1; // newly accept()ed socket descriptor
63  char buf[4096]; // buffer for client data
64  int yes=1; // for setsockopt() SO_REUSEADDR, below
65  bool quit = false; // quit flag
66 
67  bool debug = false; // debug mode enabled
68  bool daemon_mode = false; // is daemon mode enabled
69  int port = PORT; // port we're listening on
70  string logfile; // log file
71  string zmconfig = ZM_CONFIG; // location of zoneminders default config file
72  string zmoverideconfig = ZM_OVERRIDECONFIG; // location of zoneminders override config file
73 
74  // Check command line arguments
75  for (int argpos = 1; argpos < argc; ++argpos)
76  {
77  if (strcmp(argv[argpos],"-d") == 0 ||
78  strcmp(argv[argpos],"--daemon") == 0)
79  {
80  daemon_mode = true;
81  }
82  else if (strcmp(argv[argpos],"-n") == 0 ||
83  strcmp(argv[argpos],"--nodaemon") == 0)
84  {
85  daemon_mode = false;
86  }
87  else if (strcmp(argv[argpos],"-p") == 0 ||
88  strcmp(argv[argpos],"--port") == 0)
89  {
90  if (argc > argpos)
91  {
92  port = atoi(argv[argpos+1]);
93 
94  if (port < 1 || port > 65534)
95  {
96  cout << "Bad port number: " << port << endl;
97  return EXIT_INVALID_CMDLINE;
98  }
99  ++argpos;
100  }
101  else
102  {
103  cout << "Missing argument to -p/--port option\n";
104  return EXIT_INVALID_CMDLINE;
105  }
106  }
107  else if (strcmp(argv[argpos],"-l") == 0 ||
108  strcmp(argv[argpos],"--logfile") == 0)
109  {
110  if (argc > argpos)
111  {
112  logfile = argv[argpos+1];
113  if (logfile[0] == '-')
114  {
115  cerr << "Invalid or missing argument to -l/--logfile option\n";
116  return EXIT_INVALID_CMDLINE;
117  }
118 
119  ++argpos;
120  }
121  else
122  {
123  cerr << "Missing argument to -l/--logfile option\n";
124  return EXIT_INVALID_CMDLINE;
125  }
126  }
127  else if (strcmp(argv[argpos],"-c") == 0 ||
128  strcmp(argv[argpos],"--zmconfig") == 0)
129  {
130  if (argc > argpos)
131  {
132  zmconfig = argv[argpos+1];
133  if (zmconfig[0] == '-')
134  {
135  cerr << "Invalid or missing argument to -c/--zmconfig option\n";
136  return EXIT_INVALID_CMDLINE;
137  }
138 
139  ++argpos;
140  }
141  else
142  {
143  cerr << "Missing argument to -c/--zmconfig option\n";
144  return EXIT_INVALID_CMDLINE;
145  }
146  }
147  else if (strcmp(argv[argpos],"-o") == 0 ||
148  strcmp(argv[argpos],"--zmoverrideconfig") == 0)
149  {
150  if (argc > argpos)
151  {
152  zmconfig = argv[argpos+1];
153  if (zmconfig[0] == '-')
154  {
155  cerr << "Invalid or missing argument to -o/--zmoverrideconfig option\n";
156  return EXIT_INVALID_CMDLINE;
157  }
158 
159  ++argpos;
160  }
161  else
162  {
163  cerr << "Missing argument to -o/--zmoverrideconfig option\n";
164  return EXIT_INVALID_CMDLINE;
165  }
166  }
167  else if (strcmp(argv[argpos],"-v") == 0 ||
168  strcmp(argv[argpos],"--verbose") == 0)
169  {
170  debug = true;
171  }
172  else
173  {
174  cerr << "Invalid argument: " << argv[argpos] << endl <<
175  "Valid options are: " << endl <<
176  "-p or --port number A port number to listen on (default is 6548) " << endl <<
177  "-d or --daemon Runs mythzmserver as a daemon " << endl <<
178  "-n or --nodaemon Does not run mythzmserver as a daemon (default)" << endl <<
179  "-c or --zmconfig Location of zoneminders default config file (default is " << ZM_CONFIG << ")" << endl <<
180  "-o or --zmoverrideconfig Location of zoneminders override config file (default is " << ZM_OVERRIDECONFIG << ")" << endl <<
181  "-l or --logfile filename Writes STDERR and STDOUT messages to filename" << endl <<
182  "-v or --verbose Prints more debug output" << endl;
183  return EXIT_INVALID_CMDLINE;
184  }
185  }
186 
187  // set up log file
188  int logfd = -1;
189 
190  if (!logfile.empty())
191  {
192  logfd = open(logfile.c_str(), O_WRONLY|O_CREAT|O_APPEND, 0664);
193 
194  if (logfd < 0)
195  {
196  perror("open(logfile)");
198  }
199  }
200 
201  if (logfd != -1)
202  {
203  // Send stdout and stderr to the logfile
204  dup2(logfd, 1);
205  dup2(logfd, 2);
206 
207  // Close the unduplicated logfd
208  if (logfd != 1 && logfd != 2)
209  close(logfd);
210  }
211 
212  if (signal(SIGPIPE, SIG_IGN) == SIG_ERR)
213  cout << "Unable to ignore SIGPIPE\n";
214 
215  // Switch to daemon mode?
216  if (daemon_mode)
217  {
218  if (daemon(0, 0) < 0)
219  {
220  cout << "Failed to run as a daemon. Bailing out.\n";
221  return EXIT_DAEMONIZING_ERROR;
222  }
223  cout << endl;
224  }
225 
226  map<int, ZMServer*> serverList; // list of ZMServers
227 
228  // load the default config
229  loadZMConfig(zmconfig);
230 
231  // load the override config
232  loadZMConfig(zmoverideconfig);
233 
234  // check we have a version (default to 1.32.3 if not found)
235  if (g_zmversion.length() == 0)
236  {
237  cout << "ZM version not found. Assuming at least v1.32.0 is installed" << endl;
238  g_majorVersion = 1;
239  g_minorVersion = 32;
240  g_revisionVersion = 3;
241  }
242  else
243  {
244  sscanf(g_zmversion.c_str(), "%10d.%10d.%10d", &g_majorVersion, &g_minorVersion, &g_revisionVersion);
245 
246  // we support version 1.24.0 or later
247  if (checkVersion(1, 24, 0))
248  {
249  cout << "ZM is version '" << g_zmversion << "'" << endl;
250  }
251  else
252  {
253  cout << "This version of ZM is to old you need 1.24.0 or later '" << g_zmversion << "'" << endl;
254  return EXIT_VERSION_ERROR;
255  }
256  }
257 
258  // connect to the DB
260 
261  // clear the master and temp sets
262  FD_ZERO(&master); // NOLINT(readability-isolate-declaration)
263  FD_ZERO(&read_fds); // NOLINT(readability-isolate-declaration)
264 
265  // get the listener
266  if ((listener = socket(AF_INET, SOCK_STREAM, 0)) == -1)
267  {
268  perror("socket");
269  return EXIT_SOCKET_ERROR;
270  }
271 
272  // lose the pesky "address already in use" error message
273  if (setsockopt(listener, SOL_SOCKET, SO_REUSEADDR, &yes,
274  sizeof(int)) == -1)
275  {
276  perror("setsockopt");
277  return EXIT_SOCKET_ERROR;
278  }
279 
280  // bind
281  myaddr.sin_family = AF_INET;
282  myaddr.sin_addr.s_addr = INADDR_ANY;
283  myaddr.sin_port = htons(port);
284  memset(&(myaddr.sin_zero), '\0', 8);
285  if (::bind(listener, (struct sockaddr *)&myaddr, sizeof(myaddr)) == -1)
286  {
287  perror("bind");
288  return EXIT_SOCKET_ERROR;
289  }
290 
291  // listen
292  if (listen(listener, 10) == -1)
293  {
294  perror("listen");
295  return EXIT_SOCKET_ERROR;
296  }
297 
298  cout << "Listening on port: " << port << endl;
299 
300  // add the listener to the master set
301  FD_SET(listener, &master);
302 
303  // keep track of the biggest file descriptor
304  fdmax = listener; // so far, it's this one
305 
306  // main loop
307  while (!quit)
308  {
309  // the maximum time select() should wait
310  struct timeval timeout {DB_CHECK_TIME, 0};
311 
312  read_fds = master; // copy it
313  int res = select(fdmax+1, &read_fds, nullptr, nullptr, &timeout);
314 
315  if (res == -1)
316  {
317  perror("select");
318  return EXIT_SOCKET_ERROR;
319  }
320  if (res == 0)
321  {
322  // select timed out
323  // just kick the DB connection to keep it alive
325  continue;
326  }
327 
328  // run through the existing connections looking for data to read
329  for (int i = 0; i <= fdmax; i++)
330  {
331  if (FD_ISSET(i, &read_fds))
332  {
333  // we got one!!
334  if (i == listener)
335  {
336  // handle new connections
337  socklen_t addrlen = sizeof(remoteaddr);
338  if ((newfd = accept(listener,
339  (struct sockaddr *) &remoteaddr,
340  &addrlen)) == -1)
341  {
342  perror("accept");
343  }
344  else
345  {
346  // add to master set
347  FD_SET(newfd, &master);
348  if (newfd > fdmax)
349  { // keep track of the maximum
350  fdmax = newfd;
351  }
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, sizeof(buf), 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, 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 
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:92
ZM_CONFIG
#define ZM_CONFIG
Definition: mythplugins/mythzoneminder/mythzmserver/main.cpp:37
ZMServer
Definition: zmserver.h:241
close
#define close
Definition: compat.h:16
quit
@ quit
Definition: lirc_client.h:30
DB_CHECK_TIME
#define DB_CHECK_TIME
Definition: zmserver.h:48
logfile
QString logfile
Definition: backendcontext.cpp:14
daemon
#define daemon(x, y)
Definition: compat.h:321
ZM_OVERRIDECONFIG
#define ZM_OVERRIDECONFIG
Definition: mythplugins/mythzoneminder/mythzmserver/main.cpp:40
debug
VERBOSE_PREAMBLE Most debug(nodatabase, notimestamp, noextra)") VERBOSE_MAP(VB_GENERAL
loadZMConfig
void loadZMConfig(const string &configfile)
Definition: zmserver.cpp:106
SIGPIPE
#define SIGPIPE
Definition: compat.h:218
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:81
checkVersion
bool checkVersion(int major, int minor, int revision)
Definition: zmserver.cpp:99
g_minorVersion
int g_minorVersion
Definition: zmserver.cpp:93
g_revisionVersion
int g_revisionVersion
Definition: zmserver.cpp:94
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
g_zmversion
string g_zmversion
Definition: zmserver.cpp:82
main
int main(int argc, char **argv)
Definition: mythplugins/mythzoneminder/mythzmserver/main.cpp:54
kickDatabase
void kickDatabase(bool debug)
Definition: zmserver.cpp:211
ZMServer::processRequest
bool processRequest(char *buf, int nbytes)
Definition: zmserver.cpp:598
connectToDatabase
void connectToDatabase(void)
Definition: zmserver.cpp:186
EXIT_OK
#define EXIT_OK
Definition: mythplugins/mythzoneminder/mythzmserver/main.cpp:45