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