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
36 static constexpr uint16_t PORT { 6548 };
37 
38 // default location of zoneminders default config file
39 static constexpr const char* ZM_CONFIG { "/etc/zm/zm.conf" };
40 
41 // default location of zoneminders override config file
42 static 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).
47 enum EXIT_CODES : std::uint8_t {
48  EXIT_OK = 0,
50  EXIT_OPENING_LOGFILE_ERROR = 136, // mapped to _PERMISSIONS_ERROR
54 };
55 
56 int 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;
99  return EXIT_INVALID_CMDLINE;
100  }
101  ++argpos;
102  }
103  else
104  {
105  std::cout << "Missing argument to -p/--port option\n";
106  return EXIT_INVALID_CMDLINE;
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";
118  return EXIT_INVALID_CMDLINE;
119  }
120 
121  ++argpos;
122  }
123  else
124  {
125  std::cerr << "Missing argument to -l/--logfile option\n";
126  return EXIT_INVALID_CMDLINE;
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";
138  return EXIT_INVALID_CMDLINE;
139  }
140 
141  ++argpos;
142  }
143  else
144  {
145  std::cerr << "Missing argument to -c/--zmconfig option\n";
146  return EXIT_INVALID_CMDLINE;
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";
158  return EXIT_INVALID_CMDLINE;
159  }
160 
161  ++argpos;
162  }
163  else
164  {
165  std::cerr << "Missing argument to -o/--zmoverrideconfig option\n";
166  return EXIT_INVALID_CMDLINE;
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;
185  return EXIT_INVALID_CMDLINE;
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";
223  return EXIT_DAEMONIZING_ERROR;
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;
242  g_revisionVersion = 16;
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 
EXIT_OPENING_LOGFILE_ERROR
@ EXIT_OPENING_LOGFILE_ERROR
Definition: mythzmserver.cpp:50
EXIT_DAEMONIZING_ERROR
@ EXIT_DAEMONIZING_ERROR
Definition: mythzmserver.cpp:51
hardwareprofile.smolt.timeout
float timeout
Definition: smolt.py:101
g_majorVersion
int g_majorVersion
Definition: zmserver.cpp:95
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
EXIT_VERSION_ERROR
@ EXIT_VERSION_ERROR
Definition: mythzmserver.cpp:53
EXIT_OK
@ EXIT_OK
Definition: mythzmserver.cpp:48
PORT
static constexpr uint16_t PORT
Definition: mythzmserver.cpp:36
main
int main(int argc, char **argv)
Definition: mythzmserver.cpp:56
ZM_OVERRIDECONFIG
static constexpr const char * ZM_OVERRIDECONFIG
Definition: mythzmserver.cpp:42
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
EXIT_CODES
EXIT_CODES
Definition: mythzmserver.cpp:47
zmserver.h
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:39
uint16_t
unsigned short uint16_t
Definition: iso6937tables.h:3
kickDatabase
void kickDatabase(bool debug)
Definition: zmserver.cpp:206
EXIT_SOCKET_ERROR
@ EXIT_SOCKET_ERROR
Definition: mythzmserver.cpp:52
EXIT_INVALID_CMDLINE
@ EXIT_INVALID_CMDLINE
Definition: mythzmserver.cpp:49
ZMServer::processRequest
bool processRequest(char *buf, int nbytes)
Definition: zmserver.cpp:634
connectToDatabase
void connectToDatabase(void)
Definition: zmserver.cpp:181