GNU file utilities
[gnulib.git] / lib / backupfile.c
1 /* backupfile.c -- make Emacs style backup file names
2    Copyright (C) 1990 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 /* David MacKenzie <djm@gnu.ai.mit.edu>.
19    Some algorithms adapted from GNU Emacs. */
20
21 #include <stdio.h>
22 #include <ctype.h>
23 #include <sys/types.h>
24 #include "backupfile.h"
25 #if defined(STDC_HEADERS) || defined(HAVE_STRING_H)
26 #include <string.h>
27 #define index strchr
28 #define rindex strrchr
29 #else
30 #include <strings.h>
31 #endif
32
33 #if defined(DIRENT) || defined(_POSIX_VERSION)
34 #include <dirent.h>
35 #define NLENGTH(direct) (strlen((direct)->d_name))
36 #else /* not (DIRENT or _POSIX_VERSION) */
37 #define dirent direct
38 #define NLENGTH(direct) ((direct)->d_namlen)
39 #ifdef SYSNDIR
40 #include <sys/ndir.h>
41 #endif /* SYSNDIR */
42 #ifdef SYSDIR
43 #include <sys/dir.h>
44 #endif /* SYSDIR */
45 #ifdef NDIR
46 #include <ndir.h>
47 #endif /* NDIR */
48 #endif /* DIRENT or _POSIX_VERSION */
49
50 #ifdef VOID_CLOSEDIR
51 /* Fake a return value. */
52 #define CLOSEDIR(d) (closedir (d), 0)
53 #else
54 #define CLOSEDIR(d) closedir (d)
55 #endif
56
57 #ifdef STDC_HEADERS
58 #include <stdlib.h>
59 #else
60 char *malloc ();
61 #endif
62
63 #ifndef isascii
64 #define ISDIGIT(c) (isdigit ((unsigned char) (c)))
65 #else
66 #define ISDIGIT(c) (isascii (c) && isdigit (c))
67 #endif
68
69 #if defined (HAVE_UNISTD_H)
70 #include <unistd.h>
71 #endif
72
73 #if defined (_POSIX_VERSION)
74 /* POSIX does not require that the d_ino field be present, and some
75    systems do not provide it. */
76 #define REAL_DIR_ENTRY(dp) 1
77 #else
78 #define REAL_DIR_ENTRY(dp) ((dp)->d_ino != 0)
79 #endif
80
81 /* Which type of backup file names are generated. */
82 enum backup_type backup_type = none;
83
84 /* The extension added to file names to produce a simple (as opposed
85    to numbered) backup file name. */
86 char *simple_backup_suffix = "~";
87
88 char *basename ();
89 char *dirname ();
90 static char *concat ();
91 char *find_backup_file_name ();
92 static char *make_version_name ();
93 static int max_backup_version ();
94 static int version_number ();
95
96 /* Return the name of the new backup file for file FILE,
97    allocated with malloc.  Return 0 if out of memory.
98    FILE must not end with a '/' unless it is the root directory.
99    Do not call this function if backup_type == none. */
100
101 char *
102 find_backup_file_name (file)
103      char *file;
104 {
105   char *dir;
106   char *base_versions;
107   int highest_backup;
108
109   if (backup_type == simple)
110     return concat (file, simple_backup_suffix);
111   base_versions = concat (basename (file), ".~");
112   if (base_versions == 0)
113     return 0;
114   dir = dirname (file);
115   if (dir == 0)
116     {
117       free (base_versions);
118       return 0;
119     }
120   highest_backup = max_backup_version (base_versions, dir);
121   free (base_versions);
122   free (dir);
123   if (backup_type == numbered_existing && highest_backup == 0)
124     return concat (file, simple_backup_suffix);
125   return make_version_name (file, highest_backup + 1);
126 }
127
128 /* Return the number of the highest-numbered backup file for file
129    FILE in directory DIR.  If there are no numbered backups
130    of FILE in DIR, or an error occurs reading DIR, return 0.
131    FILE should already have ".~" appended to it. */
132
133 static int
134 max_backup_version (file, dir)
135      char *file, *dir;
136 {
137   DIR *dirp;
138   struct dirent *dp;
139   int highest_version;
140   int this_version;
141   int file_name_length;
142   
143   dirp = opendir (dir);
144   if (!dirp)
145     return 0;
146   
147   highest_version = 0;
148   file_name_length = strlen (file);
149
150   while ((dp = readdir (dirp)) != 0)
151     {
152       if (!REAL_DIR_ENTRY (dp) || NLENGTH (dp) <= file_name_length)
153         continue;
154       
155       this_version = version_number (file, dp->d_name, file_name_length);
156       if (this_version > highest_version)
157         highest_version = this_version;
158     }
159   if (CLOSEDIR (dirp))
160     return 0;
161   return highest_version;
162 }
163
164 /* Return a string, allocated with malloc, containing
165    "FILE.~VERSION~".  Return 0 if out of memory. */
166
167 static char *
168 make_version_name (file, version)
169      char *file;
170      int version;
171 {
172   char *backup_name;
173
174   backup_name = malloc (strlen (file) + 16);
175   if (backup_name == 0)
176     return 0;
177   sprintf (backup_name, "%s.~%d~", file, version);
178   return backup_name;
179 }
180
181 /* If BACKUP is a numbered backup of BASE, return its version number;
182    otherwise return 0.  BASE_LENGTH is the length of BASE.
183    BASE should already have ".~" appended to it. */
184
185 static int
186 version_number (base, backup, base_length)
187      char *base;
188      char *backup;
189      int base_length;
190 {
191   int version;
192   char *p;
193   
194   version = 0;
195   if (!strncmp (base, backup, base_length) && ISDIGIT (backup[base_length]))
196     {
197       for (p = &backup[base_length]; ISDIGIT (*p); ++p)
198         version = version * 10 + *p - '0';
199       if (p[0] != '~' || p[1])
200         version = 0;
201     }
202   return version;
203 }
204
205 /* Return the newly-allocated concatenation of STR1 and STR2.
206    If out of memory, return 0. */
207
208 static char *
209 concat (str1, str2)
210      char *str1, *str2;
211 {
212   char *newstr;
213   char str1_length = strlen (str1);
214
215   newstr = malloc (str1_length + strlen (str2) + 1);
216   if (newstr == 0)
217     return 0;
218   strcpy (newstr, str1);
219   strcpy (newstr + str1_length, str2);
220   return newstr;
221 }