c8f3238a80cdc2f7a7c5d9479cc72c2bbf191df5
[pspp] / src / data / make-file.c
1 /* PSPP - a program for statistical analysis.
2    Copyright (C) 2004, 2010 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 <assert.h>
23 #include <errno.h>
24 #include <fcntl.h>
25 #include <stdio.h>
26 #include <stdlib.h>
27 #include <string.h>
28 #include <sys/stat.h>
29 #include <unistd.h>
30
31 #include "data/file-name.h"
32 #include "data/file-handle-def.h"
33 #include "libpspp/ll.h"
34 #include "libpspp/message.h"
35
36 #include "gl/fatal-signal.h"
37 #include "gl/tempname.h"
38 #include "gl/xalloc.h"
39 #include "gl/xvasprintf.h"
40
41 #include "gettext.h"
42 #define _(msgid) gettext (msgid)
43
44 struct replace_file
45   {
46     struct ll ll;
47     char *file_name;
48     char *tmp_name;
49   };
50  
51 static struct ll_list all_files = LL_INITIALIZER (all_files);
52
53 static void free_replace_file (struct replace_file *);
54 static void unlink_replace_files (void);
55
56 struct replace_file *
57 replace_file_start (const struct file_handle *fh, const char *mode,
58                     mode_t permissions, FILE **fp)
59 {
60   static bool registered;
61   struct stat s;
62   struct replace_file *rf;
63   int fd;
64   int saved_errno = errno;
65
66   const char *file_name = fh_get_file_name (fh);
67
68   /* If FILE_NAME represents a special file, write to it directly
69      instead of trying to replace it. */
70   if (stat (file_name, &s) == 0 && !S_ISREG (s.st_mode))
71     {
72       /* Open file descriptor. */
73       fd = open (file_name, O_WRONLY);
74       if (fd < 0)
75         {
76           saved_errno = errno;     
77           msg (ME, _("Opening %s for writing: %s."),
78                file_name, strerror (saved_errno));
79           return NULL;
80         }
81
82       /* Open file as stream. */
83       *fp = fdopen (fd, mode);
84       if (*fp == NULL)
85         {
86           saved_errno = errno;     
87           msg (ME, _("Opening stream for %s: %s."),
88                file_name, strerror (saved_errno));
89           close (fd);
90           return NULL;
91         }
92
93       rf = xmalloc (sizeof *rf);
94       rf->file_name = NULL;
95       rf->tmp_name = xstrdup (file_name);
96       return rf;
97     }
98
99   if (!registered)
100     {
101       at_fatal_signal (unlink_replace_files);
102       registered = true;
103     }
104   block_fatal_signals ();
105
106   rf = xmalloc (sizeof *rf);
107   rf->file_name = xstrdup (file_name);
108   for (;;)
109     {
110       /* Generate unique temporary file name. */
111       rf->tmp_name = xasprintf ("%stmpXXXXXX", file_name);
112       if (gen_tempname (rf->tmp_name, 0, 0600, GT_NOCREATE) < 0)
113         {
114           saved_errno = errno;
115           msg (ME, _("Creating temporary file to replace %s: %s."),
116                rf->file_name, strerror (saved_errno));
117           goto error;
118         }
119
120       /* Create file by that name. */
121       fd = open (rf->tmp_name, O_WRONLY | O_CREAT | O_EXCL | O_BINARY, permissions);
122       if (fd >= 0)
123         break;
124       if (errno != EEXIST)
125         {
126           saved_errno = errno;
127           msg (ME, _("Creating temporary file %s: %s."),
128                rf->tmp_name, strerror (saved_errno));
129           goto error;
130         }
131       free (rf->tmp_name);
132     }
133
134
135   /* Open file as stream. */
136   *fp = fdopen (fd, mode);
137   if (*fp == NULL)
138     {
139       saved_errno = errno;
140       msg (ME, _("Opening stream for temporary file %s: %s."),
141            rf->tmp_name, strerror (saved_errno));
142       close (fd);
143       unlink (rf->tmp_name);
144       goto error;
145     }
146
147   /* Register file for deletion. */
148   ll_push_head (&all_files, &rf->ll);
149   unblock_fatal_signals ();
150
151   return rf;
152
153 error:
154   unblock_fatal_signals ();
155   free_replace_file (rf);
156   *fp = NULL;
157   errno = saved_errno;
158   return NULL;
159 }
160
161 bool
162 replace_file_commit (struct replace_file *rf)
163 {
164   bool ok = true;
165
166   if (rf->file_name != NULL)
167     {
168       int save_errno;
169
170       block_fatal_signals ();
171       ok = rename (rf->tmp_name, rf->file_name) == 0;
172       save_errno = errno;
173       ll_remove (&rf->ll);
174       unblock_fatal_signals ();
175
176       if (!ok)
177         msg (ME, _("Replacing %s by %s: %s."),
178              rf->tmp_name, rf->file_name, strerror (save_errno));
179     }
180   else
181     {
182       /* Special file: no temporary file to rename. */
183     }
184   free_replace_file (rf);
185
186   return ok;
187 }
188
189 bool
190 replace_file_abort (struct replace_file *rf)
191 {
192   bool ok = true;
193
194   if (rf->file_name != NULL)
195     {
196       int save_errno;
197
198       block_fatal_signals ();
199       ok = unlink (rf->tmp_name) == 0;
200       save_errno = errno;
201       ll_remove (&rf->ll);
202       unblock_fatal_signals ();
203
204       if (!ok)
205         msg (ME, _("Removing %s: %s."), rf->tmp_name, strerror (save_errno));
206     }
207   else
208     {
209       /* Special file: no temporary file to unlink. */
210     }
211   free_replace_file (rf);
212
213   return ok;
214 }
215
216 static void
217 free_replace_file (struct replace_file *rf)
218 {
219   free (rf->file_name);
220   free (rf->tmp_name);
221   free (rf);
222 }
223
224 static void
225 unlink_replace_files (void)
226 {
227   struct replace_file *rf;
228
229   block_fatal_signals ();
230   ll_for_each (rf, struct replace_file, ll, &all_files)
231     {
232       /* We don't free_replace_file(RF) because calling free is unsafe
233          from an asynchronous signal handler. */
234       unlink (rf->tmp_name);
235     }
236   unblock_fatal_signals ();
237 }