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