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