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