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