hardlink.c revision 54427
1/* This program is free software; you can redistribute it and/or modify 2 it under the terms of the GNU General Public License as published by 3 the Free Software Foundation; either version 2, or (at your option) 4 any later version. 5 6 This program is distributed in the hope that it will be useful, 7 but WITHOUT ANY WARRANTY; without even the implied warranty of 8 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 9 GNU General Public License for more details. */ 10 11/* Collect and manage hardlink info associated with a particular file. */ 12 13#include "cvs.h" 14#include "hardlink.h" 15 16/* The structure currently used to manage hardlink info is a list. 17 Therefore, most of the functions which manipulate hardlink data 18 are walklist procedures. This is not a very efficient implementation; 19 if someone decides to use a real hash table (for instance), then 20 much of this code can be rewritten to be a little less arcane. 21 22 Each element of `hardlist' represents an inode. It is keyed on the 23 inode number, and points to a list of files. This is to make it 24 easy to find out what files are linked to a given file FOO: find 25 FOO's inode, look it up in hardlist, and retrieve the list of files 26 associated with that inode. 27 28 Each file node, in turn, is represented by a `hardlink_info' struct, 29 which includes `status' and `links' fields. The `status' field should 30 be used by a procedure like commit_fileproc or update_fileproc to 31 record each file's status; that way, after all file links have been 32 recorded, CVS can check the linkage of files which are in doubt 33 (i.e. T_NEEDS_MERGE files). 34 35 TODO: a diagram of an example hardlist would help here. */ 36 37/* TODO: change this to something with a marginal degree of 38 efficiency, like maybe a hash table. Yeah. */ 39 40List *hardlist; /* Record hardlink information for working files */ 41char *working_dir; /* The top-level working directory, used for 42 constructing full pathnames. */ 43 44/* Return a pointer to FILEPATH's node in the hardlist. This means 45 looking up its inode, retrieving the list of files linked to that 46 inode, and then looking up FILE in that list. If the file doesn't 47 seem to exist, return NULL. */ 48Node * 49lookup_file_by_inode (filepath) 50 const char *filepath; 51{ 52 char *inodestr, *file; 53 struct stat sb; 54 Node *hp, *p; 55 56 /* Get file's basename, so that we can stat it. */ 57 file = strrchr (filepath, '/'); 58 if (file) 59 ++file; 60 else 61 file = (char *) filepath; 62 63 /* inodestr contains the hexadecimal representation of an 64 inode, so it requires two bytes of text to represent 65 each byte of the inode number. */ 66 inodestr = (char *) xmalloc (2*sizeof(ino_t)*sizeof(char) + 1); 67 if (stat (file, &sb) < 0) 68 { 69 if (existence_error (errno)) 70 { 71 /* The file doesn't exist; we may be doing an update on a 72 file that's been removed. A nonexistent file has no 73 link information, so return without changing hardlist. */ 74 free (inodestr); 75 return NULL; 76 } 77 error (1, errno, "cannot stat %s", file); 78 } 79 80 sprintf (inodestr, "%lx", (unsigned long) sb.st_ino); 81 82 /* Find out if this inode is already in the hardlist, adding 83 a new entry to the list if not. */ 84 hp = findnode (hardlist, inodestr); 85 if (hp == NULL) 86 { 87 hp = getnode (); 88 hp->type = UNKNOWN; 89 hp->key = inodestr; 90 hp->data = (char *) getlist(); 91 hp->delproc = dellist; 92 (void) addnode (hardlist, hp); 93 } 94 else 95 { 96 free (inodestr); 97 } 98 99 p = findnode ((List *) hp->data, filepath); 100 if (p == NULL) 101 { 102 p = getnode(); 103 p->type = UNKNOWN; 104 p->key = xstrdup (filepath); 105 p->data = NULL; 106 (void) addnode ((List *) hp->data, p); 107 } 108 109 return p; 110} 111 112/* After a file has been checked out, add a node for it to the hardlist 113 (if necessary) and mark it as checked out. */ 114void 115update_hardlink_info (file) 116 const char *file; 117{ 118 char *path; 119 Node *n; 120 struct hardlink_info *hlinfo; 121 122 if (file[0] == '/') 123 { 124 path = xstrdup (file); 125 } 126 else 127 { 128 /* file is a relative pathname; assume it's from the current 129 working directory. */ 130 char *dir = xgetwd(); 131 path = xmalloc (sizeof(char) * (strlen(dir) + strlen(file) + 2)); 132 sprintf (path, "%s/%s", dir, file); 133 free (dir); 134 } 135 136 n = lookup_file_by_inode (path); 137 if (n == NULL) 138 { 139 /* Something is *really* wrong if the file doesn't exist here; 140 update_hardlink_info should be called only when a file has 141 just been checked out to a working directory. */ 142 error (1, 0, "lost hardlink info for %s", file); 143 } 144 145 if (n->data == NULL) 146 n->data = (char *) xmalloc (sizeof (struct hardlink_info)); 147 hlinfo = (struct hardlink_info *) n->data; 148 hlinfo->status = T_UPTODATE; 149 hlinfo->checked_out = 1; 150} 151 152/* Return a List with all the files known to be linked to FILE in 153 the working directory. Used by special_file_mismatch, to determine 154 whether it is safe to merge two files. 155 156 FIXME: What is the memory allocation for the return value? We seem 157 to sometimes allocate a new list (getlist() call below) and sometimes 158 return an existing list (where we return n->data). */ 159List * 160list_linked_files_on_disk (file) 161 char *file; 162{ 163 char *inodestr, *path; 164 struct stat sb; 165 Node *n; 166 167 /* If hardlist is NULL, we have not been doing an operation that 168 would permit us to know anything about the file's hardlinks 169 (cvs update, cvs commit, etc). Return an empty list. */ 170 if (hardlist == NULL) 171 return getlist(); 172 173 /* Get the full pathname of file (assuming the working directory) */ 174 if (file[0] == '/') 175 path = xstrdup (file); 176 else 177 { 178 char *dir = xgetwd(); 179 path = (char *) xmalloc (sizeof(char) * 180 (strlen(dir) + strlen(file) + 2)); 181 sprintf (path, "%s/%s", dir, file); 182 free (dir); 183 } 184 185 /* We do an extra lookup_file here just to make sure that there 186 is a node for `path' in the hardlist. If that were not so, 187 comparing the working directory linkage against the repository 188 linkage for a file would always fail. */ 189 (void) lookup_file_by_inode (path); 190 191 if (stat (path, &sb) < 0) 192 error (1, errno, "cannot stat %s", file); 193 /* inodestr contains the hexadecimal representation of an 194 inode, so it requires two bytes of text to represent 195 each byte of the inode number. */ 196 inodestr = (char *) xmalloc (2*sizeof(ino_t)*sizeof(char) + 1); 197 sprintf (inodestr, "%lx", (unsigned long) sb.st_ino); 198 199 /* Make sure the files linked to this inode are sorted. */ 200 n = findnode (hardlist, inodestr); 201 sortlist ((List *) n->data, fsortcmp); 202 203 free (inodestr); 204 return (List *) n->data; 205} 206 207/* Compare the files in the `key' fields of two lists, returning 1 if 208 the lists are equivalent and 0 otherwise. 209 210 Only the basenames of each file are compared. This is an awful hack 211 that exists because list_linked_files_on_disk returns full paths 212 and the `hardlinks' structure of a RCSVers node contains only 213 basenames. That in turn is a result of the awful hack that only 214 basenames are stored in the RCS file. If anyone ever solves the 215 problem of correctly managing cross-directory hardlinks, this 216 function (along with most functions in this file) must be fixed. */ 217 218int 219compare_linkage_lists (links1, links2) 220 List *links1; 221 List *links2; 222{ 223 Node *n1, *n2; 224 char *p1, *p2; 225 226 sortlist (links1, fsortcmp); 227 sortlist (links2, fsortcmp); 228 229 n1 = links1->list->next; 230 n2 = links2->list->next; 231 232 while (n1 != links1->list && n2 != links2->list) 233 { 234 /* Get the basenames of both files. */ 235 p1 = strrchr (n1->key, '/'); 236 if (p1 == NULL) 237 p1 = n1->key; 238 else 239 ++p1; 240 241 p2 = strrchr (n2->key, '/'); 242 if (p2 == NULL) 243 p2 = n2->key; 244 else 245 ++p2; 246 247 /* Compare the files' basenames. */ 248 if (strcmp (p1, p2) != 0) 249 return 0; 250 251 n1 = n1->next; 252 n2 = n2->next; 253 } 254 255 /* At this point we should be at the end of both lists; if not, 256 one file has more links than the other, and return 1. */ 257 return (n1 == links1->list && n2 == links2->list); 258} 259 260/* Find a checked-out file in a list of filenames. Used by RCS_checkout 261 when checking out a new hardlinked file, to decide whether this file 262 can be linked to any others that already exist. The return value 263 is not currently used. */ 264 265int 266find_checkedout_proc (node, data) 267 Node *node; 268 void *data; 269{ 270 Node **uptodate = (Node **) data; 271 Node *link; 272 char *dir = xgetwd(); 273 char *path; 274 struct hardlink_info *hlinfo; 275 276 /* If we have already found a file, don't do anything. */ 277 if (*uptodate != NULL) 278 return 0; 279 280 /* Look at this file in the hardlist and see whether the checked_out 281 field is 1, meaning that it has been checked out during this CVS run. */ 282 path = (char *) 283 xmalloc (sizeof(char) * (strlen (dir) + strlen (node->key) + 2)); 284 sprintf (path, "%s/%s", dir, node->key); 285 link = lookup_file_by_inode (path); 286 free (path); 287 free (dir); 288 289 if (link == NULL) 290 { 291 /* We haven't seen this file -- maybe it hasn't been checked 292 out yet at all. */ 293 return 0; 294 } 295 296 hlinfo = (struct hardlink_info *) link->data; 297 if (hlinfo->checked_out) 298 { 299 /* This file has been checked out recently, so it's safe to 300 link to it. */ 301 *uptodate = link; 302 } 303 304 return 0; 305} 306 307