backupfile.c revision 118136
1299118Sbr/* backupfile.c -- make Emacs style backup file names
2299118Sbr   Copyright (C) 1990, 1991, 1992 Free Software Foundation, Inc.
3299118Sbr
4299118Sbr   This program is free software; you can redistribute it and/or modify
5299118Sbr   it under the terms of the GNU General Public License as published by
6299118Sbr   the Free Software Foundation; either version 2, or (at your option)
7299118Sbr   any later version.
8299118Sbr
9299118Sbr   This program is distributed in the hope that it will be useful,
10299118Sbr   but WITHOUT ANY WARRANTY; without even the implied warranty of
11299118Sbr   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12299118Sbr   GNU General Public License for more details.
13299118Sbr
14299118Sbr   You should have received a copy of the GNU General Public License
15299118Sbr   along with this program; if not, write to the Free Software
16299118Sbr   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.  */
17299118Sbr
18299118Sbr/* Written by David MacKenzie <djm@gnu.ai.mit.edu>.
19299118Sbr   Some algorithms adapted from GNU Emacs.
20299118Sbr */
21299118Sbr
22299118Sbr#include <sys/cdefs.h>
23299118Sbr__FBSDID("$FreeBSD: head/gnu/usr.bin/patch/backupfile.c 118136 2003-07-29 00:31:07Z jwd $");
24299118Sbr
25299118Sbr#include "config.h"
26299118Sbr#include <stdio.h>
27299118Sbr#include <ctype.h>
28299118Sbr#include <sys/types.h>
29299118Sbr#include "backupfile.h"
30299118Sbr#ifdef STDC_HEADERS
31299118Sbr#include <string.h>
32299118Sbr#include <stdlib.h>
33299118Sbr#else
34299118Sbrchar *malloc ();
35299118Sbr#endif
36299118Sbr
37299118Sbr#if defined (HAVE_UNISTD_H)
38299118Sbr#include <unistd.h>
39299118Sbr#endif
40299118Sbr
41299118Sbr#if defined(DIRENT) || defined(_POSIX_VERSION)
42299118Sbr#include <dirent.h>
43299118Sbr#define NLENGTH(direct) (strlen((direct)->d_name))
44299118Sbr#else /* not (DIRENT or _POSIX_VERSION) */
45299118Sbr#define dirent direct
46299118Sbr#define NLENGTH(direct) ((direct)->d_namlen)
47299118Sbr#ifdef SYSNDIR
48299118Sbr#include <sys/ndir.h>
49299118Sbr#endif /* SYSNDIR */
50299118Sbr#ifdef SYSDIR
51299118Sbr#include <sys/dir.h>
52299118Sbr#endif /* SYSDIR */
53299118Sbr#ifdef NDIR
54299118Sbr#include <ndir.h>
55299118Sbr#endif /* NDIR */
56299118Sbr#endif /* DIRENT or _POSIX_VERSION */
57299118Sbr
58299118Sbr#ifndef isascii
59299118Sbr#define ISDIGIT(c) (isdigit ((unsigned char) (c)))
60299118Sbr#else
61299118Sbr#define ISDIGIT(c) (isascii (c) && isdigit (c))
62299118Sbr#endif
63299118Sbr
64299118Sbr#if defined (_POSIX_VERSION)
65299118Sbr/* POSIX does not require that the d_ino field be present, and some
66299118Sbr   systems do not provide it. */
67299118Sbr#define REAL_DIR_ENTRY(dp) 1
68299118Sbr#else
69299118Sbr#define REAL_DIR_ENTRY(dp) ((dp)->d_ino != 0)
70299118Sbr#endif
71299118Sbr
72299118Sbr/* Which type of backup file names are generated. */
73299118Sbrenum backup_type backup_type = none;
74299118Sbr
75299118Sbr/* The extension added to file names to produce a simple (as opposed
76299118Sbr   to numbered) backup file name. */
77299118Sbrchar *simple_backup_suffix = "~";
78299118Sbr
79299118Sbrint		 argmatch(char *_arg, char **_optlist);
80299118Sbrconst char	*basename(const char *_name);
81299118Sbrchar		*dirname(const char *_path);
82299118Sbrstatic char	*concat(const char *_str1, const char *_str2);
83299118Sbrchar		*find_backup_file_name(char *_file);
84299118Sbrstatic char	*make_version_name (char *_file, int _version);
85299118Sbrstatic int	 max_backup_version(char *_file, char *_dir);
86299118Sbrstatic int	 version_number(char *base, char *backup, int base_length);
87299118Sbrvoid		 invalid_arg(const char *_kind, char *_value, int _problem);
88299118Sbr
89299118Sbr/* Return NAME with any leading path stripped off.  */
90299118Sbr
91299118Sbrconst char *
92299118Sbrbasename(const char *name)
93299118Sbr{
94299118Sbr  const char *r = name, *p = name;
95299118Sbr
96299118Sbr  while (*p)
97299118Sbr    if (*p++ == '/')
98299118Sbr      r = p;
99299118Sbr  return r;
100299118Sbr}
101299118Sbr
102299118Sbr#ifndef NODIR
103299118Sbr/* Return the name of the new backup file for file FILE,
104299118Sbr   allocated with malloc.  Return 0 if out of memory.
105299118Sbr   FILE must not end with a '/' unless it is the root directory.
106299118Sbr   Do not call this function if backup_type == none. */
107299118Sbr
108299118Sbrchar *
109299118Sbrfind_backup_file_name(char *file)
110299118Sbr{
111299118Sbr  char *dir;
112299118Sbr  char *base_versions;
113299118Sbr  int highest_backup;
114299118Sbr
115299118Sbr  if (backup_type == simple)
116299118Sbr    {
117299118Sbr      char *s = malloc (strlen (file) + strlen (simple_backup_suffix) + 1);
118299118Sbr      strcpy (s, file);
119299118Sbr      addext (s, simple_backup_suffix, '~');
120299118Sbr      return s;
121299118Sbr    }
122299118Sbr  base_versions = concat (basename (file), ".~");
123299118Sbr  if (base_versions == 0)
124299118Sbr    return 0;
125299118Sbr  dir = dirname (file);
126299118Sbr  if (dir == 0)
127299118Sbr    {
128299118Sbr      free (base_versions);
129299118Sbr      return 0;
130299118Sbr    }
131299118Sbr  highest_backup = max_backup_version (base_versions, dir);
132299118Sbr  free (base_versions);
133299118Sbr  free (dir);
134299118Sbr  if (backup_type == numbered_existing && highest_backup == 0)
135299118Sbr    return concat (file, simple_backup_suffix);
136299118Sbr  return make_version_name (file, highest_backup + 1);
137299118Sbr}
138299118Sbr
139299118Sbr/* Return the number of the highest-numbered backup file for file
140299118Sbr   FILE in directory DIR.  If there are no numbered backups
141299118Sbr   of FILE in DIR, or an error occurs reading DIR, return 0.
142299118Sbr   FILE should already have ".~" appended to it. */
143299118Sbr
144299118Sbrstatic int
145299118Sbrmax_backup_version(char *file, char *dir)
146299118Sbr{
147299118Sbr  DIR *dirp;
148299118Sbr  struct dirent *dp;
149299118Sbr  int highest_version;
150299118Sbr  int this_version;
151299118Sbr  int file_name_length;
152299118Sbr
153299118Sbr  dirp = opendir (dir);
154299118Sbr  if (!dirp)
155299118Sbr    return 0;
156299118Sbr
157299118Sbr  highest_version = 0;
158299118Sbr  file_name_length = strlen (file);
159299118Sbr
160299118Sbr  while ((dp = readdir (dirp)) != 0)
161299118Sbr    {
162299118Sbr      if (!REAL_DIR_ENTRY (dp) || NLENGTH (dp) <= file_name_length)
163299118Sbr	continue;
164299118Sbr
165      this_version = version_number (file, dp->d_name, file_name_length);
166      if (this_version > highest_version)
167	highest_version = this_version;
168    }
169  closedir (dirp);
170  return highest_version;
171}
172
173/* Return a string, allocated with malloc, containing
174   "FILE.~VERSION~".  Return 0 if out of memory. */
175
176static char *
177make_version_name(char *file, int version)
178{
179  char *backup_name;
180
181  backup_name = malloc (strlen (file) + 16);
182  if (backup_name == 0)
183    return 0;
184  sprintf (backup_name, "%s.~%d~", file, version);
185  return backup_name;
186}
187
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. */
191
192static int
193version_number(char *base, char *backup, int base_length)
194{
195  int version;
196  char *p;
197
198  version = 0;
199  if (!strncmp (base, backup, base_length) && ISDIGIT (backup[base_length]))
200    {
201      for (p = &backup[base_length]; ISDIGIT (*p); ++p)
202	version = version * 10 + *p - '0';
203      if (p[0] != '~' || p[1])
204	version = 0;
205    }
206  return version;
207}
208
209/* Return the newly-allocated concatenation of STR1 and STR2.
210   If out of memory, return 0. */
211
212static char *
213concat(const char *str1, const char *str2)
214{
215  char *newstr;
216  int str1_length = strlen (str1);
217
218  newstr = malloc (str1_length + strlen (str2) + 1);
219  if (newstr == 0)
220    return 0;
221  strcpy (newstr, str1);
222  strcpy (newstr + str1_length, str2);
223  return newstr;
224}
225
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
229   removed.  */
230
231char *
232dirname(const char *path)
233{
234  char *newpath;
235  const char *slash;
236  int length;    /* Length of result, not including NUL. */
237
238  slash = basename (path);
239  if (slash == path)
240	{
241	  /* File is in the current directory.  */
242	  path = ".";
243	  length = 1;
244	}
245  else
246	{
247	  /* Remove any trailing slashes from result. */
248	  while (*--slash == '/' && slash > path)
249	    ;
250
251	  length = slash - path + 1;
252	}
253  newpath = malloc (length + 1);
254  if (newpath == 0)
255    return 0;
256  strncpy (newpath, path, length);
257  newpath[length] = 0;
258  return newpath;
259}
260
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). */
265
266int
267argmatch(char *arg, char **optlist)
268{
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). */
273
274  arglen = strlen (arg);
275
276  /* Test all elements for either exact match or abbreviated matches.  */
277  for (i = 0; optlist[i]; i++)
278    {
279      if (!strncmp (optlist[i], arg, arglen))
280	{
281	  if (strlen (optlist[i]) == arglen)
282	    /* Exact match found.  */
283	    return i;
284	  else if (matchind == -1)
285	    /* First nonexact match found.  */
286	    matchind = i;
287	  else
288	    /* Second nonexact match found.  */
289	    ambiguous = 1;
290	}
291    }
292  if (ambiguous)
293    return -2;
294  else
295    return matchind;
296}
297
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. */
302
303void
304invalid_arg(const char *kind, char *value, int problem)
305{
306  fprintf (stderr, "patch: ");
307  if (problem == -1)
308    fprintf (stderr, "invalid");
309  else				/* Assume -2. */
310    fprintf (stderr, "ambiguous");
311  fprintf (stderr, " %s `%s'\n", kind, value);
312}
313
314static const char *backup_args[] =
315{
316  "never", "simple", "nil", "existing", "t", "numbered", 0
317};
318
319static enum backup_type backup_types[] =
320{
321  simple, simple, numbered_existing, numbered_existing, numbered, numbered
322};
323
324/* Return the type of backup indicated by VERSION.
325   Unique abbreviations are accepted. */
326
327enum backup_type
328get_version(char *version)
329{
330  int i;
331
332  if (version == 0 || *version == 0)
333    return numbered_existing;
334  i = argmatch (version, backup_args);
335  if (i >= 0)
336    return backup_types[i];
337  invalid_arg ("version control type", version, i);
338  exit (1);
339}
340#endif /* NODIR */
341
342/* Append to FILENAME the extension EXT, unless the result would be too long,
343   in which case just append the character E.  */
344
345void
346addext(char *filename, char *ext, int e)
347{
348  char *s = basename (filename);
349  int slen = strlen (s), extlen = strlen (ext);
350  long slen_max = -1;
351
352#if HAVE_PATHCONF && defined (_PC_NAME_MAX)
353#ifndef _POSIX_NAME_MAX
354#define _POSIX_NAME_MAX 14
355#endif
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);
361  else
362    {
363      char c = *s;
364      *s = 0;
365      slen_max = pathconf (filename, _PC_NAME_MAX);
366      *s = c;
367    }
368#endif
369  if (slen_max == -1) {
370#ifdef HAVE_LONG_FILE_NAMES
371    slen_max = 255;
372#else
373    slen_max = 14;
374#endif
375  }
376  if (slen + extlen <= slen_max)
377    strcpy (s + slen, ext);
378  else
379    {
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];
384
385	slen = slen_max - 1;
386      }
387      s[slen] = e;
388      s[slen + 1] = 0;
389    }
390}
391