1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25: #include <config.h>
26:
27: #include "backupfile.h"
28:
29: #include "argmatch.h"
30: #include "dirname.h"
31: #include "xalloc.h"
32:
33: #include <errno.h>
34: #include <stdbool.h>
35: #include <stdlib.h>
36: #include <string.h>
37:
38: #include <limits.h>
39:
40: #include <unistd.h>
41:
42: #include <dirent.h>
43: #ifndef _D_EXACT_NAMLEN
44: # define _D_EXACT_NAMLEN(dp) strlen ((dp)->d_name)
45: #endif
46: #if D_INO_IN_DIRENT
47: # define REAL_DIR_ENTRY(dp) ((dp)->d_ino != 0)
48: #else
49: # define REAL_DIR_ENTRY(dp) 1
50: #endif
51:
52: #if ! (HAVE_PATHCONF && defined _PC_NAME_MAX)
53: # define pathconf(file, option) (errno = -1)
54: #endif
55:
56: #ifndef _POSIX_NAME_MAX
57: # define _POSIX_NAME_MAX 14
58: #endif
59: #ifndef SIZE_MAX
60: # define SIZE_MAX ((size_t) -1)
61: #endif
62:
63: #if defined _XOPEN_NAME_MAX
64: # define NAME_MAX_MINIMUM _XOPEN_NAME_MAX
65: #else
66: # define NAME_MAX_MINIMUM _POSIX_NAME_MAX
67: #endif
68:
69: #ifndef HAVE_DOS_FILE_NAMES
70: # define HAVE_DOS_FILE_NAMES 0
71: #endif
72: #ifndef HAVE_LONG_FILE_NAMES
73: # define HAVE_LONG_FILE_NAMES 0
74: #endif
75:
76:
77:
78:
79:
80:
81:
82:
83: #define ISDIGIT(c) ((unsigned int) (c) - '0' <= 9)
84:
85:
86:
87: #undef opendir
88: #undef closedir
89:
90:
91:
92: char const *simple_backup_suffix = "~";
93:
94:
95:
96:
97:
98:
99:
100: static void
101: check_extension (char *file, size_t filelen, char e)
102: {
103: char *base = last_component (file);
104: size_t baselen = base_len (base);
105: size_t baselen_max = HAVE_LONG_FILE_NAMES ? 255 : NAME_MAX_MINIMUM;
106:
107: if (HAVE_DOS_FILE_NAMES || NAME_MAX_MINIMUM < baselen)
108: {
109:
110: long name_max;
111:
112:
113:
114: char tmp[sizeof "."];
115: memcpy (tmp, base, sizeof ".");
116: strcpy (base, ".");
117: errno = 0;
118: name_max = pathconf (file, _PC_NAME_MAX);
119: if (0 <= name_max || errno == 0)
120: {
121: long size = baselen_max = name_max;
122: if (name_max != size)
123: baselen_max = SIZE_MAX;
124: }
125: memcpy (base, tmp, sizeof ".");
126: }
127:
128: if (HAVE_DOS_FILE_NAMES && baselen_max <= 12)
129: {
130:
131: char *dot = strchr (base, '.');
132: if (!dot)
133: baselen_max = 8;
134: else
135: {
136: char const *second_dot = strchr (dot + 1, '.');
137: baselen_max = (second_dot
138: ? second_dot - base
139: : dot + 1 - base + 3);
140: }
141: }
142:
143: if (baselen_max < baselen)
144: {
145: baselen = file + filelen - base;
146: if (baselen_max <= baselen)
147: baselen = baselen_max - 1;
148: base[baselen] = e;
149: base[baselen + 1] = '\0';
150: }
151: }
152:
153:
154:
155: enum numbered_backup_result
156: {
157:
158:
159: BACKUP_IS_SAME_LENGTH,
160:
161:
162:
163: BACKUP_IS_LONGER,
164:
165:
166:
167: BACKUP_IS_NEW
168: };
169:
170:
171:
172:
173:
174:
175:
176:
177:
178:
179:
180: static enum numbered_backup_result
181: numbered_backup (char **buffer, size_t buffer_size, size_t filelen)
182: {
183: enum numbered_backup_result result = BACKUP_IS_NEW;
184: DIR *dirp;
185: struct dirent *dp;
186: char *buf = *buffer;
187: size_t versionlenmax = 1;
188: char *base = last_component (buf);
189: size_t base_offset = base - buf;
190: size_t baselen = base_len (base);
191:
192:
193:
194: char tmp[sizeof "."];
195: memcpy (tmp, base, sizeof ".");
196: strcpy (base, ".");
197: dirp = opendir (buf);
198: memcpy (base, tmp, sizeof ".");
199: strcpy (base + baselen, ".~1~");
200:
201: if (!dirp)
202: return result;
203:
204: while ((dp = readdir (dirp)) != NULL)
205: {
206: char const *p;
207: char *q;
208: bool all_9s;
209: size_t versionlen;
210: size_t new_buflen;
211:
212: if (! REAL_DIR_ENTRY (dp) || _D_EXACT_NAMLEN (dp) < baselen + 4)
213: continue;
214:
215: if (memcmp (buf + base_offset, dp->d_name, baselen + 2) != 0)
216: continue;
217:
218: p = dp->d_name + baselen + 2;
219:
220:
221:
222:
223:
224: if (! ('1' <= *p && *p <= '9'))
225: continue;
226: all_9s = (*p == '9');
227: for (versionlen = 1; ISDIGIT (p[versionlen]); versionlen++)
228: all_9s &= (p[versionlen] == '9');
229:
230: if (! (p[versionlen] == '~' && !p[versionlen + 1]
231: && (versionlenmax < versionlen
232: || (versionlenmax == versionlen
233: && memcmp (buf + filelen + 2, p, versionlen) <= 0))))
234: continue;
235:
236:
237:
238:
239:
240: versionlenmax = all_9s + versionlen;
241: result = (all_9s ? BACKUP_IS_LONGER : BACKUP_IS_SAME_LENGTH);
242: new_buflen = filelen + 2 + versionlenmax + 1;
243: if (buffer_size <= new_buflen)
244: {
245: buf = xnrealloc (buf, 2, new_buflen);
246: buffer_size = new_buflen * 2;
247: }
248: q = buf + filelen;
249: *q++ = '.';
250: *q++ = '~';
251: *q = '0';
252: q += all_9s;
253: memcpy (q, p, versionlen + 2);
254:
255:
256:
257: q += versionlen;
258: while (*--q == '9')
259: *q = '0';
260: ++*q;
261: }
262:
263: closedir (dirp);
264: *buffer = buf;
265: return result;
266: }
267:
268:
269:
270:
271:
272: char *
273: find_backup_file_name (char const *file, enum backup_type backup_type)
274: {
275: size_t filelen = strlen (file);
276: char *s;
277: size_t ssize;
278: bool simple = true;
279:
280:
281:
282: size_t simple_backup_suffix_size = strlen (simple_backup_suffix) + 1;
283: size_t backup_suffix_size_guess = simple_backup_suffix_size;
284: enum { GUESS = sizeof ".~12345~" };
285: if (backup_suffix_size_guess < GUESS)
286: backup_suffix_size_guess = GUESS;
287:
288: ssize = filelen + backup_suffix_size_guess + 1;
289: s = xmalloc (ssize);
290: memcpy (s, file, filelen + 1);
291:
292: if (backup_type != simple_backups)
293: switch (numbered_backup (&s, ssize, filelen))
294: {
295: case BACKUP_IS_SAME_LENGTH:
296: return s;
297:
298: case BACKUP_IS_LONGER:
299: simple = false;
300: break;
301:
302: case BACKUP_IS_NEW:
303: simple = (backup_type == numbered_existing_backups);
304: break;
305: }
306:
307: if (simple)
308: memcpy (s + filelen, simple_backup_suffix, simple_backup_suffix_size);
309: check_extension (s, filelen, '~');
310: return s;
311: }
312:
313: static char const * const backup_args[] =
314: {
315:
316:
317: "none", "off",
318: "simple", "never",
319: "existing", "nil",
320: "numbered", "t",
321: NULL
322: };
323:
324: static const enum backup_type backup_types[] =
325: {
326: no_backups, no_backups,
327: simple_backups, simple_backups,
328: numbered_existing_backups, numbered_existing_backups,
329: numbered_backups, numbered_backups
330: };
331:
332:
333:
334: ARGMATCH_VERIFY (backup_args, backup_types);
335:
336:
337:
338:
339:
340:
341: enum backup_type
342: get_version (char const *context, char const *version)
343: {
344: if (version == 0 || *version == 0)
345: return numbered_existing_backups;
346: else
347: return XARGMATCH (context, version, backup_args, backup_types);
348: }
349:
350:
351:
352:
353:
354:
355:
356:
357: enum backup_type
358: xget_version (char const *context, char const *version)
359: {
360: if (version && *version)
361: return get_version (context, version);
362: else
363: return get_version ("$VERSION_CONTROL", getenv ("VERSION_CONTROL"));
364: }