Add support for reading and writing SPV files.
[pspp] / utilities / pspp-convert.c
1 /* PSPP - a program for statistical analysis.
2    Copyright (C) 2013, 2014, 2015, 2016 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 <errno.h>
20 #include <getopt.h>
21 #include <limits.h>
22 #include <stdlib.h>
23 #include <unistd.h>
24
25 #include "data/any-reader.h"
26 #include "data/casereader.h"
27 #include "data/casewriter.h"
28 #include "data/csv-file-writer.h"
29 #include "data/dictionary.h"
30 #include "data/encrypted-file.h"
31 #include "data/file-name.h"
32 #include "data/por-file-writer.h"
33 #include "data/settings.h"
34 #include "data/sys-file-writer.h"
35 #include "data/file-handle-def.h"
36 #include "libpspp/assertion.h"
37 #include "libpspp/cast.h"
38 #include "libpspp/i18n.h"
39
40 #include "gl/error.h"
41 #include "gl/getpass.h"
42 #include "gl/progname.h"
43 #include "gl/version-etc.h"
44
45 #include "gettext.h"
46 #define _(msgid) gettext (msgid)
47
48 static void usage (void);
49
50 static bool decrypt_file (struct encrypted_file *enc,
51                           const struct file_handle *input_filename,
52                           const struct file_handle *output_filename,
53                           const char *password,
54                           const char *alphabet, int max_length,
55                           const char *password_list);
56
57 int
58 main (int argc, char *argv[])
59 {
60   const char *input_filename;
61   const char *output_filename;
62
63   long long int max_cases = LLONG_MAX;
64   struct dictionary *dict = NULL;
65   struct casereader *reader;
66   struct file_handle *input_fh = NULL;
67   const char *encoding = NULL;
68   struct encrypted_file *enc;
69
70   const char *output_format = NULL;
71   struct file_handle *output_fh = NULL;
72   struct casewriter *writer;
73   const char *password = NULL;
74   struct string alphabet = DS_EMPTY_INITIALIZER;
75   const char *password_list = NULL;
76   int length = 0;
77
78   long long int i;
79
80   set_program_name (argv[0]);
81   i18n_init ();
82   fh_init ();
83   settings_init ();
84
85   for (;;)
86     {
87       enum
88         {
89           OPT_PASSWORD_LIST = UCHAR_MAX + 1,
90         };
91       static const struct option long_options[] =
92         {
93           { "cases",    required_argument, NULL, 'c' },
94           { "encoding", required_argument, NULL, 'e' },
95
96           { "password", required_argument, NULL, 'p' },
97           { "password-alphabet", required_argument, NULL, 'a' },
98           { "password-length", required_argument, NULL, 'l' },
99           { "password-list", required_argument, NULL, OPT_PASSWORD_LIST },
100
101           { "output-format", required_argument, NULL, 'O' },
102
103           { "help",    no_argument,       NULL, 'h' },
104           { "version", no_argument,       NULL, 'v' },
105           { NULL,      0,                 NULL, 0 },
106         };
107
108       int c;
109
110       c = getopt_long (argc, argv, "c:e:p:a:l:O:hv", long_options, NULL);
111       if (c == -1)
112         break;
113
114       switch (c)
115         {
116         case 'c':
117           max_cases = strtoull (optarg, NULL, 0);
118           break;
119
120         case 'e':
121           encoding = optarg;
122           break;
123
124         case 'p':
125           password = optarg;
126           break;
127
128         case 'l':
129           length = atoi (optarg);
130           break;
131
132         case OPT_PASSWORD_LIST:
133           password_list = optarg;
134           break;
135
136         case 'a':
137           for (const char *p = optarg; *p; )
138             if (p[1] == '-' && p[2] > p[0])
139               {
140                 for (int ch = p[0]; ch <= p[2]; ch++)
141                   ds_put_byte (&alphabet, ch);
142                 p += 3;
143               }
144             else
145               ds_put_byte (&alphabet, *p++);
146           break;
147
148         case 'O':
149           output_format = optarg;
150           break;
151
152         case 'v':
153           version_etc (stdout, "pspp-convert", PACKAGE_NAME, PACKAGE_VERSION,
154                        "Ben Pfaff", "John Darrington", NULL_SENTINEL);
155           exit (EXIT_SUCCESS);
156
157         case 'h':
158           usage ();
159           exit (EXIT_SUCCESS);
160
161         default:
162           goto error;
163         }
164     }
165
166   if (optind + 2 != argc)
167     error (1, 0, _("exactly two non-option arguments are required; "
168                    "use --help for help"));
169
170   input_filename = argv[optind];
171   output_filename = argv[optind + 1];
172   input_fh = fh_create_file (NULL, input_filename, NULL, fh_default_properties ());
173
174   if (output_format == NULL)
175     {
176       const char *dot = strrchr (output_filename, '.');
177       if (dot == NULL)
178         error (1, 0, _("%s: cannot guess output format (use -O option)"),
179                output_filename);
180
181       output_format = dot + 1;
182     }
183
184   output_fh = fh_create_file (NULL, output_filename, NULL, fh_default_properties ());
185   if (encrypted_file_open (&enc, input_fh) > 0)
186     {
187       if (encrypted_file_is_sav (enc))
188         {
189           if (strcmp (output_format, "sav") && strcmp (output_format, "sys"))
190             error (1, 0, _("can only convert encrypted data file to sav or "
191                            "sys format"));
192         }
193       else
194         {
195           if (strcmp (output_format, "sps"))
196             error (1, 0, _("can only convert encrypted syntax file to sps "
197                            "format"));
198         }
199
200       if (!decrypt_file (enc, input_fh, output_fh, password,
201                          ds_cstr (&alphabet), length, password_list))
202         goto error;
203
204       goto exit;
205     }
206
207
208   reader = any_reader_open_and_decode (input_fh, encoding, &dict, NULL);
209   if (reader == NULL)
210     goto error;
211
212   if (!strcmp (output_format, "csv") || !strcmp (output_format, "txt"))
213     {
214       struct csv_writer_options options;
215
216       csv_writer_options_init (&options);
217       options.include_var_names = true;
218       writer = csv_writer_open (output_fh, dict, &options);
219     }
220   else if (!strcmp (output_format, "sav") || !strcmp (output_format, "sys"))
221     {
222       struct sfm_write_options options;
223
224       options = sfm_writer_default_options ();
225       writer = sfm_open_writer (output_fh, dict, options);
226     }
227   else if (!strcmp (output_format, "por"))
228     {
229       struct pfm_write_options options;
230
231       options = pfm_writer_default_options ();
232       writer = pfm_open_writer (output_fh, dict, options);
233     }
234   else
235     {
236       error (1, 0, _("%s: unknown output format (use -O option)"),
237              output_filename);
238       NOT_REACHED ();
239     }
240   if (!writer)
241     error (1, 0, _("%s: error opening output file"), output_filename);
242
243   for (i = 0; i < max_cases; i++)
244     {
245       struct ccase *c;
246
247       c = casereader_read (reader);
248       if (c == NULL)
249         break;
250
251       casewriter_write (writer, c);
252     }
253
254   if (!casereader_destroy (reader))
255     error (1, 0, _("%s: error reading input file"), input_filename);
256   if (!casewriter_destroy (writer))
257     error (1, 0, _("%s: error writing output file"), output_filename);
258
259 exit:
260   ds_destroy (&alphabet);
261   dict_unref (dict);
262   fh_unref (output_fh);
263   fh_unref (input_fh);
264   fh_done ();
265   i18n_done ();
266
267   return 0;
268
269 error:
270   ds_destroy (&alphabet);
271   dict_unref (dict);
272   fh_unref (output_fh);
273   fh_unref (input_fh);
274   fh_done ();
275   i18n_done ();
276
277   return 1;
278 }
279
280 static bool
281 decrypt_file (struct encrypted_file *enc,
282               const struct file_handle *ifh,
283               const struct file_handle *ofh,
284               const char *password,
285               const char *alphabet,
286               int max_length,
287               const char *password_list)
288 {
289   FILE *out;
290   int err;
291   const char *input_filename = fh_get_file_name (ifh);
292   const char *output_filename = fh_get_file_name (ofh);
293
294   if (password_list)
295     {
296       FILE *password_file;
297       if (!strcmp (password_list, "-"))
298         password_file = stdin;
299       else
300         {
301           password_file = fopen (password_list, "r");
302           if (!password_file)
303             error (1, errno, _("%s: error opening password file"),
304                    password_list);
305         }
306
307       struct string pw = DS_EMPTY_INITIALIZER;
308       unsigned int target = 100000;
309       for (unsigned int i = 0; ; i++)
310         {
311           ds_clear (&pw);
312           if (!ds_read_line (&pw, password_file, SIZE_MAX))
313             {
314               if (isatty (STDOUT_FILENO))
315                 {
316                   putchar ('\r');
317                   fflush (stdout);
318                 }
319               error (1, 0, _("\n%s: password not in file"), password_list);
320             }
321           ds_chomp_byte (&pw, '\n');
322
323           if (i >= target)
324             {
325               target += 100000;
326               if (isatty (STDOUT_FILENO))
327                 {
328                   printf ("\r%u", i);
329                   fflush (stdout);
330                 }
331             }
332
333           if (encrypted_file_unlock__ (enc, ds_cstr (&pw)))
334             {
335               printf ("\npassword is: \"%s\"\n", ds_cstr (&pw));
336               password = ds_cstr (&pw);
337               break;
338             }
339         }
340     }
341   else if (alphabet[0] && max_length)
342     {
343       size_t alphabet_size = strlen (alphabet);
344       char *pw = xmalloc (max_length + 1);
345       int *indexes = xzalloc (max_length * sizeof *indexes);
346
347       for (int len = password ? strlen (password) : 0;
348            len <= max_length; len++)
349         {
350           if (password && len == strlen (password))
351             {
352               for (int i = 0; i < len; i++)
353                 {
354                   const char *p = strchr (alphabet, password[i]);
355                   if (!p)
356                     error (1, 0, _("%s: '%c' is not in alphabet"),
357                            password, password[i]);
358                   indexes[i] = p - alphabet;
359                   pw[i] = *p;
360                 }
361             }
362           else
363             {
364               memset (indexes, 0, len * sizeof *indexes);
365               for (int i = 0; i < len; i++)
366                 pw[i] = alphabet[0];
367             }
368           pw[len] = '\0';
369
370           unsigned int target = 0;
371           for (unsigned int j = 0; ; j++)
372             {
373               if (j >= target)
374                 {
375                   target += 100000;
376                   if (isatty (STDOUT_FILENO))
377                     {
378                       printf ("\rlength %d: %s", len, pw);
379                       fflush (stdout);
380                     }
381                 }
382               if (encrypted_file_unlock__ (enc, pw))
383                 {
384                   printf ("\npassword is: \"%s\"\n", pw);
385                   password = pw;
386                   goto success;
387                 }
388
389               int i;
390               for (i = 0; i < len; i++)
391                 if (++indexes[i] < alphabet_size)
392                   {
393                     pw[i] = alphabet[indexes[i]];
394                     break;
395                   }
396                 else
397                   {
398                     indexes[i] = 0;
399                     pw[i] = alphabet[indexes[i]];
400                   }
401               if (i == len)
402                 break;
403             }
404         }
405       free (indexes);
406       free (pw);
407
408     success:;
409     }
410   else
411     {
412       if (password == NULL)
413         {
414           password = getpass ("password: ");
415           if (password == NULL)
416             return false;
417         }
418
419       if (!encrypted_file_unlock (enc, password))
420         error (1, 0, _("sorry, wrong password"));
421     }
422
423   out = fn_open (ofh, "wb");
424   if (out == NULL)
425     error (1, errno, ("%s: error opening output file"), output_filename);
426
427   for (;;)
428     {
429       uint8_t buffer[1024];
430       size_t n;
431
432       n = encrypted_file_read (enc, buffer, sizeof buffer);
433       if (n == 0)
434         break;
435
436       if (fwrite (buffer, 1, n, out) != n)
437         error (1, errno, ("%s: write error"), output_filename);
438     }
439
440   err = encrypted_file_close (enc);
441   if (err)
442     error (1, err, ("%s: read error"), input_filename);
443
444   if (fflush (out) == EOF)
445     error (1, errno, ("%s: write error"), output_filename);
446   fn_close (ofh, out);
447
448   return true;
449 }
450
451 static void
452 usage (void)
453 {
454   printf ("\
455 %s, a utility for converting SPSS data files to other formats.\n\
456 Usage: %s [OPTION]... INPUT OUTPUT\n\
457 where INPUT is an SPSS data file or encrypted syntax file\n\
458   and OUTPUT is the name of the desired output file.\n\
459 \n\
460 The desired format of OUTPUT is by default inferred from its extension:\n\
461   csv txt             comma-separated value\n\
462   sav sys             SPSS system file\n\
463   por                 SPSS portable file\n\
464   sps                 SPSS syntax file (encrypted syntax input files only)\n\
465 \n\
466 General options:\n\
467   -O, --output-format=FORMAT  set specific output format, where FORMAT\n\
468                       is one of the extensions listed above\n\
469   -e, --encoding=CHARSET  override encoding of input data file\n\
470   -c MAXCASES         limit number of cases to copy (default is all cases)\n\
471 Password options (for used with encrypted files):\n\
472   -p PASSWORD         individual password\n\
473   -a ALPHABET         with -l, alphabet of passwords to try\n\
474   -l MAX-LENGTH       with -a, maximum number of characters to try\n\
475   --password-list=FILE  try all of the passwords in FILE (one per line)\n\
476 Other options:\n\
477   --help              display this help and exit\n\
478   --version           output version information and exit\n",
479           program_name, program_name);
480 }