xref: /netbsd-src/usr.bin/patch/backupfile.c (revision aaf4ece63a859a04e37cf3a7229b5fab0157cc06)
1 /*	$NetBSD: backupfile.c,v 1.13 2003/07/30 08:51:04 itojun 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.13 2003/07/30 08:51:04 itojun 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   snprintf(backup_name, strlen(file) + 16, "%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   size_t l = strlen(str1) + strlen(str2) + 1;
171 
172   newstr = xmalloc(l);
173   snprintf(newstr, l, "%s%s", str1, str2);
174   return newstr;
175 }
176 
177 /* Return NAME with any leading path stripped off.  */
178 
179 char *
180 basename(char *name)
181 {
182   char *base;
183 
184   base = strrchr (name, '/');
185   return base ? base + 1 : name;
186 }
187 
188 /* Return the leading directories part of PATH,
189    allocated with malloc.
190    Assumes that trailing slashes have already been
191    removed.  */
192 
193 static char *
194 dirname(const char *path)
195 {
196   char *newpath;
197   char *slash;
198   size_t length;    /* Length of result, not including NUL. */
199 
200   slash = strrchr (path, '/');
201   if (slash == 0)
202 	{
203 	  /* File is in the current directory.  */
204 	  path = ".";
205 	  length = 1;
206 	}
207   else
208 	{
209 	  /* Remove any trailing slashes from result. */
210 	  while (slash > path && *slash == '/')
211 		--slash;
212 
213 	  length = slash - path + 1;
214 	}
215   newpath = xmalloc(length + 1);
216   strncpy(newpath, path, length);
217   newpath[length] = 0;
218   return newpath;
219 }
220 
221 /* If ARG is an unambiguous match for an element of the
222    null-terminated array OPTLIST, return the index in OPTLIST
223    of the matched element, else -1 if it does not match any element
224    or -2 if it is ambiguous (is a prefix of more than one element). */
225 
226 static int
227 argmatch(char *arg, const char **optlist)
228 {
229   int i;			/* Temporary index in OPTLIST. */
230   size_t arglen;		/* Length of ARG. */
231   int matchind = -1;		/* Index of first nonexact match. */
232   int ambiguous = 0;		/* If nonzero, multiple nonexact match(es). */
233 
234   arglen = strlen (arg);
235 
236   /* Test all elements for either exact match or abbreviated matches.  */
237   for (i = 0; optlist[i]; i++)
238     {
239       if (!strncmp (optlist[i], arg, arglen))
240 	{
241 	  if (strlen (optlist[i]) == arglen)
242 	    /* Exact match found.  */
243 	    return i;
244 	  else if (matchind == -1)
245 	    /* First nonexact match found.  */
246 	    matchind = i;
247 	  else
248 	    /* Second nonexact match found.  */
249 	    ambiguous = 1;
250 	}
251     }
252   if (ambiguous)
253     return -2;
254   else
255     return matchind;
256 }
257 
258 /* Error reporting for argmatch.
259    KIND is a description of the type of entity that was being matched.
260    VALUE is the invalid value that was given.
261    PROBLEM is the return value from argmatch. */
262 
263 static void
264 invalid_arg(const char *kind, const char *value, int problem)
265 {
266   fprintf (stderr, "patch: ");
267   if (problem == -1)
268     fprintf (stderr, "invalid");
269   else				/* Assume -2. */
270     fprintf (stderr, "ambiguous");
271   fprintf (stderr, " %s `%s'\n", kind, value);
272 }
273 
274 static const char *backup_args[] =
275 {
276   "never", "simple", "nil", "existing", "t", "numbered", 0
277 };
278 
279 static enum backup_type backup_types[] =
280 {
281   simple, simple, numbered_existing, numbered_existing, numbered, numbered
282 };
283 
284 /* Return the type of backup indicated by VERSION.
285    Unique abbreviations are accepted. */
286 
287 enum backup_type
288 get_version(char *version)
289 {
290   int i;
291 
292   if (version == 0 || *version == 0)
293     return numbered_existing;
294   i = argmatch (version, backup_args);
295   if (i >= 0)
296     return backup_types[i];
297   invalid_arg ("version control type", version, i);
298   exit (1);
299   /* NOTREACHED */
300 }
301