1 /*
2  * Copyright(C) 1999-2020 National Technology & Engineering Solutions
3  * of Sandia, LLC (NTESS).  Under the terms of Contract DE-NA0003525 with
4  * NTESS, the U.S. Government retains certain rights in this software.
5  *
6  * See packages/seacas/LICENSE for details
7  */
8 
9 #include "exodusII.h"     // for ex_err, etc
10 #include "exodusII_int.h" // for ex__file_item, EX_FATAL, etc
11 #include "stdbool.h"
12 
13 /*! \file
14  * this file contains code needed to support the various floating point word
15  * size combinations for computation and i/o that applications might want to
16  * use. See the netcdf documentation for more details on the floating point
17  * conversion capabilities.
18  *
19  * netCDF supports two floating point word sizes for its files:
20  *   - NC_FLOAT  - 32 bit IEEE
21  *   - NC_DOUBLE - 64 bit IEEE
22  *
23  */
24 
25 #define NC_FLOAT_WORDSIZE 4
26 
27 static struct ex__file_item *file_list = NULL;
28 
ex__find_file_item(int exoid)29 struct ex__file_item *ex__find_file_item(int exoid)
30 {
31   /* Find base filename in case exoid refers to a group */
32   int                   base_exoid = (unsigned)exoid & EX_FILE_ID_MASK;
33   struct ex__file_item *ptr        = file_list;
34   while (ptr) {
35     if (ptr->file_id == base_exoid) {
36       break;
37     }
38     ptr = ptr->next;
39   }
40   return (ptr);
41 }
42 
43 #define EX__MAX_PATHLEN 8192
ex__check_multiple_open(const char * path,int mode,const char * func)44 int ex__check_multiple_open(const char *path, int mode, const char *func)
45 {
46   EX_FUNC_ENTER();
47   bool                  is_write = mode & EX_WRITE;
48   char                  tmp[EX__MAX_PATHLEN];
49   size_t                pathlen;
50   struct ex__file_item *ptr = file_list;
51   while (ptr) {
52     nc_inq_path(ptr->file_id, &pathlen, tmp);
53     /* If path is too long, assume it is ok... */
54     if (pathlen < EX__MAX_PATHLEN && strncmp(path, tmp, EX__MAX_PATHLEN) == 0) {
55       /* Found matching file.  See if any open for write */
56       if (ptr->is_write || is_write) {
57         char errmsg[MAX_ERR_LENGTH];
58         snprintf(errmsg, MAX_ERR_LENGTH,
59                  "ERROR: The file '%s' is open for both read and write."
60                  " File corruption or incorrect behavior can occur.\n",
61                  path);
62         ex_err(func, errmsg, EX_BADFILEID);
63 #if defined BUILT_IN_SIERRA
64         EX_FUNC_LEAVE(EX_NOERR);
65 #else
66         EX_FUNC_LEAVE(EX_FATAL);
67 #endif
68       }
69     }
70     ptr = ptr->next;
71   }
72   EX_FUNC_LEAVE(EX_NOERR);
73 }
74 
ex__check_valid_file_id(int exoid,const char * func)75 int ex__check_valid_file_id(int exoid, const char *func)
76 {
77   bool error = false;
78   if (exoid <= 0) {
79     error = true;
80   }
81 #if !defined BUILT_IN_SIERRA
82   else {
83     struct ex__file_item *file = ex__find_file_item(exoid);
84 
85     if (!file) {
86       error = true;
87     }
88   }
89 #endif
90 
91   if (error) {
92     int old_opt = ex_opts(EX_VERBOSE);
93     if (old_opt & EX_ABORT) {
94       ex_opts(EX_VERBOSE | EX_ABORT);
95     }
96     char errmsg[MAX_ERR_LENGTH];
97     snprintf(errmsg, MAX_ERR_LENGTH,
98              "ERROR: In \"%s\", the file id %d was not obtained via a call "
99              "to \"ex_open\" or \"ex_create\".\n\t\tIt does not refer to a "
100              "valid open exodus file.\n\t\tAborting to avoid file "
101              "corruption or data loss or other potential problems.",
102              func, exoid);
103     ex_err(__func__, errmsg, EX_BADFILEID);
104     ex_opts(old_opt);
105     return EX_FATAL;
106   }
107   return EX_NOERR;
108 }
109 
ex__conv_init(int exoid,int * comp_wordsize,int * io_wordsize,int file_wordsize,int int64_status,bool is_parallel,bool is_hdf5,bool is_pnetcdf,bool is_write)110 int ex__conv_init(int exoid, int *comp_wordsize, int *io_wordsize, int file_wordsize,
111                   int int64_status, bool is_parallel, bool is_hdf5, bool is_pnetcdf, bool is_write)
112 {
113   char                  errmsg[MAX_ERR_LENGTH];
114   struct ex__file_item *new_file;
115   int                   filetype = 0;
116 
117   /*! ex__conv_init() initializes the floating point conversion process.
118    *
119    * \param exoid         an integer uniquely identifying the file of interest.
120    *
121    * \param comp_wordsize compute floating point word size in the user's code.
122    *                      a zero value indicates that the user is requesting the
123    *                      default float size for the machine. The appropriate
124    *                      value is chosen and returned in comp_wordsize, and
125    *                      used in subsequent conversions.  a valid but
126    *                      inappropriate for this parameter cannot be detected.
127    *
128    * \param io_wordsize   the desired floating point word size for a netCDF
129    *                      file. For an existing file, if this parameter doesn't
130    *                      match the word size of data already stored in the file,
131    *                      a fatal error is generated.  a value of 0 for an
132    *                      existing file indicates that the word size of the file
133    *                      was not known a priori, so use whatever is in the file.
134    *                      a value of 0 for a new file means to use the default
135    *                      size, an NC_FLOAT (4 bytes).  when a value of 0 is
136    *                      specified the actual value used is returned in
137    *                      io_wordsize.
138    *
139    * \param file_wordsize floating point word size in an existing netCDF file.
140    *                      a value of 0 should be passed in for a new netCDF
141    *                      file.
142    *
143    * \param int64_status  the flags specifying how integer values should be
144    *                      stored on the database and how they should be
145    *                      passes through the api functions.
146    *
147    * \param is_parallel   1 if parallel file; 0 if serial
148    *
149    * \param is_hdf5       1 if parallel netcdf-4 mode; 0 if not.
150    *
151    * \param is_pnetcdf    1 if parallel PNetCDF file; 0 if not.
152    *
153    * \param is_write      1 if output file; 0 if readonly
154    *
155    * word size parameters are specified in bytes. valid values are 0, 4, and 8:
156    */
157 
158   EX_FUNC_ENTER();
159 
160   /* check to make sure machine word sizes are sane */
161 /* If the following line causes a compile-time error, then there is a problem
162  * which will cause exodus to not work correctly on this platform.
163  *
164  * Contact Greg Sjaardema, gdsjaar@sandia.gov for asisstance.
165  */
166 #define CT_ASSERT(e) extern char(*ct_assert(void))[sizeof(char[1 - 2 * !(e)])]
167   CT_ASSERT((sizeof(float) == 4 || sizeof(float) == 8) &&
168             (sizeof(double) == 4 || sizeof(double) == 8));
169 
170   /* check to see if requested word sizes are valid */
171   if (!*io_wordsize) {
172     if (!file_wordsize) {
173       *io_wordsize = NC_FLOAT_WORDSIZE;
174     }
175     else {
176       *io_wordsize = file_wordsize;
177     }
178   }
179 
180   else if (*io_wordsize != 4 && *io_wordsize != 8) {
181     snprintf(errmsg, MAX_ERR_LENGTH, "ERROR: unsupported I/O word size for file id: %d", exoid);
182     ex_err_fn(exoid, __func__, errmsg, EX_BADPARAM);
183     EX_FUNC_LEAVE(EX_FATAL);
184   }
185 
186   else if (file_wordsize && *io_wordsize != file_wordsize) {
187     *io_wordsize = file_wordsize;
188     snprintf(errmsg, MAX_ERR_LENGTH,
189              "ERROR: invalid I/O word size specified for existing file id: "
190              "%d, Requested I/O word size overridden.",
191              exoid);
192     ex_err_fn(exoid, __func__, errmsg, EX_BADPARAM);
193   }
194 
195   if (!*comp_wordsize) {
196     *comp_wordsize = sizeof(float);
197   }
198   else if (*comp_wordsize != 4 && *comp_wordsize != 8) {
199     ex_err_fn(exoid, __func__, "ERROR: invalid compute wordsize specified", EX_BADPARAM);
200     EX_FUNC_LEAVE(EX_FATAL);
201   }
202 
203   /* Check that the int64_status contains only valid bits... */
204   {
205     int valid_int64 = EX_ALL_INT64_API | EX_ALL_INT64_DB;
206     if ((int64_status & valid_int64) != int64_status) {
207       snprintf(errmsg, MAX_ERR_LENGTH,
208                "Warning: invalid int64_status flag (%d) specified for "
209                "existing file id: %d. Ignoring invalids",
210                int64_status, exoid);
211       ex_err_fn(exoid, __func__, errmsg, -EX_BADPARAM);
212     }
213     int64_status &= valid_int64;
214   }
215 
216   /* Verify filetype
217    *  0 -- classic format   (NC_FORMAT_CLASSIC -1)
218    *  1 -- 64 bit classic   (NC_FORMAT_64BIT   -1)
219    *  2 -- netcdf4          (NC_FORMAT_NETCDF4 -1)
220    *  3 -- netcdf4 classic  (NC_FORMAT_NETCDF4_CLASSIC -1)
221    */
222 
223   nc_inq_format(exoid, &filetype);
224 
225   if (!(new_file = malloc(sizeof(struct ex__file_item)))) {
226     snprintf(errmsg, MAX_ERR_LENGTH,
227              "ERROR: failed to allocate memory for internal file "
228              "structure storage file id %d",
229              exoid);
230     ex_err_fn(exoid, __func__, errmsg, EX_MEMFAIL);
231     EX_FUNC_LEAVE(EX_FATAL);
232   }
233 
234   new_file->file_id               = exoid;
235   new_file->user_compute_wordsize = *comp_wordsize == 4 ? 0 : 1;
236   new_file->int64_status          = int64_status;
237   new_file->maximum_name_length   = ex__default_max_name_length;
238   new_file->time_varid            = -1;
239   new_file->compression_algorithm = EX_COMPRESS_GZIP;
240   new_file->assembly_count        = 0;
241   new_file->blob_count            = 0;
242   new_file->compression_level     = 0;
243   new_file->shuffle               = 0;
244   new_file->file_type             = filetype - 1;
245   new_file->is_parallel           = is_parallel;
246   new_file->is_hdf5               = is_hdf5;
247   new_file->is_pnetcdf            = is_pnetcdf;
248   new_file->has_nodes             = 1; /* default to yes in case not set */
249   new_file->has_edges             = 1;
250   new_file->has_faces             = 1;
251   new_file->has_elems             = 1;
252   new_file->is_write              = is_write;
253 
254   new_file->next = file_list;
255   file_list      = new_file;
256 
257   if (*io_wordsize == NC_FLOAT_WORDSIZE) {
258     new_file->netcdf_type_code = NC_FLOAT;
259   }
260   else {
261     new_file->netcdf_type_code = NC_DOUBLE;
262   }
263 
264   EX_FUNC_LEAVE(EX_NOERR);
265 }
266 
267 /*............................................................................*/
268 /*............................................................................*/
269 
270 /*! ex__conv_exit() takes the structure identified by "exoid" out of the linked
271  * list which describes the files that ex_conv_array() knows how to convert.
272  *
273  * \note it is absolutely necessary for ex__conv_exit() to be called after
274  *       ncclose(), if the parameter used as "exoid" is the id returned from
275  *       an ncopen() or nccreate() call, as netCDF reuses file ids!
276  *       the best place to do this is ex_close(), which is where I did it.
277  *
278  * \param exoid  integer which uniquely identifies the file of interest.
279  */
ex__conv_exit(int exoid)280 void ex__conv_exit(int exoid)
281 {
282 
283   char                  errmsg[MAX_ERR_LENGTH];
284   struct ex__file_item *file = file_list;
285   struct ex__file_item *prev = NULL;
286 
287   EX_FUNC_ENTER();
288   while (file) {
289     if (file->file_id == exoid) {
290       break;
291     }
292 
293     prev = file;
294     file = file->next;
295   }
296 
297   if (!file) {
298     snprintf(errmsg, MAX_ERR_LENGTH, "Warning: failure to clear file id %d - not in list.", exoid);
299     ex_err(__func__, errmsg, -EX_BADFILEID);
300     EX_FUNC_VOID();
301   }
302 
303   if (prev) {
304     prev->next = file->next;
305   }
306   else {
307     file_list = file->next;
308   }
309 
310   free(file);
311   EX_FUNC_VOID();
312 }
313 
314 /*............................................................................*/
315 /*............................................................................*/
316 
nc_flt_code(int exoid)317 nc_type nc_flt_code(int exoid)
318 {
319   /*!
320    * \ingroup Utilities
321    * nc_flt_code() returns either NC_FLOAT or NC_DOUBLE, based on the parameters
322    * with which ex__conv_init() was called.  nc_flt_code() is used as the nc_type
323    * parameter on ncvardef() calls that define floating point variables.
324    *
325    * "exoid" is some integer which uniquely identifies the file of interest.
326    */
327   EX_FUNC_ENTER();
328   struct ex__file_item *file = ex__find_file_item(exoid);
329 
330   if (!file) {
331     char errmsg[MAX_ERR_LENGTH];
332     snprintf(errmsg, MAX_ERR_LENGTH, "ERROR: unknown file id %d for nc_flt_code().", exoid);
333     ex_err(__func__, errmsg, EX_BADFILEID);
334     EX_FUNC_LEAVE((nc_type)-1);
335   }
336   EX_FUNC_LEAVE(file->netcdf_type_code);
337 }
338 
ex_int64_status(int exoid)339 int ex_int64_status(int exoid)
340 {
341   /*!
342    * \ingroup Utilities
343      ex_int64_status() returns an int that can be tested
344      against the defines listed below to determine which, if any,
345      'types' in the database are to be stored as int64 types and which, if any,
346      types are passed/returned as int64 types in the API
347 
348      | Defines: | |
349      |----------|-|
350      | #EX_MAPS_INT64_DB | All maps (id, order, ...) store int64_t values |
351      | #EX_IDS_INT64_DB  | All entity ids (sets, blocks, maps) are int64_t values |
352      | #EX_BULK_INT64_DB | All integer bulk data (local indices, counts, maps); not ids |
353      | #EX_ALL_INT64_DB  | (#EX_MAPS_INT64_DB \| #EX_IDS_INT64_DB \| #EX_BULK_INT64_DB) |
354      | #EX_MAPS_INT64_API| All maps (id, order, ...) passed as int64_t values |
355      | #EX_IDS_INT64_API | All entity ids (sets, blocks, maps) are passed as int64_t values |
356      | #EX_BULK_INT64_API| All integer bulk data (local indices, counts, maps); not ids|
357      | #EX_INQ_INT64_API | Integers passed to/from ex_inquire() are int64_t |
358      | #EX_ALL_INT64_API | (#EX_MAPS_INT64_API \| #EX_IDS_INT64_API \| #EX_BULK_INT64_API \|
359    #EX_INQ_INT64_API) |
360   */
361   EX_FUNC_ENTER();
362   struct ex__file_item *file = ex__find_file_item(exoid);
363 
364   if (!file) {
365     char errmsg[MAX_ERR_LENGTH];
366     snprintf(errmsg, MAX_ERR_LENGTH, "ERROR: unknown file id %d for ex_int64_status().", exoid);
367     ex_err(__func__, errmsg, EX_BADFILEID);
368     EX_FUNC_LEAVE(0);
369   }
370   EX_FUNC_LEAVE(file->int64_status);
371 }
372 
ex_set_int64_status(int exoid,int mode)373 int ex_set_int64_status(int exoid, int mode)
374 {
375   /*!
376     \ingroup Utilities
377 
378      ex_set_int64_status() sets the value of the INT64_API flags which
379      specify how integer types are passed/returned as int64 types in
380      the API
381 
382      | Mode can be one of: | |
383      |----------|-|
384      | 0                 | All integers are passed as int32_t values. |
385      | #EX_MAPS_INT64_API| All maps (id, order, ...) passed as int64_t values |
386      | #EX_IDS_INT64_API | All entity ids (sets, blocks, maps) are passed as int64_t values |
387      | #EX_BULK_INT64_API| All integer bulk data (local indices, counts, maps); not ids|
388      | #EX_INQ_INT64_API | Integers passed to/from ex_inquire() are int64_t |
389      | #EX_ALL_INT64_API | (#EX_MAPS_INT64_API \| #EX_IDS_INT64_API \| #EX_BULK_INT64_API \|
390     #EX_INQ_INT64_API) |
391   */
392 
393   int api_mode = 0;
394   int db_mode  = 0;
395 
396   EX_FUNC_ENTER();
397   struct ex__file_item *file = ex__find_file_item(exoid);
398 
399   if (!file) {
400     char errmsg[MAX_ERR_LENGTH];
401     snprintf(errmsg, MAX_ERR_LENGTH, "ERROR: unknown file id %d for ex_int64_status().", exoid);
402     ex_err(__func__, errmsg, EX_BADFILEID);
403     EX_FUNC_LEAVE(0);
404   }
405 
406   /* Strip of all non-INT64_API values */
407   api_mode = mode & EX_ALL_INT64_API;
408   db_mode  = file->int64_status & EX_ALL_INT64_DB;
409 
410   file->int64_status = api_mode | db_mode;
411   EX_FUNC_LEAVE(file->int64_status);
412 }
413 
414 /*!
415   \ingroup Utilities
416   \undoc
417 */
ex_set_option(int exoid,ex_option_type option,int option_value)418 int ex_set_option(int exoid, ex_option_type option, int option_value)
419 {
420   EX_FUNC_ENTER();
421   struct ex__file_item *file = ex__find_file_item(exoid);
422   if (!file) {
423     char errmsg[MAX_ERR_LENGTH];
424     snprintf(errmsg, MAX_ERR_LENGTH, "ERROR: unknown file id %d for ex_set_option().", exoid);
425     ex_err(__func__, errmsg, EX_BADFILEID);
426     EX_FUNC_LEAVE(EX_FATAL);
427   }
428 
429   switch (option) {
430   case EX_OPT_MAX_NAME_LENGTH: file->maximum_name_length = option_value; break;
431   case EX_OPT_COMPRESSION_TYPE: file->compression_algorithm = option_value; break;
432   case EX_OPT_COMPRESSION_LEVEL: /* 0 (disabled/fastest) ... 9 (best/slowest) */
433     /* Check whether file type supports compression... */
434     if (file->is_hdf5) {
435       int value = option_value;
436       if (file->compression_algorithm == EX_COMPRESS_ZLIB) {
437         if (value > 9) {
438           value = 9;
439         }
440         if (value < 0) {
441           value = 0;
442         }
443       }
444       else if (file->compression_algorithm == EX_COMPRESS_SZIP) {
445         if (value % 2 != 0 || value < 4 || value > 32) {
446           char errmsg[MAX_ERR_LENGTH];
447           snprintf(errmsg, MAX_ERR_LENGTH,
448                    "ERROR: invalid value %d for SZIP Compression.  Must be even and 4 <= value <= "
449                    "32. Ignoring.",
450                    value);
451           ex_err_fn(exoid, __func__, errmsg, EX_BADPARAM);
452           EX_FUNC_LEAVE(EX_FATAL);
453         }
454       }
455       file->compression_level = value;
456       assert(value == file->compression_level);
457     }
458     else {
459       file->compression_level = 0;
460     }
461     break;
462   case EX_OPT_COMPRESSION_SHUFFLE: /* 0 (disabled); 1 (enabled) */
463     file->shuffle = option_value != 0 ? 1 : 0;
464     break;
465   case EX_OPT_INTEGER_SIZE_API: /* See *_INT64_* values above */
466     ex_set_int64_status(exoid, option_value);
467     break;
468   case EX_OPT_INTEGER_SIZE_DB: /* (query only) */ break;
469   default: {
470     char errmsg[MAX_ERR_LENGTH];
471     snprintf(errmsg, MAX_ERR_LENGTH, "ERROR: invalid option %d for ex_set_option().", (int)option);
472     ex_err_fn(exoid, __func__, errmsg, EX_BADPARAM);
473     EX_FUNC_LEAVE(EX_FATAL);
474   }
475   }
476   EX_FUNC_LEAVE(EX_NOERR);
477 }
478 
479 /*!
480  * \ingroup Utilities
481  * ex__comp_ws() returns 4 (i.e. sizeof(float)) or 8 (i.e. sizeof(double)),
482  * depending on the value of floating point word size used to initialize
483  * the conversion facility for this file id (exoid).
484  * \param exoid  integer which uniquely identifies the file of interest.
485  */
ex__comp_ws(int exoid)486 int ex__comp_ws(int exoid)
487 {
488   struct ex__file_item *file = ex__find_file_item(exoid);
489 
490   if (!file) {
491     char errmsg[MAX_ERR_LENGTH];
492     snprintf(errmsg, MAX_ERR_LENGTH, "ERROR: unknown file id %d", exoid);
493     ex_err(__func__, errmsg, EX_BADFILEID);
494     return (EX_FATAL);
495   }
496   /* Stored as 0 for 4-byte; 1 for 8-byte */
497   return ((file->user_compute_wordsize + 1) * 4);
498 }
499 
500 /*!
501  * \ingroup Utilities
502  * ex__is_parallel() returns 1 (true) or 0 (false) depending on whether
503  * the file was opened in parallel or serial/file-per-processor mode.
504  * Note that in this case parallel assumes the output of a single file,
505  * not a parallel run using file-per-processor.
506  * \param exoid  integer which uniquely identifies the file of interest.
507  */
ex__is_parallel(int exoid)508 int ex__is_parallel(int exoid)
509 {
510   EX_FUNC_ENTER();
511   struct ex__file_item *file = ex__find_file_item(exoid);
512 
513   if (!file) {
514     char errmsg[MAX_ERR_LENGTH];
515     snprintf(errmsg, MAX_ERR_LENGTH, "ERROR: unknown file id %d", exoid);
516     ex_err(__func__, errmsg, EX_BADFILEID);
517     EX_FUNC_LEAVE(EX_FATAL);
518   }
519   /* Stored as 1 for parallel, 0 for serial or file-per-processor */
520   EX_FUNC_LEAVE(file->is_parallel);
521 }
522 
523 /*!
524  * \ingroup Utilities
525  * \note
526  * Do not use this unless you know what you are doing and why you
527  * are doing it.  One use is if calling ex_get_partial_set() in a
528  * serial mode (proc 0 only) on a file opened in parallel.
529  * Make sure to reset the value to original value after done with
530  * special case...
531  *
532  * ex_set_parallel() sets the parallel setting for a file.
533  * returns 1 (true) or 0 (false) depending on the current setting.
534  * \param exoid  integer which uniquely identifies the file of interest.
535  * \param is_parallel 1 if parallel, 0 if serial.
536  */
ex_set_parallel(int exoid,int is_parallel)537 int ex_set_parallel(int exoid, int is_parallel)
538 {
539   EX_FUNC_ENTER();
540   int                   old_value = 0;
541   struct ex__file_item *file      = ex__find_file_item(exoid);
542 
543   if (!file) {
544     char errmsg[MAX_ERR_LENGTH];
545     snprintf(errmsg, MAX_ERR_LENGTH, "ERROR: unknown file id %d", exoid);
546     ex_err(__func__, errmsg, EX_BADFILEID);
547     EX_FUNC_LEAVE(EX_FATAL);
548   }
549 
550   old_value         = file->is_parallel;
551   file->is_parallel = is_parallel;
552   /* Stored as 1 for parallel, 0 for serial or file-per-processor */
553   EX_FUNC_LEAVE(old_value);
554 }
555