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