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