FOSSology  4.4.0
Open Source License Compliance by Open Source Software
database.c
Go to the documentation of this file.
1 /*
2  SPDX-FileCopyrightText: © 2010, 2011, 2012 Hewlett-Packard Development Company, L.P.
3  SPDX-FileCopyrightText: © 2015, 2018 Siemens AG
4 
5  SPDX-License-Identifier: GPL-2.0-only
6 */
11 /* local includes */
12 #include <agent.h>
13 #include <database.h>
14 #include <logging.h>
15 #include <emailformatter.h>
16 
17 /* other library includes */
18 #include <libfossdb.h>
19 #include <fcntl.h>
20 #include <sys/stat.h>
21 #include <sys/mman.h>
22 
23 /* all of the sql statements used in the database */
24 #include <sqlstatements.h>
25 
26 /* ************************************************************************** */
27 /* *** email notification *** */
28 /* *** *** */
29 /* *** There is no good location to put the code that performs the *** */
30 /* *** email notification. This is because it is one of the more *** */
31 /* *** database intensive actions that the scheduler performs, but *** */
32 /* *** also requires extensive network access. Since the database *** */
33 /* *** access is limited to database.c, this is where email *** */
34 /* *** notification lives. *** */
35 /* ************************************************************************** */
36 
37 #define EMAIL_ERROR(...) { \
38  WARNING(__VA_ARGS__); \
39  email_notify = 0; \
40  error = NULL; }
41 
42 #define EMAIL_BUILD_CMD "%s %s -s '%s' %s"
43 #define DEFAULT_HEADER "FOSSology scan complete\nmessage:\""
44 #define DEFAULT_FOOTER "\""
45 #define DEFAULT_SUBJECT "FOSSology scan complete\n"
46 #define DEFAULT_COMMAND "/usr/bin/mailx"
47 
48 #define min(x, y) (x < y ? x : y)
49 
54 typedef struct {
56  gchar* foss_url;
59 
84 static gboolean email_replace(const GMatchInfo* match, GString* ret,
85  email_replace_args* args)
86 {
87  gchar* m_str = g_match_info_fetch(match, 1);
88  gchar* sql = NULL;
89  gchar* fossy_url = args->foss_url;
90  job_t* job = args->job;
91  GPtrArray* rows = NULL;
92  /* TODO belongs to $DB if statement => gchar* table, * column; */
93  PGresult* db_result;
94  guint i;
95 
96  /* $UPLOADNAME
97  *
98  * Appends the name of the file that was uploaded and appends it to the output
99  * string. This uses the job id to find the upload name.
100  */
101  if(strcmp(m_str, "UPLOADNAME") == 0)
102  {
103  sql = g_strdup_printf(upload_name, job->id);
104  db_result = database_exec(args->scheduler, sql);
105 
106  if(PQresultStatus(db_result) != PGRES_TUPLES_OK)
107  {
108  g_string_append_printf(ret,
109  "[ERROR: unable to select upload file name for job %d]", job->id);
110  }
111  else if(PQntuples(db_result) == 0)
112  {
113  if(strcmp(job->agent_type, "delagent") == 0)
114  g_string_append_printf(ret,
115  "[File has been deleted by job %d]", job->id);
116  else
117  g_string_append_printf(ret,
118  "[ERROR: file has not been uploaded or unpacked yet for job %d]", job->id);
119  }
120  else
121  {
122  g_string_append(ret, PQgetvalue(db_result, 0, 0));
123  }
124 
125  SafePQclear(db_result);
126  g_free(sql);
127  }
128 
129  /* $BROWSELINK
130  *
131  * Appends the URL that will link to the upload in the browse menu of the user
132  * interface.
133  */
134  else if(strcmp(m_str, "BROWSELINK") == 0)
135  {
136  sql = g_strdup_printf(upload_pk, job->id);
137  db_result = database_exec(args->scheduler, sql);
138 
139  if(PQresultStatus(db_result) != PGRES_TUPLES_OK)
140  {
141  g_string_append_printf(ret,
142  "[ERROR: unable to select upload primary key for job %d]", job->id);
143  }
144  else if(PQntuples(db_result) == 0)
145  {
146  if(strcmp(job->agent_type, "delagent") == 0)
147  g_string_append_printf(ret,
148  "[File has been deleted by job %d]", job->id);
149  else
150  g_string_append_printf(ret,
151  "[ERROR: file has not been uploaded or unpacked yet for job %d]", job->id);
152  }
153  else
154  {
155  g_string_append_printf(ret,
156  "http://%s?mod=browse&upload=%s&item=%s&show=detail",
157  fossy_url, PQgetvalue(db_result, 0, 0), PQgetvalue(db_result, 0, 1));
158  }
159 
160  SafePQclear(db_result);
161  g_free(sql);
162  }
163 
164  /* $JOBQUEUELINK
165  *
166  * Appends the URL that will link to the job queue
167  */
168  else if(strcmp(m_str, "JOBQUEUELINK") == 0)
169  {
170  sql = g_strdup_printf(upload_pk, job->id);
171  db_result = database_exec(args->scheduler, sql);
172 
173  if(PQresultStatus(db_result) != PGRES_TUPLES_OK)
174  {
175  g_string_append_printf(ret,
176  "[ERROR: unable to select file name for upload %d]", job->id);
177  }
178  else
179  {
180  g_string_append_printf(ret, "http://%s?mod=showjobs&upload=%s",
181  fossy_url, PQgetvalue(db_result, 0, 0));
182  }
183 
184  SafePQclear(db_result);
185  g_free(sql);
186  }
187 
188  /* $SCHEDULERLOG
189  *
190  * Appends the URL that will link to the log file produced by the agent.
191  */
192  else if(strcmp(m_str, "SCHEDULERLOG") == 0)
193  {
194  g_string_append_printf(ret, "http://%s?mod=showjobs&job=%d",
195  fossy_url, job->id);
196  }
197 
198  /* $UPLOADFOLDERNAME
199  *
200  * Appends the name of the folder that the upload was stored under.
201  */
202  else if(strcmp(m_str, "UPLOADFOLDERNAME") == 0)
203  {
204  sql = g_strdup_printf(folder_name, job->id);
205  db_result = database_exec(args->scheduler, sql);
206 
207  if(PQresultStatus(db_result) != PGRES_TUPLES_OK || PQntuples(db_result) == 0)
208  {
209  g_string_append_printf(ret,
210  "[NOTICE: unable to select folder name for upload %d]", job->id);
211  }
212  else
213  {
214  rows = g_ptr_array_new();
215  GString *foldername = g_string_new(PQgetvalue(db_result, 0, 0));
216  guint folder_pk = atoi(PQget(db_result, 0, "folder_pk"));
217  g_ptr_array_add(rows, foldername);
218  SafePQclear(db_result);
219  g_free(sql);
220  sql = g_strdup_printf(parent_folder_name, folder_pk);
221  db_result = database_exec(args->scheduler, sql);
222  /*
223  * Get the current folder name and traverse back till the root folder.
224  * Add the folder names found in an array.
225  * array[0] => Curr_Folder
226  * array[1] => Par_Folder
227  * array[2] => Root_Folder
228  */
229  while(PQresultStatus(db_result) == PGRES_TUPLES_OK && PQntuples(db_result) == 1)
230  {
231  GString *foldername = g_string_new(PQgetvalue(db_result, 0, 0));
232  guint folder_pk = atoi(PQget(db_result, 0, "folder_pk"));
233  g_ptr_array_add(rows, foldername);
234  SafePQclear(db_result);
235  g_free(sql);
236  sql = g_strdup_printf(parent_folder_name, folder_pk);
237  db_result = database_exec(args->scheduler, sql);
238  }
239  /*
240  * Traverse the folder name array from behind and append the names with a
241  * `/` as a separator between the names.
242  * Result => Root_Folder / Par_Folder / Curr_folder
243  */
244  for(i = rows->len - 1; i > 0; i--)
245  {
246  GString *folder = g_ptr_array_index(rows, i);
247  g_string_append(ret, folder->str);
248  g_string_append(ret, " / ");
249  }
250  GString *folder = g_ptr_array_index(rows, 0);
251  g_string_append(ret, folder->str);
252  g_ptr_array_free(rows, TRUE);
253  }
254 
255  SafePQclear(db_result);
256  g_free(sql);
257  }
258 
259  /* $JOBRESULT
260  *
261  * Appends if the job finished successfully or it it failed.
262  */
263  else if(strcmp(m_str, "JOBRESULT") == 0)
264  {
265  switch(job->status)
266  {
267  case JB_COMPLETE: g_string_append(ret, "COMPLETE"); break;
268  case JB_FAILED: g_string_append(ret, "FAILED"); break;
269  default:
270  g_string_append_printf(ret, "[ERROR: illegal job status \"%s\"]",
271  job_status_strings[job->status]);
272  break;
273  }
274  }
275 
276  /* $AGENTSTATUS
277  *
278  * Appends the list of agents run with their status.
279  */
280  else if(strcmp(m_str, "AGENTSTATUS") == 0)
281  {
282  sql = g_strdup_printf(jobsql_jobinfo, job->id);
283  db_result = database_exec(args->scheduler, sql);
284  if(PQresultStatus(db_result) != PGRES_TUPLES_OK)
285  {
286  g_string_append_printf(ret,
287  "[ERROR: unable to select agent info for job %d]", job->id);
288  }
289  else
290  {
291  rows = g_ptr_array_sized_new(PQntuples(db_result));
292  /*
293  * Find all the agents for the current job and attach their jq_pk,
294  * their name and their status (true=>pass, false=>fail)
295  */
296  for(i = 0; i < PQntuples(db_result) && ret; i++)
297  {
298  agent_info *data = (agent_info *)calloc(1, sizeof(agent_info));
299  data->id = atoi(PQget(db_result, i, "jq_pk"));
300  data->agent = g_string_new(PQget(db_result, i, "jq_type"));
301  if(atoi(PQget(db_result, i, "jq_end_bits")) == 1)
302  {
303  data->status = TRUE;
304  }
305  else
306  {
307  data->status = FALSE;
308  }
309  g_ptr_array_add(rows, data);
310  }
311  /* Pass the agent data to email_formating function to convert in desired format */
312  g_string_append(ret, email_format_text(rows, fossy_url));
313  g_ptr_array_free(rows, TRUE);
314  }
315  SafePQclear(db_result);
316  g_free(sql);
317  }
318 
319  /* $DB.table.column
320  *
321  * Appends a column of a table from the database to the resulting string.
322  */
323  else if(strcmp(m_str, "DB") == 0)
324  {
325  g_string_append(ret, "[DB. syntax is NOT IMPLEMENTED]");
326  /* TODO reimplement $DB variable
327  table = g_match_info_fetch(match, 3);
328  column = g_match_info_fetch(match, 4);
329  sql = g_strdup_printf("SELECT %s FROM %s;", column, table);
330  db_result = database_exec(scheduler, sql);
331  if(PQresultStatus(db_result) != PGRES_TUPLES_OK ||
332  PQntuples(db_result) == 0 || PQnfields(db_result) == 0)
333  {
334  g_string_append_printf(ret, "[ERROR: unable to select %s.%s]", table, column);
335  }
336  else
337  {
338  g_string_append_printf(ret, "%s.%s[", table, column);
339  for(i = 0; i < PQntuples(db_result); i++)
340  {
341  g_string_append(ret, PQgetvalue(db_result, i, 0));
342  if(i != PQntuples(db_result) - 1)
343  g_string_append(ret, " ");
344  }
345  g_string_append(ret, "]");
346  }
347 
348  SafePQclear(db_result);
349  g_free(sql);
350  g_free(table);
351  g_free(column);*/
352  }
353 
354  g_free(m_str);
355  return FALSE;
356 }
357 
365 static gint email_checkjobstatus(scheduler_t* scheduler, job_t* job)
366 {
367  gchar* sql;
368  gint ret = 1;
369  PGresult* db_result;
370  int id, i;
371 
372  sql = g_strdup_printf(jobsql_anyrunnable, job->id);
373  db_result = database_exec(scheduler, sql);
374  if(PQresultStatus(db_result) != PGRES_TUPLES_OK)
375  {
376  PQ_ERROR(db_result, "unable to check job status for jq_pk %d", job->id);
377  g_free(sql);
378  SafePQclear(db_result);
379  return 0;
380  }
381 
382  /* check if all runnable jobs have been run */
383  if(PQntuples(db_result) != 0)
384  {
385  ret = 0;
386  }
387 
388  g_free(sql);
389  SafePQclear(db_result);
390 
391  sql = g_strdup_printf(jobsql_jobendbits, job->id);
392  db_result = database_exec(scheduler, sql);
393 
394  /* check for any jobs that are still running */
395  for(i = 0; i < PQntuples(db_result) && ret; i++)
396  {
397  id = atoi(PQget(db_result, i, "jq_pk"));
398  if(id != job->id && g_tree_lookup(scheduler->job_list, &id) != NULL)
399  {
400  ret = 0;
401  break;
402  }
403  }
404 
405  /* check for any failed jobs */
406  for(i = 0; i < PQntuples(db_result) && ret; i++)
407  {
408  if(atoi(PQget(db_result, i, "jq_end_bits")) == (1 << 1))
409  {
410  ret = 2;
411  break;
412  }
413  }
414 
415  g_free(sql);
416  SafePQclear(db_result);
417  return ret;
418 }
419 
429 static void email_notification(scheduler_t* scheduler, job_t* job)
430 {
431  PGresult* db_result;
432  int j_id = job->id;
433  int upload_id;
434  int status;
435  int retcode;
436  char* val;
437  char* final_cmd = NULL;
438  char sql[1024];
439  FILE* mail_io;
440  GString* email_txt;
441  job_status curr_status = job->status;
442  email_replace_args args;
443 
444  if(is_meta_special(g_tree_lookup(scheduler->meta_agents, job->agent_type), SAG_NOEMAIL) ||
445  !(status = email_checkjobstatus(scheduler, job)))
446  return;
447 
448  sprintf(sql, select_upload_fk, j_id);
449  db_result = database_exec(scheduler, sql);
450  if(PQresultStatus(db_result) != PGRES_TUPLES_OK || PQntuples(db_result) == 0)
451  {
452  PQ_ERROR(db_result, "unable to select the upload id for job %d", j_id);
453  return;
454  }
455 
456  upload_id = atoi(PQgetvalue(db_result, 0, 0));
457  SafePQclear(db_result);
458 
459  sprintf(sql, upload_common, upload_id);
460  db_result = database_exec(scheduler, sql);
461  if(PQresultStatus(db_result) != PGRES_TUPLES_OK)
462  {
463  PQ_ERROR(db_result, "unable to check common uploads to job %d", j_id);
464  return;
465  }
466  SafePQclear(db_result);
467 
468  sprintf(sql, jobsql_email, upload_id);
469  db_result = database_exec(scheduler, sql);
470  if(PQresultStatus(db_result) != PGRES_TUPLES_OK)
471  {
472  PQ_ERROR(db_result, "unable to access email info for job %d", j_id);
473  return;
474  }
475 
476  /* special for delagent, upload records have been deleted.
477  * So can't get the user info from upload table.
478  * So get the user info from job table */
479  if(PQntuples(db_result) == 0)
480  {
481  SafePQclear(db_result);
482  sprintf(sql, jobsql_email_job, j_id);
483  db_result = database_exec(scheduler, sql);
484  if(PQresultStatus(db_result) != PGRES_TUPLES_OK || PQntuples(db_result) == 0)
485  {
486  PQ_ERROR(db_result, "unable to access email info for job %d", j_id);
487  return;
488  }
489  }
490 
491  if(PQget(db_result, 0, "email_notify")[0] == 'y')
492  {
493  if(status == 2)
494  job->status = JB_FAILED;
495 
496  email_txt = g_string_new("");
497  g_string_append(email_txt, scheduler->email_header);
498  g_string_append(email_txt, job->message == NULL ? "" : job->message);
499  g_string_append(email_txt, scheduler->email_footer);
500 
501 
502  if(scheduler->parse_db_email != NULL)
503  {
504  args.foss_url = scheduler->host_url;
505  args.job = job;
506  args.scheduler = scheduler;
507  val = g_regex_replace_eval(scheduler->parse_db_email, email_txt->str,
508  email_txt->len, 0, 0, (GRegexEvalCallback)email_replace, &args, NULL);
509  }
510  else
511  {
512  val = email_txt->str;
513  }
514 
515  final_cmd = get_email_command(scheduler, PQget(db_result, 0, "user_email"));
516  if(final_cmd == NULL)
517  {
518  if(scheduler->parse_db_email != NULL)
519  g_free(val);
520  g_string_free(email_txt, TRUE);
521  return;
522  }
523  if((mail_io = popen(final_cmd, "w")) != NULL)
524  {
525  fprintf(mail_io, "%s", val);
526  fflush(mail_io);
527  retcode = WEXITSTATUS(pclose(mail_io));
528  if(retcode != 0)
529  {
530  ERROR("Received error code %d from '%s'", retcode, scheduler->email_command);
531  }
532  }
533  else
534  {
535  WARNING("Unable to spawn email notification process: '%s'.\n",
536  scheduler->email_command);
537  }
538  job->status = curr_status;
539  if(scheduler->parse_db_email != NULL)
540  g_free(val);
541  g_free(final_cmd);
542  g_string_free(email_txt, TRUE);
543  }
544  SafePQclear(db_result);
545 }
546 
557 void email_init(scheduler_t* scheduler)
558 {
559  int email_notify, fd;
560  struct stat header_sb = {};
561  struct stat footer_sb = {};
562  gchar* fname;
563  GError* error = NULL;
564 
565  if(scheduler->email_header && !scheduler->default_header)
566  munmap(scheduler->email_header, header_sb.st_size);
567  if(scheduler->email_footer && !scheduler->default_footer)
568  munmap(scheduler->email_footer, footer_sb.st_size);
569  g_free(scheduler->email_subject);
570 
571  /* load the header */
572  email_notify = 1;
573  fname = g_strdup_printf("%s/%s", scheduler->sysconfigdir,
574  fo_config_get(scheduler->sysconfig, "EMAILNOTIFY", "header", &error));
575  if(error && error->code == fo_missing_group)
576  EMAIL_ERROR("email notification setting group \"[EMAILNOTIFY]\" missing. Using defaults");
577  if(error && error->code == fo_missing_key)
578  EMAIL_ERROR("email notification setting key \"header\" missing. Using default header");
579  if(email_notify && (fd = open(fname, O_RDONLY)) == -1)
580  EMAIL_ERROR("unable to open file for email header: %s", fname);
581  if(email_notify && fstat(fd, &header_sb) == -1)
582  EMAIL_ERROR("unable to fstat email header: %s", fname);
583  if(email_notify && (scheduler->email_header = mmap(NULL, header_sb.st_size, PROT_READ,
584  MAP_SHARED, fd, 0)) == MAP_FAILED)
585  EMAIL_ERROR("unable to mmap email header: %s", fname);
586  if((scheduler->default_header = !email_notify))
587  scheduler->email_header = DEFAULT_HEADER;
588 
589  /* load the footer */
590  email_notify = 1;
591  fname = g_strdup_printf("%s/%s", scheduler->sysconfigdir,
592  fo_config_get(scheduler->sysconfig, "EMAILNOTIFY", "footer", &error));
593  if(error)
594  email_notify = 0;
595  if(error && error->code == fo_missing_key)
596  EMAIL_ERROR("email notification setting key \"footer\" missing. Using default footer");
597  if(email_notify && (fd = open(fname, O_RDONLY)) == -1)
598  EMAIL_ERROR("unable to open file for email footer: %s", fname);
599  if(email_notify && fstat(fd, &footer_sb) == -1)
600  EMAIL_ERROR("unable to fstat email footer: %s", fname);
601  if(email_notify && (scheduler->email_footer = mmap(NULL, footer_sb.st_size, PROT_READ,
602  MAP_SHARED, fd, 0)) == MAP_FAILED)
603  EMAIL_ERROR("unable to mmap email footer: %s", fname);
604  if((scheduler->default_footer = !email_notify))
605  scheduler->email_footer = DEFAULT_FOOTER;
606  error = NULL;
607 
608  /* load the email_subject */
609  scheduler->email_subject = fo_config_get(scheduler->sysconfig, "EMAILNOTIFY",
610  "subject", &error);
611  if(error)
612  scheduler->email_subject = DEFAULT_SUBJECT;
613  if(error && error->code == fo_missing_key)
614  EMAIL_ERROR("email notification setting key \"subject\" missing. Using default subject");
615  scheduler->email_subject = g_strdup(scheduler->email_subject);
616  error = NULL;
617 
618  /* load the client */
619  email_notify = 1;
620  scheduler->email_command = fo_config_get(scheduler->sysconfig, "EMAILNOTIFY",
621  "client", &error);
622  if(error)
623  scheduler->email_command = DEFAULT_COMMAND;
624  if(error && error->code == fo_missing_key)
625  EMAIL_ERROR("email notification setting key \"client\" missing. Using default client");
626  scheduler->email_command = g_strdup(scheduler->email_command);
627  error = NULL;
628 }
629 
630 /* ************************************************************************** */
631 /* **** local functions ***************************************************** */
632 /* ************************************************************************** */
633 
639 typedef struct
640 {
641  char* table;
642  uint8_t ncols;
643  char* columns[13];
644 } reqcols;
645 
655 static void check_tables(scheduler_t* scheduler)
656 {
657  /* locals */
658  PGresult* db_result;
659  GString* sql;
660  reqcols* curr;
661  uint32_t i;
662  uint32_t curr_row;
663  int passed = TRUE;
664  char sqltmp[1024] = {0};
665 
666  /* All of the tables and columns that the scheduler uses
667  *
668  * Note: the columns should be listed in alphabetical order, if they are not
669  * then the error messages that result will be erroneous
670  */
671  reqcols cols[] =
672  {
673  {"jobqueue", 13, {
674  "jq_args", "jq_end_bits", "jq_endtext", "jq_endtime", "jq_host",
675  "jq_itemsprocessed", "jq_job_fk", "jq_log", "jq_pk", "jq_runonpfile",
676  "jq_schedinfo", "jq_starttime", "jq_type" }},
677  {"sysconfig", 2, {"conf_value", "variablename" }},
678  {"job", 2, {"job_pk", "job_upload_fk" }},
679  {"folder", 2, {"folder_name", "folder_pk" }},
680  {"foldercontents", 3, {"child_id", "foldercontents_mode", "parent_fk" }},
681  {"upload", 2, {"upload_filename", "upload_pk" }},
682  {"uploadtree", 3, {"parent", "upload_fk", "uploadtree_pk" }},
683  {"users", 4, {"email_notify", "user_email", "user_name", "user_pk" }},
684  { NULL }
685  };
686 
687  /* 1st iterate across every require table and column */
688  sprintf(sqltmp, check_scheduler_tables, PQdb(scheduler->db_conn));
689  for(curr = cols; curr->table; curr++)
690  {
691  /* build the sql statement */
692  sql = g_string_new(sqltmp);
693  g_string_append_printf(sql, "'%s' AND column_name IN (", curr->table);
694  for(i = 0; i < curr->ncols; i++)
695  {
696  g_string_append_printf(sql, "'%s'", curr->columns[i]);
697  if(i != curr->ncols - 1)
698  g_string_append(sql, ", ");
699  }
700  g_string_append(sql, ") ORDER BY column_name");
701 
702  /* execute the sql */
703  db_result = database_exec(scheduler, sql->str);
704  if(PQresultStatus(db_result) != PGRES_TUPLES_OK)
705  {
706  passed = FALSE;
707  PQ_ERROR(db_result, "could not check database tables");
708  break;
709  }
710 
711  /* check that the correct number of columns was returned yr */
712  if(PQntuples(db_result) != curr->ncols)
713  {
714  /* we have failed the database check */
715  passed = FALSE;
716 
717  /* print the columns that do not exist */
718  for(i = 0, curr_row = 0; i < curr->ncols; i++)
719  {
720  if(curr_row>=PQntuples(db_result) || strcmp(PQgetvalue(db_result, curr_row, 0), curr->columns[i]) != 0)
721  {
722  ERROR("Column %s.%s does not exist", curr->table, curr->columns[i]);
723  }
724  else
725  {
726  curr_row++;
727  }
728  }
729  }
730 
731  SafePQclear(db_result);
732  g_string_free(sql, TRUE);
733  }
734 
735  if(!passed)
736  {
737  log_printf("FATAL %s.%d: Scheduler did not pass database check\n", __FILE__, __LINE__);
738  log_printf("FATAL %s.%d: Running fo_postinstall should fix the database schema\n", __FILE__, __LINE__);
739  exit(230);
740  }
741 }
742 
749 static PGconn* database_connect(gchar* configdir)
750 {
751  PGconn* ret = NULL;
752  gchar* dbconf = NULL;
753  char* error = NULL;
754 
755  dbconf = g_strdup_printf("%s/Db.conf", configdir);
756  ret = fo_dbconnect(dbconf, &error);
757 
758  if(error || PQstatus(ret) != CONNECTION_OK)
759  FATAL("Unable to connect to the database: \"%s\"", error);
760 
761  g_free(dbconf);
762  return ret;
763 }
764 
770 void database_init(scheduler_t* scheduler)
771 {
772  PGresult* db_result;
773 
774  /* create the connection to the database */
775  scheduler->db_conn = database_connect(scheduler->sysconfigdir);
776 
777  /* get the url for the fossology instance */
778  db_result = database_exec(scheduler, url_checkout);
779  if(PQresultStatus(db_result) == PGRES_TUPLES_OK && PQntuples(db_result) != 0)
780  scheduler->host_url = g_strdup(PQgetvalue(db_result, 0, 0));
781  SafePQclear(db_result);
782 
783  /* check that relevant database fields exist */
784  check_tables(scheduler);
785 }
786 
787 /* ************************************************************************** */
788 /* **** event and functions ************************************************* */
789 /* ************************************************************************** */
790 
803 PGresult* database_exec(scheduler_t* scheduler, const char* sql)
804 {
805  PGresult* ret = NULL;
806 
807  V_SPECIAL("DATABASE: exec \"%s\"\n", sql);
808 
809  ret = PQexec(scheduler->db_conn, sql);
810  if(ret == NULL || PQstatus(scheduler->db_conn) != CONNECTION_OK)
811  {
812  PQfinish(scheduler->db_conn);
813  scheduler->db_conn = database_connect(scheduler->sysconfigdir);
814 
815  ret = PQexec(scheduler->db_conn, sql);
816  }
817 
818  return ret;
819 }
820 
827 void database_exec_event(scheduler_t* scheduler, char* sql)
828 {
829  PGresult* db_result = database_exec(scheduler, sql);
830  if(PQresultStatus(db_result) != PGRES_COMMAND_OK)
831  PQ_ERROR(db_result, "failed to perform database exec: %s", sql);
832  g_free(sql);
833 }
834 
841 void database_update_event(scheduler_t* scheduler, void* unused)
842 {
843  /* locals */
844  PGresult* db_result;
845  PGresult* pri_result;
846  int i, j_id;
847  char sql[512];
848  char* value, * type, * host, * pfile, * parent, *jq_cmd_args;
849  job_t* job;
850 
851  if(closing)
852  {
853  WARNING("scheduler is closing, will not check the job queue");
854  return;
855  }
856 
857  /* make the database query */
858  db_result = database_exec(scheduler, basic_checkout);
859  if(PQresultStatus(db_result) != PGRES_TUPLES_OK)
860  {
861  PQ_ERROR(db_result, "database update failed on call to PQexec");
862  return;
863  }
864 
865  V_SPECIAL("DB: retrieved %d entries from the job queue\n",
866  PQntuples(db_result));
867  for(i = 0; i < PQntuples(db_result); i++)
868  {
869  /* start by checking that the job hasn't already been grabbed */
870  j_id = atoi(PQget(db_result, i, "jq_pk"));
871  if(g_tree_lookup(scheduler->job_list, &j_id) != NULL)
872  continue;
873 
874  /* get relevant values out of the job queue */
875  parent = PQget(db_result, i, "jq_job_fk");
876  host = PQget(db_result, i, "jq_host");
877  type = PQget(db_result, i, "jq_type");
878  pfile = PQget(db_result, i, "jq_runonpfile");
879  value = PQget(db_result, i, "jq_args");
880  jq_cmd_args =PQget(db_result, i, "jq_cmd_args");
881 
882  if(host != NULL)
883  host = (strlen(host) == 0) ? NULL : host;
884  if(jq_cmd_args != NULL)
885  jq_cmd_args = (strlen(jq_cmd_args) == 0) ? NULL : jq_cmd_args;
886 
887  V_DATABASE("DB: jq_pk[%d] added:\n jq_type = %s\n jq_host = %s\n "
888  "jq_runonpfile = %d\n jq_args = %s\n jq_cmd_args = %s\n",
889  j_id, type, host, (pfile != NULL && pfile[0] != '\0'), value, jq_cmd_args);
890 
891  /* check if this is a command */
892  if(strcmp(type, "command") == 0)
893  {
894  WARNING("DB: commands in the job queue not implemented,"
895  " using the interface api instead");
896  continue;
897  }
898 
899  sprintf(sql, jobsql_information, parent);
900  pri_result = database_exec(scheduler, sql);
901  if(PQresultStatus(pri_result) != PGRES_TUPLES_OK)
902  {
903  PQ_ERROR(pri_result, "database update failed on call to PQexec");
904  continue;
905  }
906  if(PQntuples(pri_result)==0)
907  {
908  WARNING("can not find the user information of job_pk %s\n", parent);
909  SafePQclear(pri_result);
910  continue;
911  }
912  job = job_init(scheduler->job_list, scheduler->job_queue, type, host, j_id,
913  atoi(parent),
914  atoi(PQget(pri_result, 0, "user_pk")),
915  atoi(PQget(pri_result, 0, "group_pk")),
916  atoi(PQget(pri_result, 0, "job_priority")), jq_cmd_args);
917  job_set_data(scheduler, job, value, (pfile && pfile[0] != '\0'));
918 
919  SafePQclear(pri_result);
920  }
921 
922  SafePQclear(db_result);
923 }
924 
932 {
933  PGresult* db_result = database_exec(scheduler, jobsql_resetqueue);
934  if(PQresultStatus(db_result) != PGRES_COMMAND_OK)
935  PQ_ERROR(db_result, "failed to reset job queue");
936 }
937 
945 void database_update_job(scheduler_t* scheduler, job_t* job, job_status status)
946 {
947  /* locals */
948  gchar* sql = NULL;
949  PGresult* db_result;
950  int j_id = job->id;
951  char* message = (job->message == NULL) ? "Failed": job->message;
952 
953  /* check how to update database */
954  switch(status)
955  {
956  case JB_NOT_AVAILABLE: case JB_CHECKEDOUT:
957  break;
958  case JB_STARTED:
959  sql = g_strdup_printf(jobsql_started, "localhost", getpid(), j_id);
960  break;
961  case JB_COMPLETE:
962  sql = g_strdup_printf(jobsql_complete, j_id);
963  break;
964  case JB_RESTART:
965  sql = g_strdup_printf(jobsql_restart, j_id);
966  break;
967  case JB_FAILED:
968  sql = g_strdup_printf(jobsql_failed, message, j_id);
969  break;
970  case JB_PAUSED:
971  sql = g_strdup_printf(jobsql_paused, j_id);
972  break;
973  }
974 
975  /* update the database job queue */
976  db_result = database_exec(scheduler, sql);
977  if(sql != NULL && PQresultStatus(db_result) != PGRES_COMMAND_OK)
978  PQ_ERROR(db_result, "failed to update job status in job queue");
979 
980  if(status == JB_COMPLETE || status == JB_FAILED)
981  email_notification(scheduler, job);
982 
983  g_free(sql);
984 }
985 
992 void database_job_processed(int j_id, int num)
993 {
994  gchar* sql = NULL;
995 
996  sql = g_strdup_printf(jobsql_processed, num, j_id);
997  event_signal(database_exec_event, sql);
998 }
999 
1006 void database_job_log(int j_id, char* log_name)
1007 {
1008  gchar* sql = NULL;
1009 
1010  sql = g_strdup_printf(jobsql_log, log_name, j_id);
1011  event_signal(database_exec_event, sql);
1012 }
1013 
1021 void database_job_priority(scheduler_t* scheduler, job_t* job, int priority)
1022 {
1023  gchar* sql = NULL;
1024  PGresult* db_result;
1025 
1026  sql = g_strdup_printf(jobsql_priority, priority, job->id);
1027  db_result = database_exec(scheduler, sql);
1028  if(sql != NULL && PQresultStatus(db_result) != PGRES_COMMAND_OK)
1029  PQ_ERROR(db_result, "failed to change job queue entry priority");
1030 
1031  g_free(sql);
1032 }
1033 
1040 {
1041  char cmd[] = "dpkg -s s-nail | grep -i 'Version' | awk '{print$2}' | cut -c -4";
1042  char version_str[128];
1043  char buf[128];
1044  float version_float;
1045  FILE *fp;
1046 
1047  fp = popen(cmd, "r");
1048  if(!fp)
1049  {
1050  WARNING("Unable to run the command '%s'.\n", cmd);
1051  return 0;
1052  }
1053  while(fgets(buf, sizeof(buf), fp) != NULL)
1054  {
1055  strcpy(version_str,buf);
1056  }
1057  pclose(fp);
1058  sscanf(version_str, "%f", &version_float);
1059 
1060  if(version_float - 14.8 > 0.0001)
1061  {
1062  return 1;
1063  }
1064  return 0;
1065 }
1066 
1073 char* get_email_command(scheduler_t* scheduler, char* user_email)
1074 {
1075  PGresult* db_result_smtp;
1076  int i;
1077  GString* client_cmd;
1078  GHashTable* smtpvariables;
1079  char* temp_smtpvariable;
1080  char* final_command;
1081 
1082  db_result_smtp = database_exec(scheduler, smtp_values);
1083  if(PQresultStatus(db_result_smtp) != PGRES_TUPLES_OK || PQntuples(db_result_smtp) == 0)
1084  {
1085  PQ_ERROR(db_result_smtp, "unable to get conf variables for SMTP from sysconfig");
1086  return NULL;
1087  }
1088  client_cmd = g_string_new("");
1089  smtpvariables = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free);
1090  for(i = 0; i < PQntuples(db_result_smtp); i++)
1091  {
1092  if(PQget(db_result_smtp, i, "conf_value")[0]) //Not empty
1093  {
1094  g_hash_table_insert(smtpvariables, g_strdup(PQget(db_result_smtp, i, "variablename")),
1095  g_strdup(PQget(db_result_smtp, i, "conf_value")));
1096  }
1097  }
1098  SafePQclear(db_result_smtp);
1099  if(g_hash_table_contains(smtpvariables, "SMTPHostName") && g_hash_table_contains(smtpvariables, "SMTPPort"))
1100  {
1101  if(g_hash_table_contains(smtpvariables, "SMTPStartTls"))
1102  {
1103  temp_smtpvariable = (char *)g_hash_table_lookup(smtpvariables, "SMTPStartTls");
1104  if(g_strcmp0(temp_smtpvariable, "1") == 0)
1105  {
1106  g_string_append_printf(client_cmd, " -S smtp-use-starttls");
1107  }
1108  }
1109  if(g_hash_table_contains(smtpvariables, "SMTPAuth"))
1110  {
1111  temp_smtpvariable = (char *)g_hash_table_lookup(smtpvariables, "SMTPAuth");
1112  if(g_strcmp0(temp_smtpvariable, "L") == 0)
1113  {
1114  g_string_append_printf(client_cmd, " -S smtp-auth=login");
1115  }
1116  else if(g_strcmp0(temp_smtpvariable, "P") == 0)
1117  {
1118  g_string_append_printf(client_cmd, " -S smtp-auth=plain");
1119  }
1120  else if(g_strcmp0(temp_smtpvariable, "N") == 0)
1121  {
1122  g_string_append_printf(client_cmd, " -S smtp-auth=none");
1123  }
1124  }
1125  if(g_hash_table_contains(smtpvariables, "SMTPFrom"))
1126  {
1127  g_string_append_printf(client_cmd, " -S from=\"%s\"",
1128  (char *)g_hash_table_lookup(smtpvariables, "SMTPFrom"));
1129  }
1130  if(g_hash_table_contains(smtpvariables, "SMTPSslVerify"))
1131  {
1132  temp_smtpvariable = (char *)g_hash_table_lookup(smtpvariables, "SMTPSslVerify");
1133  g_string_append(client_cmd, " -S ssl-verify=");
1134  if(g_strcmp0(temp_smtpvariable, "I") == 0)
1135  {
1136  g_string_append(client_cmd, "ignore");
1137  }
1138  else if(g_strcmp0(temp_smtpvariable, "S") == 0)
1139  {
1140  g_string_append(client_cmd, "strict");
1141  }
1142  else if(g_strcmp0(temp_smtpvariable, "W") == 0)
1143  {
1144  g_string_append(client_cmd, "warn");
1145  }
1146  }
1147  g_string_append_printf(client_cmd, " -S v15-compat");
1148  if(!check_mta_support())
1149  {
1150  g_string_append_printf(client_cmd, " -S smtp=\"");
1151  }
1152  else
1153  {
1154  g_string_append_printf(client_cmd, " -S mta=\"");
1155  }
1156  /* use smtps only if port is not 25 or SMTPStartTls is provided */
1157  if((g_strcmp0((char *)g_hash_table_lookup(smtpvariables, "SMTPPort"), "25") != 0)
1158  || g_strcmp0((char *)g_hash_table_lookup(smtpvariables, "SMTPStartTls"), "1") == 0)
1159  {
1160  g_string_append_printf(client_cmd, "smtps://");
1161  }
1162  else
1163  {
1164  g_string_append_printf(client_cmd, "smtp://");
1165  }
1166  if(g_hash_table_contains(smtpvariables, "SMTPAuthUser"))
1167  {
1168  temp_smtpvariable = g_hash_table_lookup(smtpvariables, "SMTPAuthUser");
1169  g_string_append_uri_escaped(client_cmd, temp_smtpvariable, NULL, TRUE);
1170  if(g_hash_table_lookup(smtpvariables, "SMTPAuthPasswd"))
1171  {
1172  g_string_append_printf(client_cmd, ":");
1173  temp_smtpvariable = g_hash_table_lookup(smtpvariables, "SMTPAuthPasswd");
1174  g_string_append_uri_escaped(client_cmd, temp_smtpvariable, NULL, TRUE);
1175  }
1176  g_string_append_printf(client_cmd, "@");
1177  g_string_append_printf(client_cmd, "%s:%s\"", (char *)g_hash_table_lookup(smtpvariables, "SMTPHostName"),
1178  (char *)g_hash_table_lookup(smtpvariables, "SMTPPort"));
1179  }
1180  temp_smtpvariable = NULL;
1181  final_command = g_strdup_printf(EMAIL_BUILD_CMD, scheduler->email_command,
1182  client_cmd->str, scheduler->email_subject, user_email);
1183  }
1184  else
1185  {
1186  NOTIFY("Unable to send email. SMTP host or port not found in the configuration.\n"
1187  "Please check Configuration Variables.");
1188  final_command = NULL;
1189  }
1190  g_hash_table_destroy(smtpvariables);
1191  g_string_free(client_cmd, TRUE);
1192  return final_command;
1193 }
int is_meta_special(meta_agent_t *ma, int special_type)
tests if a particular meta agent has a specific special flag set
Definition: agent.c:1341
Header file with agent related operations.
#define SAG_NOEMAIL
This agent should not send notification emails.
Definition: agent.h:35
const gchar * email_format_text(GPtrArray *rows, gchar *fossy_url)
Format rows as plain text.
char * fo_config_get(fo_conf *conf, const char *group, const char *key, GError **error)
Gets an element based on its group name and key name. If the group or key is not found,...
Definition: fossconfig.c:336
@ fo_missing_key
Required key is missing.
Definition: fossconfig.h:24
@ fo_missing_group
Required group is missing.
Definition: fossconfig.h:23
job_t * job_init(GTree *job_list, GSequence *job_queue, char *type, char *host, int id, int parent_id, int user_id, int group_id, int priority, char *jq_cmd_args)
Create a new job.
Definition: job.c:164
void job_set_data(scheduler_t *scheduler, job_t *job, char *data, int sql)
Definition: job.c:511
PGconn * fo_dbconnect(char *DBConfFile, char **ErrorBuf)
Connect to a database. The default is Db.conf.
Definition: libfossdb.c:29
Log related operations.
#define ERROR(...)
Definition: logging.h:79
#define FATAL(...)
Definition: logging.h:63
#define PQ_ERROR(pg_r,...)
Definition: logging.h:85
PGresult * database_exec(scheduler_t *scheduler, const char *sql)
Executes an sql statement for the scheduler.
Definition: database.c:803
#define DEFAULT_HEADER
Default email header.
Definition: database.c:43
void database_reset_queue(scheduler_t *scheduler)
Resets any jobs in the job queue that are not completed.
Definition: database.c:931
void database_init(scheduler_t *scheduler)
Definition: database.c:770
void database_job_priority(scheduler_t *scheduler, job_t *job, int priority)
Changes the priority of a job queue entry in the database.
Definition: database.c:1021
#define DEFAULT_COMMAND
Default email command to use.
Definition: database.c:46
void database_update_job(scheduler_t *scheduler, job_t *job, job_status status)
Change the status of a job in the database.
Definition: database.c:945
void database_update_event(scheduler_t *scheduler, void *unused)
Checks the job queue for any new entries.
Definition: database.c:841
static void check_tables(scheduler_t *scheduler)
Checks that any part of the database used by the scheduler is correct.
Definition: database.c:655
static gboolean email_replace(const GMatchInfo *match, GString *ret, email_replace_args *args)
Replaces the variables that are in the header and footer files.
Definition: database.c:84
int check_mta_support()
Find s-nail version to check if mta is supported.
Definition: database.c:1039
#define EMAIL_BUILD_CMD
Email command format.
Definition: database.c:42
void database_job_log(int j_id, char *log_name)
Enters the name of the log file for a job into the database.
Definition: database.c:1006
void database_exec_event(scheduler_t *scheduler, char *sql)
Definition: database.c:827
static void email_notification(scheduler_t *scheduler, job_t *job)
Definition: database.c:429
void email_init(scheduler_t *scheduler)
Loads information about the email that will be sent for job notifications.
Definition: database.c:557
static gint email_checkjobstatus(scheduler_t *scheduler, job_t *job)
Checks the database for the status of the job.
Definition: database.c:365
void database_job_processed(int j_id, int num)
Updates the number of items that a job queue entry has processed.
Definition: database.c:992
#define DEFAULT_SUBJECT
Default email subject.
Definition: database.c:45
#define DEFAULT_FOOTER
Default email footer.
Definition: database.c:44
char * get_email_command(scheduler_t *scheduler, char *user_email)
Build command to run to send email.
Definition: database.c:1073
static PGconn * database_connect(gchar *configdir)
Creates and performs error checking for a new database connection.
Definition: database.c:749
int closing
Set if scheduler is shutting down.
Definition: scheduler.c:58
#define SafePQclear(pgres)
Definition: scheduler.h:129
const char * upload_pk
Definition: sqlstatements.h:82
const char * jobsql_started
const char * check_scheduler_tables
Definition: sqlstatements.h:22
const char * jobsql_paused
const char * jobsql_failed
const char * upload_common
Definition: sqlstatements.h:46
const char * jobsql_information
const char * jobsql_jobinfo
const char * parent_folder_name
Definition: sqlstatements.h:65
const char * jobsql_processed
const char * jobsql_log
const char * jobsql_resetqueue
const char * basic_checkout
const char * upload_name
Definition: sqlstatements.h:73
const char * smtp_values
const char * url_checkout
Definition: sqlstatements.h:31
const char * jobsql_restart
const char * jobsql_anyrunnable
const char * jobsql_complete
const char * folder_name
Definition: sqlstatements.h:54
const char * jobsql_priority
const char * jobsql_email
Definition: sqlstatements.h:92
const char * jobsql_jobendbits
const char * jobsql_email_job
const char * select_upload_fk
Definition: sqlstatements.h:38
gboolean status
Agent status (Pass => true, fail => false)
guint id
Job queue id for the agent.
GString * agent
Agent name.
scheduler_t * scheduler
Current scheduler reference.
Definition: database.c:55
job_t * job
Current job structure.
Definition: database.c:57
gchar * foss_url
Fossology URL string.
Definition: database.c:56
The job structure.
Definition: job.h:51
int32_t id
The identifier for this job.
Definition: job.h:73
job_status status
The current status for the job.
Definition: job.h:61
gchar * message
Message that will be sent with job notification email.
Definition: job.h:69
char * agent_type
The type of agent used to analyze the data.
Definition: job.h:53
Store the results of a regex match.
Definition: scanners.hpp:28
Data type used to check if the database is correct.
Definition: database.c:640
char * columns[13]
The columns that the scheduler uses for this table.
Definition: database.c:643
char * table
The name of the table to check columns in.
Definition: database.c:641
uint8_t ncols
The number of columns in the table that the scheduler uses.
Definition: database.c:642
GTree * job_list
List of jobs that have been created.
Definition: scheduler.h:172
gchar * email_header
The beginning of the email message.
Definition: scheduler.h:179
gchar * email_command
The command that will sends emails, usually mailx.
Definition: scheduler.h:181
gchar * sysconfigdir
The system directory that contain fossology.conf.
Definition: scheduler.h:150
PGconn * db_conn
The database connection.
Definition: scheduler.h:176
GTree * meta_agents
List of all meta agents available to the scheduler.
Definition: scheduler.h:156
gboolean default_footer
Is the footer the default footer.
Definition: scheduler.h:183
gchar * email_subject
The subject to be used for emails.
Definition: scheduler.h:178
GRegex * parse_db_email
Parses database email text.
Definition: scheduler.h:187
gchar * email_footer
The end of the email message.
Definition: scheduler.h:180
GSequence * job_queue
heap of jobs that still need to be started
Definition: scheduler.h:173
gboolean default_header
Is the header the default header.
Definition: scheduler.h:182
gchar * host_url
The url that is used to get to the FOSSology instance.
Definition: scheduler.h:177
fo_conf * sysconfig
Configuration information loaded from the configuration file.
Definition: scheduler.h:149