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