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