1 /*********************************************************************
2 Functions to check and set command line argument values and files.
3 This 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) 2015-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 <stdio.h>
26 #include <errno.h>
27 #include <error.h>
28 #include <stdlib.h>
29 #include <string.h>
30 #include <unistd.h>
31 #include <sys/stat.h>
32 #include <sys/fcntl.h>
33 
34 #include <gnuastro/data.h>
35 
36 #include <gnuastro-internal/timing.h>
37 #include <gnuastro-internal/checkset.h>
38 
39 
40 
41 
42 
43 
44 
45 
46 
47 /**************************************************************/
48 /**********               Environment              ************/
49 /**************************************************************/
50 /* The GSL random number generator (RNG) reads values from the
51    environment. This function is designed to make the job easier for any
52    Gnuastro program using GSL's RNG functions. */
53 gsl_rng *
gal_checkset_gsl_rng(uint8_t envseed_bool,const char ** name,unsigned long int * seed)54 gal_checkset_gsl_rng(uint8_t envseed_bool, const char **name,
55                      unsigned long int *seed)
56 {
57   gsl_rng *rng;
58 
59   /* Let GSL read the environment and convert the type name (as string) to
60      'gsl_rng_type'. After this function, 'gsl_rng_default' contains the
61      generator's type and 'gsl_rng_default_seed' contains the (possibly)
62      given seed.*/
63   gsl_rng_env_setup();
64 
65   /* Allocate the random number generator based on the requested type and
66      save its name. If no 'GSL_RNG_TYPE' is set, then use a fixed
67      generator.*/
68   rng=gsl_rng_alloc(secure_getenv("GSL_RNG_TYPE")
69                     ? gsl_rng_default
70                     : gsl_rng_ranlxs1);
71   *name = gsl_rng_name(rng);
72 
73   /* Initialize the random number generator, depending on the
74      'envseed_bool' argument. */
75   *seed = ( envseed_bool
76             ? gsl_rng_default_seed
77             : gal_timing_time_based_rng_seed() );
78   gsl_rng_set(rng, *seed);
79 
80   /* Return the GSL RNG structure. */
81   return rng;
82 }
83 
84 
85 
86 
87 
88 /* On the Linux kernel, due to "overcommitting" (which is activated by
89    default), malloc will not return NULL when we allocate more memory than
90    the physically available memory. It is possible to disable overcommiting
91    with root permissions, but I have not been able to find any way to do
92    this as a normal user. So the only way is to look into the
93    '/proc/meminfo' file (constantly filled by the Linux kernel) and read
94    the available memory from that.
95 
96    Note that this overcommiting apparently only occurs on Linux. From what
97    I have read, other kernels are much more strict and 'malloc' will indeed
98    return NULL if there isn't any physical RAM to support it. So if the
99    '/proc/meminfo' doesn't exist, we can assume that 'malloc' works as
100    expected, until its inverse is proven. */
101 size_t
gal_checkset_ram_available(int quietmmap)102 gal_checkset_ram_available(int quietmmap)
103 {
104   FILE *file;
105   int keyfound=0;
106   size_t *freemem=NULL;
107   size_t linelen=80, out=GAL_BLANK_SIZE_T;
108   char *token, *line, *linecp, *saveptr, delimiters[] = " ";
109   char *meminfo="/proc/meminfo", *keyname="MemAvailable", *units="kB";
110 
111   /* If /proc/meminfo exists, read it. Otherwise, don't bother doing
112      anything. */
113   if ((file = fopen(meminfo, "r")))
114     {
115       /* Allocate space to read the line. */
116       errno=0;
117       line=malloc(linelen*sizeof *line);
118       if(line==NULL)
119         error(EXIT_FAILURE, errno, "%s: allocating %zu bytes for line",
120               __func__, linelen*sizeof *line);
121 
122       /* Read it line-by-line until you find 'MemAvailable'.  */
123       while( getline(&line, &linelen, file) != -1 )
124         if( !strncmp(line, keyname, 12) )
125           {
126             /* Necessary for final check: */
127             keyfound=1;
128 
129             /* We need to work on a copied line to avoid messing up the
130                contents of the actual line. */
131             gal_checkset_allocate_copy(line, &linecp);
132 
133             /* The first token (which we don't need). */
134             token=strtok_r(linecp, delimiters, &saveptr);
135 
136             /* The second token (which is the actual number we want). */
137             token=strtok_r(NULL, delimiters, &saveptr);
138             if(token)
139               {
140                 /* Read the token as a number. */
141                 if( gal_type_from_string((void **)(&freemem), token,
142                                          GAL_TYPE_SIZE_T) )
143                   error(EXIT_SUCCESS, 0, "WARNING: %s: value of '%s' "
144                         "keyword couldn't be read as an integer. Hence "
145                         "the amount of available RAM couldn't be "
146                         "determined. If a large volume of data is "
147                         "provided, the program may crash. Please contact "
148                         "us at '%s' to fix the problem",
149                         meminfo, keyname, PACKAGE_BUGREPORT);
150                 else
151                   {
152                     /* The third token should be the units ('kB'). If it
153                        isn't, there should be an error because we currently
154                        assume kilobytes. */
155                     token=strtok_r(NULL, delimiters, &saveptr);
156                     if(token)
157                       {
158                         /* The units should be 'kB' (for kilobytes). */
159                         if( !strncmp(token, units, 2) )
160                           out=freemem[0]*1000;
161                         else
162                           error(EXIT_SUCCESS, 0, "WARNING: %s: the units of "
163                                 "the value of '%s' keyword is (usually 'kB') "
164                                 "isn't recognized. Hence the amount of "
165                                 "available RAM couldn't be determined. If a "
166                                 "large volume of data is provided, the "
167                                 "program may crash. Please contact us at "
168                                 "'%s' to fix the problem", meminfo, keyname,
169                                 PACKAGE_BUGREPORT);
170                       }
171                     else
172                       error(EXIT_SUCCESS, 0, "WARNING: %s: the units of the "
173                             "value of '%s' keyword (usually 'kB') couldn't "
174                             "be read as an integer. Hence the amount of "
175                             "available RAM couldn't be determined. If a "
176                             "large volume of data is provided, the program "
177                             "may crash. Please contact us at '%s' to fix "
178                             "the problem", meminfo, keyname, PACKAGE_BUGREPORT);
179                   }
180 
181                 /* Clean up. */
182                 if(freemem) free(freemem);
183               }
184             else
185               error(EXIT_SUCCESS, 0, "WARNING: %s: line with the '%s' "
186                     "keyword didn't have a value. Hence the amount of "
187                     "available RAM couldn't be determined. If a large "
188                     "volume of data is provided, the program may crash. "
189                     "Please contact us at '%s' to fix the problem",
190                     meminfo, keyname, PACKAGE_BUGREPORT);
191 
192             /* Clean up. */
193             free(linecp);
194           }
195 
196       /* The file existed but a keyname couldn't be found. In this case we
197          should inform the user to be aware that we can't automatically
198          determine the available memory. */
199       if(keyfound==0 && quietmmap==0)
200         error(EXIT_SUCCESS, 0, "WARNING: %s: didn't contain a '%s' keyword "
201               "hence the amount of available RAM couldn't be determined. "
202               "If a large volume of data is provided, the program may "
203               "crash. Please contact us at '%s' to fix the problem",
204               meminfo, keyname, PACKAGE_BUGREPORT);
205 
206       /* Close the opened file and free the line. */
207       free(line);
208       fclose(file);
209     }
210 
211   /* Return the final value. */
212   return out;
213 }
214 
215 
216 
217 
218 
219 int
gal_checkset_need_mmap(size_t bytesize,size_t minmapsize,int quietmmap)220 gal_checkset_need_mmap(size_t bytesize, size_t minmapsize, int quietmmap)
221 {
222   int needmmap=0;
223   size_t availableram;
224   size_t minimumtommap=10000000;
225   size_t mustremainfree=250000000;
226 
227   /* In case the given minmapsize is smaller than the default value of
228      'minimumtomap', then correct 'minimumtomap' to be the same as
229      'minmapsize' (the user has to have full control to over-write the
230      default value, but let them know in a warning that this is not
231      good). */
232   if(minmapsize < minimumtommap)
233     {
234       /* Let the user know that this is not a good choice and can cause
235          other problems. */
236       if(!quietmmap)
237         error(EXIT_SUCCESS, 0, "WARNING: it is recommended that minmapsize "
238               "have a value larger than %zu (it is currently %zu), see "
239               "\"Memory management\" section in the Gnuastro book for "
240               "more. To disable this warning, please use the option "
241               "'--quiet-mmap'", minimumtommap, minmapsize);
242 
243       /* Set the variable. */
244       minimumtommap=minmapsize;
245     }
246 
247   /* Memory mapping is only relevant here if the byte-size of the dataset
248      is larger than 'minimumtommap'. This is primarily because checking the
249      available memory can be expensive. */
250   if( bytesize >= minimumtommap )
251     {
252       /* Find the available RAM space (only relevant for Linux). */
253       availableram=gal_checkset_ram_available(quietmmap);
254 
255       /* For a check:
256       printf("check: %zu (bs), %zu (ar), %zu (nu)\n",
257              bytesize, availableram, mustremainfree);
258       */
259 
260       /* If the final size is larger than the user's maximum,
261          or is larger than the available memory minus 500Mb (to
262          leave the system some breathing space!), then read the
263          array into disk using memory-mapping (HDD/SSD). */
264       if( bytesize >= minmapsize
265           || availableram < mustremainfree
266           || bytesize > (availableram-mustremainfree) )
267         needmmap=1;
268     }
269 
270   /* Return the final choice. */
271   return needmmap;
272 }
273 
274 
275 
276 
277 
278 
279 
280 
281 
282 
283 
284 
285 
286 
287 
288 
289 
290 
291 
292 
293 /**************************************************************/
294 /**********          My String functions:          ************/
295 /**************************************************************/
296 int
gal_checkset_string_has_space(char * in)297 gal_checkset_string_has_space(char *in)
298 {
299   do
300     switch(*in)
301       {
302       case ' ': case '\t': case '\v':
303         return 1;
304       }
305   while(*(++in)!='\0');
306   return 0;
307 }
308 
309 
310 
311 
312 
313 char *
gal_checkset_malloc_cat(char * inname,char * toappend)314 gal_checkset_malloc_cat(char *inname, char *toappend)
315 {
316   char *out;
317   size_t inl, apl;
318 
319   inl=strlen(inname);
320   apl=strlen(toappend);
321 
322   errno=0;
323   out=malloc(inl+apl+1);
324   if(out==NULL)
325     error(EXIT_FAILURE, errno, "%s: allocating %zu bytes", __func__,
326           inl+apl+1);
327 
328   strcpy(out, inname);
329   strcat(out, toappend);
330   return out;
331 }
332 
333 
334 
335 
336 /* Copy the input string to the output (and also allocate the
337    output. */
338 void
gal_checkset_allocate_copy(const char * arg,char ** copy)339 gal_checkset_allocate_copy(const char *arg, char **copy)
340 {
341   if(arg)
342     {
343       errno=0;
344       *copy=malloc(strlen(arg)+1);
345       if(*copy==NULL)
346         error(EXIT_FAILURE, errno, "%s: %zu bytes to copy %s", __func__,
347               strlen(arg)+1, arg);
348       strcpy(*copy, arg);
349     }
350   else
351     *copy=NULL;
352 }
353 
354 
355 
356 
357 /* This function is mainly for reading in the arguments (from the
358    command line or configuration files) that need to be copied. The
359    set argument is for making sure that it has not already been set
360    before, see the main.h files of any program. */
361 void
gal_checkset_allocate_copy_set(char * arg,char ** copy,int * set)362 gal_checkset_allocate_copy_set(char *arg, char **copy, int *set)
363 {
364   /* Incase *set==1, then you shouldn't do anything, just return. */
365   if(*set) return;
366 
367   /* The variable was not copied, copy it: */
368   errno=0;
369   *copy=malloc(strlen(arg)+1);
370   if(*copy==NULL)
371     error(EXIT_FAILURE, errno, "%s: %zu bytes to copy %s", __func__,
372           strlen(arg)+1, arg);
373   strcpy(*copy, arg);
374   *set=1;
375 }
376 
377 
378 
379 
380 
381 /* The dataset may be alone in a file (for example a table in a text file)
382    or it may an extension of a FITS file. In error messages in particular,
383    we need to differentiate between the two. This function will check the
384    filename and if it is FITS, it will return a string with the filename
385    and HDU in parenthesis. If it isn't a FITS file, it will only return the
386    filename. Note that the output needs to be freed, although when used in
387    an error message, you can leave it to the system to free the
388    space. There is no problem. */
389 char *
gal_checkset_dataset_name(char * filename,char * hdu)390 gal_checkset_dataset_name(char *filename, char *hdu)
391 {
392   char *out;
393   if( gal_fits_name_is_fits(filename) )
394     {
395       if( asprintf(&out, "%s (hdu %s)", filename, hdu)<0 )
396         error(EXIT_FAILURE, 0, "%s: asprintf allocation", __func__);
397     }
398   else
399     gal_checkset_allocate_copy(filename, &out);
400   return out;
401 }
402 
403 
404 
405 
406 
407 
408 
409 
410 
411 
412 
413 
414 
415 
416 
417 
418 
419 
420 
421 /**************************************************************/
422 /********** Set file names and check if they exist ************/
423 /**************************************************************/
424 /* Given a filename, this function will separate its directory name
425    part. */
426 char *
gal_checkset_dir_part(char * filename)427 gal_checkset_dir_part(char *filename)
428 {
429   char *out;
430   size_t i, l=strlen(filename);
431 
432   /* Find the first slash from the end. */
433   for(i=l;i!=0;--i)
434     if(filename[i]=='/')
435       break;
436 
437   /* If there was no slash, then the current directory should be
438      given: */
439   if(i==0 && filename[0]!='/')
440     gal_checkset_allocate_copy("./", &out);
441   else
442     {
443       gal_checkset_allocate_copy(filename, &out);
444       out[i+1]='\0';
445     }
446 
447   return out;
448 }
449 
450 
451 
452 
453 
454 /* Given a file name, keep the non-directory part. Note that if there
455    is no forward slash in the input name, the full input name is
456    considered to be the notdir output.*/
457 char *
gal_checkset_not_dir_part(char * filename)458 gal_checkset_not_dir_part(char *filename)
459 {
460   size_t i, l;
461   char *out, *tmp=filename;
462 
463   /* Find the first '/' to identify the directory */
464   l=strlen(filename);
465   for(i=l;i!=0;--i)
466     if(filename[i]=='/')
467       { tmp=&filename[i+1]; break; }
468 
469   /* Get the length of the notdir name: */
470   l=strlen(tmp);
471   errno=0;
472   out=malloc((l+1)*sizeof *out);
473   if(out==NULL)
474     error(EXIT_FAILURE, errno, "%s: %zu bytes for notdir", __func__,
475           (l+1)*sizeof *out);
476 
477   strcpy(out, tmp);
478   return out;
479 }
480 
481 
482 
483 
484 
485 /* Make an allocated copy of the input string, then remove the suffix from
486    that string. */
487 char *
gal_checkset_suffix_separate(char * name,char ** outsuffix)488 gal_checkset_suffix_separate(char *name, char **outsuffix)
489 {
490   char *c, *out=NULL, *suffix=NULL;
491 
492   /* Make a copy of the input. */
493   gal_checkset_allocate_copy(name, &out);
494 
495   /* Parse the string from the end and stop when we hit a '.'. */
496   c=out+strlen(out)-1;
497   while(c!=out)
498     {
499       /* As soon as we hit the first '.' take a copy of the string after it
500          and put it in 'suffix'. */
501       if(*c=='.')
502         {
503           gal_checkset_allocate_copy(c, &suffix);
504           *c='\0';
505           break;
506         }
507       --c;
508     }
509 
510   /* Put the 'suffix' in the output pointer and return the string with no
511      suffix. */
512   *outsuffix=suffix;
513   return out;
514 }
515 
516 
517 
518 
519 
520 /* Given a reference filename, add a format of AAAAA-XXXXXX.CCCC where
521    'AAAAA' is the base name of the 'reference' argument, 'XXXXX' is a
522    random/unique sequence of characters, and 'YYYYY' is the string given to
523    'suffix'. If 'suffix' is NULL, the suffix of 'reference' will be used.*/
524 char *
gal_checkset_make_unique_suffix(char * reference,char * suffix)525 gal_checkset_make_unique_suffix(char *reference, char *suffix)
526 {
527   int tmpnamefile;
528   char *nosuff, *tmpname;
529   char *out=NULL, *insuffix;
530 
531   /* Remove the suffix. */
532   nosuff=gal_checkset_suffix_separate(reference, &insuffix);
533 
534   /* First generate the input to 'mkstemp' (the 'XXXXXX's will be replaced
535      with a unique set of strings with same number of characters). */
536   if( asprintf(&tmpname, "%s-XXXXXX", nosuff)<0 )
537     error(EXIT_FAILURE, 0, "%s: asprintf allocation", __func__);
538 
539   /* Generate the unique name with 'mkstemp', but it will actually open the
540      file (to make sure that the name is not used), so we need to close it
541      afterwards. */
542   tmpnamefile=mkstemp(tmpname);
543   errno=0;
544   if( close(tmpnamefile) != 0 )
545     error(EXIT_FAILURE, errno, "couldn't close temporary file");
546 
547   /* Delete the temporarily created file. */
548   remove(tmpname);
549 
550   /* Add the suffix. */
551   out = ( suffix
552           ? gal_checkset_malloc_cat(tmpname, suffix)
553           : ( insuffix
554               ? gal_checkset_malloc_cat(tmpname, insuffix)
555               : tmpname ) );
556 
557   /* Clean up and return the output. */
558   if(tmpname!=out) free(tmpname);
559   if(insuffix) free(insuffix);
560   free(nosuff);
561   return out;
562 }
563 
564 
565 
566 
567 /* Check if a file exists and report if it doesn't. */
568 void
gal_checkset_check_file(char * filename)569 gal_checkset_check_file(char *filename)
570 {
571   FILE *tmpfile;
572   errno=0;
573   tmpfile = fopen(filename, "r");
574   if(tmpfile)                        /* The file opened. */
575     {
576       if(fclose(tmpfile)==EOF)
577         error(EXIT_FAILURE, errno, "%s", filename);
578     }
579   else
580     error(EXIT_FAILURE, errno, "%s", filename);
581 }
582 
583 
584 
585 
586 
587 /* Similar to 'gal_checkset_check_file', but will report the result instead
588    of doing it quietly. */
589 int
gal_checkset_check_file_return(char * filename)590 gal_checkset_check_file_return(char *filename)
591 {
592   FILE *tmpfile;
593   errno=0;
594   tmpfile = fopen(filename, "r");
595   if(tmpfile)                        /* The file opened. */
596     {
597       if(fclose(tmpfile)==EOF)
598         error(EXIT_FAILURE, errno, "%s", filename);
599       return 1;
600     }
601   else
602     return 0;
603 }
604 
605 
606 
607 
608 
609 /* If a file doesn't exist and its directory is writable, return
610    1. Otherwise, return 0. */
611 int
gal_checkset_writable_notexist(char * filename)612 gal_checkset_writable_notexist(char *filename)
613 {
614   int out=1;
615   char *dir;
616   FILE *tmpfile;
617 
618   /* If the filename is 'NULL' everything is ok (it doesn't exist)! In some
619      cases, a NULL filename is interpretted to mean standard output. */
620   if(filename==NULL) return 1;
621 
622   /* We want to make sure that we can open and write to this file. But
623      the user might have asked to not delete the file, so the
624      contents should not be changed. Therefore we have to open it with
625      'r+'. */
626   errno=0;
627   tmpfile=fopen(filename, "r+");
628   if (tmpfile)                        /* The file opened. */
629     {
630       /* Close the file. */
631       errno=0;
632       if(fclose(tmpfile))
633         error(EXIT_FAILURE, errno, "%s", filename);
634 
635       /* The file exists, return 0. */
636       out=0;
637     }
638 
639   /* If the file doesn't exist, we just need to make sure if we have write
640      permissions to its host directory. */
641   else
642     {
643       /* Separate the directory part of the filename. */
644       dir=gal_checkset_dir_part(filename);
645 
646       /* See if this directory is writable by this user. */
647       errno=0;
648       if( access(dir, W_OK) ) out=0;
649 
650       /* Clean up. */
651       free(dir);
652     }
653 
654   /* Return the final value. */
655   return out;
656 }
657 
658 
659 
660 
661 
662 /* Check if a file exists and can be opened. If the 'keep' value is
663    non-zero, then the file will remain untouched, otherwise, it will be
664    deleted (since most programs need to make a clean output). When the file
665    is to be deleted and 'dontdelete' has a non-zero value, then the file
666    won't be deleted, but the program will abort with an error, informing
667    the user that the output can't be made. */
668 void
gal_checkset_writable_remove(char * filename,int keep,int dontdelete)669 gal_checkset_writable_remove(char *filename, int keep, int dontdelete)
670 {
671   char *dir;
672   FILE *tmpfile;
673 
674   /* If the filename is 'NULL' everything is ok (it doesn't exist)! In some
675      cases, a NULL filename is interpretted to mean standard output. */
676   if(filename==NULL)
677     return;
678 
679   /* We want to make sure that we can open and write to this file. But
680      the user might have asked to not delete the file, so the
681      contents should not be changed. Therefore we have to open it with
682      'r+'. */
683   errno=0;
684   tmpfile=fopen(filename, "r+");
685   if (tmpfile)                        /* The file opened. */
686     {
687       /* Close the file. */
688       errno=0;
689       if(fclose(tmpfile))
690         error(EXIT_FAILURE, errno, "%s", filename);
691 
692       /* See if the file should be deleted. */
693       if(keep==0)
694         {
695           /* Make sure it is ok to delete the file. */
696           if(dontdelete)
697             error(EXIT_FAILURE, 0, "%s already exists and you have "
698                   "asked to not remove it with the '--dontdelete' "
699                   "('-D') option", filename);
700 
701           /* Delete the file: */
702           errno=0;
703           if(unlink(filename))
704             error(EXIT_FAILURE, errno, "%s", filename);
705         }
706     }
707 
708   /* If the file doesn't exist, we just need to make sure if we have write
709      permissions to its host directory. */
710   else
711     {
712       /* Separate the directory part of the filename. */
713       dir=gal_checkset_dir_part(filename);
714 
715       /* Make sure this directory is writable by this user. */
716       errno=0;
717       if( access(dir, W_OK) )
718         error(EXIT_FAILURE, errno, "cannot create any file(s) in the "
719               "directory '%s'", dir);
720 
721       /* Clean up. */
722       free(dir);
723     }
724 }
725 
726 
727 
728 
729 
730 /* Check output file name: If a file exists or can exist and can be
731    written to, this function will return 1. If not (for example it is
732    a directory) it will return 0. Finally, if it exists but cannot be
733    deleted, report an error and abort. */
734 int
gal_checkset_dir_0_file_1(char * name,int dontdelete)735 gal_checkset_dir_0_file_1(char *name, int dontdelete)
736 {
737   FILE *tmpfile;
738   struct stat nameinfo;
739 
740   if(name==NULL)
741     error(EXIT_FAILURE, 0, "%s: a bug! The input should not be NULL. "
742           "Please contact us at %s so we can see what went wrong and "
743           "fix it in future updates", __func__, PACKAGE_BUGREPORT);
744 
745   errno=0;
746   if(stat(name, &nameinfo)!=0)
747     {
748       if(errno==ENOENT)        /* ENOENT: No such file or directory. */
749         {/* Make the file temporarily and see if everything is ok. */
750           errno=0;
751           tmpfile=fopen(name, "w");
752           if (tmpfile)
753             {
754               fprintf(tmpfile, "Only to test write access.");
755               errno=0;
756               if(fclose(tmpfile))
757                 error(EXIT_FAILURE, errno, "%s", name);
758               errno=0;
759               if(unlink(name))
760                 error(EXIT_FAILURE, errno, "%s", name);
761             }
762           else
763             error(EXIT_FAILURE, errno, "%s", name);
764           return 1;                    /* It is a file name, GOOD */
765         }
766       else                             /* Some strange condition, ABORT */
767         error(EXIT_FAILURE, errno, "%s", name);
768     }
769 
770   if(S_ISDIR(nameinfo.st_mode))        /* It is a directory, BAD */
771     return 0;
772   else if (S_ISREG(nameinfo.st_mode))  /* It is a file, GOOD. */
773     {
774       gal_checkset_writable_remove(name, 0, dontdelete);
775       return 1;
776     }
777   else                                 /* Not a file or a dir, ABORT */
778     error(EXIT_FAILURE, 0, "%s not a file or a directory", name);
779 
780   error(EXIT_FAILURE, 0, "%s: a bug! The process should not reach the end "
781         "of the function! Please contact us at %s so we can see what went "
782         "wrong and fix it in future updates", __func__, PACKAGE_BUGREPORT);
783   return 0;
784 }
785 
786 
787 
788 
789 
790 /* Allocate space and write the output name (outname) based on a given
791    input name (inname). The suffix of the input name (if present) will
792    be removed and the given suffix will be put in the end. */
793 char *
gal_checkset_automatic_output(struct gal_options_common_params * cp,char * inname,char * suffix)794 gal_checkset_automatic_output(struct gal_options_common_params *cp,
795                               char *inname, char *suffix)
796 {
797   char *out;
798   size_t i, l, offset=0;
799 
800   /* Merge the contents of the input name and suffix name (while also
801      allocating the necessary space).*/
802   out=gal_checkset_malloc_cat(inname, suffix);
803 
804   /* If there is actually a suffix, replace it with the (possibly) existing
805      suffix. */
806   if(suffix)
807     {
808       /* Start from the end of the input array*/
809       l=strlen(inname);
810       for(i=l-1;i!=0;--i)
811         {
812           /* We don't want to touch anything before a '/' (directory
813              names). We are only concerned with file names here. */
814           if(out[i]=='/')
815             {
816               /* When '/' is the last input character, then the input is
817                  clearly not a filename, but a directory name. In this
818                  case, adding a suffix is meaningless (a suffix belongs to
819                  a filename for Gnuastro's tools). So close the string
820                  after the '/' and leave the loop. However, if the '/'
821                  isn't the last input name charector, there is probably a
822                  filename (without a "." suffix), so break from the
823                  loop. No further action is required, since we initially
824                  allocated the necessary space and concatenated the input
825                  and suffix arrays. */
826               if(i==l-1)
827                 out[i+1]='\0';
828               break;
829             }
830 
831           /* The input file names can be compressed names (for example
832              '.fits.gz'). Currently the only compressed formats
833              (decompressed within CFITSIO) are listed in
834              'gal_fits_name_is_fits' and 'gal_fits_suffix_is_fits'.*/
835           else if(out[i]=='.' && !( ( out[i+1]=='g' && out[i+2]=='z' )
836                                     || (out[i+1]=='f' && out[i+2]=='z' )
837                                     || out[i+1]=='Z' ) )
838             {
839               out[i]='\0';
840               strcat(out, suffix);
841               break;
842             }
843         }
844     }
845 
846   /* If we don't want the input directory information, remove them
847      here. */
848   if(!cp->keepinputdir)
849     {
850       l=strlen(out);
851       for(i=l;i!=0;--i)         /* Find the last forward slash.      */
852         if(out[i]=='/')
853           {offset=i+1; break;}
854       if(offset)
855         for(i=offset;i<=l;++i)  /* <= because we want to shift the   */
856           out[i-offset]=out[i]; /* '\0' character in the string too. */
857     }
858 
859   /* Remove the created filename if it already exits. */
860   gal_checkset_writable_remove(out, cp->keep, cp->dontdelete);
861 
862   /* Return the resulting filename. */
863   return out;
864 }
865 
866 
867 
868 
869 
870 /* Check write-ability by trying to make a temporary file. Return 0 if the
871    directory is writable, and 'errno' if it isn't. We won't be using
872    'facccessat' because its not available on some systems (macOS 10.9 and
873    earlier, see https://github.com/conda-forge/staged-recipes/pull/9723
874    ). */
875 static int
checkset_directory_writable(char * dirname)876 checkset_directory_writable(char *dirname)
877 {
878   int file_d;
879   int errnum=0;
880   char *tmpname;
881 
882   /* Set the template for the temporary file (accounting for the possible
883      extra '/'). */
884   if(dirname[strlen(dirname)-1]=='/')
885     tmpname=gal_checkset_malloc_cat(dirname, "gnuastroXXXXXX");
886   else
887     tmpname=gal_checkset_malloc_cat(dirname, "/gnuastroXXXXXX");
888 
889   /* Make the temporary file (with the temporary name) and open it. */
890   errno=0;
891   file_d=mkstemp(tmpname);
892 
893   /* See if the file was opened (and thus created). */
894   if(file_d==-1) errnum=errno;
895   else
896     { /* The file was created, close the file. */
897       errno=0;
898       if( close(file_d) == -1 ) errnum=errno;
899       else
900         {/* The file was closed, delete it. */
901           errno=0;
902           if(unlink(tmpname)==-1) errnum=errno;
903         }
904     }
905 
906   /* Return the final value. */
907   return errnum;
908 }
909 
910 
911 
912 
913 /* Check if dirname is actually a real directory and that we can
914    actually write inside of it. To insure all conditions an actual
915    file will be made */
916 void
gal_checkset_check_dir_write_add_slash(char ** dirname)917 gal_checkset_check_dir_write_add_slash(char **dirname)
918 {
919   int file_d;
920   char *tmpname, *indir=*dirname/*, buf[]="A test"*/;
921 
922   /* Set the template for the temporary file: */
923   if(indir[strlen(indir)-1]=='/')
924     tmpname=gal_checkset_malloc_cat(indir, "gnuastroXXXXXX");
925   else
926     tmpname=gal_checkset_malloc_cat(indir, "/gnuastroXXXXXX");
927 
928   /* Make a temporary file name and try openning it. */
929   errno=0;
930   file_d=mkstemp(tmpname);
931   if(file_d==-1)
932     error(EXIT_FAILURE, errno, "cannot write output in the directory "
933           "%s", indir);
934   /*
935   errno=0;
936   printf("\n\n%s\n\n", tmpname);
937   if( write(file_d, buf, strlen(buf)) == -1 )
938     error(EXIT_FAILURE, errno, "%s: writing to this temporary file to "
939           "check the given '%s' directory", tmpname, indir);
940   */
941   errno=0;
942   if( close(file_d) == -1 )
943     error(EXIT_FAILURE, errno, "%s: Closing this temporary file to check "
944           "the given '%s' directory", tmpname, indir);
945 
946   /* Delete the temporary file: */
947   errno=0;
948   if(unlink(tmpname)==-1)
949     error(EXIT_FAILURE, errno, "%s: removing this temporary file made "
950           "to check the given '%s directory'", tmpname, indir);
951 
952   /* Remove the extra characters that were added for the random name. */
953   tmpname[strlen(tmpname)-14]='\0';
954 
955   free(*dirname);
956   *dirname=tmpname;
957 }
958 
959 
960 
961 
962 
963 /* If the given directory exists and is writable, then nothing is done and
964    this function returns 0. If it doesn't, it will be created. If it fails
965    at creating the file, or the file isn't writable it returns a non-zero
966    value: the errno, which can be directly used in 'error'. */
967 int
gal_checkset_mkdir(char * dirname)968 gal_checkset_mkdir(char *dirname)
969 {
970   int errnum=0;
971   struct stat st={0};
972 
973   /* See if the directory exists. */
974   if( stat(dirname, &st) == -1 )
975     { /* The directory doesn't exist, try making it. */
976       errno=0;
977       if( mkdir(dirname, 0700) == -1 )
978         errnum=errno;
979     }
980   else
981     /* The directory exists, see if its writable. */
982     errnum=checkset_directory_writable(dirname);
983 
984   /* Return the final 'errno'. */
985   return errnum;
986 }
987