1 /* backupfile.c -- make Emacs style backup file names
2 Copyright (C) 1990, 1991, 1992 Free Software Foundation, Inc.
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 2, or (at your option)
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.
14 You should have received a copy of the GNU General Public License
15 along with this program; if not, write to the Free Software
16 Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */
18 /* Written by David MacKenzie <djm@gnu.ai.mit.edu>.
19 Some algorithms adapted from GNU Emacs.
22 #include <sys/cdefs.h>
23 __FBSDID("$FreeBSD$");
28 #include <sys/types.h>
29 #include "backupfile.h"
37 #if defined (HAVE_UNISTD_H)
41 #if defined(DIRENT) || defined(_POSIX_VERSION)
43 #define NLENGTH(direct) (strlen((direct)->d_name))
44 #else /* not (DIRENT or _POSIX_VERSION) */
46 #define NLENGTH(direct) ((direct)->d_namlen)
56 #endif /* DIRENT or _POSIX_VERSION */
59 #define ISDIGIT(c) (isdigit ((unsigned char) (c)))
61 #define ISDIGIT(c) (isascii (c) && isdigit (c))
64 #if defined (_POSIX_VERSION)
65 /* POSIX does not require that the d_ino field be present, and some
66 systems do not provide it. */
67 #define REAL_DIR_ENTRY(dp) 1
69 #define REAL_DIR_ENTRY(dp) ((dp)->d_ino != 0)
72 /* Which type of backup file names are generated. */
73 enum backup_type backup_type = none;
75 /* The extension added to file names to produce a simple (as opposed
76 to numbered) backup file name. */
77 char *simple_backup_suffix = "~";
79 int argmatch(char *_arg, const char **_optlist);
80 const char *basename(const char *_name);
81 char *dirname(const char *_path);
82 static char *concat(const char *_str1, const char *_str2);
83 char *find_backup_file_name(char *_file);
84 static char *make_version_name (char *_file, int _version);
85 static int max_backup_version(char *_file, char *_dir);
86 static int version_number(char *base, char *backup, int base_length);
87 void invalid_arg(const char *_kind, char *_value, int _problem);
89 /* Return NAME with any leading path stripped off. */
92 basename(const char *name)
94 const char *r = name, *p = name;
103 /* Return the name of the new backup file for file FILE,
104 allocated with malloc. Return 0 if out of memory.
105 FILE must not end with a '/' unless it is the root directory.
106 Do not call this function if backup_type == none. */
109 find_backup_file_name(char *file)
115 if (backup_type == simple)
117 char *s = malloc (strlen (file) + strlen (simple_backup_suffix) + 1);
119 addext (s, simple_backup_suffix, '~');
122 base_versions = concat (basename (file), ".~");
123 if (base_versions == 0)
125 dir = dirname (file);
128 free (base_versions);
131 highest_backup = max_backup_version (base_versions, dir);
132 free (base_versions);
134 if (backup_type == numbered_existing && highest_backup == 0)
135 return concat (file, simple_backup_suffix);
136 return make_version_name (file, highest_backup + 1);
139 /* Return the number of the highest-numbered backup file for file
140 FILE in directory DIR. If there are no numbered backups
141 of FILE in DIR, or an error occurs reading DIR, return 0.
142 FILE should already have ".~" appended to it. */
145 max_backup_version(char *file, char *dir)
151 int file_name_length;
153 dirp = opendir (dir);
158 file_name_length = strlen (file);
160 while ((dp = readdir (dirp)) != 0)
162 if (!REAL_DIR_ENTRY (dp) || NLENGTH (dp) <= file_name_length)
165 this_version = version_number (file, dp->d_name, file_name_length);
166 if (this_version > highest_version)
167 highest_version = this_version;
170 return highest_version;
173 /* Return a string, allocated with malloc, containing
174 "FILE.~VERSION~". Return 0 if out of memory. */
177 make_version_name(char *file, int version)
181 backup_name = malloc (strlen (file) + 16);
182 if (backup_name == 0)
184 sprintf (backup_name, "%s.~%d~", file, version);
188 /* If BACKUP is a numbered backup of BASE, return its version number;
189 otherwise return 0. BASE_LENGTH is the length of BASE.
190 BASE should already have ".~" appended to it. */
193 version_number(char *base, char *backup, int base_length)
199 if (!strncmp (base, backup, base_length) && ISDIGIT (backup[base_length]))
201 for (p = &backup[base_length]; ISDIGIT (*p); ++p)
202 version = version * 10 + *p - '0';
203 if (p[0] != '~' || p[1])
209 /* Return the newly-allocated concatenation of STR1 and STR2.
210 If out of memory, return 0. */
213 concat(const char *str1, const char *str2)
216 int str1_length = strlen (str1);
218 newstr = malloc (str1_length + strlen (str2) + 1);
221 strcpy (newstr, str1);
222 strcpy (newstr + str1_length, str2);
226 /* Return the leading directories part of PATH,
227 allocated with malloc. If out of memory, return 0.
228 Assumes that trailing slashes have already been
232 dirname(const char *path)
236 int length; /* Length of result, not including NUL. */
238 slash = basename (path);
241 /* File is in the current directory. */
247 /* Remove any trailing slashes from result. */
248 while (*--slash == '/' && slash > path)
251 length = slash - path + 1;
253 newpath = malloc (length + 1);
256 strncpy (newpath, path, length);
261 /* If ARG is an unambiguous match for an element of the
262 null-terminated array OPTLIST, return the index in OPTLIST
263 of the matched element, else -1 if it does not match any element
264 or -2 if it is ambiguous (is a prefix of more than one element). */
267 argmatch(char *arg, const char **optlist)
269 int i; /* Temporary index in OPTLIST. */
270 int arglen; /* Length of ARG. */
271 int matchind = -1; /* Index of first nonexact match. */
272 int ambiguous = 0; /* If nonzero, multiple nonexact match(es). */
274 arglen = strlen (arg);
276 /* Test all elements for either exact match or abbreviated matches. */
277 for (i = 0; optlist[i]; i++)
279 if (!strncmp (optlist[i], arg, arglen))
281 if (strlen (optlist[i]) == arglen)
282 /* Exact match found. */
284 else if (matchind == -1)
285 /* First nonexact match found. */
288 /* Second nonexact match found. */
298 /* Error reporting for argmatch.
299 KIND is a description of the type of entity that was being matched.
300 VALUE is the invalid value that was given.
301 PROBLEM is the return value from argmatch. */
304 invalid_arg(const char *kind, char *value, int problem)
306 fprintf (stderr, "patch: ");
308 fprintf (stderr, "invalid");
309 else /* Assume -2. */
310 fprintf (stderr, "ambiguous");
311 fprintf (stderr, " %s `%s'\n", kind, value);
314 static const char *backup_args[] =
316 "never", "simple", "nil", "existing", "t", "numbered", 0
319 static enum backup_type backup_types[] =
321 simple, simple, numbered_existing, numbered_existing, numbered, numbered
324 /* Return the type of backup indicated by VERSION.
325 Unique abbreviations are accepted. */
328 get_version(char *version)
332 if (version == 0 || *version == 0)
333 return numbered_existing;
334 i = argmatch (version, backup_args);
336 return backup_types[i];
337 invalid_arg ("version control type", version, i);
342 /* Append to FILENAME the extension EXT, unless the result would be too long,
343 in which case just append the character E. */
346 addext(char *filename, char *ext, int e)
348 char *s = (char *)(uintptr_t)(const void *)basename (filename);
349 int slen = strlen (s), extlen = strlen (ext);
352 #if HAVE_PATHCONF && defined (_PC_NAME_MAX)
353 #ifndef _POSIX_NAME_MAX
354 #define _POSIX_NAME_MAX 14
356 if (slen + extlen <= _POSIX_NAME_MAX)
357 /* The file name is so short there's no need to call pathconf. */
358 slen_max = _POSIX_NAME_MAX;
359 else if (s == filename)
360 slen_max = pathconf (".", _PC_NAME_MAX);
365 slen_max = pathconf (filename, _PC_NAME_MAX);
369 if (slen_max == -1) {
370 #ifdef HAVE_LONG_FILE_NAMES
376 if (slen + extlen <= slen_max)
377 strcpy (s + slen, ext);
380 if (slen_max <= slen) {
381 /* Try to preserve difference between .h .c etc. */
382 if (slen == slen_max && s[slen - 2] == '.')
383 s[slen - 2] = s[slen - 1];