Blame gnulib-local/lib/backupfile.c

Packit 5b56b6
/* backupfile.c -- make Emacs style backup file names
Packit 5b56b6
   Copyright (C) 1990-2003, 2005-2006, 2012, 2015 Free Software
Packit 5b56b6
   Foundation, Inc.
Packit 5b56b6
Packit 5b56b6
   This program is free software: you can redistribute it and/or modify
Packit 5b56b6
   it under the terms of the GNU General Public License as published by
Packit 5b56b6
   the Free Software Foundation; either version 3 of the License, or
Packit 5b56b6
   (at your option) any later version.
Packit 5b56b6
Packit 5b56b6
   This program is distributed in the hope that it will be useful,
Packit 5b56b6
   but WITHOUT ANY WARRANTY; without even the implied warranty of
Packit 5b56b6
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
Packit 5b56b6
   GNU General Public License for more details.
Packit 5b56b6
Packit 5b56b6
   You should have received a copy of the GNU General Public License
Packit 5b56b6
   along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
Packit 5b56b6
Packit 5b56b6
/* Written by David MacKenzie <djm@gnu.ai.mit.edu>.
Packit 5b56b6
   Some algorithms adapted from GNU Emacs. */
Packit 5b56b6
Packit 5b56b6
#include <config.h>
Packit 5b56b6
Packit 5b56b6
#include "argmatch.h"
Packit 5b56b6
#include "backupfile.h"
Packit 5b56b6
Packit 5b56b6
#include <stdio.h>
Packit 5b56b6
#include <sys/types.h>
Packit 5b56b6
#if HAVE_STRING_H
Packit 5b56b6
# include <string.h>
Packit 5b56b6
#else
Packit 5b56b6
# include <strings.h>
Packit 5b56b6
#endif
Packit 5b56b6
Packit 5b56b6
#if HAVE_DIRENT_H
Packit 5b56b6
# include <dirent.h>
Packit 5b56b6
#endif
Packit 5b56b6
Packit 5b56b6
#include <stdlib.h>
Packit 5b56b6
Packit 5b56b6
#include "basename.h"
Packit 5b56b6
Packit 5b56b6
#if HAVE_DIRENT_H
Packit 5b56b6
# define HAVE_DIR 1
Packit 5b56b6
#else
Packit 5b56b6
# define HAVE_DIR 0
Packit 5b56b6
#endif
Packit 5b56b6
Packit 5b56b6
#include <limits.h>
Packit 5b56b6
Packit 5b56b6
/* Upper bound on the string length of an integer converted to string.
Packit 5b56b6
   302 / 1000 is ceil (log10 (2.0)).  Subtract 1 for the sign bit;
Packit 5b56b6
   add 1 for integer division truncation; add 1 more for a minus sign.  */
Packit 5b56b6
#define INT_STRLEN_BOUND(t) ((sizeof (t) * CHAR_BIT - 1) * 302 / 1000 + 2)
Packit 5b56b6
Packit 5b56b6
/* ISDIGIT differs from isdigit, as follows:
Packit 5b56b6
   - Its arg may be any int or unsigned int; it need not be an unsigned char.
Packit 5b56b6
   - It's guaranteed to evaluate its argument exactly once.
Packit 5b56b6
   - It's typically faster.
Packit 5b56b6
   Posix 1003.2-1992 section 2.5.2.1 page 50 lines 1556-1558 says that
Packit 5b56b6
   only '0' through '9' are digits.  Prefer ISDIGIT to isdigit unless
Packit 5b56b6
   it's important to use the locale's definition of 'digit' even when the
Packit 5b56b6
   host does not conform to Posix.  */
Packit 5b56b6
#define ISDIGIT(c) ((unsigned) (c) - '0' <= 9)
Packit 5b56b6
Packit 5b56b6
#if D_INO_IN_DIRENT
Packit 5b56b6
# define REAL_DIR_ENTRY(dp) ((dp)->d_ino != 0)
Packit 5b56b6
#else
Packit 5b56b6
# define REAL_DIR_ENTRY(dp) 1
Packit 5b56b6
#endif
Packit 5b56b6
Packit 5b56b6
/* The extension added to file names to produce a simple (as opposed
Packit 5b56b6
   to numbered) backup file name. */
