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