1 /* Open a file, without destroying an old file with the same name.
2
3 Copyright (C) 2020-2023 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
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 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);
67
68 int fd = gen_register_open_temp (temp_filename, 0, flags, mode);
69 if (fd < 0)
70 return -1;
71
72 action->final_rename_temp = temp_filename;
73 action->final_rename_dest = canon_filename;
74 return fd;
75 }
76
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)
81 {
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
96 0;
97 #endif
98
99 #if defined _WIN32 && ! defined __CYGWIN__
100 if (strcmp (filename, "/dev/null") == 0)
101 filename = "NUL";
102 #endif
103
104 if (supersede_if_exists)
105 {
106 if (supersede_if_does_not_exist)
107 {
108 struct stat statbuf;
109
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))
116 {
117 if (fd >= 0)
118 {
119 action->final_rename_temp = NULL;
120 action->final_rename_dest = NULL;
121 }
122 }
123 else
124 {
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
132 {
133 fd = create_temp_file (canon_filename, flags, mode, action);
134 if (fd < 0)
135 free (canon_filename);
136 }
137 }
138 }
139 else
140 {
141 fd = open (filename, flags | O_CREAT | O_EXCL, mode);
142 if (fd >= 0)
143 {
144 /* The file did not exist. */
145 action->final_rename_temp = NULL;
146 action->final_rename_dest = NULL;
147 }
148 else
149 {
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
157 {
158 fd = open (canon_filename, flags | O_CREAT | O_EXCL, mode);
159 if (fd >= 0)
160 {
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;
165 }
166 else
167 {
168 /* The file exists. */
169 struct stat statbuf;
170
171 if (stat (canon_filename, &statbuf) >= 0
172 && S_ISREG (statbuf.st_mode))
173 {
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);
179 }
180 else
181 {
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)
187 {
188 action->final_rename_temp = NULL;
189 action->final_rename_dest = NULL;
190 }
191 }
192 }
193 }
194 }
195 }
196 }
197 else
198 {
199 if (supersede_if_does_not_exist)
200 {
201 fd = open (filename, flags, mode);
202 if (fd >= 0)
203 {
204 /* The file exists. */
205 action->final_rename_temp = NULL;
206 action->final_rename_dest = NULL;
207 }
208 #if defined __sun || (defined _WIN32 && !defined __CYGWIN__)
209 /* See the comment regarding extra_flags, above. */
210 else if (errno == EINVAL || errno == EACCES)
211 {
212 struct stat statbuf;
213
214 if (stat (filename, &statbuf) >= 0
215 && ! S_ISREG (statbuf.st_mode))
216 {
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)
222 {
223 /* The file exists. */
224 action->final_rename_temp = NULL;
225 action->final_rename_dest = NULL;
226 }
227 }
228 }
229 #endif
230 else if (errno == ENOENT)
231 {
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
238 {
239 fd = create_temp_file (canon_filename, flags, mode, action);
240 if (fd < 0)
241 free (canon_filename);
242 }
243 }
244 }
245 else
246 {
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;
251 }
252 }
253 return fd;
254 }
255
256 static int
257 after_close_actions (int ret, const struct supersede_final_action *action)
258 {
259 if (ret < 0)
260 {
261 /* There was an error writing. Erase the temporary file. */
262 if (action->final_rename_temp != NULL)
263 {
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;
269 }
270 return ret;
271 }
272
273 if (action->final_rename_temp != NULL)
274 {
275 struct stat temp_statbuf;
276 struct stat dest_statbuf;
277
278 if (stat (action->final_rename_temp, &temp_statbuf) < 0)
279 {
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;
288 }
289
290 if (stat (action->final_rename_dest, &dest_statbuf) >= 0)
291 {
292 /* Copy the access time from the destination file to the temporary
293 file. */
294 {
295 struct timespec ts[2];
296
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));
300 }
301
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
308
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))
315 {
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;
325 }
326 #else
327 chmod (action->final_rename_temp, dest_statbuf.st_mode);
328 #endif
329 }
330 else
331 /* No chmod needed, since the mode was already passed to
332 gen_register_open_temp. */
333 ;
334
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))
348 {
349 int saved_errno;
350 switch (GetLastError ())
351 {
352 case ERROR_INVALID_PARAMETER:
353 saved_errno = EINVAL; break;
354 default:
355 saved_errno = EIO; break;
356 }
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;
362 }
363 #else
364 if (rename (action->final_rename_temp, action->final_rename_dest) < 0)
365 {
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;
372 }
373 #endif
374
375 unregister_temporary_file (action->final_rename_temp);
376
377 free (action->final_rename_temp);
378 free (action->final_rename_dest);
379 }
380
381 return ret;
382 }
383
384 int
385 close_supersede (int fd, const struct supersede_final_action *action)
386 {
387 if (fd < 0)
388 {
389 free (action->final_rename_temp);
390 free (action->final_rename_dest);
391 return fd;
392 }
393
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);
400 }
401
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)
406 {
407 /* Parse the mode. */
408 int open_direction = 0;
409 int open_flags = 0;
410 {
411 const char *p = mode;
412
413 for (; *p != '\0'; p++)
414 {
415 switch (*p)
416 {
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;
444 }
445 break;
446 }
447 }
448
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;
455
456 FILE *stream = fdopen (fd, mode);
457 if (stream == NULL)
458 {
459 int saved_errno = errno;
460 close (fd);
461 close_supersede (-1, action);
462 errno = saved_errno;
463 }
464 return stream;
465 }
466
467 int
468 fclose_supersede (FILE *stream, const struct supersede_final_action *action)
469 {
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);
478 }
479
480 #if GNULIB_FWRITEERROR
481 int
482 fwriteerror_supersede (FILE *stream, const struct supersede_final_action *action)
483 {
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);
492 }
493 #endif