better tests
[pspp] / src / data / make-file.c
1 /* PSPP - a program for statistical analysis.
2    Copyright (C) 2004, 2010, 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 "data/make-file.h"
20 #include "libpspp/i18n.h"
21
22 #include <errno.h>
23 #include <fcntl.h>
24 #include <stdio.h>
25 #include <stdlib.h>
26 #include <string.h>
27 #include <sys/stat.h>
28 #include <unistd.h>
29
30 #include "data/file-name.h"
31 #include "data/file-handle-def.h"
32 #include "libpspp/ll.h"
33 #include "libpspp/message.h"
34
35 #include "gl/fatal-signal.h"
36 #include "gl/tempname.h"
37 #include "gl/xalloc.h"
38 #include "gl/xvasprintf.h"
39 #include "gl/localcharset.h"
40
41 #include "gettext.h"
42 #define _(msgid) gettext (msgid)
43
44
45 #if defined _WIN32 || defined __WIN32__
46 #define WIN32_LEAN_AND_MEAN  /* avoid including junk */
47 #include <windows.h>
48 #define TS_stat _stat
49 #define Tunlink _wunlink
50 #define Topen _wopen
51 #define Tstat _wstat
52
53 /* Shamelessly lifted and modified from the rpl_rename function in Gnulib */
54 static int
55 Trename (TCHAR const *src, TCHAR const *dst)
56 {
57   int error;
58
59   /* MoveFileExW works if SRC is a directory without any flags, but
60      fails with MOVEFILE_REPLACE_EXISTING, so try without flags first.
61      Thankfully, MoveFileExW handles hard links correctly, even though
62      rename() does not.  */
63   if (MoveFileExW (src, dst, 0))
64     return 0;
65
66   /* Retry with MOVEFILE_REPLACE_EXISTING if the move failed
67      due to the destination already existing.  */
68   error = GetLastError ();
69   if (error == ERROR_FILE_EXISTS || error == ERROR_ALREADY_EXISTS)
70     {
71       if (MoveFileExW (src, dst, MOVEFILE_REPLACE_EXISTING))
72         return 0;
73
74       error = GetLastError ();
75     }
76
77   switch (error)
78     {
79     case ERROR_FILE_NOT_FOUND:
80     case ERROR_PATH_NOT_FOUND:
81     case ERROR_BAD_PATHNAME:
82     case ERROR_DIRECTORY:
83       errno = ENOENT;
84       break;
85
86     case ERROR_ACCESS_DENIED:
87     case ERROR_SHARING_VIOLATION:
88       errno = EACCES;
89       break;
90
91     case ERROR_OUTOFMEMORY:
92       errno = ENOMEM;
93       break;
94
95     case ERROR_CURRENT_DIRECTORY:
96       errno = EBUSY;
97       break;
98
99     case ERROR_NOT_SAME_DEVICE:
100       errno = EXDEV;
101       break;
102
103     case ERROR_WRITE_PROTECT:
104       errno = EROFS;
105       break;
106
107     case ERROR_WRITE_FAULT:
108     case ERROR_READ_FAULT:
109     case ERROR_GEN_FAILURE:
110       errno = EIO;
111       break;
112
113     case ERROR_HANDLE_DISK_FULL:
114     case ERROR_DISK_FULL:
115     case ERROR_DISK_TOO_FRAGMENTED:
116       errno = ENOSPC;
117       break;
118
119     case ERROR_FILE_EXISTS:
120     case ERROR_ALREADY_EXISTS:
121       errno = EEXIST;
122       break;
123
124     case ERROR_BUFFER_OVERFLOW:
125     case ERROR_FILENAME_EXCED_RANGE:
126       errno = ENAMETOOLONG;
127       break;
128
129     case ERROR_INVALID_NAME:
130     case ERROR_DELETE_PENDING:
131       errno = EPERM;        /* ? */
132       break;
133
134 # ifndef ERROR_FILE_TOO_LARGE
135 /* This value is documented but not defined in all versions of windows.h.  */
136 #  define ERROR_FILE_TOO_LARGE 223
137 # endif
138     case ERROR_FILE_TOO_LARGE:
139       errno = EFBIG;
140       break;
141
142     default:
143       errno = EINVAL;
144       break;
145     }
146
147   return -1;
148 }
149
150 TCHAR *
151 convert_to_filename_encoding (const char *s, size_t len, const char *current_encoding)
152 {
153   const char *enc = current_encoding;
154   if (NULL == enc || 0 == strcmp (enc, "Auto"))
155     enc = locale_charset ();
156
157   return (TCHAR *) recode_string ("UTF-16LE", enc, s, len);
158 }
159
160
161 #else
162 #define TS_stat stat
163 #define Trename rename
164 #define Tunlink unlink
165 #define Topen open
166 #define Tstat stat
167
168 TCHAR *
169 convert_to_filename_encoding (const char *s, size_t len UNUSED, const char *current_encoding UNUSED)
170 {
171   /* Non-windows systems don't care about the encoding.
172      The string is copied here, to be consistent with the w32 case.  */
173   return xstrdup (s);
174 }
175
176 #endif
177
178
179 struct replace_file
180 {
181   struct ll ll;
182   TCHAR *file_name;
183   TCHAR *tmp_name;
184
185   char *tmp_name_verbatim;
186   const char *file_name_verbatim;
187 };
188
189 static struct ll_list all_files = LL_INITIALIZER (all_files);
190
191 static void free_replace_file (struct replace_file *);
192 static void unlink_replace_files (int sig);
193
194 struct replace_file *
195 replace_file_start (const struct file_handle *fh, const char *mode,
196                     mode_t permissions, FILE **fp)
197 {
198   static bool registered;
199   struct TS_stat s;
200   struct replace_file *rf;
201   int fd;
202   int saved_errno = errno;
203
204   const char *file_name = fh_get_file_name (fh);
205
206   TCHAR * Tfile_name = convert_to_filename_encoding (file_name, strlen (file_name), fh_get_file_name_encoding (fh));
207
208   /* If FILE_NAME represents a special file, write to it directly
209      instead of trying to replace it. */
210   if (Tstat (Tfile_name, &s) == 0 && !S_ISREG (s.st_mode))
211     {
212       /* Open file descriptor. */
213       fd = Topen (Tfile_name, O_WRONLY);
214       if (fd < 0)
215         {
216           saved_errno = errno;
217           msg (ME, _("Opening %s for writing: %s."),
218                file_name, strerror (saved_errno));
219           free (Tfile_name);
220           return NULL;
221         }
222
223       /* Open file as stream. */
224       *fp = fdopen (fd, mode);
225       if (*fp == NULL)
226         {
227           saved_errno = errno;
228           msg (ME, _("Opening stream for %s: %s."),
229                file_name, strerror (saved_errno));
230           close (fd);
231           free (Tfile_name);
232           return NULL;
233         }
234
235       rf = xzalloc (sizeof *rf);
236       rf->file_name = NULL;
237       rf->tmp_name = Tfile_name;
238       return rf;
239     }
240
241   if (!registered)
242     {
243       at_fatal_signal (unlink_replace_files);
244       registered = true;
245     }
246   block_fatal_signals ();
247
248   rf = xzalloc (sizeof *rf);
249   rf->file_name = Tfile_name;
250   rf->file_name_verbatim = file_name;
251
252   for (;;)
253     {
254       /* Generate unique temporary file name. */
255       free (rf->tmp_name_verbatim);
256       rf->tmp_name_verbatim = xasprintf ("%stmpXXXXXX", file_name);
257       if (gen_tempname (rf->tmp_name_verbatim, 0, 0600, GT_NOCREATE) < 0)
258         {
259           saved_errno = errno;
260           msg (ME, _("Creating temporary file to replace %s: %s."),
261                file_name, strerror (saved_errno));
262           goto error;
263         }
264
265       rf->tmp_name = convert_to_filename_encoding (rf->tmp_name_verbatim, strlen (rf->tmp_name_verbatim), fh_get_file_name_encoding (fh));
266
267       /* Create file by that name. */
268       bool binary = strchr (mode, 'b') != NULL;
269       fd = Topen (rf->tmp_name,
270                   O_WRONLY | O_CREAT | O_EXCL | (binary ? O_BINARY : O_TEXT),
271                   permissions);
272       if (fd >= 0)
273         break;
274       if (errno != EEXIST)
275         {
276           saved_errno = errno;
277           msg (ME, _("Creating temporary file %s: %s."),
278                rf->tmp_name_verbatim, strerror (saved_errno));
279           goto error;
280         }
281     }
282
283
284   /* Open file as stream. */
285   *fp = fdopen (fd, mode);
286   if (*fp == NULL)
287     {
288       saved_errno = errno;
289       msg (ME, _("Opening stream for temporary file %s: %s."),
290            rf->tmp_name_verbatim, strerror (saved_errno));
291       close (fd);
292       Tunlink (rf->tmp_name);
293       goto error;
294     }
295
296   /* Register file for deletion. */
297   ll_push_head (&all_files, &rf->ll);
298   unblock_fatal_signals ();
299
300   return rf;
301
302  error:
303   unblock_fatal_signals ();
304   free_replace_file (rf);
305   *fp = NULL;
306   errno = saved_errno;
307   return NULL;
308 }
309
310 bool
311 replace_file_commit (struct replace_file *rf)
312 {
313   bool ok = true;
314
315   if (rf->file_name != NULL)
316     {
317       int save_errno;
318
319       block_fatal_signals ();
320       ok = Trename (rf->tmp_name, rf->file_name) == 0;
321       save_errno = errno;
322       ll_remove (&rf->ll);
323       unblock_fatal_signals ();
324
325       if (!ok)
326         msg (ME, _("Replacing %s by %s: %s."),
327              rf->file_name_verbatim, rf->tmp_name_verbatim, strerror (save_errno));
328     }
329   else
330     {
331       /* Special file: no temporary file to rename. */
332     }
333   free_replace_file (rf);
334
335   return ok;
336 }
337
338 bool
339 replace_file_abort (struct replace_file *rf)
340 {
341   bool ok = true;
342
343   if (rf->file_name != NULL)
344     {
345       int save_errno;
346
347       block_fatal_signals ();
348       ok = Tunlink (rf->tmp_name) == 0;
349       save_errno = errno;
350       ll_remove (&rf->ll);
351       unblock_fatal_signals ();
352
353       if (!ok)
354         msg (ME, _("Removing %s: %s."), rf->tmp_name_verbatim, strerror (save_errno));
355     }
356   else
357     {
358       /* Special file: no temporary file to unlink. */
359     }
360   free_replace_file (rf);
361
362   return ok;
363 }
364
365 static void
366 free_replace_file (struct replace_file *rf)
367 {
368   free (rf->file_name);
369   free (rf->tmp_name);
370   free (rf->tmp_name_verbatim);
371   free (rf);
372 }
373
374 static void
375 unlink_replace_files (int sig UNUSED)
376 {
377   struct replace_file *rf;
378
379   block_fatal_signals ();
380   ll_for_each (rf, struct replace_file, ll, &all_files)
381     {
382       /* We don't free_replace_file(RF) because calling free is unsafe
383          from an asynchronous signal handler. */
384       Tunlink (rf->tmp_name);
385     }
386   unblock_fatal_signals ();
387 }