1 /* Open a file, without destroying an old file with the same name.
3 Copyright (C) 2020-2024 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 if (temp_filename
== NULL
)
65 memcpy (temp_filename
, canon_filename
, canon_filename_length
);
66 memcpy (temp_filename
+ canon_filename_length
, ".XXXXXX", 7 + 1);
68 int fd
= gen_register_open_temp (temp_filename
, 0, flags
, mode
);
72 action
->final_rename_temp
= temp_filename
;
73 action
->final_rename_dest
= canon_filename
;
78 open_supersede (const char *filename
, int flags
, mode_t mode
,
79 bool supersede_if_exists
, bool supersede_if_does_not_exist
,
80 struct supersede_final_action
*action
)
83 /* Extra flags for existing devices. */
85 #if defined __sun || (defined _WIN32 && !defined __CYGWIN__)
86 /* open ("/dev/null", O_TRUNC | O_WRONLY) fails on Solaris zones:
87 - with error EINVAL on Illumos, see
88 <https://www.illumos.org/issues/13035>,
89 - with error EACCES on Solaris 11.3.
90 Likewise, open ("NUL", O_TRUNC | O_RDWR) fails with error EINVAL on
92 As a workaround, add the O_CREAT flag, although it ought not to be
99 #if defined _WIN32 && ! defined __CYGWIN__
100 if (strcmp (filename
, "/dev/null") == 0)
104 if (supersede_if_exists
)
106 if (supersede_if_does_not_exist
)
110 if (stat (filename
, &statbuf
) >= 0
111 && ! S_ISREG (statbuf
.st_mode
)
112 /* The file exists and is possibly a character device, socket, or
113 something like that. */
114 && ((fd
= open (filename
, flags
| extra_flags
, mode
)) >= 0
119 action
->final_rename_temp
= NULL
;
120 action
->final_rename_dest
= NULL
;
125 /* The file does not exist or is a regular file.
126 Use a temporary file. */
127 char *canon_filename
=
128 canonicalize_filename_mode (filename
, CAN_ALL_BUT_LAST
);
129 if (canon_filename
== NULL
)
133 fd
= create_temp_file (canon_filename
, flags
, mode
, action
);
135 free (canon_filename
);
141 fd
= open (filename
, flags
| O_CREAT
| O_EXCL
, mode
);
144 /* The file did not exist. */
145 action
->final_rename_temp
= NULL
;
146 action
->final_rename_dest
= NULL
;
150 /* The file exists or is a symbolic link to a nonexistent
152 char *canon_filename
=
153 canonicalize_filename_mode (filename
, CAN_ALL_BUT_LAST
);
154 if (canon_filename
== NULL
)
158 fd
= open (canon_filename
, flags
| O_CREAT
| O_EXCL
, mode
);
161 /* It was a symbolic link to a nonexistent file. */
162 free (canon_filename
);
163 action
->final_rename_temp
= NULL
;
164 action
->final_rename_dest
= NULL
;
168 /* The file exists. */
171 if (stat (canon_filename
, &statbuf
) >= 0
172 && S_ISREG (statbuf
.st_mode
))
174 /* It is a regular file. Use a temporary file. */
175 fd
= create_temp_file (canon_filename
, flags
, mode
,
178 free (canon_filename
);
182 /* It is possibly a character device, socket, or
183 something like that. */
184 fd
= open (canon_filename
, flags
| extra_flags
, mode
);
185 free (canon_filename
);
188 action
->final_rename_temp
= NULL
;
189 action
->final_rename_dest
= NULL
;
199 if (supersede_if_does_not_exist
)
201 fd
= open (filename
, flags
, mode
);
204 /* The file exists. */
205 action
->final_rename_temp
= NULL
;
206 action
->final_rename_dest
= NULL
;
208 #if defined __sun || (defined _WIN32 && !defined __CYGWIN__)
209 /* See the comment regarding extra_flags, above. */
210 else if (errno
== EINVAL
|| errno
== EACCES
)
214 if (stat (filename
, &statbuf
) >= 0
215 && ! S_ISREG (statbuf
.st_mode
))
217 /* The file exists and is possibly a character device, socket,
218 or something like that. As a workaround, add the O_CREAT
219 flag, although it ought not to be necessary.*/
220 fd
= open (filename
, flags
| extra_flags
, mode
);
223 /* The file exists. */
224 action
->final_rename_temp
= NULL
;
225 action
->final_rename_dest
= NULL
;
230 else if (errno
== ENOENT
)
232 /* The file does not exist. Use a temporary file. */
233 char *canon_filename
=
234 canonicalize_filename_mode (filename
, CAN_ALL_BUT_LAST
);
235 if (canon_filename
== NULL
)
239 fd
= create_temp_file (canon_filename
, flags
, mode
, action
);
241 free (canon_filename
);
247 /* Never use a temporary file. */
248 fd
= open (filename
, flags
| O_CREAT
, mode
);
249 action
->final_rename_temp
= NULL
;
250 action
->final_rename_dest
= NULL
;
257 after_close_actions (int ret
, const struct supersede_final_action
*action
)
261 /* There was an error writing. Erase the temporary file. */
262 if (action
->final_rename_temp
!= NULL
)
264 int saved_errno
= errno
;
265 ignore_value (unlink (action
->final_rename_temp
));
266 free (action
->final_rename_temp
);
267 free (action
->final_rename_dest
);
273 if (action
->final_rename_temp
!= NULL
)
275 struct stat temp_statbuf
;
276 struct stat dest_statbuf
;
278 if (stat (action
->final_rename_temp
, &temp_statbuf
) < 0)
280 /* We just finished writing the temporary file, but now cannot access
281 it. There's something wrong. */
282 int saved_errno
= errno
;
283 ignore_value (unlink (action
->final_rename_temp
));
284 free (action
->final_rename_temp
);
285 free (action
->final_rename_dest
);
290 if (stat (action
->final_rename_dest
, &dest_statbuf
) >= 0)
292 /* Copy the access time from the destination file to the temporary
295 struct timespec ts
[2];
297 ts
[0] = get_stat_atime (&dest_statbuf
);
298 ts
[1] = get_stat_mtime (&temp_statbuf
);
299 ignore_value (utimens (action
->final_rename_temp
, ts
));
303 /* Copy the owner and group from the destination file to the
305 ignore_value (chown (action
->final_rename_temp
,
306 dest_statbuf
.st_uid
, dest_statbuf
.st_gid
));
309 /* Copy the access permissions from the destination file to the
312 switch (qcopy_acl (action
->final_rename_dest
, -1,
313 action
->final_rename_temp
, -1,
314 dest_statbuf
.st_mode
))
317 /* Could not get the ACL of the destination file. */
319 /* Could not set the ACL on the temporary file. */
320 ignore_value (unlink (action
->final_rename_temp
));
321 free (action
->final_rename_temp
);
322 free (action
->final_rename_dest
);
327 chmod (action
->final_rename_temp
, dest_statbuf
.st_mode
);
331 /* No chmod needed, since the mode was already passed to
332 gen_register_open_temp. */
335 /* Rename the temporary file to the destination file. */
336 #if defined _WIN32 && !defined __CYGWIN__
337 /* A native Windows platform. */
339 <https://docs.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-replacefilea>
340 is atomic regarding the file's contents, says
341 https://stackoverflow.com/questions/167414/is-an-atomic-file-rename-with-overwrite-possible-on-windows>
342 But it fails with GetLastError () == ERROR_FILE_NOT_FOUND if
343 action->final_rename_dest does not exist. So better use
345 <https://docs.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-movefileexa>. */
346 if (!MoveFileEx (action
->final_rename_temp
, action
->final_rename_dest
,
347 MOVEFILE_REPLACE_EXISTING
))
350 switch (GetLastError ())
352 case ERROR_INVALID_PARAMETER
:
353 saved_errno
= EINVAL
; break;
355 saved_errno
= EIO
; break;
357 ignore_value (unlink (action
->final_rename_temp
));
358 free (action
->final_rename_temp
);
359 free (action
->final_rename_dest
);
364 if (rename (action
->final_rename_temp
, action
->final_rename_dest
) < 0)
366 int saved_errno
= errno
;
367 ignore_value (unlink (action
->final_rename_temp
));
368 free (action
->final_rename_temp
);
369 free (action
->final_rename_dest
);
375 unregister_temporary_file (action
->final_rename_temp
);
377 free (action
->final_rename_temp
);
378 free (action
->final_rename_dest
);
385 close_supersede (int fd
, const struct supersede_final_action
*action
)
389 free (action
->final_rename_temp
);
390 free (action
->final_rename_dest
);
395 if (action
->final_rename_temp
!= NULL
)
396 ret
= close_temp (fd
);
399 return after_close_actions (ret
, action
);
403 fopen_supersede (const char *filename
, const char *mode
,
404 bool supersede_if_exists
, bool supersede_if_does_not_exist
,
405 struct supersede_final_action
*action
)
407 /* Parse the mode. */
408 int open_direction
= 0;
411 const char *p
= mode
;
413 for (; *p
!= '\0'; p
++)
418 open_direction
= O_RDONLY
;
421 open_direction
= O_WRONLY
;
422 open_flags
|= /* not! O_CREAT | */ O_TRUNC
;
425 open_direction
= O_WRONLY
;
426 open_flags
|= /* not! O_CREAT | */ O_APPEND
;
429 /* While it is non-standard, O_BINARY is guaranteed by
431 open_flags
|= O_BINARY
;
434 open_direction
= O_RDWR
;
437 /* not! open_flags |= O_EXCL; */
440 open_flags
|= O_CLOEXEC
;
449 mode_t open_mode
= S_IRUSR
| S_IWUSR
| S_IRGRP
| S_IWGRP
| S_IROTH
| S_IWOTH
;
450 int fd
= open_supersede (filename
, open_direction
| open_flags
, open_mode
,
451 supersede_if_exists
, supersede_if_does_not_exist
,
456 FILE *stream
= fdopen (fd
, mode
);
459 int saved_errno
= errno
;
461 close_supersede (-1, action
);
468 fclose_supersede (FILE *stream
, const struct supersede_final_action
*action
)
473 if (action
->final_rename_temp
!= NULL
)
474 ret
= fclose_temp (stream
);
476 ret
= fclose (stream
);
477 return after_close_actions (ret
, action
);
480 #if GNULIB_FWRITEERROR
482 fwriteerror_supersede (FILE *stream
, const struct supersede_final_action
*action
)
487 if (action
->final_rename_temp
!= NULL
)
488 ret
= fclose_temp (stream
);
490 ret
= fclose (stream
);
491 return after_close_actions (ret
, action
);