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