pspp-convert: Support decrypting encrypted SPSS syntax files.
[pspp] / utilities / pspp-convert.c
1 /* PSPP - a program for statistical analysis.
2    Copyright (C) 2013, 2014, 2015 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/encrypted-file.h"
30 #include "data/file-name.h"
31 #include "data/por-file-writer.h"
32 #include "data/settings.h"
33 #include "data/sys-file-writer.h"
34 #include "data/file-handle-def.h"
35 #include "libpspp/assertion.h"
36 #include "libpspp/cast.h"
37 #include "libpspp/i18n.h"
38
39 #include "gl/error.h"
40 #include "gl/getpass.h"
41 #include "gl/progname.h"
42 #include "gl/version-etc.h"
43
44 #include "gettext.h"
45 #define _(msgid) gettext (msgid)
46
47 static void usage (void);
48
49 static void decrypt_file (struct encrypted_file *enc,
50                           const char *input_filename,
51                           const char *output_filename,
52                           const char *password);
53
54 int
55 main (int argc, char *argv[])
56 {
57   const char *input_filename;
58   const char *output_filename;
59
60   long long int max_cases = LLONG_MAX;
61   struct dictionary *dict;
62   struct casereader *reader;
63   struct file_handle *input_fh;
64   const char *encoding = NULL;
65   struct encrypted_file *enc;
66
67   const char *output_format = NULL;
68   struct file_handle *output_fh;
69   struct casewriter *writer;
70   const char *password = NULL;
71
72   long long int i;
73
74   set_program_name (argv[0]);
75   i18n_init ();
76   fh_init ();
77   settings_init ();
78
79   for (;;)
80     {
81       static const struct option long_options[] =
82         {
83           { "cases",    required_argument, NULL, 'c' },
84           { "encoding", required_argument, NULL, 'e' },
85           { "password", required_argument, NULL, 'p' },
86
87           { "output-format", required_argument, NULL, 'O' },
88
89           { "help",    no_argument,       NULL, 'h' },
90           { "version", no_argument,       NULL, 'v' },
91           { NULL,      0,                 NULL, 0 },
92         };
93
94       int c;
95
96       c = getopt_long (argc, argv, "c:e:p:O:hv", long_options, NULL);
97       if (c == -1)
98         break;
99
100       switch (c)
101         {
102         case 'c':
103           max_cases = strtoull (optarg, NULL, 0);
104           break;
105
106         case 'e':
107           encoding = optarg;
108           break;
109
110         case 'p':
111           password = optarg;
112           break;
113
114         case 'O':
115           output_format = optarg;
116           break;
117
118         case 'v':
119           version_etc (stdout, "pspp-convert", PACKAGE_NAME, PACKAGE_VERSION,
120                        "Ben Pfaff", "John Darrington", NULL_SENTINEL);
121           exit (EXIT_SUCCESS);
122
123         case 'h':
124           usage ();
125           exit (EXIT_SUCCESS);
126
127         default:
128           exit (EXIT_FAILURE);
129         }
130     }
131
132   if (optind + 2 != argc)
133     error (1, 0, _("exactly two non-option arguments are required; "
134                    "use --help for help"));
135
136   input_filename = argv[optind];
137   output_filename = argv[optind + 1];
138   if (output_format == NULL)
139     {
140       const char *dot = strrchr (output_filename, '.');
141       if (dot == NULL)
142         error (1, 0, _("%s: cannot guess output format (use -O option)"),
143                output_filename);
144
145       output_format = dot + 1;
146     }
147
148   if (encrypted_file_open (&enc, input_filename) > 0)
149     {
150       if (encrypted_file_is_sav (enc))
151         {
152           if (strcmp (output_format, "sav") && strcmp (output_format, "sys"))
153             error (1, 0, _("can only convert encrypted data file to sav or "
154                            "sys format"));
155         }
156       else
157         {
158           if (strcmp (output_format, "sps"))
159             error (1, 0, _("can only convert encrypted syntax file to sps "
160                            "format"));
161         }
162
163       decrypt_file (enc, input_filename, output_filename, password);
164       goto exit;
165     }
166
167   input_fh = fh_create_file (NULL, input_filename, fh_default_properties ());
168   reader = any_reader_open_and_decode (input_fh, encoding, &dict, NULL);
169   if (reader == NULL)
170     exit (1);
171
172   output_fh = fh_create_file (NULL, output_filename, fh_default_properties ());
173   if (!strcmp (output_format, "csv") || !strcmp (output_format, "txt"))
174     {
175       struct csv_writer_options options;
176
177       csv_writer_options_init (&options);
178       options.include_var_names = true;
179       writer = csv_writer_open (output_fh, dict, &options);
180     }
181   else if (!strcmp (output_format, "sav") || !strcmp (output_format, "sys"))
182     {
183       struct sfm_write_options options;
184
185       options = sfm_writer_default_options ();
186       writer = sfm_open_writer (output_fh, dict, options);
187     }
188   else if (!strcmp (output_format, "por"))
189     {
190       struct pfm_write_options options;
191
192       options = pfm_writer_default_options ();
193       writer = pfm_open_writer (output_fh, dict, options);
194     }
195   else
196     {
197       error (1, 0, _("%s: unknown output format (use -O option)"),
198              output_filename);
199       NOT_REACHED ();
200     }
201
202   for (i = 0; i < max_cases; i++)
203     {
204       struct ccase *c;
205
206       c = casereader_read (reader);
207       if (c == NULL)
208         break;
209
210       casewriter_write (writer, c);
211     }
212
213   if (!casereader_destroy (reader))
214     error (1, 0, _("%s: error reading input file"), input_filename);
215   if (!casewriter_destroy (writer))
216     error (1, 0, _("%s: error writing output file"), output_filename);
217
218 exit:
219   fh_done ();
220   i18n_done ();
221
222   return 0;
223 }
224
225 static void
226 decrypt_file (struct encrypted_file *enc,
227               const char *input_filename,
228               const char *output_filename,
229               const char *password)
230 {
231   FILE *out;
232   int err;
233
234   if (password == NULL)
235     {
236       password = getpass ("password: ");
237       if (password == NULL)
238         exit (1);
239     }
240
241   if (!encrypted_file_unlock (enc, password))
242     error (1, 0, _("sorry, wrong password"));
243
244   out = fn_open (output_filename, "wb");
245   if (out == NULL)
246     error (1, errno, ("%s: error opening output file"), output_filename);
247
248   for (;;)
249     {
250       uint8_t buffer[1024];
251       size_t n;
252
253       n = encrypted_file_read (enc, buffer, sizeof buffer);
254       if (n == 0)
255         break;
256
257       if (fwrite (buffer, 1, n, out) != n)
258         error (1, errno, ("%s: write error"), output_filename);
259     }
260
261   err = encrypted_file_close (enc);
262   if (err)
263     error (1, err, ("%s: read error"), input_filename);
264
265   if (fflush (out) == EOF)
266     error (1, errno, ("%s: write error"), output_filename);
267   fn_close (output_filename, out);
268 }
269
270 static void
271 usage (void)
272 {
273   printf ("\
274 %s, a utility for converting SPSS data files to other formats.\n\
275 Usage: %s [OPTION]... INPUT OUTPUT\n\
276 where INPUT is an SPSS data file or encrypted syntax file\n\
277   and OUTPUT is the name of the desired output file.\n\
278 \n\
279 The desired format of OUTPUT is by default inferred from its extension:\n\
280   csv txt             comma-separated value\n\
281   sav sys             SPSS system file\n\
282   por                 SPSS portable file\n\
283   sps                 SPSS syntax file (encrypted syntax input files only)\n\
284 \n\
285 Options:\n\
286   -O, --output-format=FORMAT  set specific output format, where FORMAT\n\
287                       is one of the extensions listed above\n\
288   -e, --encoding=CHARSET  override encoding of input data file\n\
289   -c MAXCASES         limit number of cases to copy (default is all cases)\n\
290   -p PASSWORD         password for encrypted files\n\
291   --help              display this help and exit\n\
292   --version           output version information and exit\n",
293           program_name, program_name);
294 }