1 /* backupfile.c -- make Emacs style backup file names
2    Copyright (C) 1990-2003, 2005-2006, 2012, 2020 Free Software Foundation, Inc.
3 
4    This program is free software: you can redistribute it and/or modify
5    it under the terms of the GNU General Public License as published by
6    the Free Software Foundation; either version 3 of the License, or
7    (at your option) any later version.
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.  See the
12    GNU General Public License for more details.
13 
14    You should have received a copy of the GNU General Public License
15    along with this program.  If not, see <https://www.gnu.org/licenses/>.  */
16 
17 /* Written by David MacKenzie <djm@gnu.ai.mit.edu>.
18    Some algorithms adapted from GNU Emacs. */
19 
20 #include <config.h>
21 
22 #include "argmatch.h"
23 #include "backupfile.h"
24 
25 #include <stdio.h>
26 #include <sys/types.h>
27 #if HAVE_STRING_H
28 # include <string.h>
29 #else
30 # include <strings.h>
31 #endif
32 
33 #if HAVE_DIRENT_H
34 # include <dirent.h>
35 #endif
36 
37 #include <stdlib.h>
38 
39 #include "basename-lgpl.h"
40 
41 #if HAVE_DIRENT_H
42 # define HAVE_DIR 1
43 #else
44 # define HAVE_DIR 0
45 #endif
46 
47 #include <limits.h>
48 
49 /* Upper bound on the string length of an integer converted to string.
50    302 / 1000 is ceil (log10 (2.0)).  Subtract 1 for the sign bit;
51    add 1 for integer division truncation; add 1 more for a minus sign.  */
52 #define INT_STRLEN_BOUND(t) ((sizeof (t) * CHAR_BIT - 1) * 302 / 1000 + 2)
53 
54 /* ISDIGIT differs from isdigit, as follows:
55    - Its arg may be any int or unsigned int; it need not be an unsigned char.
56    - It's guaranteed to evaluate its argument exactly once.
57    - It's typically faster.
58    Posix 1003.2-1992 section 2.5.2.1 page 50 lines 1556-1558 says that
59    only '0' through '9' are digits.  Prefer ISDIGIT to isdigit unless
60    it's important to use the locale's definition of 'digit' even when the
61    host does not conform to Posix.  */
62 #define ISDIGIT(c) ((unsigned) (c) - '0' <= 9)
63 
64 #if D_INO_IN_DIRENT
65 # define REAL_DIR_ENTRY(dp) ((dp)->d_ino != 0)
66 #else
67 # define REAL_DIR_ENTRY(dp) 1
68 #endif
69 
70 /* The extension added to file names to produce a simple (as opposed
71    to numbered) backup file name. */
72 const char *simple_backup_suffix = "~";
73 
74 #if HAVE_DIR
75 static int max_backup_version (const char *, const char *);
76 static int version_number (const char *, const char *, size_t);
77 #endif
78 
79 /* Return the name of the new backup file for file FILE,
80    allocated with malloc.  Return 0 if out of memory.
81    FILE must not end with a '/' unless it is the root directory.
82    Do not call this function if backup_type == none. */
83 
84 char *
find_backup_file_name(const char * file,enum backup_type backup_type)85 find_backup_file_name (const char *file, enum backup_type backup_type)
86 {
87   size_t backup_suffix_size_max;
88   size_t file_len = strlen (file);
89   size_t numbered_suffix_size_max = INT_STRLEN_BOUND (int) + 4;
90   char *s;
91   const char *suffix = simple_backup_suffix;
92 
93   /* Allow room for simple or '.~N~' backups.  */
94   backup_suffix_size_max = strlen (simple_backup_suffix) + 1;
95   if (HAVE_DIR && backup_suffix_size_max < numbered_suffix_size_max)
96     backup_suffix_size_max = numbered_suffix_size_max;
97 
98   s = (char *) malloc (file_len + backup_suffix_size_max
99                        + numbered_suffix_size_max);
100   if (s)
101     {
102       strcpy (s, file);
103 
104 #if HAVE_DIR
105       if (backup_type != simple)
106         {
107           int highest_backup;
108           size_t dir_len = last_component (s) - s;
109 
110           strcpy (s + dir_len, ".");
111           highest_backup = max_backup_version (file + dir_len, s);
112           if (! (backup_type == numbered_existing && highest_backup == 0))
113             {
114               char *numbered_suffix = s + (file_len + backup_suffix_size_max);
115               sprintf (numbered_suffix, ".~%d~", highest_backup + 1);
116               suffix = numbered_suffix;
117             }
118           strcpy (s, file);
119         }
120 #endif /* HAVE_DIR */
121 
122       addext (s, suffix, '~');
123     }
124   return s;
125 }
126 
127 #if HAVE_DIR
128 
129 /* Return the number of the highest-numbered backup file for file
130    FILE in directory DIR.  If there are no numbered backups
131    of FILE in DIR, or an error occurs reading DIR, return 0.
132    */
133 
134 static int
max_backup_version(const char * file,const char * dir)135 max_backup_version (const char *file, const char *dir)
136 {
137   DIR *dirp;
138   struct dirent *dp;
139   int highest_version;
140   int this_version;
141   size_t file_name_length;
142 
143   dirp = opendir (dir);
144   if (!dirp)
145     return 0;
146 
147   highest_version = 0;
148   file_name_length = strlen (file);
149 
150   while ((dp = readdir (dirp)) != 0)
151     {
152       if (!REAL_DIR_ENTRY (dp) || strlen (dp->d_name) < file_name_length + 4)
153         continue;
154 
155       this_version = version_number (file, dp->d_name, file_name_length);
156       if (this_version > highest_version)
157         highest_version = this_version;
158     }
159   if (closedir (dirp))
160     return 0;
161   return highest_version;
162 }
163 
164 /* If BACKUP is a numbered backup of BASE, return its version number;
165    otherwise return 0.  BASE_LENGTH is the length of BASE.
166    */
167 
168 static int
version_number(const char * base,const char * backup,size_t base_length)169 version_number (const char *base, const char *backup, size_t base_length)
170 {
171   int version;
172   const char *p;
173 
174   version = 0;
175   if (strncmp (base, backup, base_length) == 0
176       && backup[base_length] == '.'
177       && backup[base_length + 1] == '~')
178     {
179       for (p = &backup[base_length + 2]; ISDIGIT (*p); ++p)
180         version = version * 10 + *p - '0';
181       if (p[0] != '~' || p[1])
182         version = 0;
183     }
184   return version;
185 }
186 #endif /* HAVE_DIR */
187 
188 static const char * const backup_args[] =
189 {
190   /* In a series of synonyms, present the most meaning full first, so
191      that argmatch_valid be more readable. */
192   "none", "off",
193   "simple", "never",
194   "existing", "nil",
195   "numbered", "t",
196   0
197 };
198 
199 static const enum backup_type backup_types[] =
200 {
201   none, none,
202   simple, simple,
203   numbered_existing, numbered_existing,
204   numbered, numbered
205 };
206 
207 /* Return the type of backup specified by VERSION.
208    If VERSION is NULL or the empty string, return numbered_existing.
209    If VERSION is invalid or ambiguous, fail with a diagnostic appropriate
210    for the specified CONTEXT.  Unambiguous abbreviations are accepted.  */
211 
212 enum backup_type
get_version(const char * context,const char * version)213 get_version (const char *context, const char *version)
214 {
215   if (version == 0 || *version == 0)
216     return numbered_existing;
217   else
218     return XARGMATCH (context, version, backup_args, backup_types);
219 }
220 
221 
222 /* Return the type of backup specified by VERSION.
223    If VERSION is NULL, use the value of the envvar VERSION_CONTROL.
224    If the specified string is invalid or ambiguous, fail with a diagnostic
225    appropriate for the specified CONTEXT.
226    Unambiguous abbreviations are accepted.  */
227 
228 enum backup_type
xget_version(const char * context,const char * version)229 xget_version (const char *context, const char *version)
230 {
231   if (version && *version)
232     return get_version (context, version);
233   else
234     return get_version ("$VERSION_CONTROL", getenv ("VERSION_CONTROL"));
235 }
236