1 /*
2 ex: set tabstop=4 shiftwidth=4 autoindent:
3 +-------------------------------------------------------------------------+
4 | Copyright (C) 2004-2021 The Cacti Group |
5 | |
6 | This program is free software; you can redistribute it and/or |
7 | modify it under the terms of the GNU Lesser General Public |
8 | License as published by the Free Software Foundation; either |
9 | version 2.1 of the License, or (at your option) any later version. |
10 | |
11 | This program is distributed in the hope that it will be useful, |
12 | but WITHOUT ANY WARRANTY; without even the implied warranty of |
13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
14 | GNU Lesser General Public License for more details. |
15 | |
16 | You should have received a copy of the GNU Lesser General Public |
17 | License along with this library; if not, write to the Free Software |
18 | Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA |
19 | 02110-1301, USA |
20 | |
21 +-------------------------------------------------------------------------+
22 | spine: a backend data gatherer for cacti |
23 +-------------------------------------------------------------------------+
24 | This poller would not have been possible without: |
25 | - Larry Adams (current development and enhancements) |
26 | - Rivo Nurges (rrd support, mysql poller cache, misc functions) |
27 | - RTG (core poller code, pthreads, snmp, autoconf examples) |
28 | - Brady Alleman/Doug Warner (threading ideas, implimentation details) |
29 +-------------------------------------------------------------------------+
30 | - Cacti - http://www.cacti.net/ |
31 +-------------------------------------------------------------------------+
32 *
33 * COMMAND-LINE PARAMETERS
34 *
35 * -h | --help
36 * -v | --version
37 *
38 * Show a brief help listing, then exit.
39 *
40 * -C | --conf=F
41 *
42 * Provide the name of the Spine configuration file, which contains
43 * the parameters for connecting to the database. In the absence of
44 * this, it looks [WHERE?]
45 *
46 * -f | --first=ID
47 *
48 * Start polling with device <ID> (else starts at the beginning)
49 *
50 * -l | --last=ID
51 *
52 * Stop polling after device <ID> (else ends with the last one)
53 *
54 * -m | --mibs
55 *
56 * Collect all system mibs this pass
57 *
58 * -N | --mode=online|offline|recovery
59 *
60 * For remote pollers, the polling mode. The default is 'online'
61 *
62 * -H | --hostlist="hostid1,hostid2,hostid3,...,hostidn"
63 *
64 * Override the expected first host, last host behavior with a list of hostids.
65 *
66 * -O | --option=setting:value
67 *
68 * Override a DB-provided value from the settings table in the DB.
69 *
70 * -C | -conf=FILE
71 *
72 * Specify the location of the Spine configuration file.
73 *
74 * -R | --readonly
75 *
76 * This processing is readonly with respect to the database: it's
77 * meant only for developer testing.
78 *
79 * -S | --stdout
80 *
81 * All logging goes to the standard output
82 *
83 * -V | --verbosity=V
84 *
85 * Set the debug logging verbosity to <V>. Can be 1..5 or
86 * NONE/LOW/MEDIUM/HIGH/DEBUG (case insensitive).
87 *
88 * The First/Last device IDs are all relative to the "hosts" table in the
89 * Cacti database, and this mechanism allows us to split up the polling
90 * duties across multiple "spine" instances: each one gets a subset of
91 * the polling range.
92 *
93 * For compatibility with poller.php, we also accept the first and last
94 * device IDs as standalone parameters on the command line.
95 */
96
97 #include "common.h"
98 #include "spine.h"
99
100 /* Global Variables */
101 int entries = 0;
102 int num_hosts = 0;
103 int pending_threads = 0;
104 sem_t active_threads;
105 sem_t active_scripts;
106 double start_time;
107
108 config_t set;
109 php_t *php_processes = 0;
110 char config_paths[CONFIG_PATHS][BUFSIZE];
111 int *debug_devices;
112
113 pool_t *db_pool_local;
114 pool_t *db_pool_remote;
115
116 static char *getarg(char *opt, char ***pargv);
117 static void display_help(int only_version);
118 void poller_push_data_to_main();
119
120 #ifdef HAVE_LCAP
121 /* This patch is adapted (copied) patch for ntpd from Jarno Huuskonen and
122 * Pekka Savola that was adapted (copied) from a patch by Chris Wings to drop
123 * root for xntpd.
124 */
drop_root(uid_t server_uid,gid_t server_gid)125 void drop_root(uid_t server_uid, gid_t server_gid) {
126 cap_t caps;
127 if (prctl(PR_SET_KEEPCAPS, 1)) {
128 SPINE_LOG_HIGH(("prctl(PR_SET_KEEPCAPS, 1) failed"));
129 exit(1);
130 }
131
132 if (setgroups(0, NULL) == -1) {
133 SPINE_LOG_HIGH(("setgroups failed."));
134 exit(1);
135 }
136
137 if (setegid(server_gid) == -1 || seteuid(server_uid) == -1) {
138 SPINE_LOG_HIGH(("setegid/seteuid to uid=%d/gid=%d failed.", server_uid, server_gid));
139 exit(1);
140 }
141
142 caps = cap_from_text("cap_net_raw=eip");
143 if (caps == NULL) {
144 SPINE_LOG_HIGH(("cap_from_text failed."));
145 exit(1);
146 }
147
148 if (cap_set_proc(caps) == -1) {
149 SPINE_LOG_HIGH(("cap_set_proc failed."));
150 exit(1);
151 }
152
153 /* Try to free the memory from cap_from_text */
154 cap_free( caps );
155
156 if ( setregid(server_gid, server_gid) == -1 ||
157 setreuid(server_uid, server_uid) == -1 ) {
158 SPINE_LOG_HIGH(("setregid/setreuid to uid=%d/gid=%d failed.",
159 server_uid, server_gid));
160 exit(1);
161 }
162
163 SPINE_LOG_LOW(("running as uid(%d)/gid(%d) euid(%d)/egid(%d) with cap_net_raw=eip.",
164 getuid(), getgid(), geteuid(), getegid()));
165 }
166 #endif /* HAVE_LCAP */
167
168 /*! \fn main(int argc, char *argv[])
169 * \brief The Spine program entry point
170 * \param argc The number of arguments passed to the function plus one (+1)
171 * \param argv An array of the command line arguments
172 *
173 * The Spine entry point. This function performs the following tasks.
174 * 1) Processes command line input parameters
175 * 2) Processes the Spine configuration file to obtain database access information
176 * 3) Process runtime parameters from the settings table
177 * 4) Initialize the runtime threads and mutexes for the threaded environment
178 * 5) Initialize Net-SNMP, MySQL, and the PHP Script Server (if required)
179 * 6) Spawns X threads in order to process hosts
180 * 7) Loop until either all hosts have been processed or until the poller runtime
181 * has been exceeded
182 * 8) Close database and free variables
183 * 9) Log poller process statistics if required
184 * 10) Exit
185 *
186 * Note: Command line runtime parameters override any database settings.
187 *
188 * \return 0 if SUCCESS, or -1 if FAILED
189 *
190 */
main(int argc,char * argv[])191 int main(int argc, char *argv[]) {
192 struct timeval now;
193 char *conf_file = NULL;
194 double begin_time, end_time, cur_time;
195 int num_rows = 0;
196 int device_counter = 0;
197 int valid_conf_file = FALSE;
198 char querybuf[MEGA_BUFSIZE], *qp = querybuf;
199 char *host_time = NULL;
200 double host_time_double = 0;
201 int itemsPT = 0;
202 int device_threads;
203 sem_t thread_init_sem;
204 int a_threads_value;
205 struct timespec until_spec;
206
207 gettimeofday(&now, NULL);
208 start_time = TIMEVAL_TO_DOUBLE(now);
209
210 #ifdef HAVE_LCAP
211 if (geteuid() == 0) {
212 drop_root(getuid(), getgid());
213 }
214 #endif /* HAVE_LCAP */
215
216 pthread_t* threads = NULL;
217 poller_thread_t** details = NULL;
218 poller_thread_t* poller_details = NULL;
219 pthread_attr_t attr;
220
221 int* ids = NULL;
222 int mode = REMOTE;
223 MYSQL mysql;
224 MYSQL mysqlr;
225 MYSQL_RES *result = NULL;
226 MYSQL_RES *tresult = NULL;
227 MYSQL_ROW mysql_row;
228 int canexit = FALSE;
229 int host_id = 0;
230 int i;
231 int thread_status = 0;
232 int total_items = 0;
233 int change_host = TRUE;
234 int current_thread;
235 int threads_final = 0;
236 int threads_missing = -1;
237 int threads_count;
238
239 UNUSED_PARAMETER(argc); /* we operate strictly with argv */
240
241 /* install the spine signal handler */
242 install_spine_signal_handler();
243
244 /* establish php processes and initialize space */
245 php_processes = (php_t*) calloc(MAX_PHP_SERVERS, sizeof(php_t));
246 for (i = 0; i < MAX_PHP_SERVERS; i++) {
247 php_processes[i].php_state = PHP_BUSY;
248 }
249
250 /* create the array of debug devices */
251 debug_devices = calloc(100, sizeof(int));
252
253 /* initialize icmp_avail */
254 set.icmp_avail = TRUE;
255
256 /* detect and compensate for stdin/stderr ttys */
257 if (!isatty(fileno(stdout))) {
258 set.stdout_notty = TRUE;
259 } else {
260 set.stdout_notty = FALSE;
261 }
262
263 if (!isatty(fileno(stderr))) {
264 set.stderr_notty = TRUE;
265 } else {
266 set.stderr_notty = FALSE;
267 }
268
269 /* set start time for cacti */
270 begin_time = get_time_as_double();
271
272 /* set default verbosity */
273 set.log_level = POLLER_VERBOSITY_LOW;
274
275 /* set default log separator */
276 set.log_datetime_separator = GDC_DEFAULT;
277
278 /* set default log format */
279 set.log_datetime_format = GD_DEFAULT;
280
281 /* set the default exit code */
282 set.exit_code = 0;
283 set.exit_size = 0;
284
285 /* get static defaults for system */
286 config_defaults();
287
288 /*! ----------------------------------------------------------------
289 * PROCESS COMMAND LINE
290 *
291 * Run through the list of ARGV words looking for parameters we
292 * know about. Most have two flavors (-C + --conf), and many
293 * themselves take a parameter.
294 *
295 * These parameters can be structured in two ways:
296 *
297 * --conf=FILE both parts in one argv[] string
298 * --conf FILE two separate argv[] strings
299 *
300 * We set "arg" to point to "--conf", and "opt" to point to FILE.
301 * The helper routine
302 *
303 * In each loop we set "arg" to next argv[] string, then look
304 * to see if it has an equal sign. If so, we split it in half
305 * and point to the option separately.
306 *
307 * NOTE: most direction to the program is given with dash-type
308 * parameters, but we also allow standalone numeric device IDs
309 * in "first last" format: this is how poller.php calls this
310 * program.
311 */
312
313 /* initialize some global variables */
314 set.poller_id = 1;
315 set.start_host_id = -1;
316 set.end_host_id = -1;
317 set.host_id_list[0] = '\0';
318 set.php_initialized = FALSE;
319 set.logfile_processed = FALSE;
320 set.parent_fork = SPINE_PARENT;
321 set.mode = REMOTE_ONLINE;
322
323 for (argv++; *argv; argv++) {
324 char *arg = *argv;
325 char *opt = strchr(arg, '='); /* pick off the =VALUE part */
326
327 if (opt) *opt++ = '\0';
328
329 if (STRMATCH(arg, "-f") || STRMATCH(arg, "--first")) {
330 if (HOSTID_DEFINED(set.start_host_id)) {
331 die("ERROR: %s can only be used once", arg);
332 }
333
334 set.start_host_id = atoi(opt = getarg(opt, &argv));
335
336 if (!HOSTID_DEFINED(set.start_host_id)) {
337 die("ERROR: '%s=%s' is invalid first-host ID", arg, opt);
338 }
339 }
340
341 else if (STRMATCH(arg, "-l") || STRIMATCH(arg, "--last")) {
342 if (HOSTID_DEFINED(set.end_host_id)) {
343 die("ERROR: %s can only be used once", arg);
344 }
345
346 set.end_host_id = atoi(opt = getarg(opt, &argv));
347
348 if (!HOSTID_DEFINED(set.end_host_id)) {
349 die("ERROR: '%s=%s' is invalid last-host ID", arg, opt);
350 }
351 }
352
353 else if (STRMATCH(arg, "-p") || STRIMATCH(arg, "--poller")) {
354 set.poller_id = atoi(getarg(opt, &argv));
355 }
356
357 else if (STRMATCH(arg, "-N") || STRIMATCH(arg, "--mode")) {
358 if (STRIMATCH(getarg(opt, &argv), "online")) {
359 set.mode = REMOTE_ONLINE;
360 } else if (STRIMATCH(getarg(opt, &argv), "offline")) {
361 set.mode = REMOTE_OFFLINE;
362 } else if (STRIMATCH(getarg(opt, &argv), "recovery")) {
363 set.mode = REMOTE_RECOVERY;
364 } else {
365 die("ERROR: invalid polling mode '%s' specified", opt);
366 }
367 }
368
369 else if (STRMATCH(arg, "-H") || STRIMATCH(arg, "--hostlist")) {
370 snprintf(set.host_id_list, BIG_BUFSIZE, "%s", getarg(opt, &argv));
371 }
372
373 else if (STRMATCH(arg, "-M") || STRMATCH(arg, "--mibs")) {
374 set.mibs = 1;
375 }
376
377 else if (STRMATCH(arg, "-h") || STRMATCH(arg, "--help")) {
378 display_help(FALSE);
379
380 exit(EXIT_SUCCESS);
381 }
382
383 else if (STRMATCH(arg, "-v") || STRMATCH(arg, "--version")) {
384 display_help(TRUE);
385
386 exit(EXIT_SUCCESS);
387 }
388
389 else if (STRMATCH(arg, "-O") || STRIMATCH(arg, "--option")) {
390 char *setting = getarg(opt, &argv);
391 char *value = strchr(setting, ':');
392
393 if (*value) {
394 *value++ = '\0';
395 } else {
396 die("ERROR: -O requires setting:value");
397 }
398
399 set_option(setting, value);
400 }
401
402 else if (STRMATCH(arg, "-R") || STRMATCH(arg, "--readonly") || STRMATCH(arg, "--read-only")) {
403 set.SQL_readonly = TRUE;
404 }
405
406 else if (STRMATCH(arg, "-C") || STRMATCH(arg, "--conf")) {
407 conf_file = strdup(getarg(opt, &argv));
408 }
409
410 else if (STRMATCH(arg, "-S") || STRMATCH(arg, "--stdout")) {
411 set_option("log_destination", "STDOUT");
412 }
413
414 else if (STRMATCH(arg, "-L") || STRMATCH(arg, "--log")) {
415 set_option("log_destination", getarg(opt, &argv));
416 }
417
418 else if (STRMATCH(arg, "-V") || STRMATCH(arg, "--verbosity")) {
419 set_option("log_verbosity", getarg(opt, &argv));
420 }
421
422 else if (!HOSTID_DEFINED(set.start_host_id) && all_digits(arg)) {
423 set.start_host_id = atoi(arg);
424 }
425
426 else if (!HOSTID_DEFINED(set.end_host_id) && all_digits(arg)) {
427 set.end_host_id = atoi(arg);
428 }
429
430 else {
431 die("ERROR: %s is an unknown command-line parameter", arg);
432 }
433 }
434
435 /* we attempt to support scripts better in cygwin */
436 #if defined(__CYGWIN__)
437 setenv("CYGWIN", "nodosfilewarning", 1);
438 if (file_exists("./sh.exe")) {
439 set.cygwinshloc = 0;
440 if (set.log_level == POLLER_VERBOSITY_DEBUG) {
441 printf("The Shell Command Exists in the current directory\n");
442 }
443 } else {
444 set.cygwinshloc = 1;
445 if (set.log_level == POLLER_VERBOSITY_DEBUG) {
446 printf("The Shell Command Exists in the /bin directory\n");
447 }
448 }
449 #endif
450
451 /* we require either both the first and last hosts, or niether host */
452 if ((HOSTID_DEFINED(set.start_host_id) != HOSTID_DEFINED(set.end_host_id)) &&
453 (!strlen(set.host_id_list))) {
454 die("ERROR: must provide both -f/-l, a hostlist (-H/--hostlist), or neither");
455 }
456
457 if (set.start_host_id > set.end_host_id) {
458 die("ERROR: Invalid row spec; first host_id must be less than the second");
459 }
460
461 /* read configuration file to establish local environment */
462 if (conf_file) {
463 if ((read_spine_config(conf_file)) < 0) {
464 die("ERROR: Could not read config file: %s", conf_file);
465 } else {
466 valid_conf_file = TRUE;
467 }
468 } else {
469 if (!(conf_file = calloc(CONFIG_PATHS, DBL_BUFSIZE))) {
470 die("ERROR: Fatal malloc error: spine.c conf_file!");
471 }
472
473 for (i=0; i<CONFIG_PATHS; i++) {
474 snprintf(conf_file, DBL_BUFSIZE, "%s%s", config_paths[i], DEFAULT_CONF_FILE);
475
476 if (read_spine_config(conf_file) >= 0) {
477 valid_conf_file = TRUE;
478 break;
479 }
480
481 if (i == CONFIG_PATHS-1) {
482 snprintf(conf_file, DBL_BUFSIZE, "%s%s", config_paths[0], DEFAULT_CONF_FILE);
483 }
484 }
485 }
486
487 if (valid_conf_file) {
488 /* read settings table from the database to further establish environment */
489 read_config_options();
490 } else {
491 die("FATAL: Unable to read configuration file!");
492 }
493
494 /* set the poller interval for those who use less than 5 minute intervals */
495 if (set.poller_interval == 0) {
496 set.poller_interval = 300;
497 }
498
499 /* tokenize the debug devices */
500 if (strlen(set.selective_device_debug)) {
501 SPINE_LOG_DEBUG(("DEBUG: Selective Debug Devices %s", set.selective_device_debug));
502 int i = 0;
503 char *token = strtok(set.selective_device_debug, ",");
504 while(token) {
505 debug_devices[i] = atoi(token);
506 debug_devices[i+1] = '\0';
507 token = strtok(NULL, ",");
508 i++;
509 }
510 } else {
511 debug_devices[0] = '\0';
512 }
513
514 /* connect for main loop */
515 db_connect(LOCAL, &mysql);
516
517 /* setup local connection pool for hosts */
518 db_pool_local = (pool_t *) calloc(set.threads, sizeof(pool_t));
519 db_create_connection_pool(LOCAL);
520
521 if (set.poller_id > 1 && set.mode == REMOTE_ONLINE) {
522 db_connect(REMOTE, &mysqlr);
523 mode = REMOTE;
524
525 /* setup remote connection pool for hosts */
526 db_pool_remote = (pool_t *) calloc(set.threads, sizeof(pool_t));
527 db_create_connection_pool(REMOTE);
528 } else {
529 mode = LOCAL;
530 }
531
532 /* Since MySQL 5.7 the sql_mode defaults are too strict for cacti */
533 db_insert(&mysql, LOCAL, "SET SESSION sql_mode = (SELECT REPLACE(@@sql_mode,'NO_ZERO_DATE', ''))");
534 db_insert(&mysql, LOCAL, "SET SESSION sql_mode = (SELECT REPLACE(@@sql_mode,'ONLY_FULL_GROUP_BY', ''))");
535
536 if (set.log_level == POLLER_VERBOSITY_DEBUG) {
537 SPINE_LOG_DEBUG(("DEBUG: Version %s starting", VERSION));
538
539 if (set.poller_id > 1) {
540 if (mode == REMOTE) {
541 SPINE_LOG_DEBUG(("DEBUG: Sending entries to remote database in 'online' mode"));
542 } else {
543 SPINE_LOG_DEBUG(("DEBUG: Sending entries to local database in 'offline', or 'recovery' mode"));
544 }
545 }
546 } else {
547 if (!set.stdout_notty) {
548 printf("Version %s starting\n", VERSION);
549
550 if (set.poller_id > 1) {
551 if (mode == REMOTE) {
552 printf("Sending entries to remote database in 'online' mode\n");
553 } else {
554 printf("Sending entries to local database in 'offline', or 'recovery' mode\n");
555 }
556 }
557 }
558 }
559
560 /* see if mysql is thread safe */
561 if (mysql_thread_safe()) {
562 if (set.log_level == POLLER_VERBOSITY_DEBUG) {
563 SPINE_LOG(("DEBUG: MySQL is Thread Safe!"));
564 }
565 } else {
566 SPINE_LOG(("WARNING: MySQL is NOT Thread Safe!"));
567 }
568
569 /* test for asroot permissions for ICMP */
570 checkAsRoot();
571
572 /* initialize SNMP */
573 SPINE_LOG_DEBUG(("DEBUG: Initializing Net-SNMP API"));
574 snmp_spine_init();
575
576 /* initialize PHP if required */
577 SPINE_LOG_DEBUG(("DEBUG: Initializing PHP Script Server(s)"));
578
579 /* tell spine that it is parent, and set the poller id */
580 set.parent_fork = SPINE_PARENT;
581
582 /* initialize the script server */
583 if (set.php_required) {
584 php_init(PHP_INIT);
585 set.php_initialized = TRUE;
586 set.php_current_server = 0;
587 }
588
589 /* determine if the poller_id field exists in the host table */
590 result = db_query(&mysql, LOCAL, "SHOW COLUMNS FROM host LIKE 'poller_id'");
591 if (mysql_num_rows(result)) {
592 set.poller_id_exists = TRUE;
593 } else {
594 set.poller_id_exists = FALSE;
595
596 if (set.poller_id > 0) {
597 SPINE_LOG(("WARNING: PollerID > 0, but 'host' table does NOT contain the poller_id column!!"));
598 }
599 }
600 db_free_result(result);
601
602 /* determine if the device_threads field exists in the host table */
603 result = db_query(&mysql, LOCAL, "SHOW COLUMNS FROM host LIKE 'device_threads'");
604 if (mysql_num_rows(result)) {
605 set.device_threads_exists = TRUE;
606 } else {
607 set.device_threads_exists = FALSE;
608 }
609 db_free_result(result);
610
611 if (set.device_threads_exists) {
612 SPINE_LOG_MEDIUM(("Spine will support multithread device polling."));
613 } else {
614 SPINE_LOG_MEDIUM(("Spine did not detect multithreaded device polling."));
615 }
616
617 /* obtain the list of hosts to poll */
618 if (set.device_threads_exists) {
619 qp += sprintf(qp, "SELECT id, device_threads FROM host");
620 } else {
621 qp += sprintf(qp, "SELECT id, '1' as device_threads FROM host");
622 }
623 qp += sprintf(qp, " WHERE disabled=''");
624 if (!strlen(set.host_id_list)) {
625 qp += append_hostrange(qp, "id"); /* AND id BETWEEN a AND b */
626 } else {
627 qp += sprintf(qp, " AND id IN(%s)", set.host_id_list);
628 }
629 if (set.poller_id_exists) {
630 qp += sprintf(qp, " AND host.poller_id=%i", set.poller_id);
631 }
632 qp += sprintf(qp, " ORDER BY polling_time DESC");
633
634 result = db_query(&mysql, LOCAL, querybuf);
635
636 if (set.poller_id == 1) {
637 num_rows = mysql_num_rows(result) + 1; /* pollerid 1 takes care of not host based data sources */
638 } else {
639 num_rows = mysql_num_rows(result);
640 }
641
642 if (!(threads = (pthread_t *)malloc(num_rows * sizeof(pthread_t)))) {
643 die("ERROR: Fatal malloc error: spine.c threads!");
644 }
645
646 if (!(details = (poller_thread_t **)malloc(num_rows * sizeof(poller_thread_t*)))) {
647 die("ERROR: Fatal malloc error: spine.c details!");
648 }
649
650 if (!(ids = (int *)malloc(num_rows * sizeof(int)))) {
651 die("ERROR: Fatal malloc error: spine.c host id's!");
652 }
653
654 /* initialize winsock library on Windows */
655 SOCK_STARTUP;
656
657 /* mark the spine process as started */
658 snprintf(querybuf, BIG_BUFSIZE, "INSERT INTO poller_time (poller_id, pid, start_time, end_time) VALUES (%i, %i, NOW(), '0000-00-00 00:00:00')", set.poller_id, getpid());
659 if (mode == REMOTE) {
660 db_insert(&mysqlr, REMOTE, querybuf);
661 } else {
662 db_insert(&mysql, LOCAL, querybuf);
663 }
664
665 /* initialize threads and mutexes */
666 pthread_attr_init(&attr);
667 pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
668
669 init_mutexes();
670
671 /* initialize active_threads semaphore */
672 sem_init(&active_threads, 0, set.threads);
673
674 /* initialize active_scripts semaphore */
675 sem_init(&active_scripts, 0, MAX_SIMULTANEOUS_SCRIPTS);
676
677 /* initialize thread initialization semaphore */
678 sem_init(&thread_init_sem, 0, 1);
679
680 /* specify the point of timeout for timedwait semaphores */
681 until_spec.tv_sec = (time_t)(set.poller_interval + begin_time - 0.2);
682 until_spec.tv_nsec = 0;
683
684 sem_getvalue(&active_threads, &a_threads_value);
685 SPINE_LOG_MEDIUM(("DEBUG: Initial Value of Active Threads is %i", set.threads - a_threads_value));
686
687 /* tell fork processes that they are now active */
688 set.parent_fork = SPINE_FORK;
689
690 /* initialize the threading code */
691 device_threads = 1;
692 current_thread = 0;
693
694 /* poller 0 always polls host 0 */
695 if (set.poller_id == 1) {
696 host_id = 0;
697 change_host = FALSE;
698 } else {
699 change_host = TRUE;
700 }
701
702 /* loop through devices until done */
703 while (canexit == FALSE && device_counter < num_rows) {
704 if (change_host) {
705 mysql_row = mysql_fetch_row(result);
706 host_id = atoi(mysql_row[0]);
707 device_threads = atoi(mysql_row[1]);
708 current_thread = 1;
709
710 if (device_threads < 1) {
711 device_threads = 1;
712 }
713 } else {
714 current_thread++;
715 }
716
717 /* adjust device threads in cases where the host does not have sufficient data sources */
718 snprintf(querybuf, BIG_BUFSIZE, "SELECT COUNT(local_data_id) FROM poller_item WHERE host_id=%i AND rrd_next_step <=0", host_id);
719 tresult = db_query(&mysql, LOCAL, querybuf);
720 mysql_row = mysql_fetch_row(tresult);
721
722 total_items = atoi(mysql_row[0]);
723 db_free_result(tresult);
724
725 if (total_items && total_items < device_threads) {
726 device_threads = total_items;
727 }
728
729 change_host = (current_thread >= device_threads) ? TRUE : FALSE;
730
731 /* determine how many items will be polled per thread */
732 if (device_threads > 1) {
733 if (current_thread == 1) {
734 snprintf(querybuf, BIG_BUFSIZE, "SELECT CEIL(COUNT(local_data_id)/%i) FROM poller_item WHERE host_id=%i AND rrd_next_step <=0", device_threads, host_id);
735 tresult = db_query(&mysql, LOCAL, querybuf);
736 mysql_row = mysql_fetch_row(tresult);
737
738 itemsPT = atoi(mysql_row[0]);
739 db_free_result(tresult);
740
741 if (host_time) free(host_time);
742
743 host_time = get_host_poll_time();
744 host_time_double = get_time_as_double();
745 }
746 } else {
747 itemsPT = 0;
748
749 if (host_time) free(host_time);
750
751 host_time = get_host_poll_time();
752 host_time_double = get_time_as_double();
753 }
754
755 /* populate the thread structure */
756 if (!(poller_details = (poller_thread_t *)malloc(sizeof(poller_thread_t)))) {
757 die("ERROR: Fatal malloc error: spine.c poller_details!");
758 }
759
760 poller_details->host_id = host_id;
761 poller_details->host_thread = current_thread;
762 poller_details->last_host_thread = device_threads;
763 poller_details->host_data_ids = itemsPT;
764 poller_details->host_time = host_time;
765 poller_details->host_time_double = host_time_double;
766 poller_details->thread_init_sem = &thread_init_sem;
767 poller_details->complete = FALSE;
768
769 details[device_counter] = poller_details;
770
771 retry1:
772
773 if (sem_timedwait(&active_threads, &until_spec) == -1) {
774 if (errno == ETIMEDOUT) {
775 SPINE_LOG(("ERROR: Spine Timed Out While Processing Devices Internal"));
776 canexit = TRUE;
777 break;
778 } else if (errno == EINTR) {
779 usleep(10000);
780 goto retry1;
781 }
782 }
783
784 /* create child process */
785 sem_wait(&thread_init_sem);
786
787 thread_status = pthread_create(&threads[device_counter], &attr, child, poller_details);
788
789 switch (thread_status) {
790 case 0:
791 SPINE_LOG_DEBUG(("DEBUG: Valid Thread to be Created"));
792
793 if (change_host) {
794 device_counter++;
795 }
796
797 sem_wait(&thread_init_sem);
798 thread_mutex_lock(LOCK_PEND);
799 pending_threads++;
800
801 sem_getvalue(&active_threads, &a_threads_value);
802 SPINE_LOG_MEDIUM(("Active Threads is %i, Pending is %i", set.threads - a_threads_value, pending_threads));
803
804 thread_mutex_unlock(LOCK_PEND);
805 sem_post(&thread_init_sem);
806
807 SPINE_LOG_DEVDBG(("DEBUG: DTS: device = %d, host_id = %d, host_thread = %d,"
808 " last_host_thread = %d, host_data_ids = %d, complete = %d",
809 device_counter-1,
810 poller_details->host_id,
811 poller_details->host_thread,
812 poller_details->last_host_thread,
813 poller_details->host_data_ids,
814 poller_details->complete));
815
816 break;
817 case EAGAIN:
818 SPINE_LOG(("ERROR: The System Lacked the Resources to Create a Thread"));
819 break;
820 case EFAULT:
821 SPINE_LOG(("ERROR: The Thread or Attribute were Invalid"));
822 break;
823 case EINVAL: SPINE_LOG(("ERROR: The Thread Attribute is Not Initialized"));
824 break;
825 default:
826 SPINE_LOG(("ERROR: Unknown Thread Creation Error"));
827 break;
828 }
829
830 /* Restore thread initialization semaphore if thread creation failed */
831 if (thread_status) {
832 sem_post(&thread_init_sem);
833 }
834 }
835
836 /* wait for all threads to 'complete'
837 * using the mutex here as the semaphore will
838 * show zero before the children are done */
839 while (TRUE) {
840 thread_mutex_lock(LOCK_PEND);
841
842 cur_time = get_time_as_double();
843
844 if (pending_threads == 0) {
845 break;
846 } else if (cur_time - begin_time > set.poller_interval) {
847 SPINE_LOG(("ERROR: Spine Timed Out While Waiting for %d Threads to End", pending_threads));
848 break;
849 }
850
851 thread_mutex_unlock(LOCK_PEND);
852
853 usleep(100000);
854 }
855
856 sem_getvalue(&active_threads, &a_threads_value);
857
858 threads_final = set.threads - a_threads_value;
859
860 if (threads_final) {
861 SPINE_LOG_HIGH(("The final count of Threads is %i", threads_final));
862
863 for (threads_count = 0; threads_count < num_rows; threads_count++) {
864 poller_thread_t* det = details[threads_count];
865
866 if (threads_missing == -1 && det == NULL) {
867 threads_missing = threads_count;
868 }
869
870 if (det != NULL) { // && !det->complete) {
871 SPINE_LOG_HIGH(("INFO: Thread %scomplete for Device[%d] and %d to %d sources",
872 det->complete?"":"in",
873 det->host_id,
874 det->host_data_ids * (det->host_thread - 1),
875 det->host_data_ids * (det->host_thread)));
876
877 SPINE_LOG_DEVDBG(("DEBUG: DTF: device = %d, host_id = %d, host_thread = %d,"
878 " last_host_thread = %d, host_data_ids = %d, complete = %d",
879 threads_count,
880 det->host_id,
881 det->host_thread,
882 det->last_host_thread,
883 det->host_data_ids,
884 det->complete));
885 }
886 }
887
888 if (threads_missing > -1) {
889 SPINE_LOG_HIGH(("ERROR: There were %d threads which did not run", num_rows - threads_missing));
890 }
891 } else {
892 SPINE_LOG_MEDIUM(("The Final Value of Threads is %i", threads_final));
893 }
894
895 /* tell Spine that it is now parent */
896 set.parent_fork = SPINE_PARENT;
897
898 /* push data back to the main server */
899 if (set.poller_id > 1 && set.mode == REMOTE_ONLINE && !set.SQL_readonly) {
900 poller_push_data_to_main();
901 }
902
903 /* print out stats */
904 gettimeofday(&now, NULL);
905
906 /* update the db for |data_time| on graphs */
907 if (set.poller_id == 1) {
908 db_insert(&mysql, LOCAL, "REPLACE INTO settings (name,value) VALUES ('date',NOW())");
909 }
910
911 snprintf(querybuf, BIG_BUFSIZE, "UPDATE poller_time SET end_time=NOW() WHERE poller_id=%i AND pid=%i", set.poller_id, getpid());
912 if (mode == REMOTE) {
913 db_insert(&mysqlr, REMOTE, querybuf);
914 } else {
915 db_insert(&mysql, LOCAL, querybuf);
916 }
917
918 if (db_pool_local) {
919 db_close_connection_pool(LOCAL);
920 }
921
922 if (db_pool_remote) {
923 db_close_connection_pool(REMOTE);
924 }
925
926 /* cleanup and exit program */
927 pthread_attr_destroy(&attr);
928
929 SPINE_LOG_DEBUG(("DEBUG: Thread Cleanup Complete"));
930
931 /* close the php script server */
932 if (set.php_required) {
933 php_close(PHP_INIT);
934 }
935
936 SPINE_LOG_DEBUG(("DEBUG: PHP Script Server Pipes Closed"));
937
938 /* free malloc'd variables */
939 for (i = 0; i < num_rows; i++) {
940 if (details[i] != NULL) {
941 free(details[i]);
942 }
943 }
944
945 free(details);
946 free(threads);
947 free(ids);
948 free(conf_file);
949 free(debug_devices);
950
951 SPINE_LOG_DEBUG(("DEBUG: Allocated Variable Memory Freed"));
952
953 /* close mysql */
954 db_free_result(result);
955 db_disconnect(&mysql);
956
957 if (set.poller_id > 1 && set.mode == REMOTE_ONLINE) {
958 db_disconnect(&mysqlr);
959 }
960
961 SPINE_LOG_DEBUG(("DEBUG: MYSQL Free & Close Completed"));
962
963 /* close snmp */
964 snmp_spine_close();
965
966 SPINE_LOG_DEBUG(("DEBUG: Net-SNMP Close Completed"));
967
968 /* finally add some statistics to the log and exit */
969 end_time = TIMEVAL_TO_DOUBLE(now);
970
971 if (set.log_level >= POLLER_VERBOSITY_MEDIUM) {
972 SPINE_LOG(("Time: %.4f s, Threads: %i, Devices: %i", (end_time - begin_time), set.threads, num_rows));
973 } else {
974 /* provide output if running from command line */
975 if (!set.stdout_notty) {
976 fprintf(stdout,"Time: %.4f s, Threads: %i, Devices: %i\n", (end_time - begin_time), set.threads, num_rows);
977 }
978 }
979
980 /* uninstall the spine signal handler */
981 uninstall_spine_signal_handler();
982
983 /* clueanup winsock library on Windows */
984 SOCK_CLEANUP;
985
986 exit(EXIT_SUCCESS);
987 }
988
989 /*! \fn static void display_help()
990 * \brief Display Spine usage information to the caller.
991 *
992 * Display the help listing: the first line is created at runtime with
993 * the version information, and the rest is strictly static text which
994 * is dumped literally.
995 *
996 */
display_help(int only_version)997 static void display_help(int only_version) {
998 static const char *const *p;
999 static const char * const helptext[] = {
1000 "Usage: spine [options] [[firstid lastid] || [-H/--hostlist='hostid1,hostid2,...,hostidn']]",
1001 "",
1002 "Options:",
1003 " -h/--help Show this brief help listing",
1004 " -f/--first=X Start polling with host id X",
1005 " -l/--last=X End polling with host id X",
1006 " -H/--hostlist=X Poll the list of host ids, separated by comma's",
1007 " -p/--poller=X Set the poller id to X",
1008 " -C/--conf=F Read spine configuration from file F",
1009 " -O/--option=S:V Override DB settings 'set' with value 'V'",
1010 " -M/--mibs Refresh the device System Mib data",
1011 " -N/--mode=online For remote pollers, the operating mode.",
1012 " Options include: online, offline, recovery.",
1013 " The default is 'online'.",
1014 " -R/--readonly Spine will not write output to the DB",
1015 " -S/--stdout Logging is performed to standard output",
1016 " -V/--verbosity=V Set logging verbosity to <V>",
1017 "",
1018 "Either both of --first/--last must be provided, a valid hostlist must be provided.",
1019 "In their absence, all hosts are processed.",
1020 "",
1021 "Without the --conf parameter, spine searches for its spine.conf",
1022 "file in the usual places.",
1023 "",
1024 "Verbosity is one of NONE/LOW/MEDIUM/HIGH/DEBUG or 1..5",
1025 "",
1026 "Runtime options are read from the 'settings' table in the Cacti",
1027 "database, but they can be overridden with the --option=S:V",
1028 "parameter.",
1029 "",
1030 "Spine is distributed under the Terms of the GNU Lesser",
1031 "General Public License Version 2.1. (http://www.gnu.org/licenses/lgpl.txt)",
1032 "For more information, see http://www.cacti.net",
1033
1034 0 /* ENDMARKER */
1035 };
1036
1037 printf("SPINE %s Copyright 2004-2021 by The Cacti Group\n", VERSION);
1038
1039 if (only_version == FALSE) {
1040 printf("\n");
1041 for (p = helptext; *p; p++) {
1042 puts(*p); /* automatically adds a newline */
1043 }
1044 }
1045 }
1046
1047 /*! \fn static char *getarg(char *opt, char ***pargv)
1048 * \brief A function to parse calling parameters
1049 *
1050 * This is a helper for the main arg-processing loop: we work with
1051 * options which are either of the form "-X=FOO" or "-X FOO"; we
1052 * want an easy way to handle either one.
1053 *
1054 * The idea is that if the parameter has an = sign, we use the rest
1055 * of that same argv[X] string, otherwise we have to get the *next*
1056 * argv[X] string. But it's an error if an option-requiring param
1057 * is at the end of the list with no argument to follow.
1058 *
1059 * The option name could be of the form "-C" or "--conf", but we
1060 * grab it from the existing argv[] so we can report it well.
1061 *
1062 * \return character pointer to the argument
1063 *
1064 */
getarg(char * opt,char *** pargv)1065 static char *getarg(char *opt, char ***pargv) {
1066 const char *const optname = **pargv;
1067
1068 /* option already set? */
1069 if (opt) return opt;
1070
1071 /* advance to next argv[] and try that one */
1072 if ((opt = *++(*pargv)) != 0) return opt;
1073
1074 die("ERROR: option %s requires a parameter", optname);
1075 }
1076