xref: /dragonfly/contrib/cvs-1.12/src/hardlink.c (revision 73610d44)
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