root/maint/gnulib/lib/backupfile.c

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

DEFINITIONS

This source file includes following definitions.
  1. set_simple_backup_suffix
  2. check_extension
  3. numbered_backup
  4. backupfile_internal

   1 /* backupfile.c -- make Emacs style backup file names
   2 
   3    Copyright (C) 1990-2006, 2009-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 Paul Eggert and David MacKenzie.
  19    Some algorithms adapted from GNU Emacs.  */
  20 
  21 #include <config.h>
  22 
  23 #include "backup-internal.h"
  24 
  25 #include <dirent.h>
  26 #include <errno.h>
  27 #include <fcntl.h>
  28 #include <stdbool.h>
  29 #include <stdint.h>
  30 #include <stdlib.h>
  31 #include <string.h>
  32 #include <unistd.h>
  33 
  34 #include "attribute.h"
  35 #include "basename-lgpl.h"
  36 #include "idx.h"
  37 #include "intprops.h"
  38 #include "opendirat.h"
  39 #include "renameatu.h"
  40 
  41 #ifndef _D_EXACT_NAMLEN
  42 # define _D_EXACT_NAMLEN(dp) strlen ((dp)->d_name)
  43 #endif
  44 
  45 #if ! (HAVE_PATHCONF && defined _PC_NAME_MAX)
  46 # define pathconf(file, option) (errno = -1)
  47 # define fpathconf(fd, option) (errno = -1)
  48 #endif
  49 
  50 #ifndef _POSIX_NAME_MAX
  51 # define _POSIX_NAME_MAX 14
  52 #endif
  53 
  54 #if defined _XOPEN_NAME_MAX
  55 # define NAME_MAX_MINIMUM _XOPEN_NAME_MAX
  56 #else
  57 # define NAME_MAX_MINIMUM _POSIX_NAME_MAX
  58 #endif
  59 
  60 #ifndef HAVE_DOS_FILE_NAMES
  61 # define HAVE_DOS_FILE_NAMES 0
  62 #endif
  63 #ifndef HAVE_LONG_FILE_NAMES
  64 # define HAVE_LONG_FILE_NAMES 0
  65 #endif
  66 
  67 /* ISDIGIT differs from isdigit, as follows:
  68    - Its arg may be any int or unsigned int; it need not be an unsigned char
  69      or EOF.
  70    - It's typically faster.
  71    POSIX says that only '0' through '9' are digits.  Prefer ISDIGIT to
  72    ISDIGIT unless it's important to use the locale's definition
  73    of "digit" even when the host does not conform to POSIX.  */
  74 #define ISDIGIT(c) ((unsigned int) (c) - '0' <= 9)
  75 
  76 /* The extension added to file names to produce a simple (as opposed
  77    to numbered) backup file name. */
  78 char const *simple_backup_suffix = NULL;
  79 
  80 /* Set SIMPLE_BACKUP_SUFFIX to S, or to a default specified by the
  81    environment if S is null.  If S or the environment does not specify
  82    a valid backup suffix, use "~".  */
  83 void
  84 set_simple_backup_suffix (char const *s)
     /* [previous][next][first][last][top][bottom][index][help] */
  85 {
  86   if (!s)
  87     s = getenv ("SIMPLE_BACKUP_SUFFIX");
  88   simple_backup_suffix = s && *s && s == last_component (s) ? s : "~";
  89 }
  90 
  91 /* If FILE (which was of length FILELEN before an extension was
  92    appended to it) is too long, replace the extension with the single
  93    char E.  If the result is still too long, remove the char just
  94    before E.
  95 
  96    If DIR_FD is nonnegative, it is a file descriptor for FILE's parent.
  97    *NAME_MAX is either 0, or the cached result of a previous call for
  98    FILE's parent's _PC_NAME_MAX.  */
  99 
 100 static void
 101 check_extension (char *file, size_t filelen, char e,
     /* [previous][next][first][last][top][bottom][index][help] */
 102                  int dir_fd, size_t *base_max)
 103 {
 104   char *base = last_component (file);
 105   size_t baselen = base_len (base);
 106   size_t baselen_max = HAVE_LONG_FILE_NAMES ? 255 : NAME_MAX_MINIMUM;
 107 
 108   if (HAVE_DOS_FILE_NAMES || NAME_MAX_MINIMUM < baselen)
 109     {
 110       /* The new base name is long enough to require a pathconf check.  */
 111       if (*base_max == 0)
 112         {
 113           long name_max;
 114           if (dir_fd < 0)
 115             {
 116               /* Temporarily modify the buffer into its parent
 117                  directory name, invoke pathconf on the directory, and
 118                  then restore the buffer.  */
 119               char tmp[sizeof "."];
 120               memcpy (tmp, base, sizeof ".");
 121               strcpy (base, ".");
 122               errno = 0;
 123               name_max = pathconf (file, _PC_NAME_MAX);
 124               name_max -= !errno;
 125               memcpy (base, tmp, sizeof ".");
 126             }
 127           else
 128             {
 129               errno = 0;
 130               name_max = fpathconf (dir_fd, _PC_NAME_MAX);
 131               name_max -= !errno;
 132             }
 133 
 134           *base_max = (0 <= name_max && name_max <= SIZE_MAX ? name_max
 135                        : name_max < -1 ? NAME_MAX_MINIMUM : SIZE_MAX);
 136         }
 137 
 138       baselen_max = *base_max;
 139     }
 140 
 141   if (HAVE_DOS_FILE_NAMES && baselen_max <= 12)
 142     {
 143       /* Live within DOS's 8.3 limit.  */
 144       char *dot = strchr (base, '.');
 145       if (!dot)
 146         baselen_max = 8;
 147       else
 148         {
 149           char const *second_dot = strchr (dot + 1, '.');
 150           baselen_max = (second_dot
 151                          ? second_dot - base
 152                          : dot + 1 - base + 3);
 153         }
 154     }
 155 
 156   if (baselen_max < baselen)
 157     {
 158       baselen = file + filelen - base;
 159       if (baselen_max <= baselen)
 160         baselen = baselen_max - 1;
 161       base[baselen] = e;
 162       base[baselen + 1] = '\0';
 163     }
 164 }
 165 
 166 /* Returned values for NUMBERED_BACKUP.  */
 167 
 168 enum numbered_backup_result
 169   {
 170     /* The new backup name is the same length as an existing backup
 171        name, so it's valid for that directory.  */
 172     BACKUP_IS_SAME_LENGTH,
 173 
 174     /* Some backup names already exist, but the returned name is longer
 175        than any of them, and its length should be checked.  */
 176     BACKUP_IS_LONGER,
 177 
 178     /* There are no existing backup names.  The new name's length
 179        should be checked.  */
 180     BACKUP_IS_NEW,
 181 
 182     /* Memory allocation failure.  */
 183     BACKUP_NOMEM
 184   };
 185 
 186 /* Relative to DIR_FD, *BUFFER contains a file name.
 187    Store into *BUFFER the next backup name for the named file,
 188    with a version number greater than all the
 189    existing numbered backups.  Reallocate *BUFFER as necessary; its
 190    initial allocated size is BUFFER_SIZE, which must be at least 4
 191    bytes longer than the file name to make room for the initially
 192    appended ".~1".  FILELEN is the length of the original file name.
 193    BASE_OFFSET is the offset of the basename in *BUFFER.
 194    The returned value indicates what kind of backup was found.  If an
 195    I/O or other read error occurs, use the highest backup number that
 196    was found.
 197 
 198    *DIRPP is the destination directory.  If *DIRPP is null, open the
 199    destination directory and store the resulting stream into *DIRPP
 200    and its file descriptor into *PNEW_FD without closing the stream.  */
 201 
 202 static enum numbered_backup_result
 203 numbered_backup (int dir_fd, char **buffer, size_t buffer_size, size_t filelen,
     /* [previous][next][first][last][top][bottom][index][help] */
 204                  idx_t base_offset, DIR **dirpp, int *pnew_fd)
 205 {
 206   enum numbered_backup_result result = BACKUP_IS_NEW;
 207   DIR *dirp = *dirpp;
 208   struct dirent *dp;
 209   char *buf = *buffer;
 210   size_t versionlenmax = 1;
 211   char *base = buf + base_offset;
 212   size_t baselen = base_len (base);
 213 
 214   if (dirp)
 215     rewinddir (dirp);
 216   else
 217     {
 218       /* Temporarily modify the buffer into its parent directory name,
 219          open the directory, and then restore the buffer.  */
 220       char tmp[sizeof "."];
 221       memcpy (tmp, base, sizeof ".");
 222       strcpy (base, ".");
 223       dirp = opendirat (dir_fd, buf, 0, pnew_fd);
 224       if (!dirp && errno == ENOMEM)
 225         result = BACKUP_NOMEM;
 226       memcpy (base, tmp, sizeof ".");
 227       strcpy (base + baselen, ".~1~");
 228       if (!dirp)
 229         return result;
 230       *dirpp = dirp;
 231     }
 232 
 233   while ((dp = readdir (dirp)) != NULL)
 234     {
 235       char const *p;
 236       char *q;
 237       bool all_9s;
 238       size_t versionlen;
 239 
 240       if (_D_EXACT_NAMLEN (dp) < baselen + 4)
 241         continue;
 242 
 243       if (memcmp (buf + base_offset, dp->d_name, baselen + 2) != 0)
 244         continue;
 245 
 246       p = dp->d_name + baselen + 2;
 247 
 248       /* Check whether this file has a version number and if so,
 249          whether it is larger.  Use string operations rather than
 250          integer arithmetic, to avoid problems with integer overflow.  */
 251 
 252       if (! ('1' <= *p && *p <= '9'))
 253         continue;
 254       all_9s = (*p == '9');
 255       for (versionlen = 1; ISDIGIT (p[versionlen]); versionlen++)
 256         all_9s &= (p[versionlen] == '9');
 257 
 258       if (! (p[versionlen] == '~' && !p[versionlen + 1]
 259              && (versionlenmax < versionlen
 260                  || (versionlenmax == versionlen
 261                      && memcmp (buf + filelen + 2, p, versionlen) <= 0))))
 262         continue;
 263 
 264       /* This entry has the largest version number seen so far.
 265          Append this highest numbered extension to the file name,
 266          prepending '0' to the number if it is all 9s.  */
 267 
 268       versionlenmax = all_9s + versionlen;
 269       result = (all_9s ? BACKUP_IS_LONGER : BACKUP_IS_SAME_LENGTH);
 270       size_t new_buffer_size = filelen + 2 + versionlenmax + 2;
 271       if (buffer_size < new_buffer_size)
 272         {
 273           size_t grown;
 274           if (! INT_ADD_WRAPV (new_buffer_size, new_buffer_size >> 1, &grown))
 275             new_buffer_size = grown;
 276           char *new_buf = realloc (buf, new_buffer_size);
 277           if (!new_buf)
 278             {
 279               *buffer = buf;
 280               return BACKUP_NOMEM;
 281             }
 282           buf = new_buf;
 283           buffer_size = new_buffer_size;
 284         }
 285       q = buf + filelen;
 286       *q++ = '.';
 287       *q++ = '~';
 288       *q = '0';
 289       q += all_9s;
 290       memcpy (q, p, versionlen + 2);
 291 
 292       /* Add 1 to the version number.  */
 293 
 294       q += versionlen;
 295       while (*--q == '9')
 296         *q = '0';
 297       ++*q;
 298     }
 299 
 300   *buffer = buf;
 301   return result;
 302 }
 303 
 304 /* Relative to DIR_FD, return the name of the new backup file for the
 305    existing file FILE, allocated with malloc.
 306    If RENAME, also rename FILE to the new name.
 307    On failure, return NULL and set errno.
 308    Do not call this function if backup_type == no_backups.  */
 309 
 310 char *
 311 backupfile_internal (int dir_fd, char const *file,
     /* [previous][next][first][last][top][bottom][index][help] */
 312                      enum backup_type backup_type, bool rename)
 313 {
 314   idx_t base_offset = last_component (file) - file;
 315   size_t filelen = base_offset + strlen (file + base_offset);
 316 
 317   if (! simple_backup_suffix)
 318     set_simple_backup_suffix (NULL);
 319 
 320   /* Allow room for simple or ".~N~" backups.  The guess must be at
 321      least sizeof ".~1~", but otherwise will be adjusted as needed.  */
 322   size_t simple_backup_suffix_size = strlen (simple_backup_suffix) + 1;
 323   size_t backup_suffix_size_guess = simple_backup_suffix_size;
 324   enum { GUESS = sizeof ".~12345~" };
 325   if (backup_suffix_size_guess < GUESS)
 326     backup_suffix_size_guess = GUESS;
 327 
 328   ssize_t ssize = filelen + backup_suffix_size_guess + 1;
 329   char *s = malloc (ssize);
 330   if (!s)
 331     return s;
 332 
 333   DIR *dirp = NULL;
 334   int sdir = -1;
 335   size_t base_max = 0;
 336   while (true)
 337     {
 338       memcpy (s, file, filelen + 1);
 339 
 340       if (backup_type == simple_backups)
 341         memcpy (s + filelen, simple_backup_suffix, simple_backup_suffix_size);
 342       else
 343         switch (numbered_backup (dir_fd, &s, ssize, filelen, base_offset,
 344                                  &dirp, &sdir))
 345           {
 346           case BACKUP_IS_SAME_LENGTH:
 347             break;
 348 
 349           case BACKUP_IS_NEW:
 350             if (backup_type == numbered_existing_backups)
 351               {
 352                 backup_type = simple_backups;
 353                 memcpy (s + filelen, simple_backup_suffix,
 354                         simple_backup_suffix_size);
 355               }
 356             FALLTHROUGH;
 357           case BACKUP_IS_LONGER:
 358             check_extension (s, filelen, '~', sdir, &base_max);
 359             break;
 360 
 361           case BACKUP_NOMEM:
 362             if (dirp)
 363               closedir (dirp);
 364             free (s);
 365             errno = ENOMEM;
 366             return NULL;
 367           }
 368 
 369       if (! rename)
 370         break;
 371 
 372       if (sdir < 0)
 373         {
 374           sdir = AT_FDCWD;
 375           base_offset = 0;
 376         }
 377       unsigned flags = backup_type == simple_backups ? 0 : RENAME_NOREPLACE;
 378       if (renameatu (AT_FDCWD, file, sdir, s + base_offset, flags) == 0)
 379         break;
 380       int e = errno;
 381       if (e != EEXIST)
 382         {
 383           if (dirp)
 384             closedir (dirp);
 385           free (s);
 386           errno = e;
 387           return NULL;
 388         }
 389     }
 390 
 391   if (dirp)
 392     closedir (dirp);
 393   return s;
 394 }

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