backupfile.c revision 1.10 1 /* $NetBSD: backupfile.c,v 1.10 2003/05/30 23:08:12 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.10 2003/05/30 23:08:12 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 #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 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(char *, char *);
61 static char *dirname(char *);
62 static int argmatch(char *, char **);
63 static void invalid_arg(char *, 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 sprintf (backup_name, "%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(char *str1, char *str2)
168 {
169 char *newstr;
170 char str1_length = strlen (str1);
171
172 newstr = xmalloc(str1_length + strlen (str2) + 1);
173 strcpy (newstr, str1);
174 strcpy (newstr + str1_length, str2);
175 return newstr;
176 }
177
178 /* Return NAME with any leading path stripped off. */
179
180 char *
181 basename(char *name)
182 {
183 char *base;
184
185 base = strrchr (name, '/');
186 return base ? base + 1 : name;
187 }
188
189 /* Return the leading directories part of PATH,
190 allocated with malloc.
191 Assumes that trailing slashes have already been
192 removed. */
193
194 static char *
195 dirname(char *path)
196 {
197 char *newpath;
198 char *slash;
199 size_t length; /* Length of result, not including NUL. */
200
201 slash = strrchr (path, '/');
202 if (slash == 0)
203 {
204 /* File is in the current directory. */
205 path = ".";
206 length = 1;
207 }
208 else
209 {
210 /* Remove any trailing slashes from result. */
211 while (slash > path && *slash == '/')
212 --slash;
213
214 length = slash - path + 1;
215 }
216 newpath = xmalloc(length + 1);
217 strncpy (newpath, path, length);
218 newpath[length] = 0;
219 return newpath;
220 }
221
222 /* If ARG is an unambiguous match for an element of the
223 null-terminated array OPTLIST, return the index in OPTLIST
224 of the matched element, else -1 if it does not match any element
225 or -2 if it is ambiguous (is a prefix of more than one element). */
226
227 static int
228 argmatch(char *arg, char **optlist)
229 {
230 int i; /* Temporary index in OPTLIST. */
231 size_t arglen; /* Length of ARG. */
232 int matchind = -1; /* Index of first nonexact match. */
233 int ambiguous = 0; /* If nonzero, multiple nonexact match(es). */
234
235 arglen = strlen (arg);
236
237 /* Test all elements for either exact match or abbreviated matches. */
238 for (i = 0; optlist[i]; i++)
239 {
240 if (!strncmp (optlist[i], arg, arglen))
241 {
242 if (strlen (optlist[i]) == arglen)
243 /* Exact match found. */
244 return i;
245 else if (matchind == -1)
246 /* First nonexact match found. */
247 matchind = i;
248 else
249 /* Second nonexact match found. */
250 ambiguous = 1;
251 }
252 }
253 if (ambiguous)
254 return -2;
255 else
256 return matchind;
257 }
258
259 /* Error reporting for argmatch.
260 KIND is a description of the type of entity that was being matched.
261 VALUE is the invalid value that was given.
262 PROBLEM is the return value from argmatch. */
263
264 static void
265 invalid_arg(char *kind, char *value, int problem)
266 {
267 fprintf (stderr, "patch: ");
268 if (problem == -1)
269 fprintf (stderr, "invalid");
270 else /* Assume -2. */
271 fprintf (stderr, "ambiguous");
272 fprintf (stderr, " %s `%s'\n", kind, value);
273 }
274
275 static char *backup_args[] =
276 {
277 "never", "simple", "nil", "existing", "t", "numbered", 0
278 };
279
280 static enum backup_type backup_types[] =
281 {
282 simple, simple, numbered_existing, numbered_existing, numbered, numbered
283 };
284
285 /* Return the type of backup indicated by VERSION.
286 Unique abbreviations are accepted. */
287
288 enum backup_type
289 get_version(char *version)
290 {
291 int i;
292
293 if (version == 0 || *version == 0)
294 return numbered_existing;
295 i = argmatch (version, backup_args);
296 if (i >= 0)
297 return backup_types[i];
298 invalid_arg ("version control type", version, i);
299 exit (1);
300 /* NOTREACHED */
301 }
302