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