1 /* Open a file, without destroying an old file with the same name.
3 Copyright (C) 2020 Free Software Foundation, Inc.
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.
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.
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/>. */
18 /* Written by Bruno Haible, 2020. */
23 #include "supersede.h"
31 #if defined _WIN32 && !defined __CYGWIN__
32 /* A native Windows platform. */
33 # define WIN32_LEAN_AND_MEAN /* avoid including junk */
40 #include "canonicalize.h"
41 #include "clean-temp.h"
42 #include "ignore-value.h"
43 #include "stat-time.h"
47 #if defined _WIN32 && !defined __CYGWIN__
48 /* Don't assume that UNICODE is not defined. */
50 # define MoveFileEx MoveFileExA
54 create_temp_file (char *canon_filename
, int flags
, mode_t mode
,
55 struct supersede_final_action
*action
)
57 /* Use a temporary file always. */
58 size_t canon_filename_length
= strlen (canon_filename
);
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);
66 int fd
= gen_register_open_temp (temp_filename
, 0, flags
, mode
);
70 action
->final_rename_temp
= temp_filename
;
71 action
->final_rename_dest
= canon_filename
;
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
)
81 /* Extra flags for existing devices. */
84 /* open ("/dev/null", O_TRUNC | O_WRONLY) fails with error EINVAL on Solaris
85 zones. See <https://www.illumos.org/issues/13035>. As a workaround, add
86 the O_CREAT flag, although it ought not to be necessary. */
92 if (supersede_if_exists
)
94 if (supersede_if_does_not_exist
)
98 if (stat (filename
, &statbuf
) >= 0
99 && ! S_ISREG (statbuf
.st_mode
)
100 /* The file exists and is possibly a character device, socket, or
101 something like that. */
102 && ((fd
= open (filename
, flags
| extra_flags
, mode
)) >= 0
107 action
->final_rename_temp
= NULL
;
108 action
->final_rename_dest
= NULL
;
113 /* The file does not exist or is a regular file.
114 Use a temporary file. */
115 char *canon_filename
=
116 canonicalize_filename_mode (filename
, CAN_ALL_BUT_LAST
);
117 if (canon_filename
== NULL
)
121 fd
= create_temp_file (canon_filename
, flags
, mode
, action
);
124 int saved_errno
= errno
;
125 free (canon_filename
);
133 fd
= open (filename
, flags
| O_CREAT
| O_EXCL
, mode
);
136 /* The file did not exist. */
137 action
->final_rename_temp
= NULL
;
138 action
->final_rename_dest
= NULL
;
142 /* The file exists or is a symbolic link to a nonexistent
144 char *canon_filename
=
145 canonicalize_filename_mode (filename
, CAN_ALL_BUT_LAST
);
146 if (canon_filename
== NULL
)
150 fd
= open (canon_filename
, flags
| O_CREAT
| O_EXCL
, mode
);
153 /* It was a symbolic link to a nonexistent file. */
154 free (canon_filename
);
155 action
->final_rename_temp
= NULL
;
156 action
->final_rename_dest
= NULL
;
160 /* The file exists. */
163 if (stat (canon_filename
, &statbuf
) >= 0
164 && S_ISREG (statbuf
.st_mode
))
166 /* It is a regular file. Use a temporary file. */
167 fd
= create_temp_file (canon_filename
, flags
, mode
,
171 int saved_errno
= errno
;
172 free (canon_filename
);
178 /* It is possibly a character device, socket, or
179 something like that. */
180 fd
= open (canon_filename
, flags
| extra_flags
, mode
);
183 free (canon_filename
);
184 action
->final_rename_temp
= NULL
;
185 action
->final_rename_dest
= NULL
;
189 int saved_errno
= errno
;
190 free (canon_filename
);
201 if (supersede_if_does_not_exist
)
203 fd
= open (filename
, flags
, mode
);
206 /* The file exists. */
207 action
->final_rename_temp
= NULL
;
208 action
->final_rename_dest
= NULL
;
211 /* Work around <https://www.illumos.org/issues/13035>. */
212 else if (errno
== EINVAL
)
216 if (stat (filename
, &statbuf
) >= 0
217 && ! S_ISREG (statbuf
.st_mode
))
219 /* The file exists and is possibly a character device, socket,
220 or something like that. As a workaround, add the O_CREAT
221 flag, although it ought not to be necessary.*/
222 fd
= open (filename
, flags
| extra_flags
, mode
);
225 /* The file exists. */
226 action
->final_rename_temp
= NULL
;
227 action
->final_rename_dest
= NULL
;
232 else if (errno
== ENOENT
)
234 /* The file does not exist. Use a temporary file. */
235 char *canon_filename
=
236 canonicalize_filename_mode (filename
, CAN_ALL_BUT_LAST
);
237 if (canon_filename
== NULL
)
241 fd
= create_temp_file (canon_filename
, flags
, mode
, action
);
244 int saved_errno
= errno
;
245 free (canon_filename
);
253 /* Never use a temporary file. */
254 fd
= open (filename
, flags
| O_CREAT
, mode
);
255 action
->final_rename_temp
= NULL
;
256 action
->final_rename_dest
= NULL
;
263 after_close_actions (int ret
, const struct supersede_final_action
*action
)
267 /* There was an error writing. Erase the temporary file. */
268 if (action
->final_rename_temp
!= NULL
)
270 int saved_errno
= errno
;
271 ignore_value (unlink (action
->final_rename_temp
));
272 free (action
->final_rename_temp
);
273 free (action
->final_rename_dest
);
279 if (action
->final_rename_temp
!= NULL
)
281 struct stat temp_statbuf
;
282 struct stat dest_statbuf
;
284 if (stat (action
->final_rename_temp
, &temp_statbuf
) < 0)
286 /* We just finished writing the temporary file, but now cannot access
287 it. There's something wrong. */
288 int saved_errno
= errno
;
289 ignore_value (unlink (action
->final_rename_temp
));
290 free (action
->final_rename_temp
);
291 free (action
->final_rename_dest
);
296 if (stat (action
->final_rename_dest
, &dest_statbuf
) >= 0)
298 /* Copy the access time from the destination file to the temporary
301 struct timespec ts
[2];
303 ts
[0] = get_stat_atime (&dest_statbuf
);
304 ts
[1] = get_stat_mtime (&temp_statbuf
);
305 ignore_value (utimens (action
->final_rename_temp
, ts
));
309 /* Copy the owner and group from the destination file to the
311 ignore_value (chown (action
->final_rename_temp
,
312 dest_statbuf
.st_uid
, dest_statbuf
.st_gid
));
315 /* Copy the access permissions from the destination file to the
318 switch (qcopy_acl (action
->final_rename_dest
, -1,
319 action
->final_rename_temp
, -1,
320 dest_statbuf
.st_mode
))
323 /* Could not get the ACL of the destination file. */
325 /* Could not set the ACL on the temporary file. */
326 ignore_value (unlink (action
->final_rename_temp
));
327 free (action
->final_rename_temp
);
328 free (action
->final_rename_dest
);
333 chmod (action
->final_rename_temp
, dest_statbuf
.st_mode
);
337 /* No chmod needed, since the mode was already passed to
338 gen_register_open_temp. */
341 /* Rename the temporary file to the destination file. */
342 #if defined _WIN32 && !defined __CYGWIN__
343 /* A native Windows platform. */
345 <https://docs.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-replacefilea>
346 is atomic regarding the file's contents, says
347 https://stackoverflow.com/questions/167414/is-an-atomic-file-rename-with-overwrite-possible-on-windows>
348 But it fails with GetLastError () == ERROR_FILE_NOT_FOUND if
349 action->final_rename_dest does not exist. So better use
351 <https://docs.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-movefileexa>. */
352 if (!MoveFileEx (action
->final_rename_temp
, action
->final_rename_dest
,
353 MOVEFILE_REPLACE_EXISTING
))
356 switch (GetLastError ())
358 case ERROR_INVALID_PARAMETER
:
359 saved_errno
= EINVAL
; break;
361 saved_errno
= EIO
; break;
363 ignore_value (unlink (action
->final_rename_temp
));
364 free (action
->final_rename_temp
);
365 free (action
->final_rename_dest
);
370 if (rename (action
->final_rename_temp
, action
->final_rename_dest
) < 0)
372 int saved_errno
= errno
;
373 ignore_value (unlink (action
->final_rename_temp
));
374 free (action
->final_rename_temp
);
375 free (action
->final_rename_dest
);
381 unregister_temporary_file (action
->final_rename_temp
);
383 free (action
->final_rename_temp
);
384 free (action
->final_rename_dest
);
391 close_supersede (int fd
, const struct supersede_final_action
*action
)
395 int saved_errno
= errno
;
396 free (action
->final_rename_temp
);
397 free (action
->final_rename_dest
);
403 if (action
->final_rename_temp
!= NULL
)
404 ret
= close_temp (fd
);
407 return after_close_actions (ret
, action
);
411 fopen_supersede (const char *filename
, const char *mode
,
412 bool supersede_if_exists
, bool supersede_if_does_not_exist
,
413 struct supersede_final_action
*action
)
415 /* Parse the mode. */
416 int open_direction
= 0;
419 const char *p
= mode
;
421 for (; *p
!= '\0'; p
++)
426 open_direction
= O_RDONLY
;
429 open_direction
= O_WRONLY
;
430 open_flags
|= /* not! O_CREAT | */ O_TRUNC
;
433 open_direction
= O_WRONLY
;
434 open_flags
|= /* not! O_CREAT | */ O_APPEND
;
437 /* While it is non-standard, O_BINARY is guaranteed by
439 open_flags
|= O_BINARY
;
442 open_direction
= O_RDWR
;
445 /* not! open_flags |= O_EXCL; */
448 open_flags
|= O_CLOEXEC
;
457 mode_t open_mode
= S_IRUSR
| S_IWUSR
| S_IRGRP
| S_IWGRP
| S_IROTH
| S_IWOTH
;
458 int fd
= open_supersede (filename
, open_direction
| open_flags
, open_mode
,
459 supersede_if_exists
, supersede_if_does_not_exist
,
464 FILE *stream
= fdopen (fd
, mode
);
467 int saved_errno
= errno
;
469 close_supersede (-1, action
);
476 fclose_supersede (FILE *stream
, const struct supersede_final_action
*action
)
481 if (action
->final_rename_temp
!= NULL
)
482 ret
= fclose_temp (stream
);
484 ret
= fclose (stream
);
485 return after_close_actions (ret
, action
);
488 #if GNULIB_FWRITEERROR
490 fwriteerror_supersede (FILE *stream
, const struct supersede_final_action
*action
)
495 if (action
->final_rename_temp
!= NULL
)
496 ret
= fclose_temp (stream
);
498 ret
= fclose (stream
);
499 return after_close_actions (ret
, action
);