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