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