1. Remove a rather strangely gratuitous bit of profanity
[unix-history] / gnu / usr.bin / patch / backupfile.c
CommitLineData
fb3ebe05
PR
1/* backupfile.c -- make Emacs style backup file names
2 Copyright (C) 1990, 1991, 1992 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 2, or (at your option)
7 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, write to the Free Software
16 Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */
17
18/* Written by David MacKenzie <djm@gnu.ai.mit.edu>.
19 Some algorithms adapted from GNU Emacs. */
20
21#include "config.h"
22#include <stdio.h>
23#include <ctype.h>
24#include <sys/types.h>
25#include "backupfile.h"
26#ifdef STDC_HEADERS
27#include <string.h>
28#include <stdlib.h>
29#else
30char *malloc ();
31#endif
32
33#if defined (HAVE_UNISTD_H)
34#include <unistd.h>
35#endif
36
37#if defined(DIRENT) || defined(_POSIX_VERSION)
38#include <dirent.h>
39#define NLENGTH(direct) (strlen((direct)->d_name))
40#else /* not (DIRENT or _POSIX_VERSION) */
41#define dirent direct
42#define NLENGTH(direct) ((direct)->d_namlen)
43#ifdef SYSNDIR
44#include <sys/ndir.h>
45#endif /* SYSNDIR */
46#ifdef SYSDIR
47#include <sys/dir.h>
48#endif /* SYSDIR */
49#ifdef NDIR
50#include <ndir.h>
51#endif /* NDIR */
52#endif /* DIRENT or _POSIX_VERSION */
53
54#ifndef isascii
55#define ISDIGIT(c) (isdigit ((unsigned char) (c)))
56#else
57#define ISDIGIT(c) (isascii (c) && isdigit (c))
58#endif
59
60#if defined (_POSIX_VERSION)
61/* POSIX does not require that the d_ino field be present, and some
62 systems do not provide it. */
63#define REAL_DIR_ENTRY(dp) 1
64#else
65#define REAL_DIR_ENTRY(dp) ((dp)->d_ino != 0)
66#endif
67
68/* Which type of backup file names are generated. */
69enum backup_type backup_type = none;
70
71/* The extension added to file names to produce a simple (as opposed
72 to numbered) backup file name. */
73char *simple_backup_suffix = "~";
74
75char *basename ();
76char *dirname ();
77static char *concat ();
78char *find_backup_file_name ();
79static char *make_version_name ();
80static int max_backup_version ();
81static int version_number ();
82
83/* Return NAME with any leading path stripped off. */
84
85char *
86basename (name)
87 char *name;
88{
89 char *r = name, *p = name;
90
91 while (*p)
92 if (*p++ == '/')
93 r = p;
94 return r;
95}
96
97#ifndef NODIR
98/* Return the name of the new backup file for file FILE,
99 allocated with malloc. Return 0 if out of memory.
100 FILE must not end with a '/' unless it is the root directory.
101 Do not call this function if backup_type == none. */
102
103char *
104find_backup_file_name (file)
105 char *file;
106{
107 char *dir;
108 char *base_versions;
109 int highest_backup;
110
111 if (backup_type == simple)
112 {
113 char *s = malloc (strlen (file) + strlen (simple_backup_suffix) + 1);
114 strcpy (s, file);
115 addext (s, simple_backup_suffix, '~');
116 return s;
117 }
118 base_versions = concat (basename (file), ".~");
119 if (base_versions == 0)
120 return 0;
121 dir = dirname (file);
122 if (dir == 0)
123 {
124 free (base_versions);
125 return 0;
126 }
127 highest_backup = max_backup_version (base_versions, dir);
128 free (base_versions);
129 free (dir);
130 if (backup_type == numbered_existing && highest_backup == 0)
131 return concat (file, simple_backup_suffix);
132 return make_version_name (file, highest_backup + 1);
133}
134
135/* Return the number of the highest-numbered backup file for file
136 FILE in directory DIR. If there are no numbered backups
137 of FILE in DIR, or an error occurs reading DIR, return 0.
138 FILE should already have ".~" appended to it. */
139
140static int
141max_backup_version (file, dir)
142 char *file, *dir;
143{
144 DIR *dirp;
145 struct dirent *dp;
146 int highest_version;
147 int this_version;
148 int file_name_length;
149
150 dirp = opendir (dir);
151 if (!dirp)
152 return 0;
153
154 highest_version = 0;
155 file_name_length = strlen (file);
156
157 while ((dp = readdir (dirp)) != 0)
158 {
159 if (!REAL_DIR_ENTRY (dp) || NLENGTH (dp) <= file_name_length)
160 continue;
161
162 this_version = version_number (file, dp->d_name, file_name_length);
163 if (this_version > highest_version)
164 highest_version = this_version;
165 }
166 closedir (dirp);
167 return highest_version;
168}
169
170/* Return a string, allocated with malloc, containing
171 "FILE.~VERSION~". Return 0 if out of memory. */
172
173static char *
174make_version_name (file, version)
175 char *file;
176 int version;
177{
178 char *backup_name;
179
180 backup_name = malloc (strlen (file) + 16);
181 if (backup_name == 0)
182 return 0;
183 sprintf (backup_name, "%s.~%d~", file, version);
184 return backup_name;
185}
186
187/* If BACKUP is a numbered backup of BASE, return its version number;
188 otherwise return 0. BASE_LENGTH is the length of BASE.
189 BASE should already have ".~" appended to it. */
190
191static int
192version_number (base, backup, base_length)
193 char *base;
194 char *backup;
195 int base_length;
196{
197 int version;
198 char *p;
199
200 version = 0;
201 if (!strncmp (base, backup, base_length) && ISDIGIT (backup[base_length]))
202 {
203 for (p = &backup[base_length]; ISDIGIT (*p); ++p)
204 version = version * 10 + *p - '0';
205 if (p[0] != '~' || p[1])
206 version = 0;
207 }
208 return version;
209}
210
211/* Return the newly-allocated concatenation of STR1 and STR2.
212 If out of memory, return 0. */
213
214static char *
215concat (str1, str2)
216 char *str1, *str2;
217{
218 char *newstr;
219 char str1_length = strlen (str1);
220
221 newstr = malloc (str1_length + strlen (str2) + 1);
222 if (newstr == 0)
223 return 0;
224 strcpy (newstr, str1);
225 strcpy (newstr + str1_length, str2);
226 return newstr;
227}
228
229/* Return the leading directories part of PATH,
230 allocated with malloc. If out of memory, return 0.
231 Assumes that trailing slashes have already been
232 removed. */
233
234char *
235dirname (path)
236 char *path;
237{
238 char *newpath;
239 char *slash;
240 int length; /* Length of result, not including NUL. */
241
242 slash = basename (path);
243 if (slash == path)
244 {
245 /* File is in the current directory. */
246 path = ".";
247 length = 1;
248 }
249 else
250 {
251 /* Remove any trailing slashes from result. */
252 while (*--slash == '/' && slash > path)
253 ;
254
255 length = slash - path + 1;
256 }
257 newpath = malloc (length + 1);
258 if (newpath == 0)
259 return 0;
260 strncpy (newpath, path, length);
261 newpath[length] = 0;
262 return newpath;
263}
264
265/* If ARG is an unambiguous match for an element of the
266 null-terminated array OPTLIST, return the index in OPTLIST
267 of the matched element, else -1 if it does not match any element
268 or -2 if it is ambiguous (is a prefix of more than one element). */
269
270int
271argmatch (arg, optlist)
272 char *arg;
273 char **optlist;
274{
275 int i; /* Temporary index in OPTLIST. */
276 int arglen; /* Length of ARG. */
277 int matchind = -1; /* Index of first nonexact match. */
278 int ambiguous = 0; /* If nonzero, multiple nonexact match(es). */
279
280 arglen = strlen (arg);
281
282 /* Test all elements for either exact match or abbreviated matches. */
283 for (i = 0; optlist[i]; i++)
284 {
285 if (!strncmp (optlist[i], arg, arglen))
286 {
287 if (strlen (optlist[i]) == arglen)
288 /* Exact match found. */
289 return i;
290 else if (matchind == -1)
291 /* First nonexact match found. */
292 matchind = i;
293 else
294 /* Second nonexact match found. */
295 ambiguous = 1;
296 }
297 }
298 if (ambiguous)
299 return -2;
300 else
301 return matchind;
302}
303
304/* Error reporting for argmatch.
305 KIND is a description of the type of entity that was being matched.
306 VALUE is the invalid value that was given.
307 PROBLEM is the return value from argmatch. */
308
309void
310invalid_arg (kind, value, problem)
311 char *kind;
312 char *value;
313 int problem;
314{
315 fprintf (stderr, "patch: ");
316 if (problem == -1)
317 fprintf (stderr, "invalid");
318 else /* Assume -2. */
319 fprintf (stderr, "ambiguous");
320 fprintf (stderr, " %s `%s'\n", kind, value);
321}
322
323static char *backup_args[] =
324{
325 "never", "simple", "nil", "existing", "t", "numbered", 0
326};
327
328static enum backup_type backup_types[] =
329{
330 simple, simple, numbered_existing, numbered_existing, numbered, numbered
331};
332
333/* Return the type of backup indicated by VERSION.
334 Unique abbreviations are accepted. */
335
336enum backup_type
337get_version (version)
338 char *version;
339{
340 int i;
341
342 if (version == 0 || *version == 0)
343 return numbered_existing;
344 i = argmatch (version, backup_args);
345 if (i >= 0)
346 return backup_types[i];
347 invalid_arg ("version control type", version, i);
348 exit (1);
349}
350#endif /* NODIR */
351
352/* Append to FILENAME the extension EXT, unless the result would be too long,
353 in which case just append the character E. */
354
355void
356addext (filename, ext, e)
357 char *filename, *ext;
358 int e;
359{
360 char *s = basename (filename);
361 int slen = strlen (s), extlen = strlen (ext);
362 long slen_max = -1;
363
364#if HAVE_PATHCONF && defined (_PC_NAME_MAX)
365#ifndef _POSIX_NAME_MAX
366#define _POSIX_NAME_MAX 14
367#endif
368 if (slen + extlen <= _POSIX_NAME_MAX)
369 /* The file name is so short there's no need to call pathconf. */
370 slen_max = _POSIX_NAME_MAX;
371 else if (s == filename)
372 slen_max = pathconf (".", _PC_NAME_MAX);
373 else
374 {
375 char c = *s;
376 *s = 0;
377 slen_max = pathconf (filename, _PC_NAME_MAX);
378 *s = c;
379 }
380#endif
381 if (slen_max == -1) {
382#ifdef HAVE_LONG_FILE_NAMES
383 slen_max = 255;
384#else
385 slen_max = 14;
386#endif
387 }
388 if (slen + extlen <= slen_max)
389 strcpy (s + slen, ext);
390 else
391 {
392 if (slen_max <= slen) {
393 /* Try to preserve difference between .h .c etc. */
394 if (slen == slen_max && s[slen - 2] == '.')
395 s[slen - 2] = s[slen - 1];
396
397 slen = slen_max - 1;
398 }
399 s[slen] = e;
400 s[slen + 1] = 0;
401 }
402}