1 /*********************************************************************
2 Fits - View and manipulate FITS extensions and/or headers.
3 Fits is part of GNU Astronomy Utilities (Gnuastro) package.
4 
5 Original author:
6      Mohammad Akhlaghi <mohammad@akhlaghi.org>
7 Contributing author(s):
8 Copyright (C) 2016-2021, Free Software Foundation, Inc.
9 
10 Gnuastro is free software: you can redistribute it and/or modify it
11 under the terms of the GNU General Public License as published by the
12 Free Software Foundation, either version 3 of the License, or (at your
13 option) any later version.
14 
15 Gnuastro is distributed in the hope that it will be useful, but
16 WITHOUT ANY WARRANTY; without even the implied warranty of
17 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
18 General Public License for more details.
19 
20 You should have received a copy of the GNU General Public License
21 along with Gnuastro. If not, see <http://www.gnu.org/licenses/>.
22 **********************************************************************/
23 #include <config.h>
24 
25 #include <argp.h>
26 #include <errno.h>
27 #include <error.h>
28 #include <stdio.h>
29 #include <string.h>
30 
31 #include <gnuastro/wcs.h>
32 #include <gnuastro/fits.h>
33 
34 #include <gnuastro-internal/options.h>
35 #include <gnuastro-internal/checkset.h>
36 #include <gnuastro-internal/fixedstringmacros.h>
37 
38 #include "main.h"
39 
40 #include "ui.h"
41 #include "authors-cite.h"
42 
43 
44 
45 
46 
47 /**************************************************************/
48 /*********      Argp necessary global entities     ************/
49 /**************************************************************/
50 /* Definition parameters for the Argp: */
51 const char *
52 argp_program_version = PROGRAM_STRING "\n"
53                        GAL_STRINGS_COPYRIGHT
54                        "\n\nWritten/developed by "PROGRAM_AUTHORS;
55 
56 const char *
57 argp_program_bug_address = PACKAGE_BUGREPORT;
58 
59 static char
60 args_doc[] = "ASTRdata";
61 
62 const char
63 doc[] = GAL_STRINGS_TOP_HELP_INFO PROGRAM_NAME" allows you to view and "
64   "manipulate (add, delete, or modify) FITS extensions (or HDUs) and FITS "
65   "header keywords within one extension.\n"
66   GAL_STRINGS_MORE_HELP_INFO
67   /* After the list of options: */
68   "\v"
69   PACKAGE_NAME" home page: "PACKAGE_URL;
70 
71 
72 
73 
74 
75 
76 
77 
78 
79 
80 
81 
82 
83 
84 
85 
86 
87 
88 
89 
90 /**************************************************************/
91 /*********    Initialize & Parse command-line    **************/
92 /**************************************************************/
93 static void
ui_initialize_options(struct fitsparams * p,struct argp_option * program_options,struct argp_option * gal_commonopts_options)94 ui_initialize_options(struct fitsparams *p,
95                       struct argp_option *program_options,
96                       struct argp_option *gal_commonopts_options)
97 {
98   size_t i;
99   struct gal_options_common_params *cp=&p->cp;
100 
101   /* Set the necessary common parameters structure. */
102   cp->keep               = 1;
103   cp->program_struct     = p;
104   cp->poptions           = program_options;
105   cp->program_name       = PROGRAM_NAME;
106   cp->program_exec       = PROGRAM_EXEC;
107   cp->program_bibtex     = PROGRAM_BIBTEX;
108   cp->program_authors    = PROGRAM_AUTHORS;
109   cp->coptions           = gal_commonopts_options;
110 
111   /* For clarity and non-zero initializations. */
112   p->mode                = FITS_MODE_INVALID;
113 
114   /* Modify common options. */
115   for(i=0; !gal_options_is_last(&cp->coptions[i]); ++i)
116     {
117       /* Select individually. */
118       switch(cp->coptions[i].key)
119         {
120         case GAL_OPTIONS_KEY_SEARCHIN:
121         case GAL_OPTIONS_KEY_IGNORECASE:
122         case GAL_OPTIONS_KEY_TYPE:
123         case GAL_OPTIONS_KEY_WCSLINEARMATRIX:
124         case GAL_OPTIONS_KEY_DONTDELETE:
125         case GAL_OPTIONS_KEY_LOG:
126         case GAL_OPTIONS_KEY_NUMTHREADS:
127         case GAL_OPTIONS_KEY_STDINTIMEOUT:
128           cp->coptions[i].flags=OPTION_HIDDEN;
129           break;
130 
131         case GAL_OPTIONS_KEY_OUTPUT:
132           cp->coptions[i].doc="Output file name (only for writing HDUs).";
133           break;
134         }
135 
136       /* Select by group. */
137       switch(cp->coptions[i].group)
138         {
139         case GAL_OPTIONS_GROUP_TESSELLATION:
140           cp->coptions[i].doc=NULL; /* Necessary to remove title. */
141           cp->coptions[i].flags=OPTION_HIDDEN;
142           break;
143         }
144     }
145 }
146 
147 
148 
149 
150 
151 /* Parse a single option: */
152 error_t
parse_opt(int key,char * arg,struct argp_state * state)153 parse_opt(int key, char *arg, struct argp_state *state)
154 {
155   struct fitsparams *p = state->input;
156 
157   /* Pass 'gal_options_common_params' into the child parser.  */
158   state->child_inputs[0] = &p->cp;
159 
160   /* In case the user incorrectly uses the equal sign (for example
161      with a short format or with space in the long format, then 'arg'
162      start with (if the short version was called) or be (if the long
163      version was called with a space) the equal sign. So, here we
164      check if the first character of arg is the equal sign, then the
165      user is warned and the program is stopped: */
166   if(arg && arg[0]=='=')
167     argp_error(state, "incorrect use of the equal sign ('='). For short "
168                "options, '=' should not be used and for long options, "
169                "there should be no space between the option, equal sign "
170                "and value");
171 
172   /* Set the key to this option. */
173   switch(key)
174     {
175     /* Read the non-option tokens (arguments). */
176     case ARGP_KEY_ARG:
177       /* The user may give a shell variable that is empty! In that case
178          'arg' will be an empty string! We don't want to account for such
179          cases (and give a clear error that no input has been given). */
180       if(arg[0]!='\0')
181         {
182           if( gal_fits_file_recognized(arg) )
183             gal_list_str_add(&p->input, arg, 1);
184           else
185             argp_error(state, "%s is not a recognized FITS file", arg);
186         }
187       break;
188 
189 
190     /* This is an option, set its value. */
191     default:
192       return gal_options_set_from_key(key, arg, p->cp.poptions, &p->cp);
193     }
194 
195   return 0;
196 }
197 
198 
199 
200 
201 
202 
203 
204 
205 
206 
207 
208 
209 
210 
211 
212 
213 
214 
215 
216 
217 /**************************************************************/
218 /***************       Sanity Check         *******************/
219 /**************************************************************/
220 static void
ui_check_copykeys(struct fitsparams * p)221 ui_check_copykeys(struct fitsparams *p)
222 {
223   long read;
224   char *tailptr;
225   /* size_t group=0; */
226   char forl='f', *pt=p->copykeys;
227 
228   /* For copykeys, an output filename is mandatory. */
229   if(p->cp.output==NULL || p->outhdu==NULL)
230     error(EXIT_FAILURE, 0, "an output FITS extension (in an existing "
231           "FITS file, specified with the '--output' and '--outhdu') are "
232           "mandatory for running '--copykeys'");
233 
234   /* Initialize the values. */
235   p->copykeysrange[0]=p->copykeysrange[1]=GAL_BLANK_LONG;
236 
237   /* Parse the string: 'forl': "first-or-last". */
238   while(*pt!='\0')
239     {
240       switch(*pt)
241         {
242         case ':':
243           forl='l';
244           ++pt;
245           break;
246         case '.':
247           error(EXIT_FAILURE, 0, "the numbers in the argument to "
248                 "'--section' ('-s') have to be integers. You input "
249                 "includes a float number: %s", p->copykeys);
250           break;
251         case ' ': case '\t':
252           ++pt;
253           break;
254 
255         /* Numerical characters signify the start of a number, so we don't
256            need to increment the pointer and can just break out. */
257         case '0': case '1': case '2': case '3': case '4': case '5':
258         case '6': case '7': case '8': case '9': case '-':
259           break;
260 
261         /* Identifier for next group of ranges. However, For the time
262            being, we just support one group. So we are commenting the break
263            here for it to follow onto default.
264         case ',':
265           ++group;
266           forl='f';
267           ++pt;
268           break;
269           */
270         default:
271           error(EXIT_FAILURE, 0, "value to '--copykeys' must only contain "
272                 "integer numbers and these special characters between them: "
273                 "':' when necessary. But it is '%s' (the first "
274                 "non-acceptable character is '%c').\n", p->copykeys, *pt);
275           break;
276         }
277 
278       /* Read the number: */
279       read=strtol(pt, &tailptr, 0);
280 
281       /* Check the progress.
282       printf("\n\n------\n%c: %ld (%s)\n", *pt, read, tailptr);
283       */
284 
285       /* Make sure if a number was read at all? */
286       if(tailptr==pt) continue;   /* No number was read!                 */
287 
288       /* Put it in the correct place. */
289       p->copykeysrange[ forl=='f' ? 0 : 1 ]=read;
290       pt=tailptr;
291     }
292 
293   /* Basic sanity checks. */
294   if( p->copykeysrange[1]==GAL_BLANK_LONG )
295     error(EXIT_FAILURE, 0, "no ending keyword number given to '--copykeys'. "
296           "If you want to copy all the keywords after a certain one "
297           "(without worrying about how many there are), you can use '-1'.\n\n"
298           "For example if you want to copy all the keywords after the 20th, "
299           "you can use '--copykeys=20,-1'. Generally, you can use negative "
300           "numbers for the last keyword number to count from the end.");
301   if( p->copykeysrange[0]<=0 )
302     error(EXIT_FAILURE, 0, "the first number given to '--copykeys' must be "
303           "positive");
304   if( p->copykeysrange[1]>=0 && p->copykeysrange[0]>=p->copykeysrange[1] )
305     error(EXIT_FAILURE, 0, "the first number (%ld) given to '--copykeys' "
306           "must be smaller than the second (%ld)", p->copykeysrange[0],
307           p->copykeysrange[1]);
308 
309   /* For a check:
310   printf("copykeys: %ld, %ld\n", p->copykeysrange[0], p->copykeysrange[1]);
311   exit(0);
312   */
313 }
314 
315 
316 
317 
318 
319 /* Read and check ONLY the options. When arguments are involved, do the
320    check in 'ui_check_options_and_arguments'. */
321 static void
ui_read_check_only_options(struct fitsparams * p)322 ui_read_check_only_options(struct fitsparams *p)
323 {
324   int checkkeys;
325   uint8_t stdoutcheck=0;
326 
327   /* If any of the keyword manipulation options are requested, then set the
328      mode flag to keyword-mode. */
329   if( p->date || p->comment || p->history || p->asis || p->keyvalue
330       || p->delete || p->rename || p->update || p->write || p->verify
331       || p->printallkeys || p->printkeynames || p->copykeys || p->datetosec
332       || p->wcscoordsys || p->wcsdistortion )
333     {
334       /* Check if a HDU is given. */
335       if(p->cp.hdu==NULL)
336         error(EXIT_FAILURE, 0, "a HDU (extension) is necessary for keyword "
337               "related options but none was defined. Please use the "
338               "'--hdu' (or '-h') option to select one");
339 
340       /* If Copy keys has been given, read it and make sure its setup. */
341       if(p->copykeys)
342         ui_check_copykeys(p);
343 
344       /* Keyword-related options that must be called alone. */
345       checkkeys = ( (p->keyvalue!=NULL)
346                     + (p->datetosec!=NULL)
347                     + (p->printkeynames!=0)
348                     + (p->wcscoordsys!=NULL)
349                     + (p->wcsdistortion!=NULL) );
350       if( ( checkkeys
351             && ( p->date || p->comment || p->history || p->asis
352                  || p->delete || p->rename || p->update || p->write
353                  || p->verify || p->printallkeys || p->copykeys ) )
354           || checkkeys>1 )
355         error(EXIT_FAILURE, 0, "'--keyvalue', '--datetosec', "
356               "'--wcscoordsys' and '--wcsdistortion' cannot "
357               "currently be called with any other option");
358 
359       /* Give an ID to recognized coordinate systems. */
360       if(p->wcscoordsys)
361         p->coordsysid=gal_wcs_coordsys_from_string(p->wcscoordsys);
362 
363       /* Identify the requested distortion. Note that this also acts as a
364          sanity check because it will crash with an error if the given
365          string isn't recognized. */
366       if(p->wcsdistortion)
367         p->distortionid=gal_wcs_distortion_from_string(p->wcsdistortion);
368 
369       /* Make sure the value of '--keyvalue' is actually present. */
370       if(p->keyvalue && p->keyvalue->v[0]=='\0')
371         error(EXIT_FAILURE, 0, "the '--keyvalue' option requires a value: "
372               "the name(s) of keywords you want the value of");
373 
374       /* Set the operating mode. */
375       p->mode=FITS_MODE_KEY;
376     }
377 
378   /* Same for the extension-related options */
379   if( p->remove || p->copy || p->cut || p->numhdus || p->datasum
380       || p->pixelscale || p->skycoverage || p->hastablehdu
381       || p->hasimagehdu || p->listtablehdus || p->listimagehdus )
382     {
383       /* A small sanity check. */
384       if(p->mode!=FITS_MODE_INVALID)
385         error(EXIT_FAILURE, 0, "extension and keyword manipulation options "
386               "cannot be called together");
387 
388       /* Some HDU options cannot be called with other options. */
389       stdoutcheck = ( p->numhdus + p->datasum + p->pixelscale
390                       + p->skycoverage + p->hastablehdu + p->hasimagehdu
391                       + p->listtablehdus + p->listimagehdus );
392 
393       /* Make sure if an output file is needed. */
394       if(stdoutcheck)
395         {
396           /* Make sure HDU reading and editing options aren't called
397              together. */
398           if(p->remove || p->copy || p->cut)
399             error(EXIT_FAILURE, 0, "HDU reading options (like "
400                   "'--numhdus', '--datasum' and etc) cannot be called "
401                   "with any of the HDU modification options like "
402                   "'--remove', '--copy' or '--cut' options");
403 
404           /* Make sure these options are called alone. */
405           if(stdoutcheck>1)
406             error(EXIT_FAILURE, 0, "HDU info options, like '--numhdus', "
407                   "'--datasum', '--pixelscale' or '--skycoverage', cannot "
408                   "be called together, only one at a time");
409 
410           /* Make sure the HDU is given if any of the options except
411              '--numhdus' are called. */
412           if( ( p->numhdus || p->hastablehdu || p->hasimagehdu
413                 || p->listtablehdus || p->listimagehdus)
414               && p->cp.hdu==NULL )
415             error(EXIT_FAILURE, 0, "a HDU (extension) is necessary for the "
416                   "'--datasum', '--pixelscale' or '--skycoverage' options. "
417                   "Please use the '--hdu' (or '-h') option to select one");
418         }
419       else
420         {
421           if(p->cp.output)
422             gal_checkset_writable_remove(p->cp.output, 1, p->cp.dontdelete);
423           else
424             p->cp.output=gal_checkset_automatic_output(&p->cp, p->input->v,
425                                                        "_ext.fits");
426         }
427 
428       /* Set the operating mode. */
429       p->mode=FITS_MODE_HDU;
430     }
431 
432   /* If no options are given, go into HDU mode, which will print the HDU
433      information when nothing is asked. */
434   if(p->mode==FITS_MODE_INVALID)
435     {
436       if(p->hdu_in_commandline)
437         {
438           p->printallkeys=1;
439           p->mode = FITS_MODE_KEY;
440         }
441       else
442         p->mode = FITS_MODE_HDU;
443     }
444 }
445 
446 
447 
448 
449 
450 static void
ui_check_options_and_arguments(struct fitsparams * p)451 ui_check_options_and_arguments(struct fitsparams *p)
452 {
453   /* Make sure an input file name was given and if it was a FITS file, that
454      a HDU is also given. */
455   if(p->input==NULL)
456     error(EXIT_FAILURE, 0, "no input file is specified");
457   gal_list_str_reverse(&p->input);
458 
459   /* More than one input is currently only acceptable with the '--keyvalue'
460      option. */
461   if( gal_list_str_number(p->input) > 1 && p->keyvalue==NULL)
462     error(EXIT_FAILURE, 0, "one input file is expected but %zu input "
463           "files are given", gal_list_str_number(p->input));
464 }
465 
466 
467 
468 
469 
470 
471 
472 
473 
474 
475 
476 
477 
478 
479 
480 
481 
482 
483 
484 
485 /**************************************************************/
486 /*****************       Preparations      ********************/
487 /**************************************************************/
488 /* The '--update' and '--write' options take multiple values for each
489    keyword, so here, we tokenize them and put them into a
490    'gal_fits_list_key_t' list. */
491 static void
ui_fill_fits_headerll(gal_list_str_t * input,gal_fits_list_key_t ** output,char * option_name)492 ui_fill_fits_headerll(gal_list_str_t *input, gal_fits_list_key_t **output,
493                       char *option_name)
494 {
495   long l, *lp;
496   void *fvalue;
497   double d, *dp;
498   gal_list_str_t *tmp;
499   char *c, *cf, *start, *tailptr;
500   int i=0, type, vfree, needsvalue;
501   char *original, *keyname, *value, *comment, *unit;
502 
503   for(tmp=input; tmp!=NULL; tmp=tmp->next)
504     {
505       i=0;
506       tailptr=NULL;
507 
508       /* 'c' is created in case of an error, so the input value can be
509          reported. */
510       errno=0;
511       original=malloc(strlen(tmp->v)+1);
512       if(original==NULL)
513         error(EXIT_FAILURE, errno, "%s: allocating %zu bytes for 'original'",
514               __func__, strlen(tmp->v)+1);
515       strcpy(original, tmp->v);
516 
517       /* Tokenize the input. Note that strlen does not include the \0
518          character. So we have added it with a 1. */
519       cf=(c=start=tmp->v)+strlen(tmp->v)+1;
520       keyname=value=comment=unit=NULL;
521       do
522         {
523           switch(*c)
524             {
525             case ',': case '\0':
526               *c='\0';
527               if(start!=c)
528                 switch(i)
529                   {
530                   case 0:
531                     keyname=start;
532                     break;
533                   case 1:
534                     value=start;
535                     break;
536                   case 2:
537                     comment=start;
538                     break;
539                   case 3:
540                     unit=start;
541                     break;
542                   default:
543                     error(EXIT_FAILURE, 0, "%s: only three commas should "
544                           "be given in the write or update keyword "
545                           "options. The general expected format is:\n"
546                           "    KEYWORD,value,\"a comment string\",unit\n",
547                           original);
548                   }
549               ++i;
550               start=c+1;
551               break;
552 
553             default:
554               break;
555             }
556         }
557       while(++c<cf);
558 
559       /* See if this is an option that needs a value or not.*/
560       needsvalue=1;
561       if(keyname)
562         {
563           if( !strcasecmp(keyname,"checksum")
564               || !strcasecmp(keyname,"datasum") )
565             needsvalue=0;
566         }
567 
568       /* Make sure the keyname and value (if necessary) is given. */
569       if( keyname==NULL || (needsvalue && value==NULL) )
570         error(EXIT_FAILURE, 0, "'--%s' option string (%s) can't be parsed. "
571               "The general expected format is (a comment string and unit "
572               "are optional):\n\n"
573               "    --%s=KEYWORD,value,\"a comment string\",unit\n\n"
574               "Any space characters around the the comma (,) characters "
575               "will be seen as part of the respective token.\n\n"
576               "Note that there are some exceptions (where no value is need)"
577               "please see the manual for more ('$ info astfits')",
578               option_name, original, option_name);
579       /*
580       printf("\n\n-%s-\n-%s-\n-%s-\n-%s-\n", keyname, value, comment, unit);
581       */
582 
583 
584       /* Find the type of the value: */
585       if(value)
586         {
587           errno=0;
588           l=strtol(value, &tailptr, 10);
589           if(*tailptr=='\0' && errno==0)
590             {
591               vfree=1;
592               type=GAL_TYPE_INT64;
593               errno=0;
594               fvalue=lp=malloc(sizeof *lp);
595               if(lp==NULL)
596                 error(EXIT_FAILURE, errno, "%s: %zu bytes for 'lp'",
597                       __func__, sizeof *lp);
598               *lp=l;
599             }
600           else
601             {
602               errno=0;
603               d=strtod(value, &tailptr);
604               if(*tailptr=='\0' && errno==0)
605                 {
606                   vfree=1;
607                   type=GAL_TYPE_FLOAT64;
608                   errno=0;
609                   fvalue=dp=malloc(sizeof *dp);
610                   if(dp==NULL)
611                     error(EXIT_FAILURE, errno, "%s: allocating %zu bytes "
612                           "for 'dp'", __func__, sizeof *dp);
613                   *dp=d;
614                 }
615               else
616                 { fvalue=value; type=GAL_TYPE_STRING; vfree=0; }
617             }
618         }
619       else
620         {
621           fvalue=NULL; type=GAL_TYPE_UINT8; vfree=0;
622         }
623 
624 
625       /* Add it to the list of keywords. */
626       gal_fits_key_list_add(output, type, keyname, 0, fvalue, vfree,
627                             comment, 0, unit, 0);
628       free(original);
629     }
630 
631   /* Reverse the list */
632   gal_fits_key_list_reverse(output);
633 }
634 
635 
636 
637 
638 
639 static void
ui_preparations(struct fitsparams * p)640 ui_preparations(struct fitsparams *p)
641 {
642   /* Fill in the key linked lists. We want to do this here so if there is
643      any error in parsing the user's input, the error is reported before
644      any change is made in the input file. */
645   if(p->write)  ui_fill_fits_headerll(p->write, &p->write_keys, "write");
646   if(p->update) ui_fill_fits_headerll(p->update, &p->update_keys, "update");
647 }
648 
649 
650 
651 
652 
653 
654 
655 
656 
657 
658 
659 
660 
661 
662 
663 
664 
665 
666 /**************************************************************/
667 /************         Set the parameters          *************/
668 /**************************************************************/
669 
670 void
ui_read_check_inputs_setup(int argc,char * argv[],struct fitsparams * p)671 ui_read_check_inputs_setup(int argc, char *argv[], struct fitsparams *p)
672 {
673   size_t i;
674   struct gal_options_common_params *cp=&p->cp;
675 
676 
677   /* Include the parameters necessary for argp from this program ('args.h')
678      and for the common options to all Gnuastro ('commonopts.h'). We want
679      to directly put the pointers to the fields in 'p' and 'cp', so we are
680      simply including the header here to not have to use long macros in
681      those headers which make them hard to read and modify. This also helps
682      in having a clean environment: everything in those headers is only
683      available within the scope of this function. */
684 #include <gnuastro-internal/commonopts.h>
685 #include "args.h"
686 
687 
688   /* Initialize the options and necessary information.  */
689   ui_initialize_options(p, program_options, gal_commonopts_options);
690 
691 
692   /* Read the command-line options and arguments. */
693   errno=0;
694   if(argp_parse(&thisargp, argc, argv, 0, 0, p))
695     error(EXIT_FAILURE, errno, "parsing arguments");
696 
697 
698   /* Check if the HDU is specified on the command-line. If so, then later,
699      if no operation is requested, we will print the header of the given
700      HDU.*/
701   for(i=0; !gal_options_is_last(&cp->coptions[i]); ++i)
702     if(cp->coptions[i].key==GAL_OPTIONS_KEY_HDU
703        && cp->coptions[i].set)
704       p->hdu_in_commandline=1;
705 
706 
707   /* Read the configuration files and set the common values. */
708   gal_options_read_config_set(&p->cp);
709 
710 
711   /* Read the options into the program's structure, and check them and
712      their relations prior to printing. */
713   ui_read_check_only_options(p);
714 
715 
716   /* Print the option values if asked. Note that this needs to be done
717      after the option checks so un-sane values are not printed in the
718      output state. */
719   gal_options_print_state(&p->cp);
720 
721 
722   /* Check that the options and arguments fit well with each other. Note
723      that arguments don't go in a configuration file. So this test should
724      be done after (possibly) printing the option values. */
725   ui_check_options_and_arguments(p);
726 
727 
728   /* Read/allocate all the necessary starting arrays. */
729   ui_preparations(p);
730 }
731 
732 
733 
734 
735 
736 
737 
738 
739 
740 
741 
742 
743 
744 
745 
746 
747 
748 
749 
750 
751 /**************************************************************/
752 /************      Free allocated, report         *************/
753 /**************************************************************/
754 void
ui_free_and_report(struct fitsparams * p)755 ui_free_and_report(struct fitsparams *p)
756 {
757   /* Free the allocated arrays: */
758   free(p->cp.output);
759 }
760