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