1 /* Open a file, without destroying an old file with the same name.
2
3 Copyright (C) 2020 Free Software Foundation, Inc.
4
5 This program is free software: you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation; either version 3 of the License, or
8 (at your option) any later version.
9
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU General Public License for more details.
14
15 You should have received a copy of the GNU General Public License
16 along with this program. If not, see <https://www.gnu.org/licenses/>. */
17
18 /* Written by Bruno Haible, 2020. */
19
20 #include <config.h>
21
22 /* Specification. */
23 #include "supersede.h"
24
25 #include <errno.h>
26 #include <fcntl.h>
27 #include <stdlib.h>
28 #include <string.h>
29 #include <sys/stat.h>
30
31 #if defined _WIN32 && !defined __CYGWIN__
32 /* A native Windows platform. */
33 # define WIN32_LEAN_AND_MEAN /* avoid including junk */
34 # include <windows.h>
35 # include <io.h>
36 #else
37 # include <unistd.h>
38 #endif
39
40 #include "canonicalize.h"
41 #include "clean-temp.h"
42 #include "ignore-value.h"
43 #include "stat-time.h"
44 #include "utimens.h"
45 #include "acl.h"
46
47 #if defined _WIN32 && !defined __CYGWIN__
48 /* Don't assume that UNICODE is not defined. */
49 # undef MoveFileEx
50 # define MoveFileEx MoveFileExA
51 #endif
52
53 static int
create_temp_file(char * canon_filename,int flags,mode_t mode,struct supersede_final_action * action)54 create_temp_file (char *canon_filename, int flags, mode_t mode,
55 struct supersede_final_action *action)
56 {
57 /* Use a temporary file always. */
58 size_t canon_filename_length = strlen (canon_filename);
59
60 /* The temporary file needs to be in the same directory, otherwise the
61 final rename may fail. */
62 char *temp_filename = (char *) malloc (canon_filename_length + 7 + 1);
63 memcpy (temp_filename, canon_filename, canon_filename_length);
64 memcpy (temp_filename + canon_filename_length, ".XXXXXX", 7 + 1);
65
66 int fd = gen_register_open_temp (temp_filename, 0, flags, mode);
67 if (fd < 0)
68 return -1;
69
70 action->final_rename_temp = temp_filename;
71 action->final_rename_dest = canon_filename;
72 return fd;
73 }
74
75 int
open_supersede(const char * filename,int flags,mode_t mode,bool supersede_if_exists,bool supersede_if_does_not_exist,struct supersede_final_action * action)76 open_supersede (const char *filename, int flags, mode_t mode,
77 bool supersede_if_exists, bool supersede_if_does_not_exist,
78 struct supersede_final_action *action)
79 {
80 int fd;
81
82 if (supersede_if_exists)
83 {
84 if (supersede_if_does_not_exist)
85 {
86 struct stat statbuf;
87
88 if (stat (filename, &statbuf) >= 0
89 && ! S_ISREG (statbuf.st_mode)
90 /* The file exists and is possibly a character device, socket, or
91 something like that. */
92 && ((fd = open (filename, flags, mode)) >= 0
93 || errno != ENOENT))
94 {
95 if (fd >= 0)
96 {
97 action->final_rename_temp = NULL;
98 action->final_rename_dest = NULL;
99 }
100 }
101 else
102 {
103 /* The file does not exist or is a regular file.
104 Use a temporary file. */
105 char *canon_filename =
106 canonicalize_filename_mode (filename, CAN_ALL_BUT_LAST);
107 if (canon_filename == NULL)
108 fd = -1;
109 else
110 {
111 fd = create_temp_file (canon_filename, flags, mode, action);
112 if (fd < 0)
113 {
114 int saved_errno = errno;
115 free (canon_filename);
116 errno = saved_errno;
117 }
118 }
119 }
120 }
121 else
122 {
123 fd = open (filename, flags | O_CREAT | O_EXCL, mode);
124 if (fd >= 0)
125 {
126 /* The file did not exist. */
127 action->final_rename_temp = NULL;
128 action->final_rename_dest = NULL;
129 }
130 else
131 {
132 /* The file exists or is a symbolic link to a nonexistent
133 file. */
134 char *canon_filename =
135 canonicalize_filename_mode (filename, CAN_ALL_BUT_LAST);
136 if (canon_filename == NULL)
137 fd = -1;
138 else
139 {
140 fd = open (canon_filename, flags | O_CREAT | O_EXCL, mode);
141 if (fd >= 0)
142 {
143 /* It was a symbolic link to a nonexistent file. */
144 free (canon_filename);
145 action->final_rename_temp = NULL;
146 action->final_rename_dest = NULL;
147 }
148 else
149 {
150 /* The file exists. */
151 struct stat statbuf;
152
153 if (stat (canon_filename, &statbuf) >= 0
154 && S_ISREG (statbuf.st_mode))
155 {
156 /* It is a regular file. Use a temporary file. */
157 fd = create_temp_file (canon_filename, flags, mode,
158 action);
159 if (fd < 0)
160 {
161 int saved_errno = errno;
162 free (canon_filename);
163 errno = saved_errno;
164 }
165 }
166 else
167 {
168 /* It is possibly a character device, socket, or
169 something like that. */
170 fd = open (canon_filename, flags, mode);
171 if (fd >= 0)
172 {
173 free (canon_filename);
174 action->final_rename_temp = NULL;
175 action->final_rename_dest = NULL;
176 }
177 else
178 {
179 int saved_errno = errno;
180 free (canon_filename);
181 errno = saved_errno;
182 }
183 }
184 }
185 }
186 }
187 }
188 }
189 else
190 {
191 if (supersede_if_does_not_exist)
192 {
193 fd = open (filename, flags, mode);
194 if (fd >= 0)
195 {
196 /* The file exists. */
197 action->final_rename_temp = NULL;
198 action->final_rename_dest = NULL;
199 }
200 else if (errno == ENOENT)
201 {
202 /* The file does not exist. Use a temporary file. */
203 char *canon_filename =
204 canonicalize_filename_mode (filename, CAN_ALL_BUT_LAST);
205 if (canon_filename == NULL)
206 fd = -1;
207 else
208 {
209 fd = create_temp_file (canon_filename, flags, mode, action);
210 if (fd < 0)
211 {
212 int saved_errno = errno;
213 free (canon_filename);
214 errno = saved_errno;
215 }
216 }
217 }
218 }
219 else
220 {
221 /* Never use a temporary file. */
222 fd = open (filename, flags | O_CREAT, mode);
223 action->final_rename_temp = NULL;
224 action->final_rename_dest = NULL;
225 }
226 }
227 return fd;
228 }
229
230 static int
after_close_actions(int ret,const struct supersede_final_action * action)231 after_close_actions (int ret, const struct supersede_final_action *action)
232 {
233 if (ret < 0)
234 {
235 /* There was an error writing. Erase the temporary file. */
236 if (action->final_rename_temp != NULL)
237 {
238 int saved_errno = errno;
239 ignore_value (unlink (action->final_rename_temp));
240 free (action->final_rename_temp);
241 free (action->final_rename_dest);
242 errno = saved_errno;
243 }
244 return ret;
245 }
246
247 if (action->final_rename_temp != NULL)
248 {
249 struct stat temp_statbuf;
250 struct stat dest_statbuf;
251
252 if (stat (action->final_rename_temp, &temp_statbuf) < 0)
253 {
254 /* We just finished writing the temporary file, but now cannot access
255 it. There's something wrong. */
256 int saved_errno = errno;
257 ignore_value (unlink (action->final_rename_temp));
258 free (action->final_rename_temp);
259 free (action->final_rename_dest);
260 errno = saved_errno;
261 return -1;
262 }
263
264 if (stat (action->final_rename_dest, &dest_statbuf) >= 0)
265 {
266 /* Copy the access time from the destination file to the temporary
267 file. */
268 {
269 struct timespec ts[2];
270
271 ts[0] = get_stat_atime (&dest_statbuf);
272 ts[1] = get_stat_mtime (&temp_statbuf);
273 ignore_value (utimens (action->final_rename_temp, ts));
274 }
275
276 #if HAVE_CHOWN
277 /* Copy the owner and group from the destination file to the
278 temporary file. */
279 ignore_value (chown (action->final_rename_temp,
280 dest_statbuf.st_uid, dest_statbuf.st_gid));
281 #endif
282
283 /* Copy the access permissions from the destination file to the
284 temporary file. */
285 #if USE_ACL
286 switch (qcopy_acl (action->final_rename_dest, -1,
287 action->final_rename_temp, -1,
288 dest_statbuf.st_mode))
289 {
290 case -2:
291 /* Could not get the ACL of the destination file. */
292 case -1:
293 /* Could not set the ACL on the temporary file. */
294 ignore_value (unlink (action->final_rename_temp));
295 free (action->final_rename_temp);
296 free (action->final_rename_dest);
297 errno = EPERM;
298 return -1;
299 }
300 #else
301 chmod (action->final_rename_temp, dest_statbuf.st_mode);
302 #endif
303 }
304 else
305 /* No chmod needed, since the mode was already passed to
306 gen_register_open_temp. */
307 ;
308
309 /* Rename the temporary file to the destination file. */
310 #if defined _WIN32 && !defined __CYGWIN__
311 /* A native Windows platform. */
312 /* ReplaceFile
313 <https://docs.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-replacefilea>
314 is atomic regarding the file's contents, says
315 https://stackoverflow.com/questions/167414/is-an-atomic-file-rename-with-overwrite-possible-on-windows>
316 But it fails with GetLastError () == ERROR_FILE_NOT_FOUND if
317 action->final_rename_dest does not exist. So better use
318 MoveFileEx
319 <https://docs.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-movefileexa>. */
320 if (!MoveFileEx (action->final_rename_temp, action->final_rename_dest,
321 MOVEFILE_REPLACE_EXISTING))
322 {
323 int saved_errno;
324 switch (GetLastError ())
325 {
326 case ERROR_INVALID_PARAMETER:
327 saved_errno = EINVAL; break;
328 default:
329 saved_errno = EIO; break;
330 }
331 ignore_value (unlink (action->final_rename_temp));
332 free (action->final_rename_temp);
333 free (action->final_rename_dest);
334 errno = saved_errno;
335 return -1;
336 }
337 #else
338 if (rename (action->final_rename_temp, action->final_rename_dest) < 0)
339 {
340 int saved_errno = errno;
341 ignore_value (unlink (action->final_rename_temp));
342 free (action->final_rename_temp);
343 free (action->final_rename_dest);
344 errno = saved_errno;
345 return -1;
346 }
347 #endif
348
349 unregister_temporary_file (action->final_rename_temp);
350
351 free (action->final_rename_temp);
352 free (action->final_rename_dest);
353 }
354
355 return ret;
356 }
357
358 int
close_supersede(int fd,const struct supersede_final_action * action)359 close_supersede (int fd, const struct supersede_final_action *action)
360 {
361 if (fd < 0)
362 {
363 int saved_errno = errno;
364 free (action->final_rename_temp);
365 free (action->final_rename_dest);
366 errno = saved_errno;
367 return fd;
368 }
369
370 int ret;
371 if (action->final_rename_temp != NULL)
372 ret = close_temp (fd);
373 else
374 ret = close (fd);
375 return after_close_actions (ret, action);
376 }
377
378 FILE *
fopen_supersede(const char * filename,const char * mode,bool supersede_if_exists,bool supersede_if_does_not_exist,struct supersede_final_action * action)379 fopen_supersede (const char *filename, const char *mode,
380 bool supersede_if_exists, bool supersede_if_does_not_exist,
381 struct supersede_final_action *action)
382 {
383 /* Parse the mode. */
384 int open_direction = 0;
385 int open_flags = 0;
386 {
387 const char *p = mode;
388
389 for (; *p != '\0'; p++)
390 {
391 switch (*p)
392 {
393 case 'r':
394 open_direction = O_RDONLY;
395 continue;
396 case 'w':
397 open_direction = O_WRONLY;
398 open_flags |= /* not! O_CREAT | */ O_TRUNC;
399 continue;
400 case 'a':
401 open_direction = O_WRONLY;
402 open_flags |= /* not! O_CREAT | */ O_APPEND;
403 continue;
404 case 'b':
405 /* While it is non-standard, O_BINARY is guaranteed by
406 gnulib <fcntl.h>. */
407 open_flags |= O_BINARY;
408 continue;
409 case '+':
410 open_direction = O_RDWR;
411 continue;
412 case 'x':
413 /* not! open_flags |= O_EXCL; */
414 continue;
415 case 'e':
416 open_flags |= O_CLOEXEC;
417 continue;
418 default:
419 break;
420 }
421 break;
422 }
423 }
424
425 mode_t open_mode = S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH;
426 int fd = open_supersede (filename, open_direction | open_flags, open_mode,
427 supersede_if_exists, supersede_if_does_not_exist,
428 action);
429 if (fd < 0)
430 return NULL;
431
432 FILE *stream = fdopen (fd, mode);
433 if (stream == NULL)
434 {
435 int saved_errno = errno;
436 close (fd);
437 close_supersede (-1, action);
438 errno = saved_errno;
439 }
440 return stream;
441 }
442
443 int
fclose_supersede(FILE * stream,const struct supersede_final_action * action)444 fclose_supersede (FILE *stream, const struct supersede_final_action *action)
445 {
446 if (stream == NULL)
447 return -1;
448 int ret;
449 if (action->final_rename_temp != NULL)
450 ret = fclose_temp (stream);
451 else
452 ret = fclose (stream);
453 return after_close_actions (ret, action);
454 }
455
456 #if GNULIB_FWRITEERROR
457 int
fwriteerror_supersede(FILE * stream,const struct supersede_final_action * action)458 fwriteerror_supersede (FILE *stream, const struct supersede_final_action *action)
459 {
460 if (stream == NULL)
461 return -1;
462 int ret;
463 if (action->final_rename_temp != NULL)
464 ret = fclose_temp (stream);
465 else
466 ret = fclose (stream);
467 return after_close_actions (ret, action);
468 }
469 #endif
470