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