1 /*
2  * Copyright (c) 2012-2021, Christopher C. Hulbert
3  * All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions are met:
7  *
8  * 1. Redistributions of source code must retain the above copyright notice, this
9  *    list of conditions and the following disclaimer.
10  *
11  * 2. Redistributions in binary form must reproduce the above copyright notice,
12  *    this list of conditions and the following disclaimer in the documentation
13  *    and/or other materials provided with the distribution.
14  *
15  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
16  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
17  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
18  * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
19  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
20  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
21  * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
22  * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
23  * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
24  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
25  */
26 
27 #include "matio_private.h"
28 #include <stdlib.h>
29 #include <string.h>
30 #if defined(_MSC_VER) || defined(__MINGW32__)
31 #define strdup _strdup
32 #endif
33 
34 /** @brief Creates a structure MATLAB variable with the given name and fields
35  *
36  * @ingroup MAT
37  * @param name Name of the structure variable to create
38  * @param rank Rank of the variable
39  * @param dims array of dimensions of the variable of size rank
40  * @param fields Array of @c nfields fieldnames
41  * @param nfields Number of fields in the structure
42  * @return Pointer to the new structure MATLAB variable on success, NULL on error
43  */
44 matvar_t *
Mat_VarCreateStruct(const char * name,int rank,size_t * dims,const char ** fields,unsigned nfields)45 Mat_VarCreateStruct(const char *name, int rank, size_t *dims, const char **fields, unsigned nfields)
46 {
47     size_t nelems = 1;
48     int j;
49     matvar_t *matvar;
50 
51     if ( NULL == dims )
52         return NULL;
53 
54     matvar = Mat_VarCalloc();
55     if ( NULL == matvar )
56         return NULL;
57 
58     matvar->compression = MAT_COMPRESSION_NONE;
59     if ( NULL != name )
60         matvar->name = strdup(name);
61     matvar->rank = rank;
62     matvar->dims = (size_t *)malloc(matvar->rank * sizeof(*matvar->dims));
63     for ( j = 0; j < matvar->rank; j++ ) {
64         matvar->dims[j] = dims[j];
65         nelems *= dims[j];
66     }
67     matvar->class_type = MAT_C_STRUCT;
68     matvar->data_type = MAT_T_STRUCT;
69 
70     matvar->data_size = sizeof(matvar_t *);
71 
72     if ( nfields ) {
73         matvar->internal->num_fields = nfields;
74         matvar->internal->fieldnames =
75             (char **)malloc(nfields * sizeof(*matvar->internal->fieldnames));
76         if ( NULL == matvar->internal->fieldnames ) {
77             Mat_VarFree(matvar);
78             matvar = NULL;
79         } else {
80             size_t i;
81             for ( i = 0; i < nfields; i++ ) {
82                 if ( NULL == fields[i] ) {
83                     Mat_VarFree(matvar);
84                     matvar = NULL;
85                     break;
86                 } else {
87                     matvar->internal->fieldnames[i] = strdup(fields[i]);
88                 }
89             }
90         }
91         if ( NULL != matvar && nelems > 0 ) {
92             size_t nelems_x_nfields;
93             int err = Mul(&nelems_x_nfields, nelems, nfields);
94             err |= Mul(&matvar->nbytes, nelems_x_nfields, matvar->data_size);
95             if ( err ) {
96                 Mat_VarFree(matvar);
97                 return NULL;
98             }
99             matvar->data = calloc(nelems_x_nfields, matvar->data_size);
100         }
101     }
102 
103     return matvar;
104 }
105 
106 /** @brief Adds a field to a structure
107  *
108  * Adds the given field to the structure. fields should be an array of matvar_t
109  * pointers of the same size as the structure (i.e. 1 field per structure
110  * element).
111  * @ingroup MAT
112  * @param matvar Pointer to the Structure MAT variable
113  * @param fieldname Name of field to be added
114  * @retval 0 on success
115  */
116 int
Mat_VarAddStructField(matvar_t * matvar,const char * fieldname)117 Mat_VarAddStructField(matvar_t *matvar, const char *fieldname)
118 {
119     int err;
120     int cnt = 0;
121     size_t i, nfields, nelems = 1;
122     matvar_t **new_data, **old_data;
123     char **fieldnames;
124 
125     if ( matvar == NULL || fieldname == NULL )
126         return -1;
127 
128     err = Mat_MulDims(matvar, &nelems);
129     if ( err )
130         return -1;
131 
132     matvar->internal->num_fields++;
133     nfields = matvar->internal->num_fields;
134     fieldnames = (char **)realloc(matvar->internal->fieldnames,
135                                   nfields * sizeof(*matvar->internal->fieldnames));
136     if ( NULL == fieldnames )
137         return -1;
138     matvar->internal->fieldnames = fieldnames;
139     matvar->internal->fieldnames[nfields - 1] = strdup(fieldname);
140 
141     {
142         size_t nelems_x_nfields;
143         err = Mul(&nelems_x_nfields, nelems, nfields);
144         err |= Mul(&matvar->nbytes, nelems_x_nfields, sizeof(*new_data));
145         if ( err ) {
146             matvar->nbytes = 0;
147             return -1;
148         }
149     }
150     new_data = (matvar_t **)malloc(matvar->nbytes);
151     if ( new_data == NULL ) {
152         matvar->nbytes = 0;
153         return -1;
154     }
155 
156     old_data = (matvar_t **)matvar->data;
157     for ( i = 0; i < nelems; i++ ) {
158         size_t f;
159         for ( f = 0; f < nfields - 1; f++ )
160             new_data[cnt++] = old_data[i * (nfields - 1) + f];
161         new_data[cnt++] = NULL;
162     }
163 
164     free(matvar->data);
165     matvar->data = new_data;
166 
167     return 0;
168 }
169 
170 /** @brief Returns the number of fields in a structure variable
171  *
172  * Returns the number of fields in the given structure.
173  * @ingroup MAT
174  * @param matvar Structure matlab variable
175  * @returns Number of fields
176  */
177 unsigned
Mat_VarGetNumberOfFields(matvar_t * matvar)178 Mat_VarGetNumberOfFields(matvar_t *matvar)
179 {
180     int nfields;
181     if ( matvar == NULL || matvar->class_type != MAT_C_STRUCT || NULL == matvar->internal ) {
182         nfields = 0;
183     } else {
184         nfields = matvar->internal->num_fields;
185     }
186     return nfields;
187 }
188 
189 /** @brief Returns the fieldnames of a structure variable
190  *
191  * Returns the fieldnames for the given structure. The returned pointers are
192  * internal to the structure and should not be free'd.
193  * @ingroup MAT
194  * @param matvar Structure matlab variable
195  * @returns Array of fieldnames
196  */
197 char *const *
Mat_VarGetStructFieldnames(const matvar_t * matvar)198 Mat_VarGetStructFieldnames(const matvar_t *matvar)
199 {
200     if ( matvar == NULL || matvar->class_type != MAT_C_STRUCT || NULL == matvar->internal ) {
201         return NULL;
202     } else {
203         return matvar->internal->fieldnames;
204     }
205 }
206 
207 /** @brief Finds a field of a structure by the field's index
208  *
209  * Returns a pointer to the structure field at the given 0-relative index.
210  * @ingroup MAT
211  * @param matvar Pointer to the Structure MAT variable
212  * @param field_index 0-relative index of the field.
213  * @param index linear index of the structure array
214  * @return Pointer to the structure field on success, NULL on error
215  */
216 matvar_t *
Mat_VarGetStructFieldByIndex(matvar_t * matvar,size_t field_index,size_t index)217 Mat_VarGetStructFieldByIndex(matvar_t *matvar, size_t field_index, size_t index)
218 {
219     int err;
220     matvar_t *field = NULL;
221     size_t nelems = 1, nfields;
222 
223     if ( matvar == NULL || matvar->class_type != MAT_C_STRUCT || matvar->data_size == 0 )
224         return NULL;
225 
226     err = Mat_MulDims(matvar, &nelems);
227     if ( err )
228         return NULL;
229 
230     nfields = matvar->internal->num_fields;
231 
232     if ( nelems > 0 && index >= nelems ) {
233         Mat_Critical("Mat_VarGetStructField: structure index out of bounds");
234     } else if ( nfields > 0 ) {
235         if ( field_index > nfields ) {
236             Mat_Critical("Mat_VarGetStructField: field index out of bounds");
237         } else {
238             field = *((matvar_t **)matvar->data + index * nfields + field_index);
239         }
240     }
241 
242     return field;
243 }
244 
245 /** @brief Finds a field of a structure by the field's name
246  *
247  * Returns a pointer to the structure field at the given 0-relative index.
248  * @ingroup MAT
249  * @param matvar Pointer to the Structure MAT variable
250  * @param field_name Name of the structure field
251  * @param index linear index of the structure array
252  * @return Pointer to the structure field on success, NULL on error
253  */
254 matvar_t *
Mat_VarGetStructFieldByName(matvar_t * matvar,const char * field_name,size_t index)255 Mat_VarGetStructFieldByName(matvar_t *matvar, const char *field_name, size_t index)
256 {
257     int i, nfields, field_index, err;
258     matvar_t *field = NULL;
259     size_t nelems = 1;
260 
261     if ( matvar == NULL || matvar->class_type != MAT_C_STRUCT || matvar->data_size == 0 )
262         return NULL;
263 
264     err = Mat_MulDims(matvar, &nelems);
265     if ( err )
266         return NULL;
267 
268     nfields = matvar->internal->num_fields;
269     field_index = -1;
270     for ( i = 0; i < nfields; i++ ) {
271         if ( !strcmp(matvar->internal->fieldnames[i], field_name) ) {
272             field_index = i;
273             break;
274         }
275     }
276 
277     if ( index >= nelems ) {
278         Mat_Critical("Mat_VarGetStructField: structure index out of bounds");
279     } else if ( field_index >= 0 ) {
280         field = *((matvar_t **)matvar->data + index * nfields + field_index);
281     }
282 
283     return field;
284 }
285 
286 /** @brief Finds a field of a structure
287  *
288  * Returns a pointer to the structure field at the given 0-relative index.
289  * @ingroup MAT
290  * @param matvar Pointer to the Structure MAT variable
291  * @param name_or_index Name of the field, or the 1-relative index of the field
292  * If the index is used, it should be the address of an integer variable whose
293  * value is the index number.
294  * @param opt MAT_BY_NAME if the name_or_index is the name or MAT_BY_INDEX if
295  *            the index was passed.
296  * @param index linear index of the structure to find the field of
297  * @return Pointer to the Structure Field on success, NULL on error
298  */
299 matvar_t *
Mat_VarGetStructField(matvar_t * matvar,void * name_or_index,int opt,int index)300 Mat_VarGetStructField(matvar_t *matvar, void *name_or_index, int opt, int index)
301 {
302     int err, nfields;
303     matvar_t *field = NULL;
304     size_t nelems = 1;
305 
306     err = Mat_MulDims(matvar, &nelems);
307     nfields = matvar->internal->num_fields;
308     if ( index < 0 || (nelems > 0 && (size_t)index >= nelems) )
309         err = 1;
310     else if ( nfields < 1 )
311         err = 1;
312 
313     if ( !err && (opt == MAT_BY_INDEX) ) {
314         size_t field_index = *(int *)name_or_index;
315         if ( field_index > 0 )
316             field = Mat_VarGetStructFieldByIndex(matvar, field_index - 1, index);
317     } else if ( !err && (opt == MAT_BY_NAME) ) {
318         field = Mat_VarGetStructFieldByName(matvar, (const char *)name_or_index, index);
319     }
320 
321     return field;
322 }
323 
324 /** @brief Indexes a structure
325  *
326  * Finds structures of a structure array given a start, stride, and edge for
327  * each dimension.  The structures are placed in a new structure array.  If
328  * copy_fields is non-zero, the indexed structures are copied and should be
329  * freed, but if copy_fields is zero, the indexed structures are pointers to
330  * the original, but should still be freed. The structures have a flag set
331  * so that the structure fields are not freed.
332  *
333  * Note that this function is limited to structure arrays with a rank less than
334  * 10.
335  *
336  * @ingroup MAT
337  * @param matvar Structure matlab variable
338  * @param start vector of length rank with 0-relative starting coordinates for
339  *              each dimension.
340  * @param stride vector of length rank with strides for each dimension.
341  * @param edge vector of length rank with the number of elements to read in
342  *              each dimension.
343  * @param copy_fields 1 to copy the fields, 0 to just set pointers to them.
344  * @returns A new structure array with fields indexed from @c matvar.
345  */
346 matvar_t *
Mat_VarGetStructs(matvar_t * matvar,int * start,int * stride,int * edge,int copy_fields)347 Mat_VarGetStructs(matvar_t *matvar, int *start, int *stride, int *edge, int copy_fields)
348 {
349     size_t i, N, I, nfields, field,
350         idx[10] =
351             {
352                 0,
353             },
354         cnt[10] =
355             {
356                 0,
357             },
358         dimp[10] = {
359             0,
360         };
361     matvar_t **fields, *struct_slab;
362     int j;
363 
364     if ( matvar == NULL || start == NULL || stride == NULL || edge == NULL ) {
365         return NULL;
366     } else if ( matvar->rank > 9 ) {
367         return NULL;
368     } else if ( matvar->class_type != MAT_C_STRUCT ) {
369         return NULL;
370     }
371 
372     struct_slab = Mat_VarDuplicate(matvar, 0);
373     if ( !copy_fields )
374         struct_slab->mem_conserve = 1;
375 
376     nfields = matvar->internal->num_fields;
377 
378     dimp[0] = matvar->dims[0];
379     N = edge[0];
380     I = start[0];
381     struct_slab->dims[0] = edge[0];
382     idx[0] = start[0];
383     for ( j = 1; j < matvar->rank; j++ ) {
384         idx[j] = start[j];
385         dimp[j] = dimp[j - 1] * matvar->dims[j];
386         N *= edge[j];
387         I += start[j] * dimp[j - 1];
388         struct_slab->dims[j] = edge[j];
389     }
390     I *= nfields;
391     struct_slab->nbytes = N * nfields * sizeof(matvar_t *);
392     struct_slab->data = malloc(struct_slab->nbytes);
393     if ( struct_slab->data == NULL ) {
394         Mat_VarFree(struct_slab);
395         return NULL;
396     }
397     fields = (matvar_t **)struct_slab->data;
398     for ( i = 0; i < N; i += edge[0] ) {
399         for ( j = 0; j < edge[0]; j++ ) {
400             for ( field = 0; field < nfields; field++ ) {
401                 if ( copy_fields )
402                     fields[(i + j) * nfields + field] =
403                         Mat_VarDuplicate(*((matvar_t **)matvar->data + I), 1);
404                 else
405                     fields[(i + j) * nfields + field] = *((matvar_t **)matvar->data + I);
406                 I++;
407             }
408             I += (stride[0] - 1) * nfields;
409         }
410         idx[0] = start[0];
411         I = idx[0];
412         cnt[1]++;
413         idx[1] += stride[1];
414         for ( j = 1; j < matvar->rank; j++ ) {
415             if ( cnt[j] == (size_t)edge[j] ) {
416                 cnt[j] = 0;
417                 idx[j] = start[j];
418                 if ( j < matvar->rank - 1 ) {
419                     cnt[j + 1]++;
420                     idx[j + 1] += stride[j + 1];
421                 }
422             }
423             I += idx[j] * dimp[j - 1];
424         }
425         I *= nfields;
426     }
427     return struct_slab;
428 }
429 
430 /** @brief Indexes a structure
431  *
432  * Finds structures of a structure array given a single (linear)start, stride,
433  * and edge.  The structures are placed in a new structure array.  If
434  * copy_fields is non-zero, the indexed structures are copied and should be
435  * freed, but if copy_fields is zero, the indexed structures are pointers to
436  * the original, but should still be freed since the mem_conserve flag is set
437  * so that the structures are not freed.
438  * MAT file version must be 5.
439  * @ingroup MAT
440  * @param matvar Structure matlab variable
441  * @param start starting index (0-relative)
442  * @param stride stride (1 reads consecutive elements)
443  * @param edge Number of elements to read
444  * @param copy_fields 1 to copy the fields, 0 to just set pointers to them.
445  * @returns A new structure with fields indexed from matvar
446  */
447 matvar_t *
Mat_VarGetStructsLinear(matvar_t * matvar,int start,int stride,int edge,int copy_fields)448 Mat_VarGetStructsLinear(matvar_t *matvar, int start, int stride, int edge, int copy_fields)
449 {
450     matvar_t *struct_slab;
451 
452     if ( matvar == NULL || matvar->rank > 10 ) {
453         struct_slab = NULL;
454     } else {
455         int i, I, field, nfields;
456         matvar_t **fields;
457 
458         struct_slab = Mat_VarDuplicate(matvar, 0);
459         if ( !copy_fields )
460             struct_slab->mem_conserve = 1;
461 
462         nfields = matvar->internal->num_fields;
463 
464         struct_slab->nbytes = (size_t)edge * nfields * sizeof(matvar_t *);
465         struct_slab->data = malloc(struct_slab->nbytes);
466         if ( struct_slab->data == NULL ) {
467             Mat_VarFree(struct_slab);
468             return NULL;
469         }
470         struct_slab->dims[0] = edge;
471         struct_slab->dims[1] = 1;
472         fields = (matvar_t **)struct_slab->data;
473         I = start * nfields;
474         for ( i = 0; i < edge; i++ ) {
475             if ( copy_fields ) {
476                 for ( field = 0; field < nfields; field++ ) {
477                     fields[i * nfields + field] =
478                         Mat_VarDuplicate(*((matvar_t **)matvar->data + I), 1);
479                     I++;
480                 }
481             } else {
482                 for ( field = 0; field < nfields; field++ ) {
483                     fields[i * nfields + field] = *((matvar_t **)matvar->data + I);
484                     I++;
485                 }
486             }
487             I += (stride - 1) * nfields;
488         }
489     }
490     return struct_slab;
491 }
492 
493 /** @brief Sets the structure field to the given variable
494  *
495  * Sets the structure field specified by the 0-relative field index
496  * @c field_index for the given 0-relative structure index @c index to
497  * @c field.
498  * @ingroup MAT
499  * @param matvar Pointer to the structure MAT variable
500  * @param field_index 0-relative index of the field.
501  * @param index linear index of the structure array
502  * @param field New field variable
503  * @return Pointer to the previous field (NULL if no previous field)
504  */
505 matvar_t *
Mat_VarSetStructFieldByIndex(matvar_t * matvar,size_t field_index,size_t index,matvar_t * field)506 Mat_VarSetStructFieldByIndex(matvar_t *matvar, size_t field_index, size_t index, matvar_t *field)
507 {
508     int err;
509     matvar_t *old_field = NULL;
510     size_t nelems = 1, nfields;
511 
512     if ( matvar == NULL || matvar->class_type != MAT_C_STRUCT || matvar->data == NULL )
513         return NULL;
514 
515     err = Mat_MulDims(matvar, &nelems);
516     if ( err )
517         return NULL;
518 
519     nfields = matvar->internal->num_fields;
520 
521     if ( index < nelems && field_index < nfields ) {
522         matvar_t **fields = (matvar_t **)matvar->data;
523         old_field = fields[index * nfields + field_index];
524         fields[index * nfields + field_index] = field;
525         if ( NULL != field->name ) {
526             free(field->name);
527         }
528         field->name = strdup(matvar->internal->fieldnames[field_index]);
529     }
530 
531     return old_field;
532 }
533 
534 /** @brief Sets the structure field to the given variable
535  *
536  * Sets the specified structure fieldname at the given 0-relative @c index to
537  * @c field.
538  * @ingroup MAT
539  * @param matvar Pointer to the Structure MAT variable
540  * @param field_name Name of the structure field
541  * @param index linear index of the structure array
542  * @param field New field variable
543  * @return Pointer to the previous field (NULL if no previous field)
544  */
545 matvar_t *
Mat_VarSetStructFieldByName(matvar_t * matvar,const char * field_name,size_t index,matvar_t * field)546 Mat_VarSetStructFieldByName(matvar_t *matvar, const char *field_name, size_t index, matvar_t *field)
547 {
548     int err, i, nfields, field_index;
549     matvar_t *old_field = NULL;
550     size_t nelems = 1;
551 
552     if ( matvar == NULL || matvar->class_type != MAT_C_STRUCT || matvar->data == NULL )
553         return NULL;
554 
555     err = Mat_MulDims(matvar, &nelems);
556     if ( err )
557         return NULL;
558 
559     nfields = matvar->internal->num_fields;
560     field_index = -1;
561     for ( i = 0; i < nfields; i++ ) {
562         if ( !strcmp(matvar->internal->fieldnames[i], field_name) ) {
563             field_index = i;
564             break;
565         }
566     }
567 
568     if ( index < nelems && field_index >= 0 ) {
569         matvar_t **fields = (matvar_t **)matvar->data;
570         old_field = fields[index * nfields + field_index];
571         fields[index * nfields + field_index] = field;
572         if ( NULL != field->name ) {
573             free(field->name);
574         }
575         field->name = strdup(matvar->internal->fieldnames[field_index]);
576     }
577 
578     return old_field;
579 }
580