(root)/
gettext-0.22.4/
gettext-tools/
gnulib-lib/
supersede.c
       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