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