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