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