work on docs
[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 "language/command.h"
37 #include "language/lexer/lexer.h"
38 #include "language/lexer/variable-parser.h"
39 #include "libpspp/assertion.h"
40 #include "libpspp/cast.h"
41 #include "libpspp/i18n.h"
42
43 #include "gl/error.h"
44 #include "gl/getpass.h"
45 #include "gl/localcharset.h"
46 #include "gl/progname.h"
47 #include "gl/version-etc.h"
48
49 #include "gettext.h"
50 #define _(msgid) gettext (msgid)
51
52 static void usage (void);
53
54 static bool decrypt_file (struct encrypted_file *enc,
55                           const struct file_handle *input_filename,
56                           const struct file_handle *output_filename,
57                           const char *password,
58                           const char *alphabet, int max_length,
59                           const char *password_list);
60
61 static void
62 parse_character_option (const char *arg, const char *option_name, char *out)
63 {
64   if (strlen (arg) != 1)
65     {
66       /* XXX support multibyte characters */
67       error (1, 0, _("%s argument must be a single character"), option_name);
68     }
69   *out = arg[0];
70 }
71
72 static bool
73 parse_variables_option (const char *arg, struct dictionary *dict,
74                         struct variable ***vars, size_t *n_vars)
75 {
76   struct lexer *lexer = lex_create ();
77   lex_append (lexer, lex_reader_for_string (arg, locale_charset ()));
78   lex_get (lexer);
79
80   bool ok = parse_variables (lexer, dict, vars, n_vars, 0);
81   if (ok && (lex_token (lexer) != T_STOP && lex_token (lexer) != T_ENDCMD))
82     {
83       lex_error (lexer, _("expecting variable name"));
84       ok = false;
85     }
86
87   lex_destroy (lexer);
88   if (!ok)
89     {
90       free (*vars);
91       *vars = NULL;
92       *n_vars = 0;
93     }
94   return ok;
95 }
96
97 int
98 main (int argc, char *argv[])
99 {
100   const char *input_filename;
101   const char *output_filename;
102
103   long long int max_cases = LLONG_MAX;
104   const char *keep = NULL;
105   const char *drop = NULL;
106   struct dictionary *dict = NULL;
107   struct casereader *reader = NULL;
108   struct file_handle *input_fh = NULL;
109   const char *encoding = NULL;
110   struct encrypted_file *enc;
111
112   const char *output_format = NULL;
113   struct file_handle *output_fh = NULL;
114   struct casewriter *writer;
115   const char *password = NULL;
116   struct string alphabet = DS_EMPTY_INITIALIZER;
117   const char *password_list = NULL;
118   int length = 0;
119
120   struct csv_writer_options csv_opts = {
121     .include_var_names = true,
122     .decimal = '.',
123     .delimiter = 0,             /* The default will be set later. */
124     .qualifier = '"',
125   };
126
127   long long int i;
128
129   set_program_name (argv[0]);
130   i18n_init ();
131   fh_init ();
132   settings_init ();
133
134   for (;;)
135     {
136       enum
137         {
138           OPT_PASSWORD_LIST = UCHAR_MAX + 1,
139           OPT_RECODE,
140           OPT_NO_VAR_NAMES,
141           OPT_LABELS,
142           OPT_PRINT_FORMATS,
143           OPT_DECIMAL,
144           OPT_DELIMITER,
145           OPT_QUALIFIER,
146         };
147       static const struct option long_options[] =
148         {
149           { "cases", required_argument, NULL, 'c' },
150           { "keep", required_argument, NULL, 'k' },
151           { "drop", required_argument, NULL, 'd' },
152           { "encoding", required_argument, NULL, 'e' },
153
154           { "recode", no_argument, NULL, OPT_RECODE },
155           { "no-var-names", no_argument, NULL, OPT_NO_VAR_NAMES },
156           { "labels", no_argument, NULL, OPT_LABELS },
157           { "print-formats", no_argument, NULL, OPT_PRINT_FORMATS },
158           { "decimal", required_argument, NULL, OPT_DECIMAL },
159           { "delimiter", required_argument, NULL, OPT_DELIMITER },
160           { "qualifier", required_argument, NULL, OPT_QUALIFIER },
161
162           { "password", required_argument, NULL, 'p' },
163           { "password-alphabet", required_argument, NULL, 'a' },
164           { "password-length", required_argument, NULL, 'l' },
165           { "password-list", required_argument, NULL, OPT_PASSWORD_LIST },
166
167           { "output-format", required_argument, NULL, 'O' },
168
169           { "help",    no_argument,       NULL, 'h' },
170           { "version", no_argument,       NULL, 'v' },
171           { NULL,      0,                 NULL, 0 },
172         };
173
174       int c;
175
176       c = getopt_long (argc, argv, "c:k:d:e:p:a:l:O:hv", long_options, NULL);
177       if (c == -1)
178         break;
179
180       switch (c)
181         {
182         case 'c':
183           max_cases = strtoull (optarg, NULL, 0);
184           break;
185
186         case 'k':
187           keep = optarg;
188           break;
189
190         case 'd':
191           drop = optarg;
192           break;
193
194         case 'e':
195           encoding = optarg;
196           break;
197
198         case 'p':
199           password = optarg;
200           break;
201
202         case 'l':
203           length = atoi (optarg);
204           break;
205
206         case OPT_PASSWORD_LIST:
207           password_list = optarg;
208           break;
209
210         case OPT_RECODE:
211           csv_opts.recode_user_missing = true;
212           break;
213
214         case OPT_NO_VAR_NAMES:
215           csv_opts.include_var_names = false;
216           break;
217
218         case OPT_LABELS:
219           csv_opts.use_value_labels = true;
220           break;
221
222         case OPT_DECIMAL:
223           parse_character_option (optarg, "--decimal", &csv_opts.decimal);
224           break;
225
226         case OPT_DELIMITER:
227           parse_character_option (optarg, "--delimiter", &csv_opts.delimiter);
228           break;
229
230         case OPT_QUALIFIER:
231           parse_character_option (optarg, "--qualifier", &csv_opts.qualifier);
232           break;
233
234         case 'a':
235           for (const char *p = optarg; *p;)
236             if (p[1] == '-' && p[2] > p[0])
237               {
238                 for (int ch = p[0]; ch <= p[2]; ch++)
239                   ds_put_byte (&alphabet, ch);
240                 p += 3;
241               }
242             else
243               ds_put_byte (&alphabet, *p++);
244           break;
245
246         case 'O':
247           output_format = optarg;
248           break;
249
250         case 'v':
251           version_etc (stdout, "pspp-convert", PACKAGE_NAME, PACKAGE_VERSION,
252                        "Ben Pfaff", "John Darrington", NULL_SENTINEL);
253           exit (EXIT_SUCCESS);
254
255         case 'h':
256           usage ();
257           exit (EXIT_SUCCESS);
258
259         default:
260           goto error;
261         }
262     }
263
264   if (optind + 2 != argc)
265     error (1, 0, _("exactly two non-option arguments are required; "
266                    "use --help for help"));
267
268   input_filename = argv[optind];
269   output_filename = argv[optind + 1];
270   input_fh = fh_create_file (NULL, input_filename, NULL, fh_default_properties ());
271
272   if (output_format == NULL)
273     {
274       const char *dot = strrchr (output_filename, '.');
275       if (dot == NULL)
276         error (1, 0, _("%s: cannot guess output format (use -O option)"),
277                output_filename);
278
279       output_format = dot + 1;
280     }
281
282   output_fh = fh_create_file (NULL, output_filename, NULL, fh_default_properties ());
283   if (encrypted_file_open (&enc, input_fh) > 0)
284     {
285       if (decrypt_file (enc, input_fh, output_fh, password,
286                          ds_cstr (&alphabet), length, password_list))
287         goto exit;
288       else
289         goto error;
290     }
291
292
293   reader = any_reader_open_and_decode (input_fh, encoding, &dict, NULL);
294   if (reader == NULL)
295     goto error;
296
297   if (keep)
298     {
299       struct variable **keep_vars;
300       size_t n_keep_vars;
301       if (!parse_variables_option (keep, dict, &keep_vars, &n_keep_vars))
302         goto error;
303       dict_reorder_vars (dict, keep_vars, n_keep_vars);
304       dict_delete_consecutive_vars (dict, n_keep_vars,
305                                     dict_get_n_vars (dict) - n_keep_vars);
306       free (keep_vars);
307     }
308
309   if (drop)
310     {
311       struct variable **drop_vars;
312       size_t n_drop_vars;
313       if (!parse_variables_option (drop, dict, &drop_vars, &n_drop_vars))
314         goto error;
315       dict_delete_vars (dict, drop_vars, n_drop_vars);
316       free (drop_vars);
317     }
318
319   if (!strcmp (output_format, "csv") || !strcmp (output_format, "txt"))
320     {
321       if (!csv_opts.delimiter)
322         csv_opts.delimiter = csv_opts.decimal == '.' ? ',' : ';';
323       writer = csv_writer_open (output_fh, dict, &csv_opts);
324     }
325   else if (!strcmp (output_format, "sav") || !strcmp (output_format, "sys"))
326     {
327       struct sfm_write_options options;
328
329       options = sfm_writer_default_options ();
330       writer = sfm_open_writer (output_fh, dict, options);
331     }
332   else if (!strcmp (output_format, "por"))
333     {
334       struct pfm_write_options options;
335
336       options = pfm_writer_default_options ();
337       writer = pfm_open_writer (output_fh, dict, options);
338     }
339   else
340     {
341       error (1, 0, _("%s: unknown output format (use -O option)"),
342              output_filename);
343       NOT_REACHED ();
344     }
345   if (!writer)
346     error (1, 0, _("%s: error opening output file"), output_filename);
347
348   for (i = 0; i < max_cases; i++)
349     {
350       struct ccase *c;
351
352       c = casereader_read (reader);
353       if (c == NULL)
354         break;
355
356       casewriter_write (writer, c);
357     }
358
359   if (!casereader_destroy (reader))
360     error (1, 0, _("%s: error reading input file"), input_filename);
361   if (!casewriter_destroy (writer))
362     error (1, 0, _("%s: error writing output file"), output_filename);
363
364 exit:
365   ds_destroy (&alphabet);
366   dict_unref (dict);
367   fh_unref (output_fh);
368   fh_unref (input_fh);
369   fh_done ();
370   i18n_done ();
371
372   return 0;
373
374 error:
375   casereader_destroy (reader);
376   ds_destroy (&alphabet);
377   dict_unref (dict);
378   fh_unref (output_fh);
379   fh_unref (input_fh);
380   fh_done ();
381   i18n_done ();
382
383   return 1;
384 }
385
386 static bool
387 decrypt_file (struct encrypted_file *enc,
388               const struct file_handle *ifh,
389               const struct file_handle *ofh,
390               const char *password,
391               const char *alphabet,
392               int max_length,
393               const char *password_list)
394 {
395   FILE *out;
396   int err;
397   const char *input_filename = fh_get_file_name (ifh);
398   const char *output_filename = fh_get_file_name (ofh);
399
400   if (password_list)
401     {
402       FILE *password_file;
403       if (!strcmp (password_list, "-"))
404         password_file = stdin;
405       else
406         {
407           password_file = fopen (password_list, "r");
408           if (!password_file)
409             error (1, errno, _("%s: error opening password file"),
410                    password_list);
411         }
412
413       struct string pw = DS_EMPTY_INITIALIZER;
414       unsigned int target = 100000;
415       for (unsigned int i = 0; ; i++)
416         {
417           ds_clear (&pw);
418           if (!ds_read_line (&pw, password_file, SIZE_MAX))
419             {
420               if (isatty (STDOUT_FILENO))
421                 {
422                   putchar ('\r');
423                   fflush (stdout);
424                 }
425               error (1, 0, _("\n%s: password not in file"), password_list);
426             }
427           ds_chomp_byte (&pw, '\n');
428
429           if (i >= target)
430             {
431               target += 100000;
432               if (isatty (STDOUT_FILENO))
433                 {
434                   printf ("\r%u", i);
435                   fflush (stdout);
436                 }
437             }
438
439           if (encrypted_file_unlock__ (enc, ds_cstr (&pw)))
440             {
441               printf ("\npassword is: \"%s\"\n", ds_cstr (&pw));
442               password = ds_cstr (&pw);
443               break;
444             }
445         }
446     }
447   else if (alphabet[0] && max_length)
448     {
449       size_t alphabet_size = strlen (alphabet);
450       char *pw = xmalloc (max_length + 1);
451       int *indexes = xzalloc (max_length * sizeof *indexes);
452
453       for (int len = password ? strlen (password) : 0;
454            len <= max_length; len++)
455         {
456           if (password && len == strlen (password))
457             {
458               for (int i = 0; i < len; i++)
459                 {
460                   const char *p = strchr (alphabet, password[i]);
461                   if (!p)
462                     error (1, 0, _("%s: '%c' is not in alphabet"),
463                            password, password[i]);
464                   indexes[i] = p - alphabet;
465                   pw[i] = *p;
466                 }
467             }
468           else
469             {
470               memset (indexes, 0, len * sizeof *indexes);
471               for (int i = 0; i < len; i++)
472                 pw[i] = alphabet[0];
473             }
474           pw[len] = '\0';
475
476           unsigned int target = 0;
477           for (unsigned int j = 0; ; j++)
478             {
479               if (j >= target)
480                 {
481                   target += 100000;
482                   if (isatty (STDOUT_FILENO))
483                     {
484                       printf ("\rlength %d: %s", len, pw);
485                       fflush (stdout);
486                     }
487                 }
488               if (encrypted_file_unlock__ (enc, pw))
489                 {
490                   printf ("\npassword is: \"%s\"\n", pw);
491                   password = pw;
492                   goto success;
493                 }
494
495               int i;
496               for (i = 0; i < len; i++)
497                 if (++indexes[i] < alphabet_size)
498                   {
499                     pw[i] = alphabet[indexes[i]];
500                     break;
501                   }
502                 else
503                   {
504                     indexes[i] = 0;
505                     pw[i] = alphabet[indexes[i]];
506                   }
507               if (i == len)
508                 break;
509             }
510         }
511       free (indexes);
512       free (pw);
513
514     success:;
515     }
516   else
517     {
518       if (password == NULL)
519         {
520           password = getpass ("password: ");
521           if (password == NULL)
522             return false;
523         }
524
525       if (!encrypted_file_unlock (enc, password))
526         error (1, 0, _("sorry, wrong password"));
527     }
528
529   out = fn_open (ofh, "wb");
530   if (out == NULL)
531     error (1, errno, ("%s: error opening output file"), output_filename);
532
533   for (;;)
534     {
535       uint8_t buffer[1024];
536       size_t n;
537
538       n = encrypted_file_read (enc, buffer, sizeof buffer);
539       if (n == 0)
540         break;
541
542       if (fwrite (buffer, 1, n, out) != n)
543         error (1, errno, ("%s: write error"), output_filename);
544     }
545
546   err = encrypted_file_close (enc);
547   if (err)
548     error (1, err, ("%s: read error"), input_filename);
549
550   if (fflush (out) == EOF)
551     error (1, errno, ("%s: write error"), output_filename);
552   fn_close (ofh, out);
553
554   return true;
555 }
556
557 static void
558 usage (void)
559 {
560   printf ("\
561 %s, a utility for converting SPSS data files to other formats.\n\
562 Usage: %s [OPTION]... INPUT OUTPUT\n\
563 where INPUT is an SPSS data file or encrypted syntax file\n\
564   and OUTPUT is the name of the desired output file.\n\
565 \n\
566 The desired format of OUTPUT is by default inferred from its extension:\n\
567   csv txt             comma-separated value\n\
568   sav sys             SPSS system file\n\
569   por                 SPSS portable file\n\
570   sps                 SPSS syntax file (encrypted syntax input files only)\n\
571 \n\
572 General options:\n\
573   -O, --output-format=FORMAT  set specific output format, where FORMAT\n\
574                       is one of the extensions listed above\n\
575   -e, --encoding=CHARSET  override encoding of input data file\n\
576   -c MAXCASES         limit number of cases to copy (default is all cases)\n\
577   -k, --keep=VAR...   include only the given variables in output\n\
578   -d, --drop=VAR...   drop the given variables from output\n\
579 CSV output options:\n\
580   --recode            convert user-missing values to system-missing\n\
581   --no-var-names      do not include variable names as first row\n\
582   --labels            write value labels to output\n\
583   --print-formats     honor variables' print formats\n\
584   --decimal=CHAR      use CHAR as the decimal point (default: .)\n\
585   --delimiter=CHAR    use CHAR to separate fields (default: ,)\n\
586   --qualifier=CHAR    use CHAR to quote the delimiter (default: \")\n\
587 Password options (for used with encrypted files):\n\
588   -p PASSWORD         individual password\n\
589   -a ALPHABET         with -l, alphabet of passwords to try\n\
590   -l MAX-LENGTH       with -a, maximum number of characters to try\n\
591   --password-list=FILE  try all of the passwords in FILE (one per line)\n\
592 Other options:\n\
593   --help              display this help and exit\n\
594   --version           output version information and exit\n",
595           program_name, program_name);
596 }