xref: /openbsd-src/usr.bin/patch/backupfile.c (revision 47911bd667ac77dc523b8a13ef40b012dbffa741)
1 /*	$OpenBSD: backupfile.c,v 1.7 1999/12/04 21:00:03 provos Exp $	*/
2 
3 /* backupfile.c -- make Emacs style backup file names
4    Copyright (C) 1990 Free Software Foundation, Inc.
5 
6    This program is free software; you can redistribute it and/or modify
7    it without restriction.
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.  */
12 
13 /* David MacKenzie <djm@ai.mit.edu>.
14    Some algorithms adapted from GNU Emacs. */
15 
16 #ifndef lint
17 static char rcsid[] = "$OpenBSD: backupfile.c,v 1.7 1999/12/04 21:00:03 provos Exp $";
18 #endif /* not lint */
19 
20 #include <stdio.h>
21 #include <stdlib.h>
22 #include <string.h>
23 #include <ctype.h>
24 #include <libgen.h>
25 #include <sys/types.h>
26 #include "backupfile.h"
27 #include "config.h"
28 
29 #ifdef DIRENT
30 #include <dirent.h>
31 #ifdef direct
32 #undef direct
33 #endif
34 #define direct dirent
35 #define NLENGTH(direct) (strlen((direct)->d_name))
36 #else /* !DIRENT */
37 #define NLENGTH(direct) ((direct)->d_namlen)
38 #ifdef USG
39 #ifdef SYSNDIR
40 #include <sys/ndir.h>
41 #else /* !SYSNDIR */
42 #include <ndir.h>
43 #endif /* !SYSNDIR */
44 #else /* !USG */
45 #ifdef SYSDIR
46 #include <sys/dir.h>
47 #endif /* SYSDIR */
48 #endif /* !USG */
49 #endif /* !DIRENT */
50 
51 #ifndef isascii
52 #define ISDIGIT(c) (isdigit ((unsigned char) (c)))
53 #else
54 #define ISDIGIT(c) (isascii (c) && isdigit (c))
55 #endif
56 
57 #if defined (HAVE_UNISTD_H)
58 #include <unistd.h>
59 #endif
60 
61 #if defined (_POSIX_VERSION)
62 /* POSIX does not require that the d_ino field be present, and some
63    systems do not provide it. */
64 #define REAL_DIR_ENTRY(dp) 1
65 #else
66 #define REAL_DIR_ENTRY(dp) ((dp)->d_ino != 0)
67 #endif
68 
69 /* Which type of backup file names are generated. */
70 enum backup_type backup_type = none;
71 
72 /* The extension added to file names to produce a simple (as opposed
73    to numbered) backup file name. */
74 char *simple_backup_suffix = "~";
75 
76 static char *concat ();
77 char *find_backup_file_name ();
78 static char *make_version_name ();
79 static int max_backup_version ();
80 static int version_number ();
81 
82 #ifndef NODIR
83 /* Return the name of the new backup file for file FILE,
84    allocated with malloc.  Return 0 if out of memory.
85    FILE must not end with a '/' unless it is the root directory.
86    Do not call this function if backup_type == none. */
87 
88 char *
89 find_backup_file_name (file)
90      char *file;
91 {
92   char *dir;
93   char *base_versions;
94   int highest_backup;
95 
96   if (backup_type == simple)
97     return concat (file, simple_backup_suffix);
98   base_versions = concat (basename (file), ".~");
99   if (base_versions == 0)
100     return 0;
101   dir = dirname (file);
102   if (dir == 0)
103     {
104       free (base_versions);
105       return 0;
106     }
107   highest_backup = max_backup_version (base_versions, dir);
108   free (base_versions);
109   if (backup_type == numbered_existing && highest_backup == 0)
110     return concat (file, simple_backup_suffix);
111   return make_version_name (file, highest_backup + 1);
112 }
113 
114 /* Return the number of the highest-numbered backup file for file
115    FILE in directory DIR.  If there are no numbered backups
116    of FILE in DIR, or an error occurs reading DIR, return 0.
117    FILE should already have ".~" appended to it. */
118 
119 static int
120 max_backup_version (file, dir)
121      char *file, *dir;
122 {
123   DIR *dirp;
124   struct direct *dp;
125   int highest_version;
126   int this_version;
127   int file_name_length;
128 
129   dirp = opendir (dir);
130   if (!dirp)
131     return 0;
132 
133   highest_version = 0;
134   file_name_length = strlen (file);
135 
136   while ((dp = readdir (dirp)) != 0)
137     {
138       if (!REAL_DIR_ENTRY (dp) || NLENGTH (dp) <= file_name_length)
139 	continue;
140 
141       this_version = version_number (file, dp->d_name, file_name_length);
142       if (this_version > highest_version)
143 	highest_version = this_version;
144     }
145   closedir (dirp);
146   return highest_version;
147 }
148 
149 /* Return a string, allocated with malloc, containing
150    "FILE.~VERSION~".  Return 0 if out of memory. */
151 
152 static char *
153 make_version_name (file, version)
154      char *file;
155      int version;
156 {
157   char *backup_name;
158 
159   backup_name = malloc (strlen (file) + 16);
160   if (backup_name == 0)
161     return 0;
162   sprintf (backup_name, "%s.~%d~", file, version);
163   return backup_name;
164 }
165 
166 /* If BACKUP is a numbered backup of BASE, return its version number;
167    otherwise return 0.  BASE_LENGTH is the length of BASE.
168    BASE should already have ".~" appended to it. */
169 
170 static int
171 version_number (base, backup, base_length)
172      char *base;
173      char *backup;
174      int base_length;
175 {
176   int version;
177   char *p;
178 
179   version = 0;
180   if (!strncmp (base, backup, base_length) && ISDIGIT (backup[base_length]))
181     {
182       for (p = &backup[base_length]; ISDIGIT (*p); ++p)
183 	version = version * 10 + *p - '0';
184       if (p[0] != '~' || p[1])
185 	version = 0;
186     }
187   return version;
188 }
189 
190 /* Return the newly-allocated concatenation of STR1 and STR2.
191    If out of memory, return 0. */
192 
193 static char *
194 concat (str1, str2)
195      char *str1, *str2;
196 {
197   char *newstr;
198   int str1_length = strlen (str1);
199 
200   newstr = malloc (str1_length + strlen (str2) + 1);
201   if (newstr == 0)
202     return 0;
203   strcpy (newstr, str1);
204   strcpy (newstr + str1_length, str2);
205   return newstr;
206 }
207 
208 /* If ARG is an unambiguous match for an element of the
209    null-terminated array OPTLIST, return the index in OPTLIST
210    of the matched element, else -1 if it does not match any element
211    or -2 if it is ambiguous (is a prefix of more than one element). */
212 
213 int
214 argmatch (arg, optlist)
215      char *arg;
216      char **optlist;
217 {
218   int i;			/* Temporary index in OPTLIST. */
219   int arglen;			/* Length of ARG. */
220   int matchind = -1;		/* Index of first nonexact match. */
221   int ambiguous = 0;		/* If nonzero, multiple nonexact match(es). */
222 
223   arglen = strlen (arg);
224 
225   /* Test all elements for either exact match or abbreviated matches.  */
226   for (i = 0; optlist[i]; i++)
227     {
228       if (!strncmp (optlist[i], arg, arglen))
229 	{
230 	  if (strlen (optlist[i]) == arglen)
231 	    /* Exact match found.  */
232 	    return i;
233 	  else if (matchind == -1)
234 	    /* First nonexact match found.  */
235 	    matchind = i;
236 	  else
237 	    /* Second nonexact match found.  */
238 	    ambiguous = 1;
239 	}
240     }
241   if (ambiguous)
242     return -2;
243   else
244     return matchind;
245 }
246 
247 /* Error reporting for argmatch.
248    KIND is a description of the type of entity that was being matched.
249    VALUE is the invalid value that was given.
250    PROBLEM is the return value from argmatch. */
251 
252 void
253 invalid_arg (kind, value, problem)
254      char *kind;
255      char *value;
256      int problem;
257 {
258   fprintf (stderr, "patch: ");
259   if (problem == -1)
260     fprintf (stderr, "invalid");
261   else				/* Assume -2. */
262     fprintf (stderr, "ambiguous");
263   fprintf (stderr, " %s `%s'\n", kind, value);
264 }
265 
266 static char *backup_args[] =
267 {
268   "never", "simple", "nil", "existing", "t", "numbered", 0
269 };
270 
271 static enum backup_type backup_types[] =
272 {
273   simple, simple, numbered_existing, numbered_existing, numbered, numbered
274 };
275 
276 /* Return the type of backup indicated by VERSION.
277    Unique abbreviations are accepted. */
278 
279 enum backup_type
280 get_version (version)
281      char *version;
282 {
283   int i;
284 
285   if (version == 0 || *version == 0)
286     return numbered_existing;
287   i = argmatch (version, backup_args);
288   if (i >= 0)
289     return backup_types[i];
290   invalid_arg ("version control type", version, i);
291   exit (1);
292 }
293 #endif /* NODIR */
294