hardlink.c revision 225736
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
42List *hardlist;		/* Record hardlink information for working files */
43char *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. */
50Node *
51lookup_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. */
116void
117update_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).  */
161List *
162list_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
219int
220compare_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
266int
267find_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