1 /***************************************************************
2 Copyright (C) 2010-2011 Hewlett-Packard Development Company, L.P.
3 Copyright (C) 2015 Siemens AG
4 
5 This program is free software; you can redistribute it and/or
6 modify it under the terms of the GNU General Public License
7 version 2 as published by the Free Software Foundation.
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 You should have received a copy of the GNU General Public License along
15 with this program; if not, write to the Free Software Foundation, Inc.,
16 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
17 
18 ***************************************************************/
19 
20 /*!
21  * \file
22  * \brief Scheduler API for agents.
23 */
24 
25 /* local includes */
26 #include "libfossscheduler.h"
27 #include "libfossdb.h"
28 #include "fossconfig.h"
29 
30 /* unix includes */
31 #include <stdio.h>
32 #include <getopt.h>
33 #include <libgen.h>
34 #include <glib.h>
35 
36 /* ************************************************************************** */
37 /* **** Locals ************************************************************** */
38 /* ************************************************************************** */
39 
40 volatile gint items_processed; ///< The number of items processed by the agent
41 volatile int alive;            ///< If the agent has updated with a hearbeat
42 char buffer[2048];   ///< The last thing received from the scheduler
43 int valid;           ///< If the information stored in buffer is valid
44 int sscheduler;      ///< Whether the agent was started by the scheduler
45 int userID;          ///< The id of the user that created the job
46 int groupID;         ///< The id of the group of the user that created the job
47 int jobId;           ///< The id of the job
48 char* module_name = NULL;   ///< The name of the agent
49 
50 /** Check for an agent in DB */
51 const static char* sql_check = "\
52   SELECT * FROM agent \
53     WHERE agent_name = '%s' AND agent_rev='%s.%s'";
54 
55 /** Insert new agent in DB */
56 const static char* sql_insert = "\
57   INSERT INTO agent (agent_name, agent_rev, agent_desc) \
58     VALUES ('%s', '%s.%s', '%s')";
59 
60 /** System configuration settings */
61 fo_conf* sysconfig = NULL;
62 /** System configuration directory */
63 char* sysconfigdir = NULL;
64 
65 /* these will be freed in fo_scheduler_disconnect */
66 extern GRegex* fo_conf_parse;
67 extern GRegex* fo_conf_replace;
68 
69 /**
70 * Global verbose flags that agents should use instead of specific verbose
71 * flags. This is used by the scheduler to turn verbose on a particular agent
72 * on during run time. When the verbose flag is turned on by the scheduler
73 * the on_verbose function will be called. If nothing needs to be done when
74 * verbose is turned on, simply pass NULL to scheduler_connect
75 */
76 int agent_verbose;
77 
78 /**
79 * @brief Internal function to send a heartbeat to the
80 * scheduler along with the number of items processed.
81  *
82  * \note Agents should NOT call this function directly.
83  * \note This is the alarm SIGALRM function.
84 * @return void
85  * @todo These functions are not safe for a signal handler
86 */
fo_heartbeat()87 void fo_heartbeat()
88 {
89   int processed = g_atomic_int_get(&items_processed);
90   // TODO these functions are not safe for a signal handler
91   fprintf(stdout, "HEART: %d %d\n", processed, alive);
92   fflush(stdout);
93   fflush(stderr);
94   alarm(ALARM_SECS);
95   alive = FALSE;
96 }
97 
98 /**
99 * @brief Checks that the agent is already in the agent table.
100 *
101 * This uses the VERSION and COMMIT_HASH in the system configuration information to
102 * determine if a new agent record needs to be created for this agent in the
103 * database.
104 */
fo_check_agentdb(PGconn * db_conn)105 void fo_check_agentdb(PGconn* db_conn)
106 {
107   PGresult* db_result = NULL;
108   char* db_sql = NULL;
109 
110   db_sql = g_strdup_printf(sql_check, module_name,
111     fo_config_get(sysconfig, module_name, "VERSION", NULL),
112     fo_config_get(sysconfig, module_name, "COMMIT_HASH", NULL));
113   db_result = PQexec(db_conn, db_sql);
114   if (PQresultStatus(db_result) != PGRES_TUPLES_OK)
115   {
116     fprintf(stderr, "FATAL %s.%d: unable to check agent table: %s",
117       __FILE__, __LINE__, PQresultErrorMessage(db_result));
118     fflush(stderr);
119     PQfinish(db_conn);
120     PQclear(db_result);
121     g_free(db_sql);
122     exit(252);
123   }
124 
125   if (PQntuples(db_result) != 1)
126   {
127     g_free(db_sql);
128     PQclear(db_result);
129 
130     db_sql = g_strdup_printf(sql_insert, module_name,
131       fo_config_get(sysconfig, module_name, "VERSION", NULL),
132       fo_config_get(sysconfig, module_name, "COMMIT_HASH", NULL),
133       fo_config_get(sysconfig, module_name, "DESCRIPTION", NULL));
134     db_result = PQexec(db_conn, db_sql);
135     if (PQresultStatus(db_result) != PGRES_COMMAND_OK)
136     {
137       fprintf(stderr, "FATAL %s.%d: unable to insert into agent table: %s",
138         __FILE__, __LINE__, PQresultErrorMessage(db_result));
139       fflush(stderr);
140       PQfinish(db_conn);
141       PQclear(db_result);
142       g_free(db_sql);
143       exit(251);
144     }
145   }
146 
147   g_free(db_sql);
148   PQclear(db_result);
149 }
150 
151 /* ************************************************************************** */
152 /* **** Global Functions **************************************************** */
153 /* ************************************************************************** */
154 
155 /**
156 * @brief This function must be called by agents to let the scheduler know they
157 * are alive and how many items they have processed.
158 *
159 * @param i   This is the number of itmes processed since the last call to
160 * fo_scheduler_heart()
161 *
162 * @return void
163 */
fo_scheduler_heart(int i)164 void fo_scheduler_heart(int i)
165 {
166   g_atomic_int_add(&items_processed, i);
167   alive = TRUE;
168 
169   fflush(stdout);
170   fflush(stderr);
171 }
172 
173 /**
174  * @brief Establish a connection between an agent and the scheduler.
175  * @param[in] argc     Command line agrument count
176  * @param[in] argv     Command line agrument vector
177  * @param[out] db_conn DB Connection
178  * @param[out] db_conf DB conf file
179  * @sa fo_scheduler_connect()
180  */
fo_scheduler_connect_conf(int * argc,char ** argv,PGconn ** db_conn,char ** db_conf)181 void fo_scheduler_connect_conf(int* argc, char** argv, PGconn** db_conn, char** db_conf)
182 {
183   /* locals */
184   GError* error = NULL;
185   GOptionContext* parsed;
186   fo_conf* version;
187   char fname[FILENAME_MAX + 1];
188 
189   char* db_config = NULL;
190   char* db_error = NULL;
191 
192   /* command line options */
193   GOptionEntry options[] =
194     {
195       {"config", 'c', 0, G_OPTION_ARG_STRING, &sysconfigdir, ""},
196       {"userID", 0, 0, G_OPTION_ARG_INT, &userID, ""},
197       {"groupID", 0, 0, G_OPTION_ARG_INT, &groupID, ""},
198       {"scheduler_start", 0, 0, G_OPTION_ARG_NONE, &sscheduler, ""},
199       {"jobId", 0, 0, G_OPTION_ARG_INT, &jobId, ""},
200       {NULL}
201     };
202 
203   /* initialize memory associated with agent connection */
204   module_name = g_strdup(basename(argv[0]));
205   sysconfigdir = DEFAULT_SETUP;
206   items_processed = 0;
207   valid = 0;
208   sscheduler = 0;
209   userID = -1;
210   groupID = -1;
211   agent_verbose = 0;
212   memset(buffer, 0, sizeof(buffer));
213 
214   if (sysconfig != NULL)
215   {
216     LOG_WARNING("fo_scheduler_connect() has already been called.");
217     sscheduler = 1;
218     return;
219   }
220 
221   if (getenv("FO_SYSCONFDIR"))
222     sysconfigdir = getenv("FO_SYSCONFDIR");
223 
224   /* parse command line options */
225   parsed = g_option_context_new("");
226   g_option_context_add_main_entries(parsed, options, NULL);
227   g_option_context_set_ignore_unknown_options(parsed, TRUE);
228   g_option_context_set_help_enabled(parsed, FALSE);
229   g_option_context_parse(parsed, argc, &argv, NULL);
230   g_option_context_free(parsed);
231 
232   /* load system configuration */
233   if (sysconfigdir)
234   {
235     snprintf(fname, FILENAME_MAX, "%s/%s", sysconfigdir, "fossology.conf");
236     sysconfig = fo_config_load(fname, &error);
237     if (error)
238     {
239       fprintf(stderr, "FATAL %s.%d: unable to open system configuration: %s\n",
240         __FILE__, __LINE__, error->message);
241       exit(255);
242     }
243 
244     snprintf(fname, FILENAME_MAX, "%s/mods-enabled/%s/VERSION",
245       sysconfigdir, module_name);
246 
247     version = fo_config_load(fname, &error);
248     if (error)
249     {
250       fprintf(stderr, "FATAL %s.%d: unable to open VERSION configuration: %s\n",
251         __FILE__, __LINE__, error->message);
252       exit(254);
253     }
254 
255     fo_config_join(sysconfig, version, &error);
256     if (error)
257     {
258       fprintf(stderr, "FATAL %s.%d: unable to join configuration files: %s\n",
259         __FILE__, __LINE__, error->message);
260       exit(250);
261     }
262 
263 
264     fo_config_free(version);
265   }
266 
267   /* create the database connection */
268   if (db_conn)
269   {
270     db_config = g_strdup_printf("%s/Db.conf", sysconfigdir);
271     (*db_conn) = fo_dbconnect(db_config, &db_error);
272     if (db_conf)
273       *db_conf = db_config;
274     else
275       g_free(db_config);
276     if (db_error)
277     {
278       fprintf(stderr, "FATAL %s.%d: unable to open database connection: %s\n",
279         __FILE__, __LINE__, db_error);
280       fflush(stderr);
281       exit(253);
282     }
283   }
284 
285   /* send "OK" to the scheduler */
286   if (sscheduler)
287   {
288     /* check that the agent record exists */
289     if (db_conn)
290       fo_check_agentdb(*db_conn);
291 
292     if (fo_config_has_key(sysconfig, module_name, "VERSION"))
293       fprintf(stdout, "VERSION: %s\n",
294         fo_config_get(sysconfig, module_name, "VERSION", &error));
295     else fprintf(stdout, "VERSION: unknown\n");
296     fprintf(stdout, "\nOK\n");
297     fflush(stdout);
298 
299     /* set up the heartbeat() */
300     signal(SIGALRM, fo_heartbeat);
301     alarm(ALARM_SECS);
302   }
303 
304   fflush(stdout);
305   fflush(stderr);
306 
307   alive = TRUE;
308 }
309 
310 /**
311 * @brief Establish a connection between an agent and the scheduler.
312 *
313 * Steps taken by this function:
314 *   - initialize memory associated with agent connection
315 *   - send "SPAWNED" to the scheduler
316 *   - receive the number of items between notifications
317 *   - check the nfs mounts for the agent
318 *   - set up the heartbeat()
319 *
320 * Making a call to this function should be the first thing that an agent does
321 * after parsing its command line arguments.
322 *
323 * If the database connection passed is NULL, then this will not return a
324 * database connection, and will not check the agent's database record.
325 *
326 * @param argc     pointer to the number of arguments passed to the agent
327 * @param argv     the command line arguments for the agent
328 * @param db_conn  pointer to the location for the database connection
329 * @return void
330 */
fo_scheduler_connect(int * argc,char ** argv,PGconn ** db_conn)331 void fo_scheduler_connect(int* argc, char** argv, PGconn** db_conn)
332 {
333   fo_scheduler_connect_conf(argc, argv, db_conn, NULL);
334 }
335 
336 /**
337  * @brief Make a connection from an agent to the scheduler and create a DB
338  * manager as well.
339  * @param[out] dbManager New DB manager
340  */
fo_scheduler_connect_dbMan(int * argc,char ** argv,fo_dbManager ** dbManager)341 void fo_scheduler_connect_dbMan(int* argc, char** argv, fo_dbManager** dbManager)
342 {
343   char* dbConf;
344   PGconn* dbConnection;
345   fo_scheduler_connect_conf(argc, argv, &dbConnection, &dbConf);
346   *dbManager = fo_dbManager_new_withConf(dbConnection, dbConf);
347   free(dbConf);
348 }
349 
350 /**
351 * @brief Disconnect the scheduler connection.
352 *
353 * Making a call to this function should be the last thing that an agent does
354 * before exiting. Any error reporting to stdout or stderr will not work after
355 * this function has finished execution.
356 * @param retcode Return code to the scheduler
357 */
fo_scheduler_disconnect(int retcode)358 void fo_scheduler_disconnect(int retcode)
359 {
360   if (module_name != NULL) {
361     /* send "CLOSED" to the scheduler */
362     if (sscheduler)
363     {
364       fo_heartbeat();
365       fprintf(stdout, "\nBYE %d\n", retcode);
366       fflush(stdout);
367 
368       valid = 0;
369       sscheduler = 0;
370 
371       g_free(module_name);
372     }
373 
374     if (strcmp(sysconfigdir, DEFAULT_SETUP))
375       g_free(sysconfigdir);
376 
377     fo_config_free(sysconfig);
378   }
379   g_regex_unref(fo_conf_parse);
380   g_regex_unref(fo_conf_replace);
381 
382   sysconfigdir = NULL;
383   sysconfig = NULL;
384   fo_conf_parse = NULL;
385   fo_conf_replace = NULL;
386   module_name = NULL;
387 
388   fflush(stdout);
389   fflush(stderr);
390 }
391 
392 /**
393 * @brief Get the next data to process from the scheduler.
394 *
395 * It is the job of the agent to decide how this string is
396 * interpreted.
397 *
398 * Steps taken by this function:
399 *   - get the next line from the scheduler
400 *     - if the scheduler has paused this agent this will block till unpaused
401 *   - check for "CLOSE" from scheduler, return NULL if received
402 *   - check for "VERBOSE" from scheduler
403 *     - if this is received turn the verbose flag to whatever is specified
404 *     - a new line must be received, perform same task (i.e. recursive call)
405 *   - check for "END" from scheduler, if received print OK and recurse
406 *     - this is used to simplify communications within the scheduler
407 *   - return whatever has been received
408 *
409 * @return char* for the next thing to analyze, NULL if there is nothing
410 *          left in this job, in which case the agent should close
411 */
fo_scheduler_next()412 char* fo_scheduler_next()
413 {
414   fflush(stdout);
415 
416   /* get the next line from the scheduler and possibly WAIT */
417   while (fgets(buffer, sizeof(buffer), stdin) != NULL)
418   {
419     if (agent_verbose)
420       printf("\nNOTE: received %s\n", buffer);
421     if (strncmp(buffer, "CLOSE", 5) == 0)
422       break;
423     if (strncmp(buffer, "END", 3) == 0)
424     {
425       fprintf(stdout, "\nOK\n");
426       fflush(stdout);
427       fflush(stderr);
428       valid = 0;
429       continue;
430     }
431     else if (strncmp(buffer, "VERBOSE", 7) == 0)
432     {
433       agent_verbose = atoi(&buffer[8]);
434       valid = 0;
435       continue;
436     }
437     else if (strncmp(buffer, "VERSION", 7) == 0)
438     {
439       if (fo_config_has_key(sysconfig, module_name, "VERSION"))
440         fprintf(stdout, "VERSION: %s\n",
441           fo_config_get(sysconfig, module_name, "VERSION", NULL));
442       else fprintf(stdout, "VERSION: unknown\n");
443       fflush(stdout);
444       fflush(stderr);
445       valid = 0;
446       continue;
447     }
448 
449     buffer[strlen(buffer) - 1] = '\0';
450     valid = 1;
451     return buffer;
452   }
453 
454   valid = 0;
455 
456   fflush(stdout);
457   fflush(stderr);
458 
459   return NULL;
460 }
461 
462 /**
463 * @brief Get the last read string from the scheduler.
464 *
465 * @return Returns the string buffer if it is valid.
466 * If it is not valid, return NULL.
467 * The buffer is not valid if the last received data from the scheduler
468 * was a command, rather than data to operate on.
469 */
fo_scheduler_current()470 char* fo_scheduler_current()
471 {
472   return valid ? buffer : NULL;
473 }
474 
475 /**
476 * @brief Sets something special about the agent within the scheduler.
477 *
478 * Possible Options:
479 *   `SPECIAL_NOKILL`: instruct the scheduler to not kill the agent
480 *
481 * @param option  the option to set
482 * @param value   whether to set the option to true or false
483 */
fo_scheduler_set_special(int option,int value)484 void fo_scheduler_set_special(int option, int value)
485 {
486   fprintf(stdout, "SPECIAL: %d %d\n", option, value);
487   fflush(stdout);
488 }
489 
490 /**
491 * @brief Gets if a particular special attribute is set in the scheduler.
492 *
493 * Possible Options:
494 * - `SPECIAL_NOKILL`   : the agent will not be killed if it stops updating status
495 * - `SPECIAL_EXCLUSIVE`: the agent cannot run simultaneously with any other agent
496 * - `SPECIAL_NOEMAIL`  : the scheduler will not send notification emails for this agent
497 * - `SPECIAL_LOCAL`    : the agent is required to run on the same machine as the scheduler
498 *
499 * @param option  the relevant option to the get the value of
500 * @return  if the value of the special option was true
501 */
fo_scheduler_get_special(int option)502 int fo_scheduler_get_special(int option)
503 {
504   int value;
505 
506   fprintf(stdout, "GETSPECIAL: %d\n", option);
507   fflush(stdout);
508 
509   if (fscanf(stdin, "VALUE: %d\n", &value) != 1)
510     return 0;
511   return value;
512 }
513 
514 /**
515 * @brief Gets the id of the job that the agent is running
516 *
517 * @return the job id
518 */
fo_scheduler_jobId()519 int fo_scheduler_jobId()
520 {
521   return jobId;
522 }
523 
524 /**
525 * @brief Gets the id of the user that created the job that the agent is running
526 *
527 * @return the user id
528 */
fo_scheduler_userID()529 int fo_scheduler_userID()
530 {
531   return userID;
532 }
533 
534 /**
535 * @brief Gets the id of the group that created the job that the agent is running
536 *
537 * @return the group id
538 */
fo_scheduler_groupID()539 int fo_scheduler_groupID()
540 {
541   return groupID;
542 }
543 
544 /**
545 * @brief gets a system configuration variable from the configuration data.
546 *
547 * This function should be called after fo_scheduler_connect has been called.
548 * This is because the configuration data it not loaded until after that.
549 *
550 * @param sectionname the group of the variable
551 * @param variablename the name of the variable
552 * @return the value of the variable
553 */
fo_sysconfig(const char * sectionname,const char * variablename)554 char* fo_sysconfig(const char* sectionname, const char* variablename)
555 {
556   GError* error = NULL;
557   char* ret;
558 
559   ret = fo_config_get(
560     sysconfig,
561     sectionname,
562     variablename,
563     &error);
564 
565   fflush(stdout);
566   fflush(stderr);
567 
568   if (error)
569   {
570     g_clear_error(&error);
571     ret = NULL;
572   }
573 
574   return ret;
575 }
576