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