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