1 /* $NetBSD: backupfile.c,v 1.13 2003/07/30 08:51:04 itojun 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.13 2003/07/30 08:51:04 itojun 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 snprintf(backup_name, strlen(file) + 16, "%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 size_t l = strlen(str1) + strlen(str2) + 1; 171 172 newstr = xmalloc(l); 173 snprintf(newstr, l, "%s%s", str1, str2); 174 return newstr; 175 } 176 177 /* Return NAME with any leading path stripped off. */ 178 179 char * 180 basename(char *name) 181 { 182 char *base; 183 184 base = strrchr (name, '/'); 185 return base ? base + 1 : name; 186 } 187 188 /* Return the leading directories part of PATH, 189 allocated with malloc. 190 Assumes that trailing slashes have already been 191 removed. */ 192 193 static char * 194 dirname(const char *path) 195 { 196 char *newpath; 197 char *slash; 198 size_t length; /* Length of result, not including NUL. */ 199 200 slash = strrchr (path, '/'); 201 if (slash == 0) 202 { 203 /* File is in the current directory. */ 204 path = "."; 205 length = 1; 206 } 207 else 208 { 209 /* Remove any trailing slashes from result. */ 210 while (slash > path && *slash == '/') 211 --slash; 212 213 length = slash - path + 1; 214 } 215 newpath = xmalloc(length + 1); 216 strncpy(newpath, path, length); 217 newpath[length] = 0; 218 return newpath; 219 } 220 221 /* If ARG is an unambiguous match for an element of the 222 null-terminated array OPTLIST, return the index in OPTLIST 223 of the matched element, else -1 if it does not match any element 224 or -2 if it is ambiguous (is a prefix of more than one element). */ 225 226 static int 227 argmatch(char *arg, const char **optlist) 228 { 229 int i; /* Temporary index in OPTLIST. */ 230 size_t arglen; /* Length of ARG. */ 231 int matchind = -1; /* Index of first nonexact match. */ 232 int ambiguous = 0; /* If nonzero, multiple nonexact match(es). */ 233 234 arglen = strlen (arg); 235 236 /* Test all elements for either exact match or abbreviated matches. */ 237 for (i = 0; optlist[i]; i++) 238 { 239 if (!strncmp (optlist[i], arg, arglen)) 240 { 241 if (strlen (optlist[i]) == arglen) 242 /* Exact match found. */ 243 return i; 244 else if (matchind == -1) 245 /* First nonexact match found. */ 246 matchind = i; 247 else 248 /* Second nonexact match found. */ 249 ambiguous = 1; 250 } 251 } 252 if (ambiguous) 253 return -2; 254 else 255 return matchind; 256 } 257 258 /* Error reporting for argmatch. 259 KIND is a description of the type of entity that was being matched. 260 VALUE is the invalid value that was given. 261 PROBLEM is the return value from argmatch. */ 262 263 static void 264 invalid_arg(const char *kind, const char *value, int problem) 265 { 266 fprintf (stderr, "patch: "); 267 if (problem == -1) 268 fprintf (stderr, "invalid"); 269 else /* Assume -2. */ 270 fprintf (stderr, "ambiguous"); 271 fprintf (stderr, " %s `%s'\n", kind, value); 272 } 273 274 static const char *backup_args[] = 275 { 276 "never", "simple", "nil", "existing", "t", "numbered", 0 277 }; 278 279 static enum backup_type backup_types[] = 280 { 281 simple, simple, numbered_existing, numbered_existing, numbered, numbered 282 }; 283 284 /* Return the type of backup indicated by VERSION. 285 Unique abbreviations are accepted. */ 286 287 enum backup_type 288 get_version(char *version) 289 { 290 int i; 291 292 if (version == 0 || *version == 0) 293 return numbered_existing; 294 i = argmatch (version, backup_args); 295 if (i >= 0) 296 return backup_types[i]; 297 invalid_arg ("version control type", version, i); 298 exit (1); 299 /* NOTREACHED */ 300 } 301