xref: /netbsd-src/external/gpl2/xcvs/dist/src/hardlink.c (revision 5a6c14c844c4c665da5632061aebde7bb2cb5766)
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 
46 static void
delhardlist(Node * p)47 delhardlist (Node *p)
48 {
49     if (p->data)
50 	dellist ((List **)&p->data);
51 }
52 
53 
54 
55 List *hardlist;		/* Record hardlink information for working files */
56 char *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. */
63 Node *
lookup_file_by_inode(const char * filepath)64 lookup_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. */
127 void
update_hardlink_info(const char * file)128 update_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).  */
170 List *
list_linked_files_on_disk(char * file)171 list_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 
224 int
compare_linkage_lists(List * links1,List * links2)225 compare_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 
269 int
find_checkedout_proc(Node * node,void * data)270 find_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