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