exp2l: Work around a NetBSD 10.0/i386 bug.
[gnulib.git] / lib / supersede.c
blobb58e0b93dbc29e1c47ea00960d0928bc9f6b7397
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. */
20 #include <config.h>
22 /* Specification. */
23 #include "supersede.h"
25 #include <errno.h>
26 #include <fcntl.h>
27 #include <stdlib.h>
28 #include <string.h>
29 #include <sys/stat.h>
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
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"
47 #if defined _WIN32 && !defined __CYGWIN__
48 /* Don't assume that UNICODE is not defined. */
49 # undef MoveFileEx
50 # define MoveFileEx MoveFileExA
51 #endif
53 static int
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)
64 return -1;
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);
69 if (fd < 0)
70 return -1;
72 action->final_rename_temp = temp_filename;
73 action->final_rename_dest = canon_filename;
74 return fd;
77 int
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)
82 int fd;
83 /* Extra flags for existing devices. */
84 int extra_flags =
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
91 native Windows.
92 As a workaround, add the O_CREAT flag, although it ought not to be
93 necessary. */
94 O_CREAT;
95 #else
97 #endif
99 #if defined _WIN32 && ! defined __CYGWIN__
100 if (strcmp (filename, "/dev/null") == 0)
101 filename = "NUL";
102 #endif
104 if (supersede_if_exists)
106 if (supersede_if_does_not_exist)
108 struct stat statbuf;
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
115 || errno != ENOENT))
117 if (fd >= 0)
119 action->final_rename_temp = NULL;
120 action->final_rename_dest = NULL;
123 else
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)
130 fd = -1;
131 else
133 fd = create_temp_file (canon_filename, flags, mode, action);
134 if (fd < 0)
135 free (canon_filename);
139 else
141 fd = open (filename, flags | O_CREAT | O_EXCL, mode);
142 if (fd >= 0)
144 /* The file did not exist. */
145 action->final_rename_temp = NULL;
146 action->final_rename_dest = NULL;
148 else
150 /* The file exists or is a symbolic link to a nonexistent
151 file. */
152 char *canon_filename =
153 canonicalize_filename_mode (filename, CAN_ALL_BUT_LAST);
154 if (canon_filename == NULL)
155 fd = -1;
156 else
158 fd = open (canon_filename, flags | O_CREAT | O_EXCL, mode);
159 if (fd >= 0)
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;
166 else
168 /* The file exists. */
169 struct stat statbuf;
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,
176 action);
177 if (fd < 0)
178 free (canon_filename);
180 else
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);
186 if (fd >= 0)
188 action->final_rename_temp = NULL;
189 action->final_rename_dest = NULL;
197 else
199 if (supersede_if_does_not_exist)
201 fd = open (filename, flags, mode);
202 if (fd >= 0)
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)
212 struct stat statbuf;
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);
221 if (fd >= 0)
223 /* The file exists. */
224 action->final_rename_temp = NULL;
225 action->final_rename_dest = NULL;
229 #endif
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)
236 fd = -1;
237 else
239 fd = create_temp_file (canon_filename, flags, mode, action);
240 if (fd < 0)
241 free (canon_filename);
245 else
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;
253 return fd;
256 static int
257 after_close_actions (int ret, const struct supersede_final_action *action)
259 if (ret < 0)
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);
268 errno = saved_errno;
270 return ret;
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);
286 errno = saved_errno;
287 return -1;
290 if (stat (action->final_rename_dest, &dest_statbuf) >= 0)
292 /* Copy the access time from the destination file to the temporary
293 file. */
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));
302 #if HAVE_CHOWN
303 /* Copy the owner and group from the destination file to the
304 temporary file. */
305 ignore_value (chown (action->final_rename_temp,
306 dest_statbuf.st_uid, dest_statbuf.st_gid));
307 #endif
309 /* Copy the access permissions from the destination file to the
310 temporary file. */
311 #if USE_ACL
312 switch (qcopy_acl (action->final_rename_dest, -1,
313 action->final_rename_temp, -1,
314 dest_statbuf.st_mode))
316 case -2:
317 /* Could not get the ACL of the destination file. */
318 case -1:
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);
323 errno = EPERM;
324 return -1;
326 #else
327 chmod (action->final_rename_temp, dest_statbuf.st_mode);
328 #endif
330 else
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. */
338 /* ReplaceFile
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
344 MoveFileEx
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))
349 int saved_errno;
350 switch (GetLastError ())
352 case ERROR_INVALID_PARAMETER:
353 saved_errno = EINVAL; break;
354 default:
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);
360 errno = saved_errno;
361 return -1;
363 #else
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);
370 errno = saved_errno;
371 return -1;
373 #endif
375 unregister_temporary_file (action->final_rename_temp);
377 free (action->final_rename_temp);
378 free (action->final_rename_dest);
381 return ret;
385 close_supersede (int fd, const struct supersede_final_action *action)
387 if (fd < 0)
389 free (action->final_rename_temp);
390 free (action->final_rename_dest);
391 return fd;
394 int ret;
395 if (action->final_rename_temp != NULL)
396 ret = close_temp (fd);
397 else
398 ret = close (fd);
399 return after_close_actions (ret, action);
402 FILE *
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;
409 int open_flags = 0;
411 const char *p = mode;
413 for (; *p != '\0'; p++)
415 switch (*p)
417 case 'r':
418 open_direction = O_RDONLY;
419 continue;
420 case 'w':
421 open_direction = O_WRONLY;
422 open_flags |= /* not! O_CREAT | */ O_TRUNC;
423 continue;
424 case 'a':
425 open_direction = O_WRONLY;
426 open_flags |= /* not! O_CREAT | */ O_APPEND;
427 continue;
428 case 'b':
429 /* While it is non-standard, O_BINARY is guaranteed by
430 gnulib <fcntl.h>. */
431 open_flags |= O_BINARY;
432 continue;
433 case '+':
434 open_direction = O_RDWR;
435 continue;
436 case 'x':
437 /* not! open_flags |= O_EXCL; */
438 continue;
439 case 'e':
440 open_flags |= O_CLOEXEC;
441 continue;
442 default:
443 break;
445 break;
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,
452 action);
453 if (fd < 0)
454 return NULL;
456 FILE *stream = fdopen (fd, mode);
457 if (stream == NULL)
459 int saved_errno = errno;
460 close (fd);
461 close_supersede (-1, action);
462 errno = saved_errno;
464 return stream;
468 fclose_supersede (FILE *stream, const struct supersede_final_action *action)
470 if (stream == NULL)
471 return -1;
472 int ret;
473 if (action->final_rename_temp != NULL)
474 ret = fclose_temp (stream);
475 else
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)
484 if (stream == NULL)
485 return -1;
486 int ret;
487 if (action->final_rename_temp != NULL)
488 ret = fclose_temp (stream);
489 else
490 ret = fclose (stream);
491 return after_close_actions (ret, action);
493 #endif