exp2l: Work around a NetBSD 10.0/i386 bug.
[gnulib.git] / lib / backupfile.c
blobd332604cdae6e4a5f5c25c329d2f10ffbaa01e68
1 /* backupfile.c -- make Emacs style backup file names
3 Copyright (C) 1990-2006, 2009-2024 Free Software Foundation, Inc.
5 This program is free software: you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation, either version 3 of the License, or
8 (at your option) any later version.
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU General Public License for more details.
15 You should have received a copy of the GNU General Public License
16 along with this program. If not, see <https://www.gnu.org/licenses/>. */
18 /* Written by Paul Eggert and David MacKenzie.
19 Some algorithms adapted from GNU Emacs. */
21 #include <config.h>
23 #include "backup-internal.h"
25 #include <dirent.h>
26 #include <errno.h>
27 #include <fcntl.h>
28 #include <stdckdint.h>
29 #include <stdint.h>
30 #include <stdlib.h>
31 #include <string.h>
32 #include <unistd.h>
34 #include "attribute.h"
35 #include "basename-lgpl.h"
36 #include "ialloc.h"
37 #include "opendirat.h"
38 #include "renameatu.h"
40 #ifndef _D_EXACT_NAMLEN
41 # define _D_EXACT_NAMLEN(dp) strlen ((dp)->d_name)
42 #endif
44 #if ! (HAVE_PATHCONF && defined _PC_NAME_MAX)
45 # define pathconf(file, option) (errno = -1)
46 # define fpathconf(fd, option) (errno = -1)
47 #endif
49 #ifndef _POSIX_NAME_MAX
50 # define _POSIX_NAME_MAX 14
51 #endif
53 #if defined _XOPEN_NAME_MAX
54 # define NAME_MAX_MINIMUM _XOPEN_NAME_MAX
55 #else
56 # define NAME_MAX_MINIMUM _POSIX_NAME_MAX
57 #endif
59 #ifndef HAVE_DOS_FILE_NAMES
60 # define HAVE_DOS_FILE_NAMES 0
61 #endif
62 #ifndef HAVE_LONG_FILE_NAMES
63 # define HAVE_LONG_FILE_NAMES 0
64 #endif
66 /* ISDIGIT differs from isdigit, as follows:
67 - Its arg may be any int or unsigned int; it need not be an unsigned char
68 or EOF.
69 - It's typically faster.
70 POSIX says that only '0' through '9' are digits. Prefer ISDIGIT to
71 ISDIGIT unless it's important to use the locale's definition
72 of "digit" even when the host does not conform to POSIX. */
73 #define ISDIGIT(c) ((unsigned int) (c) - '0' <= 9)
75 /* The extension added to file names to produce a simple (as opposed
76 to numbered) backup file name. */
77 char const *simple_backup_suffix = NULL;
79 /* Set SIMPLE_BACKUP_SUFFIX to S, or to a default specified by the
80 environment if S is null. If S or the environment does not specify
81 a valid backup suffix, use "~". */
82 void
83 set_simple_backup_suffix (char const *s)
85 if (!s)
86 s = getenv ("SIMPLE_BACKUP_SUFFIX");
87 simple_backup_suffix = s && *s && s == last_component (s) ? s : "~";
90 /* If FILE (which was of length FILELEN before an extension was
91 appended to it) is too long, replace the extension with the single
92 char E. If the result is still too long, remove the char just
93 before E. Return true if the extension was OK already, false
94 if it needed replacement.
96 If DIR_FD is nonnegative, it is a file descriptor for FILE's parent.
97 *BASE_MAX is either 0, or the cached result of a previous call for
98 FILE's parent's _PC_NAME_MAX. */
100 static bool
101 check_extension (char *file, idx_t filelen, char e,
102 int dir_fd, idx_t *base_max)
104 char *base = last_component (file);
105 idx_t baselen = base_len (base);
106 idx_t baselen_max = HAVE_LONG_FILE_NAMES ? 255 : NAME_MAX_MINIMUM;
108 if (HAVE_DOS_FILE_NAMES || NAME_MAX_MINIMUM < baselen)
110 /* The new base name is long enough to require a pathconf check. */
111 if (*base_max == 0)
113 long name_max;
114 if (dir_fd < 0)
116 /* Temporarily modify the buffer into its parent
117 directory name, invoke pathconf on the directory, and
118 then restore the buffer. */
119 char tmp[sizeof "."];
120 memcpy (tmp, base, sizeof ".");
121 strcpy (base, ".");
122 errno = 0;
123 name_max = pathconf (file, _PC_NAME_MAX);
124 name_max -= !errno;
125 memcpy (base, tmp, sizeof ".");
127 else
129 errno = 0;
130 name_max = fpathconf (dir_fd, _PC_NAME_MAX);
131 name_max -= !errno;
134 *base_max = (0 <= name_max && name_max <= SIZE_MAX ? name_max
135 : name_max < -1 ? NAME_MAX_MINIMUM : SIZE_MAX);
138 baselen_max = *base_max;
141 if (HAVE_DOS_FILE_NAMES && baselen_max <= 12)
143 /* Live within DOS's 8.3 limit. */
144 char *dot = strchr (base, '.');
145 if (!dot)
146 baselen_max = 8;
147 else
149 char const *second_dot = strchr (dot + 1, '.');
150 baselen_max = (second_dot
151 ? second_dot - base
152 : dot + 1 - base + 3);
156 if (baselen <= baselen_max)
157 return true;
158 else
160 baselen = file + filelen - base;
161 if (baselen_max <= baselen)
162 baselen = baselen_max - 1;
163 base[baselen] = e;
164 base[baselen + 1] = '\0';
165 return false;
169 /* Returned values for NUMBERED_BACKUP. */
171 enum numbered_backup_result
173 /* The new backup name is the same length as an existing backup
174 name, so it's valid for that directory. */
175 BACKUP_IS_SAME_LENGTH,
177 /* Some backup names already exist, but the returned name is longer
178 than any of them, and its length should be checked. */
179 BACKUP_IS_LONGER,
181 /* There are no existing backup names. The new name's length
182 should be checked. */
183 BACKUP_IS_NEW,
185 /* Memory allocation failure. */
186 BACKUP_NOMEM
189 /* Relative to DIR_FD, *BUFFER contains a file name.
190 Store into *BUFFER the next backup name for the named file,
191 with a version number greater than all the
192 existing numbered backups. Reallocate *BUFFER as necessary; its
193 initial allocated size is BUFFER_SIZE, which must be at least 5
194 bytes longer than the file name to make room for the initially
195 appended ".~1~". FILELEN is the length of the original file name.
196 (The original file name is not necessarily null-terminated;
197 FILELEN does not count trailing slashes after a non-slash.)
198 BASE_OFFSET is the offset of the basename in *BUFFER.
199 The returned value indicates what kind of backup was found. If an
200 I/O or other read error occurs, use the highest backup number that
201 was found.
203 *DIRPP is the destination directory. If *DIRPP is null, open the
204 destination directory and store the resulting stream into *DIRPP
205 and its file descriptor into *PNEW_FD without closing the stream. */
207 static enum numbered_backup_result
208 numbered_backup (int dir_fd, char **buffer, idx_t buffer_size, idx_t filelen,
209 idx_t base_offset, DIR **dirpp, int *pnew_fd)
211 enum numbered_backup_result result = BACKUP_IS_NEW;
212 DIR *dirp = *dirpp;
213 char *buf = *buffer;
214 idx_t versionlenmax = 1;
215 idx_t baselen = filelen - base_offset;
217 if (dirp)
218 rewinddir (dirp);
219 else
221 /* Temporarily modify the buffer into its parent directory name,
222 open the directory, and then restore the buffer. */
223 char tmp[sizeof "."];
224 char *base = buf + base_offset;
225 memcpy (tmp, base, sizeof ".");
226 strcpy (base, ".");
227 dirp = opendirat (dir_fd, buf, 0, pnew_fd);
228 if (!dirp && errno == ENOMEM)
229 result = BACKUP_NOMEM;
230 memcpy (base, tmp, sizeof ".");
231 strcpy (base + baselen, ".~1~");
232 if (!dirp)
233 return result;
234 *dirpp = dirp;
237 for (struct dirent *dp; (dp = readdir (dirp)) != NULL; )
239 if (_D_EXACT_NAMLEN (dp) < baselen + 4)
240 continue;
242 if (memcmp (buf + base_offset, dp->d_name, baselen + 2) != 0)
243 continue;
245 char const *p = dp->d_name + baselen + 2;
247 /* Check whether this file has a version number and if so,
248 whether it is larger. Use string operations rather than
249 integer arithmetic, to avoid problems with integer overflow. */
251 if (! ('1' <= *p && *p <= '9'))
252 continue;
253 bool all_9s = (*p == '9');
254 idx_t versionlen;
255 for (versionlen = 1; ISDIGIT (p[versionlen]); versionlen++)
256 all_9s &= (p[versionlen] == '9');
258 if (! (p[versionlen] == '~' && !p[versionlen + 1]
259 && (versionlenmax < versionlen
260 || (versionlenmax == versionlen
261 && memcmp (buf + filelen + 2, p, versionlen) <= 0))))
262 continue;
264 /* This entry has the largest version number seen so far.
265 Append this highest numbered extension to the file name,
266 prepending '0' to the number if it is all 9s. */
268 versionlenmax = all_9s + versionlen;
269 result = (all_9s ? BACKUP_IS_LONGER : BACKUP_IS_SAME_LENGTH);
270 idx_t new_buffer_size = filelen + 2 + versionlenmax + 2;
271 if (buffer_size < new_buffer_size)
273 idx_t grown;
274 if (! ckd_add (&grown, new_buffer_size, new_buffer_size >> 1))
275 new_buffer_size = grown;
276 char *new_buf = irealloc (buf, new_buffer_size);
277 if (!new_buf)
279 *buffer = buf;
280 return BACKUP_NOMEM;
282 buf = new_buf;
283 buffer_size = new_buffer_size;
285 char *q = buf + filelen;
286 *q++ = '.';
287 *q++ = '~';
288 *q = '0';
289 q += all_9s;
290 memcpy (q, p, versionlen + 2);
292 /* Add 1 to the version number. */
294 q += versionlen;
295 while (*--q == '9')
296 *q = '0';
297 ++*q;
300 *buffer = buf;
301 return result;
304 /* Relative to DIR_FD, return the name of the new backup file for the
305 existing file FILE, allocated with malloc.
306 If RENAME, also rename FILE to the new name.
307 On failure, return NULL and set errno.
308 Do not call this function if backup_type == no_backups. */
310 char *
311 backupfile_internal (int dir_fd, char const *file,
312 enum backup_type backup_type, bool rename)
314 idx_t base_offset = last_component (file) - file;
315 idx_t filelen = base_offset + base_len (file + base_offset);
317 if (! simple_backup_suffix)
318 set_simple_backup_suffix (NULL);
320 /* Allow room for simple or ".~N~" backups. The guess must be at
321 least sizeof ".~1~", but otherwise will be adjusted as needed. */
322 idx_t simple_backup_suffix_size = strlen (simple_backup_suffix) + 1;
323 idx_t backup_suffix_size_guess = simple_backup_suffix_size;
324 enum { GUESS = sizeof ".~12345~" };
325 if (backup_suffix_size_guess < GUESS)
326 backup_suffix_size_guess = GUESS;
328 idx_t ssize = filelen + backup_suffix_size_guess + 1;
329 char *s = imalloc (ssize);
330 if (!s)
331 return s;
333 DIR *dirp = NULL;
334 int sdir = -1;
335 idx_t base_max = 0;
336 while (true)
338 bool extended = true;
339 memcpy (s, file, filelen);
341 if (backup_type == simple_backups)
342 memcpy (s + filelen, simple_backup_suffix, simple_backup_suffix_size);
343 else
344 switch (numbered_backup (dir_fd, &s, ssize, filelen, base_offset,
345 &dirp, &sdir))
347 case BACKUP_IS_SAME_LENGTH:
348 break;
350 case BACKUP_IS_NEW:
351 if (backup_type == numbered_existing_backups)
353 backup_type = simple_backups;
354 memcpy (s + filelen, simple_backup_suffix,
355 simple_backup_suffix_size);
357 FALLTHROUGH;
358 case BACKUP_IS_LONGER:
359 extended = check_extension (s, filelen, '~', sdir, &base_max);
360 break;
362 case BACKUP_NOMEM:
363 if (dirp)
364 closedir (dirp);
365 free (s);
366 errno = ENOMEM;
367 return NULL;
370 if (! rename)
371 break;
373 dir_fd = sdir < 0 ? dir_fd : sdir;
374 idx_t offset = sdir < 0 ? 0 : base_offset;
375 unsigned flags = backup_type == simple_backups ? 0 : RENAME_NOREPLACE;
376 if (renameatu (dir_fd, file + offset, dir_fd, s + offset, flags) == 0)
377 break;
378 int e = errno;
379 if (! (e == EEXIST && extended))
381 if (dirp)
382 closedir (dirp);
383 free (s);
384 errno = e;
385 return NULL;
389 if (dirp)
390 closedir (dirp);
391 return s;