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