pspp-output: Correct manpage.
[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 (decrypt_file (enc, input_fh, output_fh, password,
188                          ds_cstr (&alphabet), length, password_list))
189         goto exit;
190       else
191         goto error;
192     }
193
194
195   reader = any_reader_open_and_decode (input_fh, encoding, &dict, NULL);
196   if (reader == NULL)
197     goto error;
198
199   if (!strcmp (output_format, "csv") || !strcmp (output_format, "txt"))
200     {
201       struct csv_writer_options options;
202
203       csv_writer_options_init (&options);
204       options.include_var_names = true;
205       writer = csv_writer_open (output_fh, dict, &options);
206     }
207   else if (!strcmp (output_format, "sav") || !strcmp (output_format, "sys"))
208     {
209       struct sfm_write_options options;
210
211       options = sfm_writer_default_options ();
212       writer = sfm_open_writer (output_fh, dict, options);
213     }
214   else if (!strcmp (output_format, "por"))
215     {
216       struct pfm_write_options options;
217
218       options = pfm_writer_default_options ();
219       writer = pfm_open_writer (output_fh, dict, options);
220     }
221   else
222     {
223       error (1, 0, _("%s: unknown output format (use -O option)"),
224              output_filename);
225       NOT_REACHED ();
226     }
227   if (!writer)
228     error (1, 0, _("%s: error opening output file"), output_filename);
229
230   for (i = 0; i < max_cases; i++)
231     {
232       struct ccase *c;
233
234       c = casereader_read (reader);
235       if (c == NULL)
236         break;
237
238       casewriter_write (writer, c);
239     }
240
241   if (!casereader_destroy (reader))
242     error (1, 0, _("%s: error reading input file"), input_filename);
243   if (!casewriter_destroy (writer))
244     error (1, 0, _("%s: error writing output file"), output_filename);
245
246 exit:
247   ds_destroy (&alphabet);
248   dict_unref (dict);
249   fh_unref (output_fh);
250   fh_unref (input_fh);
251   fh_done ();
252   i18n_done ();
253
254   return 0;
255
256 error:
257   ds_destroy (&alphabet);
258   dict_unref (dict);
259   fh_unref (output_fh);
260   fh_unref (input_fh);
261   fh_done ();
262   i18n_done ();
263
264   return 1;
265 }
266
267 static bool
268 decrypt_file (struct encrypted_file *enc,
269               const struct file_handle *ifh,
270               const struct file_handle *ofh,
271               const char *password,
272               const char *alphabet,
273               int max_length,
274               const char *password_list)
275 {
276   FILE *out;
277   int err;
278   const char *input_filename = fh_get_file_name (ifh);
279   const char *output_filename = fh_get_file_name (ofh);
280
281   if (password_list)
282     {
283       FILE *password_file;
284       if (!strcmp (password_list, "-"))
285         password_file = stdin;
286       else
287         {
288           password_file = fopen (password_list, "r");
289           if (!password_file)
290             error (1, errno, _("%s: error opening password file"),
291                    password_list);
292         }
293
294       struct string pw = DS_EMPTY_INITIALIZER;
295       unsigned int target = 100000;
296       for (unsigned int i = 0; ; i++)
297         {
298           ds_clear (&pw);
299           if (!ds_read_line (&pw, password_file, SIZE_MAX))
300             {
301               if (isatty (STDOUT_FILENO))
302                 {
303                   putchar ('\r');
304                   fflush (stdout);
305                 }
306               error (1, 0, _("\n%s: password not in file"), password_list);
307             }
308           ds_chomp_byte (&pw, '\n');
309
310           if (i >= target)
311             {
312               target += 100000;
313               if (isatty (STDOUT_FILENO))
314                 {
315                   printf ("\r%u", i);
316                   fflush (stdout);
317                 }
318             }
319
320           if (encrypted_file_unlock__ (enc, ds_cstr (&pw)))
321             {
322               printf ("\npassword is: \"%s\"\n", ds_cstr (&pw));
323               password = ds_cstr (&pw);
324               break;
325             }
326         }
327     }
328   else if (alphabet[0] && max_length)
329     {
330       size_t alphabet_size = strlen (alphabet);
331       char *pw = xmalloc (max_length + 1);
332       int *indexes = xzalloc (max_length * sizeof *indexes);
333
334       for (int len = password ? strlen (password) : 0;
335            len <= max_length; len++)
336         {
337           if (password && len == strlen (password))
338             {
339               for (int i = 0; i < len; i++)
340                 {
341                   const char *p = strchr (alphabet, password[i]);
342                   if (!p)
343                     error (1, 0, _("%s: '%c' is not in alphabet"),
344                            password, password[i]);
345                   indexes[i] = p - alphabet;
346                   pw[i] = *p;
347                 }
348             }
349           else
350             {
351               memset (indexes, 0, len * sizeof *indexes);
352               for (int i = 0; i < len; i++)
353                 pw[i] = alphabet[0];
354             }
355           pw[len] = '\0';
356
357           unsigned int target = 0;
358           for (unsigned int j = 0; ; j++)
359             {
360               if (j >= target)
361                 {
362                   target += 100000;
363                   if (isatty (STDOUT_FILENO))
364                     {
365                       printf ("\rlength %d: %s", len, pw);
366                       fflush (stdout);
367                     }
368                 }
369               if (encrypted_file_unlock__ (enc, pw))
370                 {
371                   printf ("\npassword is: \"%s\"\n", pw);
372                   password = pw;
373                   goto success;
374                 }
375
376               int i;
377               for (i = 0; i < len; i++)
378                 if (++indexes[i] < alphabet_size)
379                   {
380                     pw[i] = alphabet[indexes[i]];
381                     break;
382                   }
383                 else
384                   {
385                     indexes[i] = 0;
386                     pw[i] = alphabet[indexes[i]];
387                   }
388               if (i == len)
389                 break;
390             }
391         }
392       free (indexes);
393       free (pw);
394
395     success:;
396     }
397   else
398     {
399       if (password == NULL)
400         {
401           password = getpass ("password: ");
402           if (password == NULL)
403             return false;
404         }
405
406       if (!encrypted_file_unlock (enc, password))
407         error (1, 0, _("sorry, wrong password"));
408     }
409
410   out = fn_open (ofh, "wb");
411   if (out == NULL)
412     error (1, errno, ("%s: error opening output file"), output_filename);
413
414   for (;;)
415     {
416       uint8_t buffer[1024];
417       size_t n;
418
419       n = encrypted_file_read (enc, buffer, sizeof buffer);
420       if (n == 0)
421         break;
422
423       if (fwrite (buffer, 1, n, out) != n)
424         error (1, errno, ("%s: write error"), output_filename);
425     }
426
427   err = encrypted_file_close (enc);
428   if (err)
429     error (1, err, ("%s: read error"), input_filename);
430
431   if (fflush (out) == EOF)
432     error (1, errno, ("%s: write error"), output_filename);
433   fn_close (ofh, out);
434
435   return true;
436 }
437
438 static void
439 usage (void)
440 {
441   printf ("\
442 %s, a utility for converting SPSS data files to other formats.\n\
443 Usage: %s [OPTION]... INPUT OUTPUT\n\
444 where INPUT is an SPSS data file or encrypted syntax file\n\
445   and OUTPUT is the name of the desired output file.\n\
446 \n\
447 The desired format of OUTPUT is by default inferred from its extension:\n\
448   csv txt             comma-separated value\n\
449   sav sys             SPSS system file\n\
450   por                 SPSS portable file\n\
451   sps                 SPSS syntax file (encrypted syntax input files only)\n\
452 \n\
453 General options:\n\
454   -O, --output-format=FORMAT  set specific output format, where FORMAT\n\
455                       is one of the extensions listed above\n\
456   -e, --encoding=CHARSET  override encoding of input data file\n\
457   -c MAXCASES         limit number of cases to copy (default is all cases)\n\
458 Password options (for used with encrypted files):\n\
459   -p PASSWORD         individual password\n\
460   -a ALPHABET         with -l, alphabet of passwords to try\n\
461   -l MAX-LENGTH       with -a, maximum number of characters to try\n\
462   --password-list=FILE  try all of the passwords in FILE (one per line)\n\
463 Other options:\n\
464   --help              display this help and exit\n\
465   --version           output version information and exit\n",
466           program_name, program_name);
467 }