Allow output files to overwrite input files (bug #21280). Thanks to
[pspp-builds.git] / src / data / make-file.c
1 /* PSPP - a program for statistical analysis.
2    Copyright (C) 2004 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 #include <assert.h>
19 #include <fcntl.h>
20 #include <stdlib.h>
21 #include <string.h>
22 #include <errno.h>
23 #include <stdio.h>
24
25 #include <data/file-name.h>
26 #include <data/make-file.h>
27 #include <libpspp/ll.h>
28 #include <libpspp/message.h>
29
30 #include "fatal-signal.h"
31 #include "tempname.h"
32 #include "xalloc.h"
33
34 #include "gettext.h"
35 #define _(msgid) gettext (msgid)
36
37 /* Non ansi compilers may set this */
38 #ifndef P_tmpdir
39 #define P_tmpdir "/tmp"
40 #endif
41
42 /* Creates a temporary file and stores its name in *FILE_NAME and
43    a file descriptor for it in *FD.  Returns success.  Caller is
44    responsible for freeing *FILE_NAME. */
45 int
46 make_temp_file (int *fd, char **file_name)
47 {
48   const char *parent_dir;
49
50   assert (file_name != NULL);
51   assert (fd != NULL);
52
53   if (getenv ("TMPDIR") != NULL)
54     parent_dir = getenv ("TMPDIR");
55   else
56     parent_dir = P_tmpdir;
57
58   *file_name = xmalloc (strlen (parent_dir) + 32);
59   sprintf (*file_name, "%s/psppXXXXXX", parent_dir);
60   *fd = mkstemp (*file_name);
61   if (*fd < 0)
62     {
63       msg (ME, _("%s: Creating temporary file: %s."),
64            *file_name, strerror (errno));
65       free (*file_name);
66       *file_name = NULL;
67       return 0;
68     }
69   return 1;
70 }
71
72
73 /* Creates a temporary file and stores its name in *FILE_NAME and
74    a file stream for it in *FP.  Returns success.  Caller is
75    responsible for freeing *FILE_NAME and for closing *FP */
76 int
77 make_unique_file_stream (FILE **fp, char **file_name)
78 {
79   static int serial = 0;
80   const char *parent_dir;
81
82
83   /* FIXME:
84      Need to check for pre-existing file name.
85      Need also to pass in the directory instead of using /tmp
86   */
87
88   assert (file_name != NULL);
89   assert (fp != NULL);
90
91   if (getenv ("TMPDIR") != NULL)
92     parent_dir = getenv ("TMPDIR");
93   else
94     parent_dir = P_tmpdir;
95
96   *file_name = xmalloc (strlen (parent_dir) + 32);
97
98
99   sprintf (*file_name, "%s/pspp%d.png", parent_dir, serial++);
100
101   *fp = fopen(*file_name, "w");
102
103   if (! *fp )
104     {
105       msg (ME, _("%s: Creating file: %s."), *file_name, strerror (errno));
106       free (*file_name);
107       *file_name = NULL;
108       return 0;
109     }
110
111   return 1;
112 }
113 \f
114 struct replace_file
115   {
116     struct ll ll;
117     char *file_name;
118     char *tmp_name;
119   };
120
121 static struct ll_list all_files = LL_INITIALIZER (all_files);
122
123 static void free_replace_file (struct replace_file *);
124 static void unlink_replace_files (void);
125
126 struct replace_file *
127 replace_file_start (const char *file_name, const char *mode,
128                     mode_t permissions, FILE **fp, char **tmp_name)
129 {
130   static bool registered;
131   struct stat s;
132   struct replace_file *rf;
133   int fd;
134
135   /* If FILE_NAME represents a special file, write to it directly
136      instead of trying to replace it. */
137   if (stat (file_name, &s) == 0 && !S_ISREG (s.st_mode))
138     {
139       /* Open file descriptor. */
140       fd = open (file_name, O_WRONLY);
141       if (fd < 0)
142         {
143           msg (ME, _("Opening %s for writing: %s."),
144                file_name, strerror (errno));
145           return NULL;
146         }
147
148       /* Open file as stream. */
149       *fp = fdopen (fd, mode);
150       if (*fp == NULL)
151         {
152           msg (ME, _("Opening stream for %s: %s."),
153                file_name, strerror (errno));
154           close (fd);
155           return NULL;
156         }
157
158       rf = xmalloc (sizeof *rf);
159       rf->file_name = NULL;
160       rf->tmp_name = xstrdup (file_name);
161       if (tmp_name != NULL)
162         *tmp_name = rf->tmp_name;
163       return rf;
164     }
165
166   if (!registered)
167     {
168       at_fatal_signal (unlink_replace_files);
169       registered = true;
170     }
171   block_fatal_signals ();
172
173   rf = xmalloc (sizeof *rf);
174   rf->file_name = xstrdup (file_name);
175   for (;;)
176     {
177       /* Generate unique temporary file name. */
178       rf->tmp_name = xasprintf ("%s.tmpXXXXXX", file_name);
179       if (gen_tempname (rf->tmp_name, GT_NOCREATE) < 0)
180         {
181           msg (ME, _("Creating temporary file to replace %s: %s."),
182                rf->file_name, strerror (errno));
183           goto error;
184         }
185
186       /* Create file by that name. */
187       fd = open (rf->tmp_name, O_WRONLY | O_CREAT | O_EXCL, permissions);
188       if (fd >= 0)
189         break;
190       if (errno != EEXIST)
191         {
192           msg (ME, _("Creating temporary file %s: %s."),
193                rf->tmp_name, strerror (errno));
194           goto error;
195         }
196       free (rf->tmp_name);
197     }
198
199
200   /* Open file as stream. */
201   *fp = fdopen (fd, mode);
202   if (*fp == NULL)
203     {
204       msg (ME, _("Opening stream for temporary file %s: %s."),
205            rf->tmp_name, strerror (errno));
206       close (fd);
207       unlink (rf->tmp_name);
208       goto error;
209     }
210
211   /* Register file for deletion. */
212   ll_push_head (&all_files, &rf->ll);
213   unblock_fatal_signals ();
214
215   if (tmp_name != NULL)
216     *tmp_name = rf->tmp_name;
217
218   return rf;
219
220 error:
221   unblock_fatal_signals ();
222   free_replace_file (rf);
223   *fp = NULL;
224   if (tmp_name != NULL)
225     *tmp_name = NULL;
226   return NULL;
227 }
228
229 bool
230 replace_file_commit (struct replace_file *rf)
231 {
232   bool ok = true;
233
234   if (rf->file_name != NULL)
235     {
236       int save_errno;
237
238       block_fatal_signals ();
239       ok = rename (rf->tmp_name, rf->file_name) == 0;
240       save_errno = errno;
241       ll_remove (&rf->ll);
242       unblock_fatal_signals ();
243
244       if (!ok)
245         msg (ME, _("Replacing %s by %s: %s."),
246              rf->tmp_name, rf->file_name, strerror (save_errno));
247     }
248   else
249     {
250       /* Special file: no temporary file to rename. */
251     }
252   free_replace_file (rf);
253
254   return ok;
255 }
256
257 bool
258 replace_file_abort (struct replace_file *rf)
259 {
260   bool ok = true;
261
262   if (rf->file_name != NULL)
263     {
264       int save_errno;
265
266       block_fatal_signals ();
267       ok = unlink (rf->tmp_name) == 0;
268       save_errno = errno;
269       ll_remove (&rf->ll);
270       unblock_fatal_signals ();
271
272       if (!ok)
273         msg (ME, _("Removing %s: %s."), rf->tmp_name, strerror (save_errno));
274     }
275   else
276     {
277       /* Special file: no temporary file to unlink. */
278     }
279   free_replace_file (rf);
280
281   return ok;
282 }
283
284 static void
285 free_replace_file (struct replace_file *rf)
286 {
287   free (rf->file_name);
288   free (rf->tmp_name);
289   free (rf);
290 }
291
292 static void
293 unlink_replace_files (void)
294 {
295   struct replace_file *rf;
296
297   block_fatal_signals ();
298   ll_for_each (rf, struct replace_file, ll, &all_files)
299     {
300       /* We don't free_replace_file(RF) because calling free is unsafe
301          from an asynchronous signal handler. */
302       unlink (rf->tmp_name);
303       fflush (stdout);
304     }
305   unblock_fatal_signals ();
306 }