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