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