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 "ialloc.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.  Return true if the extension was OK already, false
  95    if it needed replacement.
  96 
  97    If DIR_FD is nonnegative, it is a file descriptor for FILE's parent.
  98    *BASE_MAX is either 0, or the cached result of a previous call for
  99    FILE's parent's _PC_NAME_MAX.  */
 100 
 101 static bool
 102 check_extension (char *file, idx_t filelen, char e,
     /* [previous][next][first][last][top][bottom][index][help] */
 103                  int dir_fd, idx_t *base_max)
 104 {
 105   char *base = last_component (file);
 106   idx_t baselen = base_len (base);
 107   idx_t baselen_max = HAVE_LONG_FILE_NAMES ? 255 : NAME_MAX_MINIMUM;
 108 
 109   if (HAVE_DOS_FILE_NAMES || NAME_MAX_MINIMUM < baselen)
 110     {
 111       /* The new base name is long enough to require a pathconf check.  */
 112       if (*base_max == 0)
 113         {
 114           long name_max;
 115           if (dir_fd < 0)
 116             {
 117               /* Temporarily modify the buffer into its parent
 118                  directory name, invoke pathconf on the directory, and
 119                  then restore the buffer.  */
 120               char tmp[sizeof "."];
 121               memcpy (tmp, base, sizeof ".");
 122               strcpy (base, ".");
 123               errno = 0;
 124               name_max = pathconf (file, _PC_NAME_MAX);
 125               name_max -= !errno;
 126               memcpy (base, tmp, sizeof ".");
 127             }
 128           else
 129             {
 130               errno = 0;
 131               name_max = fpathconf (dir_fd, _PC_NAME_MAX);
 132               name_max -= !errno;
 133             }
 134 
 135           *base_max = (0 <= name_max && name_max <= SIZE_MAX ? name_max
 136                        : name_max < -1 ? NAME_MAX_MINIMUM : SIZE_MAX);
 137         }
 138 
 139       baselen_max = *base_max;
 140     }
 141 
 142   if (HAVE_DOS_FILE_NAMES && baselen_max <= 12)
 143     {
 144       /* Live within DOS's 8.3 limit.  */
 145       char *dot = strchr (base, '.');
 146       if (!dot)
 147         baselen_max = 8;
 148       else
 149         {
 150           char const *second_dot = strchr (dot + 1, '.');
 151           baselen_max = (second_dot
 152                          ? second_dot - base
 153                          : dot + 1 - base + 3);
 154         }
 155     }
 156 
 157   if (baselen <= baselen_max)
 158     return true;
 159   else
 160     {
 161       baselen = file + filelen - base;
 162       if (baselen_max <= baselen)
 163         baselen = baselen_max - 1;
 164       base[baselen] = e;
 165       base[baselen + 1] = '\0';
 166       return false;
 167     }
 168 }
 169 
 170 /* Returned values for NUMBERED_BACKUP.  */
 171 
 172 enum numbered_backup_result
 173   {
 174     /* The new backup name is the same length as an existing backup
 175        name, so it's valid for that directory.  */
 176     BACKUP_IS_SAME_LENGTH,
 177 
 178     /* Some backup names already exist, but the returned name is longer
 179        than any of them, and its length should be checked.  */
 180     BACKUP_IS_LONGER,
 181 
 182     /* There are no existing backup names.  The new name's length
 183        should be checked.  */
 184     BACKUP_IS_NEW,
 185 
 186     /* Memory allocation failure.  */
 187     BACKUP_NOMEM
 188   };
 189 
 190 /* Relative to DIR_FD, *BUFFER contains a file name.
 191    Store into *BUFFER the next backup name for the named file,
 192    with a version number greater than all the
 193    existing numbered backups.  Reallocate *BUFFER as necessary; its
 194    initial allocated size is BUFFER_SIZE, which must be at least 5
 195    bytes longer than the file name to make room for the initially
 196    appended ".~1~".  FILELEN is the length of the original file name.
 197    (The original file name is not necessarily null-terminated;
 198    FILELEN does not count trailing slashes after a non-slash.)
 199    BASE_OFFSET is the offset of the basename in *BUFFER.
 200    The returned value indicates what kind of backup was found.  If an
 201    I/O or other read error occurs, use the highest backup number that
 202    was found.
 203 
 204    *DIRPP is the destination directory.  If *DIRPP is null, open the
 205    destination directory and store the resulting stream into *DIRPP
 206    and its file descriptor into *PNEW_FD without closing the stream.  */
 207 
 208 static enum numbered_backup_result
 209 numbered_backup (int dir_fd, char **buffer, idx_t buffer_size, idx_t filelen,
     /* [previous][next][first][last][top][bottom][index][help] */
 210                  idx_t base_offset, DIR **dirpp, int *pnew_fd)
 211 {
 212   enum numbered_backup_result result = BACKUP_IS_NEW;
 213   DIR *dirp = *dirpp;
 214   char *buf = *buffer;
 215   idx_t versionlenmax = 1;
 216   idx_t baselen = filelen - base_offset;
 217 
 218   if (dirp)
 219     rewinddir (dirp);
 220   else
 221     {
 222       /* Temporarily modify the buffer into its parent directory name,
 223          open the directory, and then restore the buffer.  */
 224       char tmp[sizeof "."];
 225       char *base = buf + base_offset;
 226       memcpy (tmp, base, sizeof ".");
 227       strcpy (base, ".");
 228       dirp = opendirat (dir_fd, buf, 0, pnew_fd);
 229       if (!dirp && errno == ENOMEM)
 230         result = BACKUP_NOMEM;
 231       memcpy (base, tmp, sizeof ".");
 232       strcpy (base + baselen, ".~1~");
 233       if (!dirp)
 234         return result;
 235       *dirpp = dirp;
 236     }
 237 
 238   for (struct dirent *dp; (dp = readdir (dirp)) != NULL; )
 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       char const *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       bool all_9s = (*p == '9');
 255       idx_t versionlen;
 256       for (versionlen = 1; ISDIGIT (p[versionlen]); versionlen++)
 257         all_9s &= (p[versionlen] == '9');
 258 
 259       if (! (p[versionlen] == '~' && !p[versionlen + 1]
 260              && (versionlenmax < versionlen
 261                  || (versionlenmax == versionlen
 262                      && memcmp (buf + filelen + 2, p, versionlen) <= 0))))
 263         continue;
 264 
 265       /* This entry has the largest version number seen so far.
 266          Append this highest numbered extension to the file name,
 267          prepending '0' to the number if it is all 9s.  */
 268 
 269       versionlenmax = all_9s + versionlen;
 270       result = (all_9s ? BACKUP_IS_LONGER : BACKUP_IS_SAME_LENGTH);
 271       idx_t new_buffer_size = filelen + 2 + versionlenmax + 2;
 272       if (buffer_size < new_buffer_size)
 273         {
 274           idx_t grown;
 275           if (! INT_ADD_WRAPV (new_buffer_size, new_buffer_size >> 1, &grown))
 276             new_buffer_size = grown;
 277           char *new_buf = irealloc (buf, new_buffer_size);
 278           if (!new_buf)
 279             {
 280               *buffer = buf;
 281               return BACKUP_NOMEM;
 282             }
 283           buf = new_buf;
 284           buffer_size = new_buffer_size;
 285         }
 286       char *q = buf + filelen;
 287       *q++ = '.';
 288       *q++ = '~';
 289       *q = '0';
 290       q += all_9s;
 291       memcpy (q, p, versionlen + 2);
 292 
 293       /* Add 1 to the version number.  */
 294 
 295       q += versionlen;
 296       while (*--q == '9')
 297         *q = '0';
 298       ++*q;
 299     }
 300 
 301   *buffer = buf;
 302   return result;
 303 }
 304 
 305 /* Relative to DIR_FD, return the name of the new backup file for the
 306    existing file FILE, allocated with malloc.
 307    If RENAME, also rename FILE to the new name.
 308    On failure, return NULL and set errno.
 309    Do not call this function if backup_type == no_backups.  */
 310 
 311 char *
 312 backupfile_internal (int dir_fd, char const *file,
     /* [previous][next][first][last][top][bottom][index][help] */
 313                      enum backup_type backup_type, bool rename)
 314 {
 315   idx_t base_offset = last_component (file) - file;
 316   idx_t filelen = base_offset + base_len (file + base_offset);
 317 
 318   if (! simple_backup_suffix)
 319     set_simple_backup_suffix (NULL);
 320 
 321   /* Allow room for simple or ".~N~" backups.  The guess must be at
 322      least sizeof ".~1~", but otherwise will be adjusted as needed.  */
 323   idx_t simple_backup_suffix_size = strlen (simple_backup_suffix) + 1;
 324   idx_t backup_suffix_size_guess = simple_backup_suffix_size;
 325   enum { GUESS = sizeof ".~12345~" };
 326   if (backup_suffix_size_guess < GUESS)
 327     backup_suffix_size_guess = GUESS;
 328 
 329   idx_t ssize = filelen + backup_suffix_size_guess + 1;
 330   char *s = imalloc (ssize);
 331   if (!s)
 332     return s;
 333 
 334   DIR *dirp = NULL;
 335   int sdir = -1;
 336   idx_t base_max = 0;
 337   while (true)
 338     {
 339       bool extended = true;
 340       memcpy (s, file, filelen);
 341 
 342       if (backup_type == simple_backups)
 343         memcpy (s + filelen, simple_backup_suffix, simple_backup_suffix_size);
 344       else
 345         switch (numbered_backup (dir_fd, &s, ssize, filelen, base_offset,
 346                                  &dirp, &sdir))
 347           {
 348           case BACKUP_IS_SAME_LENGTH:
 349             break;
 350 
 351           case BACKUP_IS_NEW:
 352             if (backup_type == numbered_existing_backups)
 353               {
 354                 backup_type = simple_backups;
 355                 memcpy (s + filelen, simple_backup_suffix,
 356                         simple_backup_suffix_size);
 357               }
 358             FALLTHROUGH;
 359           case BACKUP_IS_LONGER:
 360             extended = check_extension (s, filelen, '~', sdir, &base_max);
 361             break;
 362 
 363           case BACKUP_NOMEM:
 364             if (dirp)
 365               closedir (dirp);
 366             free (s);
 367             errno = ENOMEM;
 368             return NULL;
 369           }
 370 
 371       if (! rename)
 372         break;
 373 
 374       if (sdir < 0)
 375         {
 376           sdir = AT_FDCWD;
 377           base_offset = 0;
 378         }
 379       unsigned flags = backup_type == simple_backups ? 0 : RENAME_NOREPLACE;
 380       if (renameatu (AT_FDCWD, file, sdir, s + base_offset, flags) == 0)
 381         break;
 382       int e = errno;
 383       if (! (e == EEXIST && extended))
 384         {
 385           if (dirp)
 386             closedir (dirp);
 387           free (s);
 388           errno = e;
 389           return NULL;
 390         }
 391     }
 392 
 393   if (dirp)
 394     closedir (dirp);
 395   return s;
 396 }

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