unistr/u{8,16,32}-uctomb: Avoid possible trouble with huge strings.
[gnulib.git] / lib / supersede.c
bloba03cc6d13d39b1fbc0ffd5072b2a660839dd42ab
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. */
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 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);
67 if (fd < 0)
68 return -1;
70 action->final_rename_temp = temp_filename;
71 action->final_rename_dest = canon_filename;
72 return fd;
75 int
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)
80 int fd;
81 /* Extra flags for existing devices. */
82 int extra_flags =
83 #if defined __sun
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. */
87 O_CREAT;
88 #else
90 #endif
92 if (supersede_if_exists)
94 if (supersede_if_does_not_exist)
96 struct stat statbuf;
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
103 || errno != ENOENT))
105 if (fd >= 0)
107 action->final_rename_temp = NULL;
108 action->final_rename_dest = NULL;
111 else
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)
118 fd = -1;
119 else
121 fd = create_temp_file (canon_filename, flags, mode, action);
122 if (fd < 0)
124 int saved_errno = errno;
125 free (canon_filename);
126 errno = saved_errno;
131 else
133 fd = open (filename, flags | O_CREAT | O_EXCL, mode);
134 if (fd >= 0)
136 /* The file did not exist. */
137 action->final_rename_temp = NULL;
138 action->final_rename_dest = NULL;
140 else
142 /* The file exists or is a symbolic link to a nonexistent
143 file. */
144 char *canon_filename =
145 canonicalize_filename_mode (filename, CAN_ALL_BUT_LAST);
146 if (canon_filename == NULL)
147 fd = -1;
148 else
150 fd = open (canon_filename, flags | O_CREAT | O_EXCL, mode);
151 if (fd >= 0)
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;
158 else
160 /* The file exists. */
161 struct stat statbuf;
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,
168 action);
169 if (fd < 0)
171 int saved_errno = errno;
172 free (canon_filename);
173 errno = saved_errno;
176 else
178 /* It is possibly a character device, socket, or
179 something like that. */
180 fd = open (canon_filename, flags | extra_flags, mode);
181 if (fd >= 0)
183 free (canon_filename);
184 action->final_rename_temp = NULL;
185 action->final_rename_dest = NULL;
187 else
189 int saved_errno = errno;
190 free (canon_filename);
191 errno = saved_errno;
199 else
201 if (supersede_if_does_not_exist)
203 fd = open (filename, flags, mode);
204 if (fd >= 0)
206 /* The file exists. */
207 action->final_rename_temp = NULL;
208 action->final_rename_dest = NULL;
210 #if defined __sun
211 /* Work around <https://www.illumos.org/issues/13035>. */
212 else if (errno == EINVAL)
214 struct stat statbuf;
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);
223 if (fd >= 0)
225 /* The file exists. */
226 action->final_rename_temp = NULL;
227 action->final_rename_dest = NULL;
231 #endif
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)
238 fd = -1;
239 else
241 fd = create_temp_file (canon_filename, flags, mode, action);
242 if (fd < 0)
244 int saved_errno = errno;
245 free (canon_filename);
246 errno = saved_errno;
251 else
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;
259 return fd;
262 static int
263 after_close_actions (int ret, const struct supersede_final_action *action)
265 if (ret < 0)
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);
274 errno = saved_errno;
276 return ret;
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);
292 errno = saved_errno;
293 return -1;
296 if (stat (action->final_rename_dest, &dest_statbuf) >= 0)
298 /* Copy the access time from the destination file to the temporary
299 file. */
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));
308 #if HAVE_CHOWN
309 /* Copy the owner and group from the destination file to the
310 temporary file. */
311 ignore_value (chown (action->final_rename_temp,
312 dest_statbuf.st_uid, dest_statbuf.st_gid));
313 #endif
315 /* Copy the access permissions from the destination file to the
316 temporary file. */
317 #if USE_ACL
318 switch (qcopy_acl (action->final_rename_dest, -1,
319 action->final_rename_temp, -1,
320 dest_statbuf.st_mode))
322 case -2:
323 /* Could not get the ACL of the destination file. */
324 case -1:
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);
329 errno = EPERM;
330 return -1;
332 #else
333 chmod (action->final_rename_temp, dest_statbuf.st_mode);
334 #endif
336 else
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. */
344 /* ReplaceFile
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
350 MoveFileEx
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))
355 int saved_errno;
356 switch (GetLastError ())
358 case ERROR_INVALID_PARAMETER:
359 saved_errno = EINVAL; break;
360 default:
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);
366 errno = saved_errno;
367 return -1;
369 #else
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);
376 errno = saved_errno;
377 return -1;
379 #endif
381 unregister_temporary_file (action->final_rename_temp);
383 free (action->final_rename_temp);
384 free (action->final_rename_dest);
387 return ret;
391 close_supersede (int fd, const struct supersede_final_action *action)
393 if (fd < 0)
395 int saved_errno = errno;
396 free (action->final_rename_temp);
397 free (action->final_rename_dest);
398 errno = saved_errno;
399 return fd;
402 int ret;
403 if (action->final_rename_temp != NULL)
404 ret = close_temp (fd);
405 else
406 ret = close (fd);
407 return after_close_actions (ret, action);
410 FILE *
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;
417 int open_flags = 0;
419 const char *p = mode;
421 for (; *p != '\0'; p++)
423 switch (*p)
425 case 'r':
426 open_direction = O_RDONLY;
427 continue;
428 case 'w':
429 open_direction = O_WRONLY;
430 open_flags |= /* not! O_CREAT | */ O_TRUNC;
431 continue;
432 case 'a':
433 open_direction = O_WRONLY;
434 open_flags |= /* not! O_CREAT | */ O_APPEND;
435 continue;
436 case 'b':
437 /* While it is non-standard, O_BINARY is guaranteed by
438 gnulib <fcntl.h>. */
439 open_flags |= O_BINARY;
440 continue;
441 case '+':
442 open_direction = O_RDWR;
443 continue;
444 case 'x':
445 /* not! open_flags |= O_EXCL; */
446 continue;
447 case 'e':
448 open_flags |= O_CLOEXEC;
449 continue;
450 default:
451 break;
453 break;
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,
460 action);
461 if (fd < 0)
462 return NULL;
464 FILE *stream = fdopen (fd, mode);
465 if (stream == NULL)
467 int saved_errno = errno;
468 close (fd);
469 close_supersede (-1, action);
470 errno = saved_errno;
472 return stream;
476 fclose_supersede (FILE *stream, const struct supersede_final_action *action)
478 if (stream == NULL)
479 return -1;
480 int ret;
481 if (action->final_rename_temp != NULL)
482 ret = fclose_temp (stream);
483 else
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)
492 if (stream == NULL)
493 return -1;
494 int ret;
495 if (action->final_rename_temp != NULL)
496 ret = fclose_temp (stream);
497 else
498 ret = fclose (stream);
499 return after_close_actions (ret, action);
501 #endif