xref: /openbsd/gnu/usr.bin/cvs/src/hardlink.c (revision 36ac2e2a)
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 
40 List *hardlist;		/* Record hardlink information for working files */
41 char *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. */
48 Node *
lookup_file_by_inode(filepath)49 lookup_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) + 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, "%llx", (unsigned long 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 = NT_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 = NT_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. */
114 void
update_hardlink_info(file)115 update_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 (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).  */
159 List *
list_linked_files_on_disk(file)160 list_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 (strlen(dir) + strlen(file) + 2);
180 	sprintf (path, "%s/%s", dir, file);
181 	free (dir);
182     }
183 
184     /* We do an extra lookup_file here just to make sure that there
185        is a node for `path' in the hardlist.  If that were not so,
186        comparing the working directory linkage against the repository
187        linkage for a file would always fail. */
188     (void) lookup_file_by_inode (path);
189 
190     if (stat (path, &sb) < 0)
191 	error (1, errno, "cannot stat %s", file);
192     /* inodestr contains the hexadecimal representation of an
193        inode, so it requires two bytes of text to represent
194        each byte of the inode number. */
195     inodestr = (char *) xmalloc (2*sizeof(ino_t) + 1);
196     sprintf (inodestr, "%llx", (unsigned long long) sb.st_ino);
197 
198     /* Make sure the files linked to this inode are sorted. */
199     n = findnode (hardlist, inodestr);
200     sortlist ((List *) n->data, fsortcmp);
201 
202     free (inodestr);
203     return (List *) n->data;
204 }
205 
206 /* Compare the files in the `key' fields of two lists, returning 1 if
207    the lists are equivalent and 0 otherwise.
208 
209    Only the basenames of each file are compared. This is an awful hack
210    that exists because list_linked_files_on_disk returns full paths
211    and the `hardlinks' structure of a RCSVers node contains only
212    basenames.  That in turn is a result of the awful hack that only
213    basenames are stored in the RCS file.  If anyone ever solves the
214    problem of correctly managing cross-directory hardlinks, this
215    function (along with most functions in this file) must be fixed. */
216 
217 int
compare_linkage_lists(links1,links2)218 compare_linkage_lists (links1, links2)
219     List *links1;
220     List *links2;
221 {
222     Node *n1, *n2;
223     char *p1, *p2;
224 
225     sortlist (links1, fsortcmp);
226     sortlist (links2, fsortcmp);
227 
228     n1 = links1->list->next;
229     n2 = links2->list->next;
230 
231     while (n1 != links1->list && n2 != links2->list)
232     {
233 	/* Get the basenames of both files. */
234 	p1 = strrchr (n1->key, '/');
235 	if (p1 == NULL)
236 	    p1 = n1->key;
237 	else
238 	    ++p1;
239 
240 	p2 = strrchr (n2->key, '/');
241 	if (p2 == NULL)
242 	    p2 = n2->key;
243 	else
244 	    ++p2;
245 
246 	/* Compare the files' basenames. */
247 	if (strcmp (p1, p2) != 0)
248 	    return 0;
249 
250 	n1 = n1->next;
251 	n2 = n2->next;
252     }
253 
254     /* At this point we should be at the end of both lists; if not,
255        one file has more links than the other, and return 1. */
256     return (n1 == links1->list && n2 == links2->list);
257 }
258 
259 /* Find a checked-out file in a list of filenames.  Used by RCS_checkout
260    when checking out a new hardlinked file, to decide whether this file
261    can be linked to any others that already exist.  The return value
262    is not currently used. */
263 
264 int
find_checkedout_proc(node,data)265 find_checkedout_proc (node, data)
266     Node *node;
267     void *data;
268 {
269     Node **uptodate = (Node **) data;
270     Node *link;
271     char *dir = xgetwd();
272     char *path;
273     struct hardlink_info *hlinfo;
274 
275     /* If we have already found a file, don't do anything. */
276     if (*uptodate != NULL)
277 	return 0;
278 
279     /* Look at this file in the hardlist and see whether the checked_out
280        field is 1, meaning that it has been checked out during this CVS run. */
281     path = (char *)
282 	xmalloc (strlen (dir) + strlen (node->key) + 2);
283     sprintf (path, "%s/%s", dir, node->key);
284     link = lookup_file_by_inode (path);
285     free (path);
286     free (dir);
287 
288     if (link == NULL)
289     {
290 	/* We haven't seen this file -- maybe it hasn't been checked
291 	   out yet at all. */
292 	return 0;
293     }
294 
295     hlinfo = (struct hardlink_info *) link->data;
296     if (hlinfo->checked_out)
297     {
298 	/* This file has been checked out recently, so it's safe to
299            link to it. */
300 	*uptodate = link;
301     }
302 
303     return 0;
304 }
305 
306