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