spv: Avoid some compiler warnings on mingw.
[pspp] / src / output / spv / spv-legacy-data.c
1 /* PSPP - a program for statistical analysis.
2    Copyright (C) 2018 Free Software Foundation, Inc.
3
4    This program is free software: you can redistribute it and/or modify
5    it under the terms of the GNU General Public License as published by
6    the Free Software Foundation, either version 3 of the License, or
7    (at your option) any later version.
8
9    This program is distributed in the hope that it will be useful,
10    but WITHOUT ANY WARRANTY; without even the implied warranty of
11    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12    GNU General Public License for more details.
13
14    You should have received a copy of the GNU General Public License
15    along with this program.  If not, see <http://www.gnu.org/licenses/>. */
16
17 #include <config.h>
18
19 #include "output/spv/spv-legacy-data.h"
20
21 #include <inttypes.h>
22 #include <math.h>
23 #include <stdlib.h>
24 #include <string.h>
25
26 #include "libpspp/cast.h"
27 #include "libpspp/float-format.h"
28 #include "data/val-type.h"
29 #include "output/spv/old-binary-parser.h"
30
31 #include "gl/minmax.h"
32 #include "gl/xalloc.h"
33 #include "gl/xmemdup0.h"
34 #include "gl/xsize.h"
35 #include "gl/xvasprintf.h"
36
37 void
38 spv_data_uninit (struct spv_data *data)
39 {
40   if (!data)
41     return;
42
43   for (size_t i = 0; i < data->n_sources; i++)
44     spv_data_source_uninit (&data->sources[i]);
45   free (data->sources);
46 }
47
48 void
49 spv_data_dump (const struct spv_data *data, FILE *stream)
50 {
51   for (size_t i = 0; i < data->n_sources; i++)
52     {
53       if (i > 0)
54         putc ('\n', stream);
55       spv_data_source_dump (&data->sources[i], stream);
56     }
57 }
58
59 struct spv_data_source *
60 spv_data_find_source (const struct spv_data *data, const char *source_name)
61 {
62   for (size_t i = 0; i < data->n_sources; i++)
63     if (!strcmp (data->sources[i].source_name, source_name))
64       return &data->sources[i];
65
66   return NULL;
67 }
68
69 struct spv_data_variable *
70 spv_data_find_variable (const struct spv_data *data,
71                         const char *source_name,
72                         const char *variable_name)
73 {
74   struct spv_data_source *source = spv_data_find_source (data, source_name);
75   return source ? spv_data_source_find_variable (source, variable_name) : NULL;
76 }
77
78 void
79 spv_data_source_uninit (struct spv_data_source *source)
80 {
81   if (!source)
82     return;
83
84   for (size_t i = 0; i < source->n_vars; i++)
85     spv_data_variable_uninit (&source->vars[i]);
86   free (source->vars);
87   free (source->source_name);
88 }
89
90 void
91 spv_data_source_dump (const struct spv_data_source *source, FILE *stream)
92 {
93   fprintf (stream, "source \"%s\" (%zu values):\n",
94            source->source_name, source->n_values);
95   for (size_t i = 0; i < source->n_vars; i++)
96     spv_data_variable_dump (&source->vars[i], stream);
97 }
98
99 struct spv_data_variable *
100 spv_data_source_find_variable (const struct spv_data_source *source,
101                                const char *variable_name)
102 {
103   for (size_t i = 0; i < source->n_vars; i++)
104     if (!strcmp (source->vars[i].var_name, variable_name))
105       return &source->vars[i];
106   return NULL;
107 }
108
109 void
110 spv_data_variable_uninit (struct spv_data_variable *var)
111 {
112   if (!var)
113     return;
114
115   free (var->var_name);
116   for (size_t i = 0; i < var->n_values; i++)
117     spv_data_value_uninit (&var->values[i]);
118   free (var->values);
119 }
120
121 void
122 spv_data_variable_dump (const struct spv_data_variable *var, FILE *stream)
123 {
124   fprintf (stream, "variable \"%s\":", var->var_name);
125   for (size_t i = 0; i < var->n_values; i++)
126     {
127       if (i)
128         putc (',', stream);
129       putc (' ', stream);
130       spv_data_value_dump (&var->values[i], stream);
131     }
132   putc ('\n', stream);
133 }
134
135 void
136 spv_data_value_uninit (struct spv_data_value *value)
137 {
138   if (value && value->width >= 0)
139     free (value->s);
140 }
141
142 bool
143 spv_data_value_equal (const struct spv_data_value *a,
144                       const struct spv_data_value *b)
145 {
146   return (a->width == b->width
147           && a->index == b->index
148           && (a->width < 0
149               ? a->d == b->d
150               : !strcmp (a->s, b->s)));
151 }
152
153 struct spv_data_value *
154 spv_data_values_clone (const struct spv_data_value *src, size_t n)
155 {
156   struct spv_data_value *dst = xmemdup (src, n * sizeof *src);
157   for (size_t i = 0; i < n; i++)
158     if (dst[i].width >= 0)
159       dst[i].s = xstrdup (dst[i].s);
160   return dst;
161 }
162
163 void
164 spv_data_value_dump (const struct spv_data_value *value, FILE *stream)
165 {
166   if (value->index != SYSMIS)
167     fprintf (stream, "%.*ge-", DBL_DIG + 1, value->index);
168   if (value->width >= 0)
169     fprintf (stream, "\"%s\"", value->s);
170   else if (value->d == SYSMIS)
171     putc ('.', stream);
172   else
173     fprintf (stream, "%.*g", DBL_DIG + 1, value->d);
174 }
175 \f
176 static char *
177 decode_fixed_string (const uint8_t *buf_, size_t size)
178 {
179   const char *buf = CHAR_CAST (char *, buf_);
180   return xmemdup0 (buf, strnlen (buf, size));
181 }
182
183 static char *
184 decode_var_name (const struct spvob_metadata *md)
185 {
186   int n0 = strnlen ((char *) md->source_name, sizeof md->source_name);
187   int n1 = (n0 < sizeof md->source_name ? 0
188             : strnlen ((char *) md->ext_source_name,
189                        sizeof md->ext_source_name));
190   return xasprintf ("%.*s%.*s",
191                     n0, (char *) md->source_name,
192                     n1, (char *) md->ext_source_name);
193 }
194
195 static char * WARN_UNUSED_RESULT
196 decode_data (const uint8_t *in, size_t size, size_t data_offset,
197              struct spv_data_source *source, size_t *end_offsetp)
198 {
199   size_t var_size = xsum (288, xtimes (source->n_values, 8));
200   size_t source_size = xtimes (source->n_vars, var_size);
201   size_t end_offset = xsum (data_offset, source_size);
202   if (size_overflow_p (end_offset))
203     return xasprintf ("Data source \"%s\" exceeds supported %zu-byte size.",
204                       source->source_name, SIZE_MAX - 1);
205   if (end_offset > size)
206     return xasprintf ("%zu-byte data source \"%s\" starting at offset %#zx "
207                       "runs past end of %zu-byte ZIP member.",
208                       source_size, source->source_name, data_offset,
209                       size);
210
211   in += data_offset;
212   for (size_t i = 0; i < source->n_vars; i++)
213     {
214       struct spv_data_variable *var = &source->vars[i];
215       var->var_name = decode_fixed_string (in, 288);
216       in += 288;
217
218       var->values = xnmalloc (source->n_values, sizeof *var->values);
219       var->n_values = source->n_values;
220       for (size_t j = 0; j < source->n_values; j++)
221         {
222           var->values[j].index = SYSMIS;
223           var->values[j].width = -1;
224           var->values[j].d = float_get_double (FLOAT_IEEE_DOUBLE_LE, in);
225           in += 8;
226         }
227     }
228
229   *end_offsetp = end_offset;
230   return NULL;
231 }
232
233 static char * WARN_UNUSED_RESULT
234 decode_variable_map (const char *source_name,
235                      const struct spvob_variable_map *in,
236                      const struct spvob_labels *labels,
237                      struct spv_data_variable *out)
238 {
239   if (strcmp (in->variable_name, out->var_name))
240     return xasprintf ("Source \"%s\" variable \"%s\" mapping is associated "
241                       "with wrong variable \"%s\".",
242                       source_name, out->var_name, in->variable_name);
243
244   for (size_t i = 0; i < in->n_data; i++)
245     {
246       const struct spvob_datum_map *map = in->data[i];
247
248       if (map->value_idx >= out->n_values)
249         return xasprintf ("Source \"%s\" variable \"%s\" mapping %zu "
250                           "attempts to set 0-based value %"PRIu32" "
251                           "but source has only %zu values.",
252                           source_name, out->var_name, i,
253                           map->value_idx, out->n_values);
254       struct spv_data_value *value = &out->values[map->value_idx];
255
256       if (map->label_idx >= labels->n_labels)
257         return xasprintf ("Source \"%s\" variable \"%s\" mapping %zu "
258                           "attempts to set value %"PRIu32" to 0-based label "
259                           "%"PRIu32" but only %"PRIu32" labels are present.",
260                           source_name, out->var_name, i,
261                           map->value_idx, map->label_idx, labels->n_labels);
262       const struct spvob_label *label = labels->labels[map->label_idx];
263
264       if (value->width >= 0)
265         return xasprintf ("Source \"%s\" variable \"%s\" mapping %zu "
266                           "attempts to change string value %"PRIu32".",
267                           source_name, out->var_name, i,
268                           map->value_idx);
269 #if 0
270       else if (value->d != SYSMIS && !isnan (value->d))
271         {
272 #if 1
273           return NULL;
274 #else
275           return xasprintf ("Source \"%s\" variable \"%s\" mapping %zu "
276                           "attempts to change non-missing value %"PRIu32" "
277                           "into \"%s\".",
278                           source_name, out->var_name, i,
279                           map->value_idx,
280                           label->label);
281 #endif
282         }
283 #endif
284
285       value->width = strlen (label->label);
286       value->s = xmemdup0 (label->label, value->width);
287     }
288
289   return NULL;
290 }
291
292 static char * WARN_UNUSED_RESULT
293 decode_source_map (const struct spvob_source_map *in,
294                    const struct spvob_labels *labels,
295                    struct spv_data_source *out)
296 {
297   if (in->n_variables > out->n_vars)
298     return xasprintf ("source map for \"%s\" has %"PRIu32" variables but "
299                       "source has only %zu",
300                       out->source_name, in->n_variables, out->n_vars);
301
302   for (size_t i = 0; i < in->n_variables; i++)
303     {
304       char *error = decode_variable_map (out->source_name, in->variables[i],
305                                          labels, &out->vars[i]);
306       if (error)
307         return error;
308     }
309
310   return NULL;
311 }
312
313 static char * WARN_UNUSED_RESULT
314 decode_strings (const struct spvob_strings *in, struct spv_data *out)
315 {
316   for (size_t i = 0; i < in->maps->n_maps; i++)
317     {
318       const struct spvob_source_map *sm = in->maps->maps[i];
319       const char *name = sm->source_name;
320       struct spv_data_source *source = spv_data_find_source (out, name);
321       if (!source)
322         return xasprintf ("cannot decode source map for unknown source \"%s\"",
323                           name);
324
325       char *error = decode_source_map (sm, in->labels, source);
326       if (error)
327         return error;
328     }
329
330   return NULL;
331 }
332
333 char * WARN_UNUSED_RESULT
334 spv_legacy_data_decode (const uint8_t *in, size_t size, struct spv_data *out)
335 {
336   char *error = NULL;
337   memset (out, 0, sizeof *out);
338
339   struct spvbin_input input;
340   spvbin_input_init (&input, in, size);
341
342   struct spvob_legacy_binary *lb;
343   bool ok = spvob_parse_legacy_binary (&input, &lb);
344   if (!ok)
345     {
346       error = spvbin_input_to_error (&input, NULL);
347       goto error;
348     }
349
350   out->sources = XCALLOC (lb->n_sources, struct spv_data_source);
351   out->n_sources = lb->n_sources;
352
353   for (size_t i = 0; i < lb->n_sources; i++)
354     {
355       const struct spvob_metadata *md = lb->metadata[i];
356       struct spv_data_source *source = &out->sources[i];
357
358       source->source_name = decode_var_name (md);
359       source->n_vars = md->n_variables;
360       source->n_values = md->n_values;
361       source->vars = xcalloc (md->n_variables, sizeof *source->vars);
362
363       size_t end = -1;
364       error = decode_data (in, size, md->data_offset, source, &end);
365       if (error)
366         goto error;
367
368       input.ofs = MAX (input.ofs, end);
369     }
370
371   if (input.ofs < input.size)
372     {
373       struct spvob_strings *strings;
374       bool ok = spvob_parse_strings (&input, &strings);
375       if (!ok)
376         error = spvbin_input_to_error (&input, NULL);
377       else
378         {
379           if (input.ofs != input.size)
380             error = xasprintf ("expected end of file at offset #%zx",
381                                input.ofs);
382           else
383             error = decode_strings (strings, out);
384           spvob_free_strings (strings);
385         }
386
387       if (error)
388         goto error;
389     }
390
391   spvob_free_legacy_binary (lb);
392
393   return NULL;
394
395 error:
396   spv_data_uninit (out);
397   memset (out, 0, sizeof *out);
398   spvob_free_legacy_binary (lb);
399   return error;
400 }