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