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