1 #include "mhdf.h"
2 #include <stdlib.h>
3 #include <stdio.h>
4 #include <string.h>
5 #include <sys/stat.h>
6 #include <H5Tpublic.h>
7
8
9
10 /* Top-level validation functions */
11
12 /* check that group ID ranges are valid and non-overlapping */
13 static int check_valid_file_ids( struct mhdf_FileDesc* desc );
14
15 /* check that file doesn't contain holes (unwritten regions) */
16 static int check_file_contains_holes( const char* filename );
17
18 /* check IDs are valid for all element connectivity data */
19 static int check_valid_connectivity( mhdf_FileHandle file, struct mhdf_FileDesc* desc );
20
21 /* check that any adjacency lists contain valid IDs */
22 static int check_valid_adjacencies( mhdf_FileHandle file, struct mhdf_FileDesc* desc );
23
24 /* check that set data is consistent and that sets contain valid ids */
25 static int check_valid_sets( mhdf_FileHandle file, struct mhdf_FileDesc* desc );
26
27 /* check tag consistency and for handle tags verify that values are valid ids */
28 static int check_valid_tags( mhdf_FileHandle file, struct mhdf_FileDesc* desc );
29
30
31
32 /* Low-level helper routines */
33
34 /* Get string name from mhdf_EntDesc pointer */
35 static const char* desc_name( struct mhdf_FileDesc* desc, struct mhdf_EntDesc* grp );
36
37 /* Given a list of ID range pairs of [start_id,count] interleaved in 'ranges',
38 test if the passed file_id is contained in at least one passed range */
39 static int id_contained( long file_id, const long* ranges, int num_range );
40
41 /* Like id_contained, only returns logical and for all ids in a passed array */
42 static int ids_contained( const long* ids, int num_ids, const long* ranges, int num_ranges );
43
44 /* Like ids_contained, both input lists are in ranged format */
45 static int ranges_contained( const long* id_ranges, int num_id_ranges, const long* ranges, int num_ranges );
46
47 /* Search for a string in a null-terminated list of strings */
48 static int string_contained( const char* str, const char* const* const list );
49
50 /* Check if an array of longs contains duplicate values. Will sort passed array */
51 static int contains_duplicates( long* array, long n );
52
53 /* Check if an array of [start_id,count] range pairs containes duplicate values/overlapping ranges.
54 Will sort passed array */
55 static int ranges_contain_duplicates( long* ranges, long nranges );
56
57 /* Get dynamically allocated array of [start_id,count] range pairs corresponding to all valid
58 file IDs. If include_null is non-zero, results will also include special ID of zero. */
59 static long* all_id_ranges( struct mhdf_FileDesc* desc, int include_null, int* num_ranges_out );
60
61 /* Get dynamically allocated array of [start_id,count] range pairs corresponding to all valid
62 file IDs for entities with a specific dimension. */
63 static long* get_dim_ranges( struct mhdf_FileDesc* desc, int dim, int* num_ranges_out );
64
65 /* Merge adjacent ranges */
66 static int merge_ranges( long* ranges, int nranges );
67
68 /* Misc. high-level helper routines */
69
70 /* Check that end-index lists used in various file data for variable-length data
71 (e.g. set contents, poly connectivity, variable-length tags) is valid. If
72 min_len is non-zero, it will be considered an error if the data for any entity
73 contains less than that number of values. It is considered an error if any
74 index in the list is past the end of a dataset containing max_value entries. */
75 static int check_valid_end_indices( const long* indices, long num_idx, int min_len,
76 long start_id, long max_value,
77 const char* typestr, const char* name );
78
79 /* Do check_valid_connectivity for an element group with constant connectivity length */
80 static int check_valid_elem_conn( int idx, mhdf_FileHandle file, struct mhdf_FileDesc* desc, int conn_dim );
81
82 /* Do check_valid_connectivity for an element group with old-format variable-length connectivity */
83 static int check_valid_poly_conn( int idx, mhdf_FileHandle file, struct mhdf_FileDesc* desc, int conn_dim );
84
85 /* Do check_valid_tags for a fixed-length tag */
86 static int check_valid_tag( int tag_idx, mhdf_FileHandle file, struct mhdf_FileDesc* desc );
87
88 /* Do check_valid_tags for a variable-length tag */
89 static int check_valid_var_len_tag( int tag_idx, mhdf_FileHandle file, struct mhdf_FileDesc* desc );
90
91 /* Do check_valid_adjacencies for the adjacency list of a single entity group */
92 static int check_valid_adj_list( long start_id, long count, const long* data, long data_len,
93 const long* valid_ranges, long num_valid_ranges, const char* name );
94
95 /* Do subset of check_valid_sets for either set parent or set child data */
96 static int check_valid_parents_children( long start_id,
97 long count,
98 hid_t meta_handle,
99 hid_t data_handle,
100 long data_len,
101 int parents );
102
103 /* Do subset of check_valid_sets for set contents */
104 static int check_valid_set_contents( struct mhdf_FileDesc* desc,
105 long start_id,
106 long count,
107 hid_t meta_handle,
108 hid_t data_handle,
109 long data_len );
110
111
112 /* Comparison routines for use with qsort */
113
114 /* Compare two long int values */
lcomp(const void * p1,const void * p2)115 static int lcomp( const void* p1, const void* p2 )
116 {
117 long l1 = *(const long*)p1;
118 long l2 = *(const long*)p2;
119 return l1 < l2 ? -1 : l1 > l2 ? 1 : 0;
120 }
121
122 /* Compare start_ids of mhdf_EntDesc pointed to by passed values */
dcomp(const void * p1,const void * p2)123 static int dcomp( const void* p1, const void* p2 )
124 {
125 const struct mhdf_EntDesc* d1 = *(const struct mhdf_EntDesc**)(p1);
126 const struct mhdf_EntDesc* d2 = *(const struct mhdf_EntDesc**)(p2);
127 return lcomp( &d1->start_id, &d2->start_id );
128 }
129
130
131
132 int verbose = 0;
133
main(int argc,char * argv[])134 int main( int argc, char* argv[] )
135 {
136 int result = 0;
137 mhdf_FileHandle file;
138 mhdf_Status status;
139 unsigned long max_id;
140 struct mhdf_FileDesc* desc;
141
142 if (argc != 2) {
143 fprintf( stderr,"Usage: %s <filename>\n", argv[0] );
144 return 1;
145 }
146
147 file = mhdf_openFile( argv[1], 0, &max_id, -1, &status );
148 if (mhdf_isError( &status )) {
149 fprintf( stderr,"%s: %s\n", argv[1], mhdf_message( &status ) );
150 return 1;
151 }
152
153 desc = mhdf_getFileSummary( file, H5T_NATIVE_LONG, &status, 0 ); /*no extra set info*/
154 if (mhdf_isError( &status )) {
155 fprintf( stderr,"%s: %s\n", argv[1], mhdf_message( &status ) );
156 return 1;
157 }
158
159 if (desc->nodes.count < 1)
160 puts("WARNING: file contains no vertices.");
161 if (desc->num_elem_desc < 1)
162 puts("WARNING: file contains no elements.");
163
164 result += check_valid_file_ids( desc );
165 result += check_file_contains_holes( argv[1] );
166 result += check_valid_connectivity( file, desc );
167 result += check_valid_adjacencies( file, desc );
168 result += check_valid_sets( file, desc );
169 result += check_valid_tags( file, desc );
170
171 free( desc );
172 mhdf_closeFile( file, &status );
173 return result;
174 }
175
176
desc_name(struct mhdf_FileDesc * desc,struct mhdf_EntDesc * grp)177 static const char* desc_name( struct mhdf_FileDesc* desc, struct mhdf_EntDesc* grp )
178 {
179 struct mhdf_ElemDesc junk, *elem;
180 const size_t diff = (char*)&junk.desc - (char*)&junk;
181 static const char nodes[] = "Vertices";
182 static const char sets[] = "Sets";
183 if (grp == &desc->nodes)
184 return nodes;
185 if (grp == &desc->sets)
186 return sets;
187
188 elem = (struct mhdf_ElemDesc*)((char*)grp - diff);
189 return elem->handle;
190 }
191
check_valid_file_ids(struct mhdf_FileDesc * desc)192 int check_valid_file_ids( struct mhdf_FileDesc* desc )
193 {
194 const int ngrp = 2 + desc->num_elem_desc;
195 int i, err = 0;
196 struct mhdf_EntDesc** sorted = malloc(ngrp*sizeof(struct mhdf_EntDesc*));
197 for (i = 0; i < desc->num_elem_desc; ++i)
198 sorted[i] = &desc->elems[i].desc;
199 sorted[i++] = &desc->nodes;
200 sorted[i] = &desc->sets;
201 qsort( sorted, ngrp, sizeof(struct mhdf_EntDesc*), &dcomp );
202 for (i = 0; i < ngrp; ++i) {
203 if (sorted[i]->count < 0) {
204 printf("Group \"%s\" has negative count!\n", desc_name( desc, sorted[i] ));
205 ++err;
206 }
207 if (sorted[i]->count && sorted[i]->start_id == 0) {
208 printf("Group \"%s\" contains NULL ID!\n", desc_name( desc, sorted[i] ));
209 ++err;
210 }
211
212
213 if (i > 0 && sorted[i-1]->start_id + sorted[i-1]->count > sorted[i]->start_id) {
214 printf("Conflicting group IDs for \"%s\" [%ld,%ld] and \"%s\" [%ld,%ld]\n",
215 desc_name(desc,sorted[i-1]), sorted[i-1]->start_id, sorted[i-1]->start_id + sorted[i-1]->count - 1,
216 desc_name(desc,sorted[i ]), sorted[i ]->start_id, sorted[i ]->start_id + sorted[i ]->count - 1 );
217 ++err;
218 }
219 }
220 free(sorted);
221 return err;
222 }
223
check_file_contains_holes(const char * filename)224 int check_file_contains_holes( const char* filename )
225 {
226 #ifndef _MSC_VER
227 const int blocksize = 512;
228 #endif
229 int errorcode;
230 struct stat buf;
231
232 errorcode = stat( filename, &buf );
233 if (errorcode) {
234 perror(filename);
235 return 1;
236 }
237
238 #ifndef _MSC_VER /*Does not have st_blocks*/
239 if (buf.st_size/blocksize > buf.st_blocks+1) {
240 printf("File \"%s\" contains holes. This indicates that portions of the file were never written.\n",
241 filename);
242 return 1;
243 }
244 #endif
245
246 return 0;
247 }
248
249
check_valid_connectivity(mhdf_FileHandle file,struct mhdf_FileDesc * desc)250 int check_valid_connectivity( mhdf_FileHandle file, struct mhdf_FileDesc* desc )
251 {
252 int idx, dim, result = 0;
253
254 for (idx = 0; idx < desc->num_elem_desc; ++idx) {
255 if (!strcmp(desc->elems[idx].type,mhdf_POLYHEDRON_TYPE_NAME))
256 dim = 2;
257 else
258 dim = 0;
259
260 if (desc->elems[idx].desc.vals_per_ent == -1)
261 result += check_valid_poly_conn( idx, file, desc, dim );
262 else
263 result += check_valid_elem_conn( idx, file, desc, dim );
264 }
265
266 return result;
267 }
268
id_contained(long file_id,const long * ranges,int num_range)269 static int id_contained( long file_id, const long* ranges, int num_range )
270 {
271 const long* end = ranges + 2*num_range;
272 for (; ranges != end; ranges+=2) {
273 if (file_id >= ranges[0] && (file_id - ranges[0]) < ranges[1])
274 return 1;
275 }
276 return 0;
277 }
278
ids_contained(const long * ids,int num_ids,const long * ranges,int num_ranges)279 static int ids_contained( const long* ids, int num_ids, const long* ranges, int num_ranges )
280 {
281 int i;
282 for (i = 0; i < num_ids; ++i)
283 if (!id_contained( ids[i], ranges, num_ranges ))
284 return 0;
285 return 1;
286 }
287
ranges_contained(const long * id_ranges,int num_id_ranges,const long * ranges,int num_ranges)288 static int ranges_contained( const long* id_ranges, int num_id_ranges, const long* ranges, int num_ranges )
289 {
290 int i;
291 long start, count, avail;
292 const long* end = ranges + 2*num_ranges;
293 const long* iter;
294 for (i = 0; i < num_id_ranges; ++i) {
295 start = id_ranges[2*i];
296 count = id_ranges[2*i+1];
297 while (count > 0) {
298 for (iter = ranges; iter != end; iter+=2) {
299 if (start >= iter[0] && (start - iter[0]) < iter[1])
300 break;
301 }
302 if (iter == end)
303 return 0;
304 avail = iter[1] - (start - iter[0]);
305 count -= avail;
306 start += avail;
307 }
308 }
309 return 1;
310 }
311
string_contained(const char * str,const char * const * list)312 static int string_contained( const char* str, const char* const* list )
313 {
314 for (; *list; ++list)
315 if (!strcmp(str,*list))
316 return 1;
317 return 0;
318 }
319
get_dim_ranges(struct mhdf_FileDesc * desc,int dim,int * num_ranges_out)320 static long* get_dim_ranges( struct mhdf_FileDesc* desc, int dim, int* num_ranges_out )
321 {
322 long* ranges = 0;
323 int i, j;
324 const char* const types1D[] = { mhdf_EDGE_TYPE_NAME, 0 };
325 const char* const types2D[] = { mhdf_TRI_TYPE_NAME, mhdf_QUAD_TYPE_NAME, mhdf_POLYGON_TYPE_NAME, 0 };
326 const char* const types3D[] = { mhdf_TET_TYPE_NAME, mhdf_PYRAMID_TYPE_NAME, mhdf_PRISM_TYPE_NAME,
327 mdhf_KNIFE_TYPE_NAME, mdhf_HEX_TYPE_NAME, mhdf_POLYHEDRON_TYPE_NAME,
328 mhdf_SEPTAHEDRON_TYPE_NAME, 0 };
329
330 char const* const* typelist;
331 switch (dim) {
332 case 0:
333 *num_ranges_out = 1;
334 ranges = malloc(2*sizeof(long));
335 ranges[0] = desc->nodes.start_id;
336 ranges[1] = desc->nodes.count;
337 return ranges;
338 case 1:
339 typelist = types1D;
340 break;
341 case 2:
342 typelist = types2D;
343 break;
344 case 3:
345 typelist = types3D;
346 break;
347 default:
348 fprintf(stderr,"Internal error at %s:%d: request for entities of dimesion %d\n",
349 __FILE__, __LINE__, dim );
350 abort();
351 }
352
353 *num_ranges_out = 0;
354 for (i = 0; i < desc->num_elem_desc; ++i)
355 if (string_contained( desc->elems[i].type, typelist ))
356 ++*num_ranges_out;
357 ranges = malloc(*num_ranges_out * 2 * sizeof(long));
358 for (i = 0, j = 0; i < desc->num_elem_desc; ++i)
359 if (string_contained( desc->elems[i].type, typelist )) {
360 ranges[j++] = desc->elems[i].desc.start_id;
361 ranges[j++] = desc->elems[i].desc.count;
362 }
363
364 *num_ranges_out = merge_ranges( ranges, *num_ranges_out );
365 return ranges;
366 }
367
check_valid_elem_conn(int idx,mhdf_FileHandle file,struct mhdf_FileDesc * desc,int conn_dim)368 int check_valid_elem_conn( int idx, mhdf_FileHandle file, struct mhdf_FileDesc* desc, int conn_dim )
369 {
370 long *ranges, *buffer, *iter;
371 int num_ranges;
372 long i, invalid = 0;
373 hid_t handle;
374 mhdf_Status status;
375 const long count = desc->elems[idx].desc.count;
376 const long len = desc->elems[idx].desc.vals_per_ent;
377
378 ranges = get_dim_ranges( desc, conn_dim, &num_ranges );
379 handle = mhdf_openConnectivitySimple( file, desc->elems[idx].handle, &status );
380 if (mhdf_isError(&status)) {
381 fprintf(stderr,"Internal error opening connectivity for %s: %s\n",
382 desc->elems[idx].handle, mhdf_message(&status));
383 free(ranges);
384 return 1;
385 }
386
387 buffer = malloc( sizeof(long) * count * len );
388 mhdf_readConnectivity( handle, 0, count, H5T_NATIVE_LONG, buffer, &status );
389 if (mhdf_isError(&status)) {
390 fprintf(stderr,"Internal error reading connectivity for %s: %s\n",
391 desc->elems[idx].handle, mhdf_message(&status));
392 free(ranges);
393 free(buffer);
394 mhdf_closeData( file, handle, &status );
395 return 1;
396 }
397 mhdf_closeData( file, handle, &status );
398
399 iter = buffer;
400 for (i = 0; i < count; ++i, iter += len) {
401 if (!ids_contained( iter, len, ranges, num_ranges)) {
402 if (verbose)
403 printf("Invalid connectivity for element %ld (ID %ld) in %s\n",
404 i, i + desc->elems[idx].desc.start_id, desc->elems[idx].handle );
405 ++invalid;
406 }
407 }
408 free( buffer );
409 free( ranges );
410
411 if (invalid) {
412 printf("%ld elements with invalid connectivity in %s\n", invalid, desc->elems[idx].handle );
413 return 1;
414 }
415 return 0;
416 }
417
check_valid_end_indices(const long * indices,long num_idx,int min_len,long start_id,long max_value,const char * typestr,const char * name)418 static int check_valid_end_indices( const long* indices, long num_idx, int min_len,
419 long start_id, long max_value, const char* typestr, const char* name )
420 {
421 long i, invalid = 0, prev = -1;
422
423 if (num_idx == 0) {
424 printf("WARNING: Empty index list for %s %s\n", name, typestr );
425 return 0;
426 }
427
428 for (i = 0; i < num_idx; ++i) {
429 if (indices[i] < prev) {
430 if (verbose) {
431 if (start_id > 0)
432 printf("Invalid end index %ld for %s %ld (ID %ld). Prev index is %ld\n", indices[i], name, i, i+start_id, prev );
433 else
434 printf("Invalid end index %ld for entry %ld in %s %s. Prev index is %ld\n", indices[i], i, name, typestr, prev );
435 }
436 ++invalid;
437 }
438 else if (indices[i] - prev < min_len) {
439 if (verbose) {
440 if (start_id > 0)
441 printf("%s %ld (ID %ld) has only %ld values\n", name, i, start_id, indices[i] - prev );
442 else
443 printf("Entry %ld in %s %s has only %ld values\n", i, name, typestr, indices[i] - prev );
444 }
445 ++invalid;
446 }
447 else if (indices[i] >= max_value) {
448 if (verbose) {
449 if (start_id > 0)
450 printf("%s %ld (ID %ld) end index exceeds upper bound of %ld\n", name, i, start_id, max_value );
451 else
452 printf("Entry %ld in %s %s end index exceeds uppper bound of %ld\n", i, name, typestr, max_value );
453 }
454 ++invalid;
455 }
456 prev = indices[i];
457 }
458
459 if (invalid) {
460 printf("%ld invalid end indices for %s %s\n", invalid, typestr, name );
461 }
462
463 return invalid;
464 }
465
check_valid_poly_conn(int idx,mhdf_FileHandle file,struct mhdf_FileDesc * desc,int conn_dim)466 int check_valid_poly_conn( int idx, mhdf_FileHandle file, struct mhdf_FileDesc* desc, int conn_dim )
467 {
468 long *ranges, *buffer, *indices, *iter;
469 int num_ranges;
470 long i, invalid, num_poly, num_conn, first_id, prev, len;
471 hid_t handles[2];
472 mhdf_Status status;
473 int min_conn_len;
474 if (!strcmp( mhdf_POLYHEDRON_TYPE_NAME, desc->elems[idx].type ))
475 min_conn_len = 4;
476 else
477 min_conn_len = 3;
478
479 mhdf_openPolyConnectivity( file, desc->elems[idx].handle, &num_poly, &num_conn, &first_id, handles, &status );
480 if (mhdf_isError(&status)) {
481 fprintf(stderr,"Internal error opening connectivity for %s: %s\n",
482 desc->elems[idx].handle, mhdf_message(&status));
483 return 1;
484 }
485
486 indices = malloc( sizeof(long) * num_poly );
487 mhdf_readPolyConnIndices( handles[0], 0, num_poly, H5T_NATIVE_LONG, indices, &status );
488 if (mhdf_isError(&status)) {
489 fprintf(stderr,"Internal error reading poly indices for %s: %s\n",
490 desc->elems[idx].handle, mhdf_message(&status));
491 free(indices);
492 mhdf_closeData( file, handles[0], &status );
493 mhdf_closeData( file, handles[1], &status );
494 return 1;
495 }
496 mhdf_closeData( file, handles[0], &status );
497
498 invalid = check_valid_end_indices( indices, num_poly, min_conn_len, first_id, num_conn, "Connectivity", desc->elems[idx].handle );
499 if (invalid) {
500 free( indices );
501 mhdf_closeData( file, handles[1], &status );
502 return 1;
503 }
504
505 ranges = get_dim_ranges( desc, conn_dim, &num_ranges );
506
507 buffer = malloc( sizeof(long) * num_conn );
508 mhdf_readPolyConnIDs( handles[1], 0, num_conn, H5T_NATIVE_LONG, buffer, &status );
509 if (mhdf_isError(&status)) {
510 fprintf(stderr,"Internal error reading connectivity for %s: %s\n",
511 desc->elems[idx].handle, mhdf_message(&status));
512 free(ranges);
513 free(indices);
514 free(buffer);
515 mhdf_closeData( file, handles[1], &status );
516 return 1;
517 }
518 mhdf_closeData( file, handles[1], &status );
519
520 prev = -1;
521 iter = buffer;
522 for (i = 0; i < num_poly; ++i) {
523 len = indices[i] - prev;
524 prev = indices[i];
525 if (!ids_contained( iter, len, ranges, num_ranges)) {
526 if (verbose)
527 printf("Invalid connectivity for element %ld (ID %ld) in %s\n",
528 i, i + first_id, desc->elems[idx].handle );
529 ++invalid;
530 }
531 iter += len;
532 }
533 free( indices );
534 free( buffer );
535 free( ranges );
536
537 if (invalid) {
538 printf("%ld elements with invalid connectivity in %s\n", invalid, desc->elems[idx].handle );
539 return 1;
540 }
541 return 0;
542 }
543
check_valid_adjacencies(mhdf_FileHandle file,struct mhdf_FileDesc * desc)544 int check_valid_adjacencies( mhdf_FileHandle file, struct mhdf_FileDesc* desc )
545 {
546 const int num_ranges = desc->num_elem_desc;
547 long *ranges, *buffer;
548 int i;
549 int invalid = 0;
550 long count;
551 hid_t handle;
552 mhdf_Status status;
553
554 /* Build list of ID ranges for all elements. Consider any element ID to be a valid
555 * thing to be adjacent to. So we disallow adjacency to nodes, sets, or undefined IDs */
556 ranges = malloc( sizeof(long) * 2 * num_ranges );
557 for (i = 0; i < num_ranges; ++i) {
558 ranges[2*i ] = desc->elems[i].desc.start_id;
559 ranges[2*i+1] = desc->elems[i].desc.count;
560 }
561
562 for (i = 0; i < num_ranges; ++i) {
563 if (!desc->elems[i].have_adj)
564 continue;
565
566 handle = mhdf_openAdjacency( file, desc->elems[i].handle, &count, &status );
567 if (mhdf_isError(&status)) {
568 fprintf(stderr,"Internal error openening adjacency list for %s: %s\n",
569 desc->elems[i].handle, mhdf_message(&status));
570 free(ranges);
571 return 1;
572 }
573
574 buffer = malloc( sizeof(long) * count );
575 mhdf_readAdjacency( handle, 0, count, H5T_NATIVE_LONG, buffer, &status );
576 if (mhdf_isError(&status)) {
577 fprintf(stderr,"Internal error reading adjacency list for %s: %s\n",
578 desc->elems[i].handle, mhdf_message(&status));
579 free(ranges);
580 mhdf_closeData( file, handle, &status );
581 return 1;
582 }
583 mhdf_closeData( file, handle, &status );
584
585 invalid += check_valid_adj_list( desc->elems[i].desc.start_id,
586 desc->elems[i].desc.count,
587 buffer, count,
588 ranges, num_ranges,
589 desc->elems[i].handle );
590 free(buffer);
591 }
592
593 free(ranges);
594 return invalid;
595 }
596
check_valid_adj_list(long start_id,long count,const long * data,long data_len,const long * valid_ranges,long num_valid_ranges,const char * name)597 static int check_valid_adj_list( long start_id, long count, const long* data, long data_len,
598 const long* valid_ranges, long num_valid_ranges, const char* name )
599 {
600 long i, n, id, invalid_id = 0, invalid_vals = 0;
601 const long* iter = data;
602 const long* const end = data + data_len;
603 int result = 0;
604
605 for (i = 0; iter != end; ++i) {
606 id = *iter; ++iter;
607 if (iter == end) {
608 printf("Entry %ld in %s adjacency data (ID %ld) is truncated by the end of the adjacency list.\n",
609 i, name, id);
610 result = 1;
611 break;
612 }
613
614 if (id < start_id || (id - start_id) >= count) {
615 if (verbose)
616 printf("Entry %ld in %s adjacency data has ID %ld outside of group range [%ld,%ld].\n",
617 i, name, id, start_id, start_id+count-1);
618 ++invalid_id;
619 }
620
621 n = *iter; ++iter;
622 if (n < 1) {
623 printf("Entry %ld in %s adjacency data (ID %ld) has %ld adjacency entries.\n",
624 i, name, id, n);
625 result = 1;
626 if (n < 0)
627 break; /* data is corrupt. don't proceed further */
628 }
629
630 if (iter + n > end) {
631 printf("Entry %ld in %s adjacency data (ID %ld) is truncated by the end of the adjacency list.\n",
632 i, name, id);
633 result = 1;
634 break;
635 }
636
637 if (!ids_contained( iter, n, valid_ranges, num_valid_ranges)) {
638 if (verbose)
639 printf("Entry %ld in %s adjacency data (ID %ld) has invalid IDs in its adjacency list.\n",
640 i, name, id);
641
642 ++invalid_vals;
643 }
644 iter += n;
645 }
646
647 if (invalid_id) {
648 printf("%ld entries in %s adjacency data were for entities not in %s\n", invalid_id, name, name );
649 result = 1;
650 }
651 if (invalid_vals) {
652 printf("%ld entries in %s adjacency data contained invalid adjacent entity ids\n", invalid_id, name );
653 result = 1;
654 }
655 return result;
656 }
657
658
check_valid_sets(mhdf_FileHandle file,struct mhdf_FileDesc * desc)659 int check_valid_sets( mhdf_FileHandle file, struct mhdf_FileDesc* desc )
660 {
661 long count, start_id, data_len;
662 mhdf_Status status;
663 hid_t meta, handle;
664 int result = 0;
665
666 if (desc->sets.count == 0)
667 return 0;
668
669 meta = mhdf_openSetMeta( file, &count, &start_id, &status );
670 if (mhdf_isError(&status)) {
671 fprintf(stderr,"Internal error opening set description table: %s\n", mhdf_message(&status));
672 return 1;
673 }
674
675 if (desc->have_set_contents) {
676 handle = mhdf_openSetData( file, &data_len, &status );
677 if (mhdf_isError(&status)) {
678 fprintf(stderr,"Internal error opening set contents table: %s\n", mhdf_message(&status));
679 }
680 else {
681 result += check_valid_set_contents( desc, start_id, count, meta, handle, data_len );
682 mhdf_closeData( file, handle, &status );
683 }
684 }
685
686 if (desc->have_set_children) {
687 handle = mhdf_openSetChildren( file, &data_len, &status );
688 if (mhdf_isError(&status)) {
689 fprintf(stderr,"Internal error opening set child table: %s\n", mhdf_message(&status));
690 mhdf_closeData( file, meta, &status );
691 }
692 else {
693 result += check_valid_parents_children( start_id, count, meta, handle, data_len, 0 );
694 mhdf_closeData( file, handle, &status );
695 }
696 }
697
698 if (desc->have_set_parents) {
699 handle = mhdf_openSetParents( file, &data_len, &status );
700 if (mhdf_isError(&status)) {
701 fprintf(stderr,"Internal error opening set child table: %s\n", mhdf_message(&status));
702 mhdf_closeData( file, meta, &status );
703 }
704 else {
705 result += check_valid_parents_children( start_id, count, meta, handle, data_len, 1 );
706 mhdf_closeData( file, handle, &status );
707 }
708 }
709
710 mhdf_closeData( file, meta, &status );
711 return result;
712 }
713
contains_duplicates(long * array,long n)714 static int contains_duplicates( long* array, long n )
715 {
716 long i;
717 qsort( array, n, sizeof(long), &lcomp );
718 for (i = 1; i < n; ++i)
719 if (array[i-1] == array[i])
720 return 1;
721 return 0;
722 }
723
ranges_contain_duplicates(long * ranges,long nranges)724 static int ranges_contain_duplicates( long* ranges, long nranges )
725 {
726 long i;
727 qsort( ranges, nranges, 2*sizeof(long), &lcomp ); /* sort by first value in each pair */
728 for (i = 1; i < nranges; ++i)
729 if (ranges[2*i-2] + ranges[2*i-1] > ranges[2*i])
730 return 1;
731 return 0;
732 }
733
734
check_valid_parents_children(long start_id,long count,hid_t meta_handle,hid_t data_handle,long data_len,int parents)735 static int check_valid_parents_children( long start_id,
736 long count,
737 hid_t meta_handle,
738 hid_t data_handle,
739 long data_len,
740 int parents )
741 {
742 mhdf_Status status;
743 long *indices, *contents;
744 const char parstr[] = "Parent";
745 const char cldstr[] = "Child";
746 const char* name = parents ? parstr : cldstr;
747 long i, prev, start, n;
748 long invalid = 0, invalid_dup = 0;
749 long range[2];
750 int result = 0;
751 range[0] = start_id;
752 range[1] = count;
753
754 indices = malloc( sizeof(long) * count );
755 if (parents)
756 mhdf_readSetParentEndIndices( meta_handle, 0, count, H5T_NATIVE_LONG, indices, &status );
757 else
758 mhdf_readSetChildEndIndices( meta_handle, 0, count, H5T_NATIVE_LONG, indices, &status );
759 if (mhdf_isError(&status)) {
760 fprintf(stderr,"Internal error reading set %s end indices: %s\n", name, mhdf_message(&status));
761 free(indices);
762 return 1;
763 }
764
765 if (check_valid_end_indices( indices, count, 0, start_id, data_len, "Set", name )) {
766 free(indices);
767 return 1;
768 }
769
770 contents = malloc( sizeof(long) * data_len );
771 mhdf_readSetParentsChildren( data_handle, 0, data_len, H5T_NATIVE_LONG, contents, &status );
772 if (mhdf_isError(&status)) {
773 fprintf(stderr,"Internal error reading set %s IDs: %s\n", name, mhdf_message(&status));
774 free(indices);
775 free(contents);
776 return 1;
777 }
778
779 prev = -1;
780 for (i = 0; i < count; ++i) {
781 start = prev + 1;
782 n = indices[i] - prev;
783 prev = indices[i];
784
785 if (!ids_contained( contents+start, n, range, 1)) {
786 if (verbose)
787 printf("Set %ld (ID %ld) has invalid %s IDs.\n", i, start_id+i, name );
788 ++invalid;
789 }
790 else if (contains_duplicates( contents+start, n )) {
791 if (verbose)
792 printf("Set %ld (ID %ld) %s list contains duplicate IDs.\n", i, start_id+i, name );
793 ++invalid_dup;
794 }
795 }
796
797 free(indices);
798 free(contents);
799
800 if (invalid) {
801 printf( "%ld sets had invalid %s lists.\n", invalid, name );
802 result = 1;
803 }
804
805 if (invalid_dup) {
806 printf( "%ld sets had duplicate handles in %s lists.\n", invalid_dup, name );
807 result = 1;
808 }
809
810 return result;
811 }
812
merge_ranges(long * ranges,int nranges)813 static int merge_ranges( long* ranges, int nranges )
814 {
815 long i, n;
816
817 if (nranges < 1)
818 return 0;
819
820 /* merge adjacent */
821 qsort( ranges, nranges, 2*sizeof(long), &lcomp );
822 n = 1;
823 for (i = 1; i < nranges; ++i) {
824 if (ranges[2*n-2] + ranges[2*n-1] == ranges[2*i]) {
825 ranges[2*n-1] += ranges[2*i+1]; /*compact the range*/
826 }
827 else {
828 /* do not compact, just copy, and increase number of ranges*/
829 ranges[2*n ] = ranges[2*i];
830 ranges[2*n+1] = ranges[2*i+1];
831 ++n;
832 }
833 }
834
835 return n;
836 }
837
all_id_ranges(struct mhdf_FileDesc * desc,int include_null,int * num_ranges_out)838 static long* all_id_ranges( struct mhdf_FileDesc* desc, int include_null, int* num_ranges_out )
839 {
840 int i, num_ranges = 0;
841 struct mhdf_EntDesc* group;
842 long* ranges = malloc(2*sizeof(long)*(desc->num_elem_desc + 2 + !!include_null));
843 if (include_null) {
844 num_ranges = 1;
845 ranges[0] = 0;
846 ranges[1] = 1;
847 }
848 for (i = -1; i <= desc->num_elem_desc; ++i) {
849 if (i == -1)
850 group = &desc->nodes;
851 else if (i == desc->num_elem_desc)
852 group = &desc->sets;
853 else
854 group = &desc->elems[i].desc;
855
856 if (num_ranges && ranges[2*num_ranges - 2] + ranges[2*num_ranges-1] == group->start_id) {
857 ranges[2*num_ranges-1] += group->count;
858 }
859 else {
860 ranges[2*num_ranges] = group->start_id;
861 ranges[2*num_ranges+1] = group->count;
862 ++num_ranges;
863 }
864 }
865
866
867 *num_ranges_out = merge_ranges( ranges, num_ranges );
868 return ranges;
869 }
870
check_valid_set_contents(struct mhdf_FileDesc * desc,long start_id,long count,hid_t meta_handle,hid_t data_handle,long data_len)871 static int check_valid_set_contents( struct mhdf_FileDesc* desc,
872 long start_id,
873 long count,
874 hid_t meta_handle,
875 hid_t data_handle,
876 long data_len )
877 {
878 mhdf_Status status;
879 long *indices, *contents;
880 short* flags;
881 long i, prev, start, n;
882 long invalid_len = 0, invalid_handle = 0, invalid_dup = 0;
883 long *ranges;
884 int num_ranges;
885 int tmpresult;
886
887 indices = malloc( sizeof(long) * count );
888 mhdf_readSetContentEndIndices( meta_handle, 0, count, H5T_NATIVE_LONG, indices, &status );
889 if (mhdf_isError(&status)) {
890 fprintf(stderr,"Internal error reading set content end indices: %s\n", mhdf_message(&status));
891 free(indices);
892 return 1;
893 }
894
895 if (check_valid_end_indices( indices, count, 0, start_id, data_len, "Set", "Content" )) {
896 free(indices);
897 return 1;
898 }
899
900 ranges = all_id_ranges( desc, 0, &num_ranges );
901
902 flags = malloc( sizeof(short) * count );
903 mhdf_readSetFlags( meta_handle, 0, count, H5T_NATIVE_SHORT, flags, &status );
904 if (mhdf_isError(&status)) {
905 fprintf(stderr,"Internal error reading set flags: %s\n", mhdf_message(&status));
906 free(indices);
907 free(flags);
908 free(ranges);
909 return 1;
910 }
911
912 contents = malloc( sizeof(long) * data_len );
913 mhdf_readSetData( data_handle, 0, data_len, H5T_NATIVE_LONG, contents, &status );
914 if (mhdf_isError(&status)) {
915 fprintf(stderr,"Internal error reading set content IDs: %s\n", mhdf_message(&status));
916 free(indices);
917 free(contents);
918 free(flags);
919 free(ranges);
920 return 1;
921 }
922
923 prev = -1;
924 for (i = 0; i < count; ++i) {
925 start = prev + 1;
926 n = indices[i] - prev;
927 prev = indices[i];
928
929 if (flags[i] & mhdf_SET_RANGE_BIT) {
930 if (n%2) {
931 if (verbose)
932 printf("Set %ld (ID %ld) has is marked as range-compressed but has odd number of content values.\n", i, start_id+i );
933 ++invalid_len;
934 continue;
935 }
936 tmpresult = ranges_contained( contents+start, n/2, ranges, num_ranges );
937 }
938 else {
939 tmpresult = ids_contained( contents+start, n, ranges, num_ranges);
940 }
941 if (!tmpresult) {
942 if (verbose)
943 printf("Set %ld (ID %ld) has invalid content IDs.\n", i, start_id+i );
944 ++invalid_handle;
945 continue;
946 }
947
948 if (flags[i] & mhdf_SET_ORDER_BIT)
949 continue;
950
951 if (flags[i] & mhdf_SET_RANGE_BIT)
952 tmpresult = ranges_contain_duplicates( contents+start, n/2 );
953 else
954 tmpresult = contains_duplicates( contents+start, n );
955 if(tmpresult) {
956 if (verbose)
957 printf("Set %ld (ID %ld) is not ordered but contains duplicate handles.\n", i, start_id+i );
958 ++invalid_dup;
959 }
960 }
961
962 free(indices);
963 free(contents);
964 free(flags);
965 free(ranges);
966
967 tmpresult = 0;
968 if (invalid_len) {
969 printf( "%ld ranged sets had invalid (odd) content list lengths.\n", invalid_len );
970 tmpresult = 1;
971 }
972 if (invalid_handle) {
973 printf( "%ld sets had invalid IDs in their content lists.\n", invalid_handle );
974 tmpresult = 1;
975 }
976 if (invalid_dup) {
977 printf( "%ld unordered sets had duplicate IDs in their content lists.\n", invalid_dup );
978 tmpresult = 1;
979 }
980
981 return tmpresult;
982 }
983
check_valid_tags(mhdf_FileHandle file,struct mhdf_FileDesc * desc)984 int check_valid_tags( mhdf_FileHandle file, struct mhdf_FileDesc* desc )
985 {
986 int i, result = 0;
987 for (i = 0; i < desc->num_tag_desc; ++i) {
988 if (desc->tags[i].size < 0)
989 result += check_valid_var_len_tag( i, file, desc );
990 else
991 result += check_valid_tag( i, file, desc );
992 }
993 return result;
994 }
995
check_valid_tag(int tag_idx,mhdf_FileHandle file,struct mhdf_FileDesc * desc)996 static int check_valid_tag( int tag_idx, mhdf_FileHandle file, struct mhdf_FileDesc* desc )
997 {
998 long *ids = 0, count, junk;
999 long *ranges;
1000 int nranges;
1001 hid_t handles[3];
1002 mhdf_Status status;
1003 const struct mhdf_TagDesc* tag = &(desc->tags[tag_idx]);
1004 int i, result = 0;
1005 long srange[2]={0,0};
1006 const char* name;
1007 struct mhdf_EntDesc* group;
1008 hid_t h5type;
1009 hsize_t size;
1010
1011
1012 if (tag->have_sparse) {
1013 mhdf_openSparseTagData( file, tag->name, &count, &junk, handles, &status );
1014 if (mhdf_isError(&status)) {
1015 fprintf(stderr,"Internal error opening sparse data for tag \"%s\": %s\n", tag->name, mhdf_message(&status));
1016 return 1;
1017 }
1018
1019 ids = malloc( sizeof(long) * count );
1020 mhdf_readSparseTagEntities( handles[0], 0, count, H5T_NATIVE_LONG, ids, &status );
1021 if (mhdf_isError(&status)) {
1022 fprintf(stderr,"Internal error reading sparse entities for tag \"%s\": %s\n", tag->name, mhdf_message(&status));
1023 mhdf_closeData( file, handles[0], &status );
1024 mhdf_closeData( file, handles[1], &status );
1025 free(ids);
1026 return 1;
1027 }
1028
1029 mhdf_closeData( file, handles[0], &status );
1030 ranges = all_id_ranges( desc, 0, &nranges );
1031 if (!ids_contained( ids, count, ranges, nranges )) {
1032 ++result;
1033 printf("Sparse data for tag \"%s\" has values for invalid IDs\n", tag->name );
1034 }
1035 else if (contains_duplicates( ids, count )) {
1036 ++result;
1037 printf("Sparse data for tag \"%s\" has duplicate values for one or more entities\n", tag->name );
1038 }
1039 free(ranges);
1040
1041 for (i = 0; i < tag->num_dense_indices; ++i) {
1042 if (tag->dense_elem_indices[i] == -2) {
1043 name = mhdf_set_type_handle();
1044 group = &desc->sets;
1045 }
1046 else if (tag->dense_elem_indices[i] == -1) {
1047 name = mhdf_node_type_handle();
1048 group = &desc->nodes;
1049 }
1050 else {
1051 name = desc->elems[ tag->dense_elem_indices[i] ].handle;
1052 group = &desc->elems[ tag->dense_elem_indices[i] ].desc;
1053 }
1054
1055 srange[0] = group->start_id;
1056 srange[1] = group->count;
1057 if (ids_contained( ids, count, srange, 2 )) {
1058 ++result;
1059 printf("Tag \"%s\" has both sparse values and dense values for one or more entities in \"%s\"\n", tag->name, name );
1060 }
1061 }
1062
1063 free(ids);
1064 }
1065
1066
1067 if (tag->type != mhdf_ENTITY_ID) {
1068 if (tag->have_sparse)
1069 mhdf_closeData( file, handles[1], &status );
1070 return result;
1071 }
1072
1073 ranges = all_id_ranges( desc, 1, &nranges );
1074 if (tag->default_value && !ids_contained( tag->default_value, tag->size, ranges, nranges )) {
1075 ++result;
1076 printf("Handle tag \"%s\" has invalid ID(s) in its default value.\n", tag->name );
1077 }
1078 if (tag->global_value && !ids_contained( tag->global_value, tag->size, ranges, nranges )) {
1079 ++result;
1080 printf("Handle tag \"%s\" has invalid ID(s) in its global/mesh value.\n", tag->name );
1081 }
1082
1083 h5type = H5T_NATIVE_LONG;
1084 if (tag->size > 1) {
1085 size = tag->size;
1086 #if defined(H5Tarray_create_vers) && H5Tarray_create_vers > 1
1087 h5type = H5Tarray_create( H5T_NATIVE_LONG, 1, &size );
1088 #else
1089 h5type = H5Tarray_create( H5T_NATIVE_LONG, 1, &size, NULL );
1090 #endif
1091 }
1092
1093 if (tag->have_sparse) {
1094 ids = malloc( tag->size * count * sizeof(long) );
1095 mhdf_readTagValues( handles[1], 0, count, h5type, ids, &status );
1096 if (mhdf_isError(&status)) {
1097 fprintf(stderr,"Internal error reading sparse values for handle tag \"%s\": %s\n", tag->name, mhdf_message(&status));
1098 mhdf_closeData( file, handles[1], &status );
1099 free(ids);
1100 free(ranges);
1101 if (tag->size > 1)
1102 H5Tclose( h5type );
1103 return 1;
1104 }
1105 mhdf_closeData( file, handles[1], &status );
1106
1107 if (!ids_contained( ids, tag->size * count, ranges, nranges )) {
1108 ++result;
1109 printf("Sparse data for one or more entities with handle tag \"%s\" has invalid ID(s).\n", tag->name );
1110 }
1111 free(ids);
1112 }
1113
1114 for (i = 0; i < tag->num_dense_indices; ++i) {
1115 if (tag->dense_elem_indices[i] == -2) {
1116 name = mhdf_set_type_handle();
1117 /*group = &desc->sets;*/
1118 }
1119 else if (tag->dense_elem_indices[i] == -1) {
1120 name = mhdf_node_type_handle();
1121 /*group = &desc->nodes;*/
1122 }
1123 else {
1124 name = desc->elems[ tag->dense_elem_indices[i] ].handle;
1125 /*group = &desc->elems[ tag->dense_elem_indices[i] ].desc;*/
1126 }
1127
1128 handles[0] = mhdf_openDenseTagData( file, tag->name, name, &count, &status );
1129 if (mhdf_isError(&status)) {
1130 fprintf(stderr,"Internal dense values for handle tag \"%s\" on \"%s\": %s\n", tag->name, name, mhdf_message(&status));
1131 ++result;
1132 continue;
1133 }
1134
1135 ids = malloc( tag->size * count * sizeof(long) );
1136 mhdf_readTagValues( handles[0], 0, count, h5type, ids, &status );
1137 if (mhdf_isError(&status)) {
1138 fprintf(stderr,"Internal error reading dense values for handle tag \"%s\" on \"%s\": %s\n", tag->name, name, mhdf_message(&status));
1139 mhdf_closeData( file, handles[0], &status );
1140 free(ids);
1141 ++result;
1142 continue;
1143 }
1144 mhdf_closeData( file, handles[1], &status );
1145
1146 if (!ids_contained( ids, count, ranges, nranges )) {
1147 ++result;
1148 printf("Dense data on \"%s\" for handle tag \"%s\" has invalid ID(s) for one or more entities.\n", name, tag->name );
1149 }
1150 free(ids);
1151 }
1152
1153 if (tag->size > 1)
1154 H5Tclose( h5type );
1155
1156 return result;
1157 }
1158
check_valid_var_len_tag(int tag_idx,mhdf_FileHandle file,struct mhdf_FileDesc * desc)1159 static int check_valid_var_len_tag( int tag_idx, mhdf_FileHandle file, struct mhdf_FileDesc* desc )
1160 {
1161 long *ids = 0, count, num_val;
1162 long *ranges;
1163 int nranges;
1164 hid_t handles[3];
1165 mhdf_Status status;
1166 const struct mhdf_TagDesc* tag = &(desc->tags[tag_idx]);
1167 int result = 0;
1168
1169 if (tag->num_dense_indices != 0) {
1170 printf("Dense data for tag \"%s\" not allowed for variable-length tags\n", tag->name);
1171 ++result;
1172 }
1173
1174 if (tag->have_sparse) {
1175 mhdf_openSparseTagData( file, tag->name, &count, &num_val, handles, &status );
1176 if (mhdf_isError(&status)) {
1177 fprintf(stderr,"Internal error opening sparse data for tag \"%s\": %s\n", tag->name, mhdf_message(&status));
1178 return 1;
1179 }
1180
1181 ids = malloc( sizeof(long) * count );
1182 mhdf_readSparseTagEntities( handles[0], 0, count, H5T_NATIVE_LONG, ids, &status );
1183 if (mhdf_isError(&status)) {
1184 fprintf(stderr,"Internal error reading sparse entities for tag \"%s\": %s\n", tag->name, mhdf_message(&status));
1185 mhdf_closeData( file, handles[0], &status );
1186 mhdf_closeData( file, handles[1], &status );
1187 mhdf_closeData( file, handles[2], &status );
1188 free(ids);
1189 return 1;
1190 }
1191
1192 mhdf_closeData( file, handles[0], &status );
1193 ranges = all_id_ranges( desc, 0, &nranges );
1194 if (!ids_contained( ids, count, ranges, nranges )) {
1195 ++result;
1196 printf("Sparse data for tag \"%s\" has values for invalid IDs\n", tag->name );
1197 }
1198 else if (contains_duplicates( ids, count )) {
1199 ++result;
1200 printf("Sparse data for tag \"%s\" has duplicate values for one or more entities\n", tag->name );
1201 }
1202 free(ranges);
1203
1204 mhdf_readSparseTagIndices( handles[2], 0, count, H5T_NATIVE_LONG, ids, &status );
1205 if (mhdf_isError(&status)) {
1206 fprintf(stderr,"Internal error reading indices for variable length tag \"%s\": %s\n", tag->name, mhdf_message(&status));
1207 mhdf_closeData( file, handles[1], &status );
1208 mhdf_closeData( file, handles[2], &status );
1209 free(ids);
1210 return 1;
1211 }
1212 mhdf_closeData( file, handles[2], &status );
1213
1214 if (check_valid_end_indices( ids, count, 1, 0, num_val, "Variable-length tag", tag->name ))
1215 ++result;
1216 free(ids);
1217 }
1218
1219
1220 if (tag->type != mhdf_ENTITY_ID) {
1221 if (tag->have_sparse)
1222 mhdf_closeData( file, handles[1], &status );
1223 return result;
1224 }
1225
1226 ranges = all_id_ranges( desc, 1, &nranges );
1227 if (tag->default_value && !ids_contained( tag->default_value, tag->default_value_size, ranges, nranges )) {
1228 ++result;
1229 printf("Handle tag \"%s\" has invalid ID(s) in its default value.\n", tag->name );
1230 }
1231 if (tag->global_value && !ids_contained( tag->global_value, tag->global_value_size, ranges, nranges )) {
1232 ++result;
1233 printf("Handle tag \"%s\" has invalid ID(s) in its global/mesh value.\n", tag->name );
1234 }
1235
1236 if (tag->have_sparse) {
1237 ids = malloc( num_val * sizeof(long) );
1238 mhdf_readTagValues( handles[1], 0, num_val, H5T_NATIVE_LONG, ids, &status );
1239 if (mhdf_isError(&status)) {
1240 fprintf(stderr,"Internal error reading values for variable-length handle tag \"%s\": %s\n", tag->name, mhdf_message(&status));
1241 mhdf_closeData( file, handles[1], &status );
1242 free(ids);
1243 free(ranges);
1244 return 1;
1245 }
1246 mhdf_closeData( file, handles[1], &status );
1247
1248 if (!ids_contained( ids, tag->size * count, ranges, nranges )) {
1249 ++result;
1250 printf("Data for one or more entities with variable-length handle tag \"%s\" has invalid ID(s).\n", tag->name );
1251 }
1252 free(ids);
1253 }
1254
1255 return result;
1256 }
1257