Packit 5b56b6
const char *simple_backup_suffix = "~";
Packit 5b56b6
Packit 5b56b6
#if HAVE_DIR
Packit 5b56b6
static int max_backup_version (const char *, const char *);
Packit 5b56b6
static int version_number (const char *, const char *, size_t);
Packit 5b56b6
#endif
Packit 5b56b6
Packit 5b56b6
/* Return the name of the new backup file for file FILE,
Packit 5b56b6
   allocated with malloc.  Return 0 if out of memory.
Packit 5b56b6
   FILE must not end with a '/' unless it is the root directory.
Packit 5b56b6
   Do not call this function if backup_type == none. */
Packit 5b56b6
Packit 5b56b6
char *
Packit 5b56b6
find_backup_file_name (const char *file, enum backup_type backup_type)
Packit 5b56b6
{
Packit 5b56b6
  size_t backup_suffix_size_max;
Packit 5b56b6
  size_t file_len = strlen (file);
Packit 5b56b6
  size_t numbered_suffix_size_max = INT_STRLEN_BOUND (int) + 4;
Packit 5b56b6
  char *s;
Packit 5b56b6
  const char *suffix = simple_backup_suffix;
Packit 5b56b6
Packit 5b56b6
  /* Allow room for simple or '.~N~' backups.  */
Packit 5b56b6
  backup_suffix_size_max = strlen (simple_backup_suffix) + 1;
Packit 5b56b6
  if (HAVE_DIR && backup_suffix_size_max < numbered_suffix_size_max)
Packit 5b56b6
    backup_suffix_size_max = numbered_suffix_size_max;
Packit 5b56b6
Packit 5b56b6
  s = (char *) malloc (file_len + backup_suffix_size_max
Packit 5b56b6
                       + numbered_suffix_size_max);
Packit 5b56b6
  if (s)
Packit 5b56b6
    {
Packit 5b56b6
      strcpy (s, file);
Packit 5b56b6
Packit 5b56b6
#if HAVE_DIR
Packit 5b56b6
      if (backup_type != simple)
Packit 5b56b6
        {
Packit 5b56b6
          int highest_backup;
Packit 5b56b6
          size_t dir_len = basename (s) - s;
Packit 5b56b6
Packit 5b56b6
          strcpy (s + dir_len, ".");
Packit 5b56b6
          highest_backup = max_backup_version (file + dir_len, s);
Packit 5b56b6
          if (! (backup_type == numbered_existing && highest_backup == 0))
Packit 5b56b6
            {
Packit 5b56b6
              char *numbered_suffix = s + (file_len + backup_suffix_size_max);
Packit 5b56b6
              sprintf (numbered_suffix, ".~%d~", highest_backup + 1);
Packit 5b56b6
              suffix = numbered_suffix;
Packit 5b56b6
            }
Packit 5b56b6
          strcpy (s, file);
Packit 5b56b6
        }
Packit 5b56b6
#endif /* HAVE_DIR */
Packit 5b56b6
Packit 5b56b6
      addext (s, suffix, '~');
Packit 5b56b6
    }
Packit 5b56b6
  return s;
Packit 5b56b6
}
Packit 5b56b6
Packit 5b56b6
#if HAVE_DIR
Packit 5b56b6
Packit 5b56b6
/* Return the number of the highest-numbered backup file for file
Packit 5b56b6
   FILE in directory DIR.  If there are no numbered backups
Packit 5b56b6
   of FILE in DIR, or an error occurs reading DIR, return 0.
Packit 5b56b6
   */
Packit 5b56b6
Packit 5b56b6
static int
Packit 5b56b6
max_backup_version (const char *file, const char *dir)
Packit 5b56b6
{
Packit 5b56b6
  DIR *dirp;
Packit 5b56b6
  struct dirent *dp;
Packit 5b56b6
  int highest_version;
Packit 5b56b6
  int this_version;
Packit 5b56b6
  size_t file_name_length;
Packit 5b56b6
Packit 5b56b6
  dirp = opendir (dir);
Packit 5b56b6
  if (!dirp)
Packit 5b56b6
    return 0;
Packit 5b56b6
Packit 5b56b6
  highest_version = 0;
Packit 5b56b6
  file_name_length = strlen (file);
Packit 5b56b6
Packit 5b56b6
  while ((dp = readdir (dirp)) != 0)
Packit 5b56b6
    {
Packit 5b56b6
      if (!REAL_DIR_ENTRY (dp) || strlen (dp->d_name) < file_name_length + 4)
Packit 5b56b6
        continue;
Packit 5b56b6
Packit 5b56b6
      this_version = version_number (file, dp->d_name, file_name_length);
Packit 5b56b6
      if (this_version > highest_version)
Packit 5b56b6
        highest_version = this_version;
Packit 5b56b6
    }
Packit 5b56b6
  if (closedir (dirp))
Packit 5b56b6
    return 0;
Packit 5b56b6
  return highest_version;
Packit 5b56b6
}
Packit 5b56b6
Packit 5b56b6
/* If BACKUP is a numbered backup of BASE, return its version number;
Packit 5b56b6
   otherwise return 0.  BASE_LENGTH is the length of BASE.
Packit 5b56b6
   */
