1 /* Open a file, without destroying an old file with the same name.
2 
3    Copyright (C) 2020 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
create_temp_file(char * canon_filename,int flags,mode_t mode,struct supersede_final_action * action)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   memcpy (temp_filename, canon_filename, canon_filename_length);
64   memcpy (temp_filename + canon_filename_length, ".XXXXXX", 7 + 1);
65 
66   int fd = gen_register_open_temp (temp_filename, 0, flags, mode);
67   if (fd < 0)
68     return -1;
69 
70   action->final_rename_temp = temp_filename;
71   action->final_rename_dest = canon_filename;
72   return fd;
73 }
74 
75 int
open_supersede(const char * filename,int flags,mode_t mode,bool supersede_if_exists,bool supersede_if_does_not_exist,struct supersede_final_action * action)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)
79 {
80   int fd;
81 
82   if (supersede_if_exists)
83     {
84       if (supersede_if_does_not_exist)
85         {
86           struct stat statbuf;
87 
88           if (stat (filename, &statbuf) >= 0
89               && ! S_ISREG (statbuf.st_mode)
90               /* The file exists and is possibly a character device, socket, or
91                  something like that.  */
92               && ((fd = open (filename, flags, mode)) >= 0
93                   || errno != ENOENT))
94             {
95               if (fd >= 0)
96                 {
97                   action->final_rename_temp = NULL;
98                   action->final_rename_dest = NULL;
99                 }
100             }
101           else
102             {
103               /* The file does not exist or is a regular file.
104                  Use a temporary file.  */
105               char *canon_filename =
106                 canonicalize_filename_mode (filename, CAN_ALL_BUT_LAST);
107               if (canon_filename == NULL)
108                 fd = -1;
109               else
110                 {
111                   fd = create_temp_file (canon_filename, flags, mode, action);
112                   if (fd < 0)
113                     {
114                       int saved_errno = errno;
115                       free (canon_filename);
116                       errno = saved_errno;
117                     }
118                 }
119             }
120         }
121       else
122         {
123           fd = open (filename, flags | O_CREAT | O_EXCL, mode);
124           if (fd >= 0)
125             {
126               /* The file did not exist.  */
127               action->final_rename_temp = NULL;
128               action->final_rename_dest = NULL;
129             }
130           else
131             {
132               /* The file exists or is a symbolic link to a nonexistent
133                  file.  */
134               char *canon_filename =
135                 canonicalize_filename_mode (filename, CAN_ALL_BUT_LAST);
136               if (canon_filename == NULL)
137                 fd = -1;
138               else
139                 {
140                   fd = open (canon_filename, flags | O_CREAT | O_EXCL, mode);
141                   if (fd >= 0)
142                     {
143                       /* It was a symbolic link to a nonexistent file.  */
144                       free (canon_filename);
145                       action->final_rename_temp = NULL;
146                       action->final_rename_dest = NULL;
147                     }
148                   else
149                     {
150                       /* The file exists.  */
151                       struct stat statbuf;
152 
153                       if (stat (canon_filename, &statbuf) >= 0
154                           && S_ISREG (statbuf.st_mode))
155                         {
156                           /* It is a regular file.  Use a temporary file.  */
157                           fd = create_temp_file (canon_filename, flags, mode,
158                                                  action);
159                           if (fd < 0)
160                             {
161                               int saved_errno = errno;
162                               free (canon_filename);
163                               errno = saved_errno;
164                             }
165                         }
166                       else
167                         {
168                           /* It is possibly a character device, socket, or
169                              something like that.  */
170                           fd = open (canon_filename, flags, mode);
171                           if (fd >= 0)
172                             {
173                               free (canon_filename);
174                               action->final_rename_temp = NULL;
175                               action->final_rename_dest = NULL;
176                             }
177                           else
178                             {
179                               int saved_errno = errno;
180                               free (canon_filename);
181                               errno = saved_errno;
182                             }
183                         }
184                     }
185                 }
186             }
187         }
188     }
189   else
190     {
191       if (supersede_if_does_not_exist)
192         {
193           fd = open (filename, flags, mode);
194           if (fd >= 0)
195             {
196               /* The file exists.  */
197               action->final_rename_temp = NULL;
198               action->final_rename_dest = NULL;
199             }
200           else if (errno == ENOENT)
201             {
202               /* The file does not exist.  Use a temporary file.  */
203               char *canon_filename =
204                 canonicalize_filename_mode (filename, CAN_ALL_BUT_LAST);
205               if (canon_filename == NULL)
206                 fd = -1;
207               else
208                 {
209                   fd = create_temp_file (canon_filename, flags, mode, action);
210                   if (fd < 0)
211                     {
212                       int saved_errno = errno;
213                       free (canon_filename);
214                       errno = saved_errno;
215                     }
216                 }
217             }
218         }
219       else
220         {
221           /* Never use a temporary file.  */
222           fd = open (filename, flags | O_CREAT, mode);
223           action->final_rename_temp = NULL;
224           action->final_rename_dest = NULL;
225         }
226     }
227   return fd;
228 }
229 
230 static int
after_close_actions(int ret,const struct supersede_final_action * action)231 after_close_actions (int ret, const struct supersede_final_action *action)
232 {
233   if (ret < 0)
234     {
235       /* There was an error writing.  Erase the temporary file.  */
236       if (action->final_rename_temp != NULL)
237         {
238           int saved_errno = errno;
239           ignore_value (unlink (action->final_rename_temp));
240           free (action->final_rename_temp);
241           free (action->final_rename_dest);
242           errno = saved_errno;
243         }
244       return ret;
245     }
246 
247   if (action->final_rename_temp != NULL)
248     {
249       struct stat temp_statbuf;
250       struct stat dest_statbuf;
251 
252       if (stat (action->final_rename_temp, &temp_statbuf) < 0)
253         {
254           /* We just finished writing the temporary file, but now cannot access
255              it.  There's something wrong.  */
256           int saved_errno = errno;
257           ignore_value (unlink (action->final_rename_temp));
258           free (action->final_rename_temp);
259           free (action->final_rename_dest);
260           errno = saved_errno;
261           return -1;
262         }
263 
264       if (stat (action->final_rename_dest, &dest_statbuf) >= 0)
265         {
266           /* Copy the access time from the destination file to the temporary
267              file.  */
268           {
269             struct timespec ts[2];
270 
271             ts[0] = get_stat_atime (&dest_statbuf);
272             ts[1] = get_stat_mtime (&temp_statbuf);
273             ignore_value (utimens (action->final_rename_temp, ts));
274           }
275 
276 #if HAVE_CHOWN
277           /* Copy the owner and group from the destination file to the
278              temporary file.  */
279           ignore_value (chown (action->final_rename_temp,
280                                dest_statbuf.st_uid, dest_statbuf.st_gid));
281 #endif
282 
283           /* Copy the access permissions from the destination file to the
284              temporary file.  */
285 #if USE_ACL
286           switch (qcopy_acl (action->final_rename_dest, -1,
287                              action->final_rename_temp, -1,
288                              dest_statbuf.st_mode))
289             {
290             case -2:
291               /* Could not get the ACL of the destination file.  */
292             case -1:
293               /* Could not set the ACL on the temporary file.  */
294               ignore_value (unlink (action->final_rename_temp));
295               free (action->final_rename_temp);
296               free (action->final_rename_dest);
297               errno = EPERM;
298               return -1;
299             }
300 #else
301           chmod (action->final_rename_temp, dest_statbuf.st_mode);
302 #endif
303         }
304       else
305         /* No chmod needed, since the mode was already passed to
306            gen_register_open_temp.  */
307         ;
308 
309       /* Rename the temporary file to the destination file.  */
310 #if defined _WIN32 && !defined __CYGWIN__
311       /* A native Windows platform.  */
312       /* ReplaceFile
313          <https://docs.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-replacefilea>
314          is atomic regarding the file's contents, says
315          https://stackoverflow.com/questions/167414/is-an-atomic-file-rename-with-overwrite-possible-on-windows>
316          But it fails with GetLastError () == ERROR_FILE_NOT_FOUND if
317          action->final_rename_dest does not exist.  So better use
318          MoveFileEx
319          <https://docs.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-movefileexa>.  */
320       if (!MoveFileEx (action->final_rename_temp, action->final_rename_dest,
321                        MOVEFILE_REPLACE_EXISTING))
322         {
323           int saved_errno;
324           switch (GetLastError ())
325             {
326             case ERROR_INVALID_PARAMETER:
327               saved_errno = EINVAL; break;
328             default:
329               saved_errno = EIO; break;
330             }
331           ignore_value (unlink (action->final_rename_temp));
332           free (action->final_rename_temp);
333           free (action->final_rename_dest);
334           errno = saved_errno;
335           return -1;
336         }
337 #else
338       if (rename (action->final_rename_temp, action->final_rename_dest) < 0)
339         {
340           int saved_errno = errno;
341           ignore_value (unlink (action->final_rename_temp));
342           free (action->final_rename_temp);
343           free (action->final_rename_dest);
344           errno = saved_errno;
345           return -1;
346         }
347 #endif
348 
349       unregister_temporary_file (action->final_rename_temp);
350 
351       free (action->final_rename_temp);
352       free (action->final_rename_dest);
353     }
354 
355   return ret;
356 }
357 
358 int
close_supersede(int fd,const struct supersede_final_action * action)359 close_supersede (int fd, const struct supersede_final_action *action)
360 {
361   if (fd < 0)
362     {
363       int saved_errno = errno;
364       free (action->final_rename_temp);
365       free (action->final_rename_dest);
366       errno = saved_errno;
367       return fd;
368     }
369 
370   int ret;
371   if (action->final_rename_temp != NULL)
372     ret = close_temp (fd);
373   else
374     ret = close (fd);
375   return after_close_actions (ret, action);
376 }
377 
378 FILE *
fopen_supersede(const char * filename,const char * mode,bool supersede_if_exists,bool supersede_if_does_not_exist,struct supersede_final_action * action)379 fopen_supersede (const char *filename, const char *mode,
380                  bool supersede_if_exists, bool supersede_if_does_not_exist,
381                  struct supersede_final_action *action)
382 {
383   /* Parse the mode.  */
384   int open_direction = 0;
385   int open_flags = 0;
386   {
387     const char *p = mode;
388 
389     for (; *p != '\0'; p++)
390       {
391         switch (*p)
392           {
393           case 'r':
394             open_direction = O_RDONLY;
395             continue;
396           case 'w':
397             open_direction = O_WRONLY;
398             open_flags |= /* not! O_CREAT | */ O_TRUNC;
399             continue;
400           case 'a':
401             open_direction = O_WRONLY;
402             open_flags |= /* not! O_CREAT | */ O_APPEND;
403             continue;
404           case 'b':
405             /* While it is non-standard, O_BINARY is guaranteed by
406                gnulib <fcntl.h>.  */
407             open_flags |= O_BINARY;
408             continue;
409           case '+':
410             open_direction = O_RDWR;
411             continue;
412           case 'x':
413             /* not! open_flags |= O_EXCL; */
414             continue;
415           case 'e':
416             open_flags |= O_CLOEXEC;
417             continue;
418           default:
419             break;
420           }
421         break;
422       }
423   }
424 
425   mode_t open_mode = S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH;
426   int fd = open_supersede (filename, open_direction | open_flags, open_mode,
427                            supersede_if_exists, supersede_if_does_not_exist,
428                            action);
429   if (fd < 0)
430     return NULL;
431 
432   FILE *stream = fdopen (fd, mode);
433   if (stream == NULL)
434     {
435       int saved_errno = errno;
436       close (fd);
437       close_supersede (-1, action);
438       errno = saved_errno;
439     }
440   return stream;
441 }
442 
443 int
fclose_supersede(FILE * stream,const struct supersede_final_action * action)444 fclose_supersede (FILE *stream, const struct supersede_final_action *action)
445 {
446   if (stream == NULL)
447     return -1;
448   int ret;
449   if (action->final_rename_temp != NULL)
450     ret = fclose_temp (stream);
451   else
452     ret = fclose (stream);
453   return after_close_actions (ret, action);
454 }
455 
456 #if GNULIB_FWRITEERROR
457 int
fwriteerror_supersede(FILE * stream,const struct supersede_final_action * action)458 fwriteerror_supersede (FILE *stream, const struct supersede_final_action *action)
459 {
460   if (stream == NULL)
461     return -1;
462   int ret;
463   if (action->final_rename_temp != NULL)
464     ret = fclose_temp (stream);
465   else
466     ret = fclose (stream);
467   return after_close_actions (ret, action);
468 }
469 #endif
470