1 /*
2 Copyright (c) 1996,1997,1998,1999,2000,2001,2004,2006,2007,2008,2009,
3               2010,2011,2012
4 Whitehead Institute for Biomedical Research, Steve Rozen
5 (http://purl.com/STEVEROZEN/), Andreas Untergasser and Helen Skaletsky
6 All rights reserved.
7 
8     This file is part of primer3 and the libprimer3 library.
9 
10     Primer3 and the libprimer3 library are free software;
11     you can redistribute them and/or modify them under the terms
12     of the GNU General Public License as published by the Free
13     Software Foundation; either version 2 of the License, or (at
14     your option) any later version.
15 
16     This software is distributed in the hope that it will be useful,
17     but WITHOUT ANY WARRANTY; without even the implied warranty of
18     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
19     GNU General Public License for more details.
20 
21     You should have received a copy of the GNU General Public License
22     along with this software (file gpl-2.0.txt in the source
23     distribution); if not, write to the Free Software
24     Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
25 
26 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
27 "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
28 LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
29 A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
30 OWNERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
31 SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
32 LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
33 DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
34 THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
35 (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
36 OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
37 */
38 
39 #include <signal.h>
40 #include <ctype.h>
41 #include <string.h> /* strcmp() */
42 #include <stdlib.h> /* free() */
43 #include <getopt.h> /* getopt() */
44 #include <unistd.h>
45 #include <sys/stat.h>
46 #include <sys/types.h>
47 #include "thal.h"
48 #include "format_output.h"
49 #include "libprimer3.h"
50 #include "read_boulder.h"
51 #include "print_boulder.h"
52 
53 /* Check on which OS we compile */
54 #if defined(_WIN32) || defined(WIN32) || defined (__WIN32__) || defined(__CYGWIN__) || defined(__MINGW32__)
55 #define OS_WIN
56 #endif
57 
58 /* Some function prototypes */
59 static void   print_usage();
60 static void   sig_handler(int);
61 static void   read_thermodynamic_parameters();
62 
63 /* Other global variables. */
64 static const char *pr_release;
65 static const char *pr_program_name;
66 
67 int
main(int argc,char * argv[])68 main(int argc, char *argv[])
69 {
70   /* Setup the input data structures handlers */
71   int format_output = 0;
72   int strict_tags = 0;
73   int echo_settings = 0;
74   int io_version = 4;
75   int default_version = 2;
76   int dump_args = 0;
77 
78   p3_global_settings *global_pa;
79   seq_args *sarg;
80   read_boulder_record_results read_boulder_record_res = {0,0};
81 
82   pr_append_str p3_settings_path;
83   pr_append_str output_path;
84   pr_append_str error_path;
85   pr_append_str fatal_parse_err;
86   pr_append_str nonfatal_parse_err;
87   pr_append_str warnings;
88 
89   /* Some variables needed by getopt */
90   int opt, option_index = 0;
91   struct option long_options[] = {
92     {"about", no_argument, 0, 'a'},
93     {"format_output", no_argument, &format_output, 1},
94     {"strict_tags", no_argument, &strict_tags, 1},
95     {"p3_settings_file", required_argument, 0, 'p'},
96     {"echo_settings_file", no_argument, &echo_settings, 1},
97     {"io_version", required_argument, 0, 'i'},
98     {"default_version", required_argument, 0, 'd'},
99     {"Dump_args", no_argument, 0, 'D'},
100     {"2x_compat", no_argument, 0, '2'},
101     {"output", required_argument, 0, 'o'},
102     {"error", required_argument, 0, 'e'},
103     {0, 0, 0, 0}
104   };
105   int about = 0, compat = 0, invalid_flag = 0;
106 
107   /* Retval will point to the return value from choose_primers(). */
108   p3retval *retval = NULL;
109   int input_found=0;
110 
111   init_pr_append_str(&fatal_parse_err);
112   init_pr_append_str(&nonfatal_parse_err);
113   init_pr_append_str(&warnings);
114   init_pr_append_str(&p3_settings_path);
115   init_pr_append_str(&output_path);
116   init_pr_append_str(&error_path);
117 
118   /* Get the program name for correct error messages */
119   pr_release = libprimer3_release();
120   pr_program_name = argv[0];
121   p3_set_program_name(pr_program_name);
122 
123   /* We set up some signal handlers in case someone starts up the program
124    * from the command line, wonders why nothing is happening, and then kills
125    * the program. */
126   signal(SIGINT, sig_handler);
127   signal(SIGTERM, sig_handler);
128 
129   /* Read in the flags provided with the program call */
130   opterr = 0;
131   while ((opt = getopt_long_only(argc, argv, "", long_options, &option_index)) != -1) {
132     switch (opt) {
133     case 'a':
134       about = 1;
135       break;
136     case 'p':
137       if (pr_append_external(&p3_settings_path, optarg)) {
138 	exit(-2); /* Out of memory. */
139       }
140       break;
141     case 'i':
142       if (!strcmp(optarg, "4"))
143         io_version = 4;
144       else
145         io_version = -1;
146       break;
147 
148     case 'd': /* default_version */
149       if (!strcmp(optarg, "1"))
150         default_version = 1;
151       else if (!strcmp(optarg, "2"))
152         default_version = 2;
153       else
154         default_version = -1;
155       break;
156 
157     case 'D':  /* Undocumented flag for testing; causes
158 		  values of arguments to be echoed to
159 		  stdout. */
160       dump_args = 1;
161       break;
162     case '2':
163       compat = 1;
164       break;
165     case 'o':
166       if (pr_append_external(&output_path, optarg)) {
167 	exit(-2); /* Out of memory. */
168       }
169 
170       break;
171     case 'e':
172       if (pr_append_external(&error_path, optarg)) {
173 	exit(-2); /* Out of memory. */
174       }
175 
176       break;
177     case '?':
178       invalid_flag = 1;
179       break;
180     }
181   }
182   /* Open the output and error files specified */
183 
184   if (!pr_is_empty(&error_path)) {
185     /* reassign stderr */
186     if (freopen(pr_append_str_chars(&error_path), "w", stderr)
187 	== NULL) {
188       fprintf(stderr, "Error creating file %s\n",
189 	      pr_append_str_chars(&error_path));
190       exit(-1);
191     }
192     destroy_pr_append_str_data(&error_path);
193   }
194 
195   if (!pr_is_empty(&output_path)) {
196     /* reassign stdout */
197     if (freopen(pr_append_str_chars(&output_path), "w", stdout)
198 	== NULL) {
199       fprintf(stderr, "Error creating file %s\n",
200 	      pr_append_str_chars(&output_path));
201       exit(-1);
202     }
203     destroy_pr_append_str_data(&output_path);
204   }
205   /* We do any printing after redirecting stdout and stderr */
206   if (about == 1) {
207     printf("%s\n", pr_release);
208     exit(0);
209   }
210   if ((io_version == -1) || (invalid_flag == 1) || (default_version == -1)) {
211     print_usage();
212     exit(-1);
213   }
214   if (compat == 1) {
215     printf("PRIMER_ERROR=flag -2x_compat is no longer supported\n=\n");
216     exit(-1);
217   }
218 
219   /* Check if an input file has been specified on the command line. */
220   if (optind < argc) {
221     if (optind + 1 != argc) {
222       print_usage();
223       exit(-1);
224     }
225     if (freopen(argv[optind], "r", stdin) == NULL) {
226       fprintf(stderr, "Error opening file %s\n", argv[optind]);
227       exit(-1);
228     }
229   }
230 
231   /* Allocate the space for global settings and fill in default parameters */
232   if (default_version == 1)
233     global_pa = p3_create_global_settings_default_version_1();
234   else if (default_version == 2)
235     global_pa = p3_create_global_settings();
236   else {
237     print_usage();
238     exit(-1);
239   }
240 
241   if (!global_pa) {
242     exit(-2); /* Out of memory. */
243   }
244   global_pa->dump = dump_args ;
245 
246   /* Settings files have to be read in just below, and
247      the functions need a temporary sarg */
248   if (!(sarg = create_seq_arg())) {
249     exit(-2);
250   }
251 
252   /* Read data from the settings file until a "=" line occurs.  Assign
253      parameter values for primer picking to pa and sa. */
254   if (!pr_is_empty(&p3_settings_path)) {
255     read_p3_file(pr_append_str_chars(&p3_settings_path),
256 		 settings,
257 		 echo_settings && !format_output,
258 		 strict_tags,
259 		 global_pa, sarg, &fatal_parse_err,
260 		 &nonfatal_parse_err, &warnings, &read_boulder_record_res);
261     destroy_pr_append_str_data(&p3_settings_path);
262     /* Check if any thermodynamical alignment flag was given */
263     if ((global_pa->thermodynamic_oligo_alignment == 1) ||
264 	(global_pa->thermodynamic_template_alignment == 1))
265       read_thermodynamic_parameters();
266   }
267 
268   /* We also need to print out errors here because the loop erases all
269      errors at start. If there are fatal errors, write the proper
270      message and exit */
271   if (fatal_parse_err.data != NULL) {
272     if (format_output) {
273         format_error(stdout, sarg->sequence_name, fatal_parse_err.data);
274     } else {
275         print_boulder_error(fatal_parse_err.data);
276     }
277     fprintf(stderr, "%s: %s\n",
278               pr_program_name, fatal_parse_err.data);
279     destroy_seq_args(sarg);
280     exit(-4);
281   }
282 
283   /* If there are nonfatal errors, write the proper message
284    * and skip to the end of the loop */
285   if (!pr_is_empty(&nonfatal_parse_err)) {
286     if (format_output) {
287         format_error(stdout, sarg->sequence_name,
288                      nonfatal_parse_err.data);
289     } else {
290         print_boulder_error(nonfatal_parse_err.data);
291     }
292   }
293 
294   /* The temporary sarg is not needed any more */
295   destroy_seq_args(sarg);
296   sarg = NULL;
297 
298   /* Read the data from input stream record by record and process it if
299    * there are no errors. This is where the work is done. */
300   while (1) {
301     /* Create and initialize a seq_args data structure. sa (seq_args *) is
302      * initialized here because Values are _not_ retained across different
303      * input records. */
304     if (!(sarg = create_seq_arg())) {
305       exit(-2);
306     }
307 
308     /* Reset all errors handlers and the return structure */
309     pr_set_empty(&fatal_parse_err);
310     pr_set_empty(&nonfatal_parse_err);
311     pr_set_empty(&warnings);
312     retval = NULL;
313 
314     /* See read_boulder.h for documentation on read_boulder_record().*/
315     if (!read_boulder_record(stdin,
316 			     &strict_tags,
317 			     &io_version,
318 			     !format_output,
319 			     all_parameters,
320 			     global_pa,
321 			     sarg,
322 			     &fatal_parse_err,
323 			     &nonfatal_parse_err,
324 			     &warnings,
325 			     &read_boulder_record_res)) {
326       break; /* There were no more boulder records */
327     }
328 
329     /* Check if any thermodynamical alignment flag was given and the
330        path to the parameter files changed - we need to reread them */
331     if (((global_pa->thermodynamic_oligo_alignment == 1) ||
332 	 (global_pa->thermodynamic_template_alignment == 1))
333 	&& (thermodynamic_path_changed == 1))
334       read_thermodynamic_parameters();
335 
336     /* Check that we found the thermodynamic parameters in case any thermodynamic flag was set to 1. */
337     if (((global_pa->thermodynamic_oligo_alignment == 1) ||
338 	 (global_pa->thermodynamic_template_alignment == 1))
339 	&& (thermodynamic_params_path == NULL)) {
340       /* no parameter directory found, error */
341       printf("PRIMER_ERROR=thermodynamic approach chosen, but path to thermodynamic parameters not specified\n=\n");
342       exit(-1);
343     }
344 
345     input_found = 1;
346     if ((global_pa->primer_task == generic)
347 	&& (global_pa->pick_internal_oligo == 1)){
348       PR_ASSERT(global_pa->pick_internal_oligo);
349     }
350 
351     /* If there are fatal errors, write the proper message and exit */
352     if (fatal_parse_err.data != NULL) {
353       if (format_output) {
354         format_error(stdout, sarg->sequence_name, fatal_parse_err.data);
355       } else {
356         print_boulder_error(fatal_parse_err.data);
357       }
358       fprintf(stderr, "%s: %s\n",
359               pr_program_name, fatal_parse_err.data);
360       destroy_p3retval(retval);
361       destroy_seq_args(sarg);
362       exit(-4);
363     }
364 
365     /* If there are nonfatal errors, write the proper message
366      * and skip to the end of the loop */
367     if (!pr_is_empty(&nonfatal_parse_err)) {
368       if (format_output) {
369         format_error(stdout, sarg->sequence_name,
370                      nonfatal_parse_err.data);
371       } else {
372         print_boulder_error(nonfatal_parse_err.data);
373       }
374       goto loop_wrap_up;
375     }
376 
377     /* Print any warnings and continue processing */
378     if (!pr_is_empty(&warnings)) {
379       if (format_output) {
380         format_warning(stdout, sarg->sequence_name,
381 		       warnings.data);
382       } else {
383         print_boulder_warning(warnings.data);
384       }
385     }
386 
387     if (read_boulder_record_res.file_flag && sarg->sequence_name == NULL) {
388       /* We will not have a base name for the files */
389       if (format_output) {
390         format_error(stdout, NULL,
391                      "Need PRIMER_SEQUENCE_ID if PRIMER_FILE_FLAG is not 0");
392       } else {
393         print_boulder_error("Need PRIMER_SEQUENCE_ID if PRIMER_FILE_FLAG is not 0");
394       }
395       goto loop_wrap_up;
396     }
397 
398     /* Pick the primers - the central function */
399     p3_set_gs_primer_file_flag(global_pa,
400                                read_boulder_record_res.file_flag);
401     retval = choose_primers(global_pa, sarg);
402     if (NULL == retval) exit(-2); /* Out of memory. */
403 
404     /* If it was necessary to use a left_input, right_input,
405        or internal_oligo_input primer that was
406        unacceptable, then add warnings. */
407     if (global_pa->pick_anyway && format_output) {
408       if (sarg->left_input) {
409         add_must_use_warnings(&retval->warnings,
410                               "Left primer", &retval->fwd.expl);
411       }
412       if (sarg->right_input) {
413         add_must_use_warnings(&retval->warnings,
414                               "Right primer", &retval->rev.expl);
415       }
416       if (sarg->internal_input) {
417         add_must_use_warnings(&retval->warnings,
418                               "Hybridization probe", &retval->intl.expl);
419       }
420     }
421 
422     if (pr_is_empty(&retval->glob_err)
423         && pr_is_empty(&retval->per_sequence_err)) {
424       /* We need to test for errors before we call
425          p3_print_oligo_lists. This function only works on retval as
426          returned when there were no errors. */
427       if (read_boulder_record_res.file_flag) {
428         /* Create files with left, right, and internal oligos. */
429         p3_print_oligo_lists(retval, sarg, global_pa,
430                              &retval->per_sequence_err,
431                              sarg->sequence_name);
432       }
433     }
434 
435     if (format_output) {
436       print_format_output(stdout, &io_version, global_pa,
437                           sarg, retval, pr_release,
438                           read_boulder_record_res.explain_flag);
439     } else {
440       /* Use boulder output */
441       print_boulder(io_version, global_pa, sarg, retval,
442                     read_boulder_record_res.explain_flag);
443     }
444 
445   loop_wrap_up: /* Here the failed loops join in again */
446     if (NULL != retval) {
447       /* Check for errors and print them */
448       if (NULL != retval->glob_err.data) {
449         fprintf(stderr, "%s: %s\n", pr_program_name, retval->glob_err.data);
450         destroy_p3retval(retval);
451         destroy_seq_args(sarg);
452         exit(-4);
453       }
454     }
455     destroy_p3retval(retval); /* This works even if retval is NULL */
456     retval = NULL;
457     destroy_seq_args(sarg);
458     sarg = NULL;
459 
460   }   /* while (1) (processing boulder io records) ...
461          End of the primary working loop */
462 
463   /* To avoid being distracted when looking for leaks: */
464   if ((global_pa->thermodynamic_oligo_alignment == 1) ||
465       (global_pa->thermodynamic_template_alignment == 1))
466     destroy_thal_structures();
467   p3_destroy_global_settings(global_pa);
468   global_pa = NULL;
469   destroy_seq_args(sarg);
470   destroy_pr_append_str_data(&nonfatal_parse_err);
471   destroy_pr_append_str_data(&fatal_parse_err);
472   destroy_pr_append_str_data(&warnings);
473   destroy_dpal_thal_arg_holder();
474   free(thermodynamic_params_path);
475   /* If it could not read input, then complain and die */
476   if (0 == input_found) {
477     print_usage();
478     exit(-3);
479   }
480   return 0;
481 }
482 
483 /* Reads the thermodynamic parameters if the thermodynamic alignment
484    tag was set to 1 */
485 static void
read_thermodynamic_parameters()486 read_thermodynamic_parameters()
487 {
488   thal_results o;
489   /* if the path to the parameter files did not change,
490      we do not want to read again */
491   if (thermodynamic_path_changed == 0) return;
492   /* check that the path to the parameters folder was given */
493   if (thermodynamic_params_path == NULL) {
494 
495 #ifdef OS_WIN
496     /* in windows check for .\\primer3_config */
497     struct stat st;
498     if ((stat(".\\primer3_config", &st) == 0) && S_ISDIR(st.st_mode)) {
499       thermodynamic_params_path =
500 	(char*) malloc(strlen(".\\primer3_config\\") * sizeof(char) + 1);
501       if (NULL == thermodynamic_params_path) exit (-2); /* Out of memory */
502       strcpy(thermodynamic_params_path, ".\\primer3_config\\");
503     } else {
504       /* no default directory found */
505       return;
506     }
507 #else
508     /* in linux, check for ./primer3_config and /opt/primer3_config */
509     struct stat st;
510     if ((stat("./primer3_config", &st) == 0) && S_ISDIR(st.st_mode)) {
511       thermodynamic_params_path =
512 	(char*) malloc(strlen("./primer3_config/") * sizeof(char) + 1);
513       if (NULL == thermodynamic_params_path) exit (-2); /* Out of memory */
514       strcpy(thermodynamic_params_path, "./primer3_config/");
515     } else if ((stat("/opt/primer3_config", &st) == 0)  && S_ISDIR(st.st_mode)) {
516       thermodynamic_params_path =
517 	(char*) malloc(strlen("/opt/primer3_config/") * sizeof(char) + 1);
518       if (NULL == thermodynamic_params_path) exit (-2); /* Out of memory */
519       strcpy(thermodynamic_params_path, "/opt/primer3_config/");
520     } else {
521       /* no default directory found */
522       return;
523     }
524 #endif
525 
526   }
527   /* read in the thermodynamic parameters */
528   if (get_thermodynamic_values(thermodynamic_params_path, &o)) {
529     fprintf(stderr, "%s\n", o.msg);
530     exit(-1);
531   }
532   /* mark that the last given path was used for reading the parameters */
533   thermodynamic_path_changed = 0;
534 }
535 
536 /* Print out copyright and a short usage message*/
537 static void
print_usage()538 print_usage()
539 {
540   fprintf(stderr, "%s", primer3_copyright());
541 
542   fprintf(stderr, "\n\nUSAGE: %s %s %s %s %s %s %s %s %s %s\n", pr_program_name,
543           "[-format_output]",
544 	  "[-default_version=1|-default_version=2]",
545 	  "[-io_version=4]",
546 	  "[-p3_settings_file=<file_path>]",
547 	  "[-echo_settings_file]",
548 	  "[-strict_tags]",
549 	  "[-output=<file_path>]",
550 	  "[-error=<file_path>]",
551 	  "[input_file]");
552   fprintf(stderr, "This is primer3 (%s)\n", pr_release);
553   fprintf(stderr, "Input can also be provided on standard input.\n");
554   fprintf(stderr, "For example:\n");
555   fprintf(stderr, "$ primer3_core < my_input_file\n");
556 }
557 
558 /* Print out copyright, a short usage message and the signal */
559 static void
sig_handler(int signal)560 sig_handler(int signal)
561 {
562     print_usage();
563     fprintf(stderr, "%s: received signal %d\n", pr_program_name, signal);
564     exit(signal);
565 }
566