Packit 5b56b6
Packit 5b56b6
static int
Packit 5b56b6
version_number (const char *base, const char *backup, size_t base_length)
Packit 5b56b6
{
Packit 5b56b6
  int version;
Packit 5b56b6
  const char *p;
Packit 5b56b6
Packit 5b56b6
  version = 0;
Packit 5b56b6
  if (strncmp (base, backup, base_length) == 0
Packit 5b56b6
      && backup[base_length] == '.'
Packit 5b56b6
      && backup[base_length + 1] == '~')
Packit 5b56b6
    {
Packit 5b56b6
      for (p = &backup[base_length + 2]; ISDIGIT (*p); ++p)
Packit 5b56b6
        version = version * 10 + *p - '0';
Packit 5b56b6
      if (p[0] != '~' || p[1])
Packit 5b56b6
        version = 0;
Packit 5b56b6
    }
Packit 5b56b6
  return version;
Packit 5b56b6
}
Packit 5b56b6
#endif /* HAVE_DIR */
Packit 5b56b6
Packit 5b56b6
static const char * const backup_args[] =
Packit 5b56b6
{
Packit 5b56b6
  /* In a series of synonyms, present the most meaning full first, so
Packit 5b56b6
     that argmatch_valid be more readable. */
Packit 5b56b6
  "none", "off",
Packit 5b56b6
  "simple", "never",
Packit 5b56b6
  "existing", "nil",
Packit 5b56b6
  "numbered", "t",
Packit 5b56b6
  0
Packit 5b56b6
};
Packit 5b56b6
Packit 5b56b6
static const enum backup_type backup_types[] =
Packit 5b56b6
{
Packit 5b56b6
  none, none,
Packit 5b56b6
  simple, simple,
Packit 5b56b6
  numbered_existing, numbered_existing,
Packit 5b56b6
  numbered, numbered
Packit 5b56b6
};
Packit 5b56b6
Packit 5b56b6
/* Return the type of backup specified by VERSION.
Packit 5b56b6
   If VERSION is NULL or the empty string, return numbered_existing.
Packit 5b56b6
   If VERSION is invalid or ambiguous, fail with a diagnostic appropriate
Packit 5b56b6
   for the specified CONTEXT.  Unambiguous abbreviations are accepted.  */
Packit 5b56b6
Packit 5b56b6
enum backup_type
Packit 5b56b6
get_version (const char *context, const char *version)
Packit 5b56b6
{
Packit 5b56b6
  if (version == 0 || *version == 0)
Packit 5b56b6
    return numbered_existing;
Packit 5b56b6
  else
Packit 5b56b6
    return XARGMATCH (context, version, backup_args, backup_types);
Packit 5b56b6
}
Packit 5b56b6
Packit 5b56b6
Packit 5b56b6
/* Return the type of backup specified by VERSION.
Packit 5b56b6
   If VERSION is NULL, use the value of the envvar VERSION_CONTROL.
Packit 5b56b6
   If the specified string is invalid or ambiguous, fail with a diagnostic
Packit 5b56b6
   appropriate for the specified CONTEXT.
Packit 5b56b6
   Unambiguous abbreviations are accepted.  */
Packit 5b56b6
Packit 5b56b6
enum backup_type
Packit 5b56b6
xget_version (const char *context, const char *version)
Packit 5b56b6
{
Packit 5b56b6
  if (version && *version)
Packit 5b56b6
    return get_version (context, version);
Packit 5b56b6
  else
Packit 5b56b6
    return get_version ("$VERSION_CONTROL", getenv ("VERSION_CONTROL"));
Packit 5b56b6
}