backupfile.c revision 1.13 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 (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.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