Home | History | Annotate | Line # | Download | only in patch
backupfile.c revision 1.8
      1 /*	$NetBSD: backupfile.c,v 1.8 2002/03/11 18:47:51 kristerw Exp $	*/
      2 
      3 /* backupfile.c -- make Emacs style backup file names
      4    Copyright (C) 1990 Free Software Foundation, Inc.
      5 
      6    This program is free software; you can redistribute it and/or modify
      7    it without restriction.
      8 
      9    This program is distributed in the hope that it will be useful,
     10    but WITHOUT ANY WARRANTY; without even the implied warranty of
     11    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  */
     12 
     13 /* David MacKenzie <djm (at) ai.mit.edu>.
     14    Some algorithms adapted from GNU Emacs. */
     15 
     16 #include <sys/cdefs.h>
     17 #ifndef lint
     18 __RCSID("$NetBSD: backupfile.c,v 1.8 2002/03/11 18:47:51 kristerw Exp $");
     19 #endif /* not lint */
     20 
     21 #include <stdio.h>
     22 #include <stdlib.h>
     23 #include <string.h>
     24 #include <ctype.h>
     25 #include <sys/types.h>
     26 #include "backupfile.h"
     27 
     28 #include <dirent.h>
     29 #ifdef direct
     30 #undef direct
     31 #endif
     32 #define direct dirent
     33 #define NLENGTH(direct) (strlen((direct)->d_name))
     34 
     35 #ifndef isascii
     36 #define ISDIGIT(c) (isdigit ((unsigned char) (c)))
     37 #else
     38 #define ISDIGIT(c) (isascii (c) && isdigit ((unsigned char)c))
     39 #endif
     40 
     41 #include <unistd.h>
     42 
     43 #if defined (_POSIX_VERSION)
     44 /* POSIX does not require that the d_ino field be present, and some
     45    systems do not provide it. */
     46 #define REAL_DIR_ENTRY(dp) 1
     47 #else
     48 #define REAL_DIR_ENTRY(dp) ((dp)->d_ino != 0)
     49 #endif
     50 
     51 /* Which type of backup file names are generated. */
     52 enum backup_type backup_type = none;
     53 
     54 /* The extension added to file names to produce a simple (as opposed
     55    to numbered) backup file name. */
     56 char *simple_backup_suffix = "~";
     57 
     58 /* backupfile.c */
     59 static int max_backup_version(char *, char *);
     60 static char *make_version_name(char *, int);
     61 static int version_number(char *, char *, int);
     62 static char *concat(char *, char *);
     63 static char *dirname(char *);
     64 static int argmatch(char *, char **);
     65 static void invalid_arg(char *, char *, int);
     66 
     67 #ifndef NODIR
     68 /* Return the name of the new backup file for file FILE,
     69    allocated with malloc.  Return 0 if out of memory.
     70    FILE must not end with a '/' unless it is the root directory.
     71    Do not call this function if backup_type == none. */
     72 
     73 char *
     74 find_backup_file_name(char *file)
     75 {
     76   char *dir;
     77   char *base_versions;
     78   int highest_backup;
     79 
     80   if (backup_type == simple)
     81     return concat (file, simple_backup_suffix);
     82   base_versions = concat (basename (file), ".~");
     83   if (base_versions == 0)
     84     return 0;
     85   dir = dirname (file);
     86   if (dir == 0)
     87     {
     88       free (base_versions);
     89       return 0;
     90     }
     91   highest_backup = max_backup_version (base_versions, dir);
     92   free (base_versions);
     93   free (dir);
     94   if (backup_type == numbered_existing && highest_backup == 0)
     95     return concat (file, simple_backup_suffix);
     96   return make_version_name (file, highest_backup + 1);
     97 }
     98 
     99 /* Return the number of the highest-numbered backup file for file
    100    FILE in directory DIR.  If there are no numbered backups
    101    of FILE in DIR, or an error occurs reading DIR, return 0.
    102    FILE should already have ".~" appended to it. */
    103 
    104 static int
    105 max_backup_version(char *file, char *dir)
    106 {
    107   DIR *dirp;
    108   struct direct *dp;
    109   int highest_version;
    110   int this_version;
    111   int file_name_length;
    112 
    113   dirp = opendir (dir);
    114   if (!dirp)
    115     return 0;
    116 
    117   highest_version = 0;
    118   file_name_length = strlen (file);
    119 
    120   while ((dp = readdir (dirp)) != 0)
    121     {
    122       if (!REAL_DIR_ENTRY (dp) || NLENGTH (dp) <= file_name_length)
    123 	continue;
    124 
    125       this_version = version_number (file, dp->d_name, file_name_length);
    126       if (this_version > highest_version)
    127 	highest_version = this_version;
    128     }
    129   closedir (dirp);
    130   return highest_version;
    131 }
    132 
    133 /* Return a string, allocated with malloc, containing
    134    "FILE.~VERSION~".  Return 0 if out of memory. */
    135 
    136 static char *
    137 make_version_name(char *file, int version)
    138 {
    139   char *backup_name;
    140 
    141   backup_name = malloc (strlen (file) + 16);
    142   if (backup_name == 0)
    143     return 0;
    144   sprintf (backup_name, "%s.~%d~", file, version);
    145   return backup_name;
    146 }
    147 
    148 /* If BACKUP is a numbered backup of BASE, return its version number;
    149    otherwise return 0.  BASE_LENGTH is the length of BASE.
    150    BASE should already have ".~" appended to it. */
    151 
    152 static int
    153 version_number(char *base, char *backup, int base_length)
    154 {
    155   int version;
    156   char *p;
    157 
    158   version = 0;
    159   if (!strncmp (base, backup, base_length) && ISDIGIT (backup[base_length]))
    160     {
    161       for (p = &backup[base_length]; ISDIGIT (*p); ++p)
    162 	version = version * 10 + *p - '0';
    163       if (p[0] != '~' || p[1])
    164 	version = 0;
    165     }
    166   return version;
    167 }
    168 
    169 /* Return the newly-allocated concatenation of STR1 and STR2.
    170    If out of memory, return 0. */
    171 
    172 static char *
    173 concat(char *str1, char *str2)
    174 {
    175   char *newstr;
    176   char str1_length = strlen (str1);
    177 
    178   newstr = malloc (str1_length + strlen (str2) + 1);
    179   if (newstr == 0)
    180     return 0;
    181   strcpy (newstr, str1);
    182   strcpy (newstr + str1_length, str2);
    183   return newstr;
    184 }
    185 
    186 /* Return NAME with any leading path stripped off.  */
    187 
    188 char *
    189 basename(char *name)
    190 {
    191   char *base;
    192 
    193   base = strrchr (name, '/');
    194   return base ? base + 1 : name;
    195 }
    196 
    197 /* Return the leading directories part of PATH,
    198    allocated with malloc.  If out of memory, return 0.
    199    Assumes that trailing slashes have already been
    200    removed.  */
    201 
    202 static char *
    203 dirname(char *path)
    204 {
    205   char *newpath;
    206   char *slash;
    207   int length;    /* Length of result, not including NUL. */
    208 
    209   slash = strrchr (path, '/');
    210   if (slash == 0)
    211 	{
    212 	  /* File is in the current directory.  */
    213 	  path = ".";
    214 	  length = 1;
    215 	}
    216   else
    217 	{
    218 	  /* Remove any trailing slashes from result. */
    219 	  while (slash > path && *slash == '/')
    220 		--slash;
    221 
    222 	  length = slash - path + 1;
    223 	}
    224   newpath = malloc (length + 1);
    225   if (newpath == 0)
    226     return 0;
    227   strncpy (newpath, path, length);
    228   newpath[length] = 0;
    229   return newpath;
    230 }
    231 
    232 /* If ARG is an unambiguous match for an element of the
    233    null-terminated array OPTLIST, return the index in OPTLIST
    234    of the matched element, else -1 if it does not match any element
    235    or -2 if it is ambiguous (is a prefix of more than one element). */
    236 
    237 static int
    238 argmatch(char *arg, char **optlist)
    239 {
    240   int i;			/* Temporary index in OPTLIST. */
    241   int arglen;			/* Length of ARG. */
    242   int matchind = -1;		/* Index of first nonexact match. */
    243   int ambiguous = 0;		/* If nonzero, multiple nonexact match(es). */
    244 
    245   arglen = strlen (arg);
    246 
    247   /* Test all elements for either exact match or abbreviated matches.  */
    248   for (i = 0; optlist[i]; i++)
    249     {
    250       if (!strncmp (optlist[i], arg, arglen))
    251 	{
    252 	  if (strlen (optlist[i]) == arglen)
    253 	    /* Exact match found.  */
    254 	    return i;
    255 	  else if (matchind == -1)
    256 	    /* First nonexact match found.  */
    257 	    matchind = i;
    258 	  else
    259 	    /* Second nonexact match found.  */
    260 	    ambiguous = 1;
    261 	}
    262     }
    263   if (ambiguous)
    264     return -2;
    265   else
    266     return matchind;
    267 }
    268 
    269 /* Error reporting for argmatch.
    270    KIND is a description of the type of entity that was being matched.
    271    VALUE is the invalid value that was given.
    272    PROBLEM is the return value from argmatch. */
    273 
    274 static void
    275 invalid_arg(char *kind, char *value, int problem)
    276 {
    277   fprintf (stderr, "patch: ");
    278   if (problem == -1)
    279     fprintf (stderr, "invalid");
    280   else				/* Assume -2. */
    281     fprintf (stderr, "ambiguous");
    282   fprintf (stderr, " %s `%s'\n", kind, value);
    283 }
    284 
    285 static char *backup_args[] =
    286 {
    287   "never", "simple", "nil", "existing", "t", "numbered", 0
    288 };
    289 
    290 static enum backup_type backup_types[] =
    291 {
    292   simple, simple, numbered_existing, numbered_existing, numbered, numbered
    293 };
    294 
    295 /* Return the type of backup indicated by VERSION.
    296    Unique abbreviations are accepted. */
    297 
    298 enum backup_type
    299 get_version(char *version)
    300 {
    301   int i;
    302 
    303   if (version == 0 || *version == 0)
    304     return numbered_existing;
    305   i = argmatch (version, backup_args);
    306   if (i >= 0)
    307     return backup_types[i];
    308   invalid_arg ("version control type", version, i);
    309   exit (1);
    310 }
    311 #endif /* NODIR */
    312