root/maint/gnulib/lib/supersede.c

/* [previous][next][first][last][top][bottom][index][help] */

DEFINITIONS

This source file includes following definitions.
  1. create_temp_file
  2. open_supersede
  3. after_close_actions
  4. close_supersede
  5. fopen_supersede
  6. fclose_supersede
  7. fwriteerror_supersede

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

/* [previous][next][first][last][top][bottom][index][help] */