New module 'fchdir'.
[gnulib.git] / lib / backupfile.c
1 /* backupfile.c -- make Emacs style backup file names
2
3    Copyright (C) 1990, 1991, 1992, 1993, 1994, 1995, 1996, 1997, 1998,
4    1999, 2000, 2001, 2002, 2003, 2004, 2005, 2006 Free Software
5    Foundation, Inc.
6
7    This program is free software; you can redistribute it and/or modify
8    it under the terms of the GNU General Public License as published by
9    the Free Software Foundation; either version 2, or (at your option)
10    any later version.
11
12    This program is distributed in the hope that it will be useful,
13    but WITHOUT ANY WARRANTY; without even the implied warranty of
14    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15    GNU General Public License for more details.
16
17    You should have received a copy of the GNU General Public License
18    along with this program; see the file COPYING.
19    If not, write to the Free Software Foundation,
20    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.  */
21
22 /* Written by Paul Eggert and David MacKenzie.
23    Some algorithms adapted from GNU Emacs.  */
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 /* ISDIGIT differs from isdigit, as follows:
77    - Its arg may be any int or unsigned int; it need not be an unsigned char
78      or EOF.
79    - It's typically faster.
80    POSIX says that only '0' through '9' are digits.  Prefer ISDIGIT to
81    ISDIGIT unless it's important to use the locale's definition
82    of `digit' even when the host does not conform to POSIX.  */
83 #define ISDIGIT(c) ((unsigned int) (c) - '0' <= 9)
84
85 /* The results of opendir() in this file are not used with dirfd and fchdir,
86    therefore save some unnecessary work in fchdir.c.  */
87 #undef opendir
88 #undef closedir
89
90 /* The extension added to file names to produce a simple (as opposed
91    to numbered) backup file name. */
92 char const *simple_backup_suffix = "~";
93
94
95 /* If FILE (which was of length FILELEN before an extension was
96    appended to it) is too long, replace the extension with the single
97    char E.  If the result is still too long, remove the char just
98    before E.  */
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       /* The new base name is long enough to require a pathconf check.  */
110       long name_max;
111
112       /* Temporarily modify the buffer into its parent directory name,
113          invoke pathconf on the directory, and then restore the buffer.  */
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       /* Live within DOS's 8.3 limit.  */
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 /* Returned values for NUMBERED_BACKUP.  */
154
155 enum numbered_backup_result
156   {
157     /* The new backup name is the same length as an existing backup
158        name, so it's valid for that directory.  */
159     BACKUP_IS_SAME_LENGTH,
160
161     /* Some backup names already exist, but the returned name is longer
162        than any of them, and its length should be checked.  */
163     BACKUP_IS_LONGER,
164
165     /* There are no existing backup names.  The new name's length
166        should be checked.  */
167     BACKUP_IS_NEW
168   };
169
170 /* *BUFFER contains a file name.  Store into *BUFFER the next backup
171    name for the named file, with a version number greater than all the
172    existing numbered backups.  Reallocate *BUFFER as necessary; its
173    initial allocated size is BUFFER_SIZE, which must be at least 4
174    bytes longer than the file name to make room for the initially
175    appended ".~1".  FILELEN is the length of the original file name.
176    The returned value indicates what kind of backup was found.  If an
177    I/O or other read error occurs, use the highest backup number that
178    was found.  */
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   /* Temporarily modify the buffer into its parent directory name,
193      open the directory, and then restore the buffer.  */
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       /* Check whether this file has a version number and if so,
221          whether it is larger.  Use string operations rather than
222          integer arithmetic, to avoid problems with integer overflow.  */
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       /* This directory has the largest version number seen so far.
237          Append this highest numbered extension to the file name,
238          prepending '0' to the number if it is all 9s.  */
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       /* Add 1 to the version number.  */
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 /* Return the name of the new backup file for the existing file FILE,
269    allocated with malloc.  Report an error and fail if out of memory.
270    Do not call this function if backup_type == no_backups.  */
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   /* Allow room for simple or ".~N~" backups.  The guess must be at
281      least sizeof ".~1~", but otherwise will be adjusted as needed.  */
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   /* In a series of synonyms, present the most meaningful first, so
316      that argmatch_valid be more readable. */
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 /* Ensure that these two vectors have the same number of elements,
333    not counting the final NULL in the first one.  */
334 ARGMATCH_VERIFY (backup_args, backup_types);
335
336 /* Return the type of backup specified by VERSION.
337    If VERSION is NULL or the empty string, return numbered_existing_backups.
338    If VERSION is invalid or ambiguous, fail with a diagnostic appropriate
339    for the specified CONTEXT.  Unambiguous abbreviations are accepted.  */
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 /* Return the type of backup specified by VERSION.
352    If VERSION is NULL, use the value of the envvar VERSION_CONTROL.
353    If the specified string is invalid or ambiguous, fail with a diagnostic
354    appropriate for the specified CONTEXT.
355    Unambiguous abbreviations are accepted.  */
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 }