1 /* $NetBSD: backupfile.c,v 1.4 1996/09/19 06:27:07 thorpej 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[] = "$NetBSD: backupfile.c,v 1.4 1996/09/19 06:27:07 thorpej Exp $"; 18 #endif /* not lint */ 19 20 #include <stdio.h> 21 #include <stdlib.h> 22 #include <string.h> 23 #include <ctype.h> 24 #include <sys/types.h> 25 #include "backupfile.h" 26 #include "config.h" 27 28 #ifdef DIRENT 29 #include <dirent.h> 30 #ifdef direct 31 #undef direct 32 #endif 33 #define direct dirent 34 #define NLENGTH(direct) (strlen((direct)->d_name)) 35 #else /* !DIRENT */ 36 #define NLENGTH(direct) ((direct)->d_namlen) 37 #ifdef USG 38 #ifdef SYSNDIR 39 #include <sys/ndir.h> 40 #else /* !SYSNDIR */ 41 #include <ndir.h> 42 #endif /* !SYSNDIR */ 43 #else /* !USG */ 44 #ifdef SYSDIR 45 #include <sys/dir.h> 46 #endif /* SYSDIR */ 47 #endif /* !USG */ 48 #endif /* !DIRENT */ 49 50 #ifndef isascii 51 #define ISDIGIT(c) (isdigit ((unsigned char) (c))) 52 #else 53 #define ISDIGIT(c) (isascii (c) && isdigit (c)) 54 #endif 55 56 #if defined (HAVE_UNISTD_H) 57 #include <unistd.h> 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. */ 69 enum 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. */ 73 char *simple_backup_suffix = "~"; 74 75 char *basename (); 76 char *dirname (); 77 static char *concat (); 78 char *find_backup_file_name (); 79 static char *make_version_name (); 80 static int max_backup_version (); 81 static int version_number (); 82 83 #ifndef NODIR 84 /* Return the name of the new backup file for file FILE, 85 allocated with malloc. Return 0 if out of memory. 86 FILE must not end with a '/' unless it is the root directory. 87 Do not call this function if backup_type == none. */ 88 89 char * 90 find_backup_file_name (file) 91 char *file; 92 { 93 char *dir; 94 char *base_versions; 95 int highest_backup; 96 97 if (backup_type == simple) 98 return concat (file, simple_backup_suffix); 99 base_versions = concat (basename (file), ".~"); 100 if (base_versions == 0) 101 return 0; 102 dir = dirname (file); 103 if (dir == 0) 104 { 105 free (base_versions); 106 return 0; 107 } 108 highest_backup = max_backup_version (base_versions, dir); 109 free (base_versions); 110 free (dir); 111 if (backup_type == numbered_existing && highest_backup == 0) 112 return concat (file, simple_backup_suffix); 113 return make_version_name (file, highest_backup + 1); 114 } 115 116 /* Return the number of the highest-numbered backup file for file 117 FILE in directory DIR. If there are no numbered backups 118 of FILE in DIR, or an error occurs reading DIR, return 0. 119 FILE should already have ".~" appended to it. */ 120 121 static int 122 max_backup_version (file, dir) 123 char *file, *dir; 124 { 125 DIR *dirp; 126 struct direct *dp; 127 int highest_version; 128 int this_version; 129 int file_name_length; 130 131 dirp = opendir (dir); 132 if (!dirp) 133 return 0; 134 135 highest_version = 0; 136 file_name_length = strlen (file); 137 138 while ((dp = readdir (dirp)) != 0) 139 { 140 if (!REAL_DIR_ENTRY (dp) || NLENGTH (dp) <= file_name_length) 141 continue; 142 143 this_version = version_number (file, dp->d_name, file_name_length); 144 if (this_version > highest_version) 145 highest_version = this_version; 146 } 147 closedir (dirp); 148 return highest_version; 149 } 150 151 /* Return a string, allocated with malloc, containing 152 "FILE.~VERSION~". Return 0 if out of memory. */ 153 154 static char * 155 make_version_name (file, version) 156 char *file; 157 int version; 158 { 159 char *backup_name; 160 161 backup_name = malloc (strlen (file) + 16); 162 if (backup_name == 0) 163 return 0; 164 sprintf (backup_name, "%s.~%d~", file, version); 165 return backup_name; 166 } 167 168 /* If BACKUP is a numbered backup of BASE, return its version number; 169 otherwise return 0. BASE_LENGTH is the length of BASE. 170 BASE should already have ".~" appended to it. */ 171 172 static int 173 version_number (base, backup, base_length) 174 char *base; 175 char *backup; 176 int base_length; 177 { 178 int version; 179 char *p; 180 181 version = 0; 182 if (!strncmp (base, backup, base_length) && ISDIGIT (backup[base_length])) 183 { 184 for (p = &backup[base_length]; ISDIGIT (*p); ++p) 185 version = version * 10 + *p - '0'; 186 if (p[0] != '~' || p[1]) 187 version = 0; 188 } 189 return version; 190 } 191 192 /* Return the newly-allocated concatenation of STR1 and STR2. 193 If out of memory, return 0. */ 194 195 static char * 196 concat (str1, str2) 197 char *str1, *str2; 198 { 199 char *newstr; 200 char str1_length = strlen (str1); 201 202 newstr = malloc (str1_length + strlen (str2) + 1); 203 if (newstr == 0) 204 return 0; 205 strcpy (newstr, str1); 206 strcpy (newstr + str1_length, str2); 207 return newstr; 208 } 209 210 /* Return NAME with any leading path stripped off. */ 211 212 char * 213 basename (name) 214 char *name; 215 { 216 char *base; 217 218 base = rindex (name, '/'); 219 return base ? base + 1 : name; 220 } 221 222 /* Return the leading directories part of PATH, 223 allocated with malloc. If out of memory, return 0. 224 Assumes that trailing slashes have already been 225 removed. */ 226 227 char * 228 dirname (path) 229 char *path; 230 { 231 char *newpath; 232 char *slash; 233 int length; /* Length of result, not including NUL. */ 234 235 slash = rindex (path, '/'); 236 if (slash == 0) 237 { 238 /* File is in the current directory. */ 239 path = "."; 240 length = 1; 241 } 242 else 243 { 244 /* Remove any trailing slashes from result. */ 245 while (slash > path && *slash == '/') 246 --slash; 247 248 length = slash - path + 1; 249 } 250 newpath = malloc (length + 1); 251 if (newpath == 0) 252 return 0; 253 strncpy (newpath, path, length); 254 newpath[length] = 0; 255 return newpath; 256 } 257 258 /* If ARG is an unambiguous match for an element of the 259 null-terminated array OPTLIST, return the index in OPTLIST 260 of the matched element, else -1 if it does not match any element 261 or -2 if it is ambiguous (is a prefix of more than one element). */ 262 263 int 264 argmatch (arg, optlist) 265 char *arg; 266 char **optlist; 267 { 268 int i; /* Temporary index in OPTLIST. */ 269 int arglen; /* Length of ARG. */ 270 int matchind = -1; /* Index of first nonexact match. */ 271 int ambiguous = 0; /* If nonzero, multiple nonexact match(es). */ 272 273 arglen = strlen (arg); 274 275 /* Test all elements for either exact match or abbreviated matches. */ 276 for (i = 0; optlist[i]; i++) 277 { 278 if (!strncmp (optlist[i], arg, arglen)) 279 { 280 if (strlen (optlist[i]) == arglen) 281 /* Exact match found. */ 282 return i; 283 else if (matchind == -1) 284 /* First nonexact match found. */ 285 matchind = i; 286 else 287 /* Second nonexact match found. */ 288 ambiguous = 1; 289 } 290 } 291 if (ambiguous) 292 return -2; 293 else 294 return matchind; 295 } 296 297 /* Error reporting for argmatch. 298 KIND is a description of the type of entity that was being matched. 299 VALUE is the invalid value that was given. 300 PROBLEM is the return value from argmatch. */ 301 302 void 303 invalid_arg (kind, value, problem) 304 char *kind; 305 char *value; 306 int problem; 307 { 308 fprintf (stderr, "patch: "); 309 if (problem == -1) 310 fprintf (stderr, "invalid"); 311 else /* Assume -2. */ 312 fprintf (stderr, "ambiguous"); 313 fprintf (stderr, " %s `%s'\n", kind, value); 314 } 315 316 static char *backup_args[] = 317 { 318 "never", "simple", "nil", "existing", "t", "numbered", 0 319 }; 320 321 static enum backup_type backup_types[] = 322 { 323 simple, simple, numbered_existing, numbered_existing, numbered, numbered 324 }; 325 326 /* Return the type of backup indicated by VERSION. 327 Unique abbreviations are accepted. */ 328 329 enum backup_type 330 get_version (version) 331 char *version; 332 { 333 int i; 334 335 if (version == 0 || *version == 0) 336 return numbered_existing; 337 i = argmatch (version, backup_args); 338 if (i >= 0) 339 return backup_types[i]; 340 invalid_arg ("version control type", version, i); 341 exit (1); 342 } 343 #endif /* NODIR */ 344