Bring cvs-1.12.9 into the CVS repository
[dragonfly.git] / contrib / cvs-1.12.9 / src / hardlink.c
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 *
49 lookup_file_by_inode (const char *filepath)
50 {
51     char *inodestr, *file;
52     struct stat sb;
53     Node *hp, *p;
54
55     /* Get file's basename, so that we can stat it. */
56     file = strrchr (filepath, '/');
57     if (file)
58         ++file;
59     else
60         file = (char *) filepath;
61
62     /* inodestr contains the hexadecimal representation of an
63        inode, so it requires two bytes of text to represent
64        each byte of the inode number. */
65     inodestr = (char *) xmalloc (2*sizeof(ino_t) + 1);
66     if( CVS_STAT( file, &sb ) < 0 )
67     {
68         if (existence_error (errno))
69         {
70             /* The file doesn't exist; we may be doing an update on a
71                file that's been removed.  A nonexistent file has no
72                link information, so return without changing hardlist. */
73             free (inodestr);
74             return NULL;
75         }
76         error (1, errno, "cannot stat %s", file);
77     }
78
79     sprintf (inodestr, "%lx", (unsigned long) sb.st_ino);
80
81     /* Find out if this inode is already in the hardlist, adding
82        a new entry to the list if not. */
83     hp = findnode (hardlist, inodestr);
84     if (hp == NULL)
85     {
86         hp = getnode ();
87         hp->type = NT_UNKNOWN;
88         hp->key = inodestr;
89         hp->data = getlist();
90         hp->delproc = dellist;
91         (void) addnode (hardlist, hp);
92     }
93     else
94     {
95         free (inodestr);
96     }
97
98     p = findnode (hp->data, filepath);
99     if (p == NULL)
100     {
101         p = getnode();
102         p->type = NT_UNKNOWN;
103         p->key = xstrdup (filepath);
104         p->data = NULL;
105         (void) addnode (hp->data, p);
106     }
107
108     return p;
109 }
110
111 /* After a file has been checked out, add a node for it to the hardlist
112    (if necessary) and mark it as checked out. */
113 void
114 update_hardlink_info (const char *file)
115 {
116     char *path;
117     Node *n;
118     struct hardlink_info *hlinfo;
119
120     if (file[0] == '/')
121     {
122         path = xstrdup (file);
123     }
124     else
125     {
126         /* file is a relative pathname; assume it's from the current
127            working directory. */
128         char *dir = xgetwd();
129         path = xmalloc (strlen(dir) + strlen(file) + 2);
130         sprintf (path, "%s/%s", dir, file);
131         free (dir);
132     }
133
134     n = lookup_file_by_inode (path);
135     if (n == NULL)
136     {
137         /* Something is *really* wrong if the file doesn't exist here;
138            update_hardlink_info should be called only when a file has
139            just been checked out to a working directory. */
140         error (1, 0, "lost hardlink info for %s", file);
141     }
142
143     if (n->data == NULL)
144         n->data = xmalloc (sizeof (struct hardlink_info));
145     hlinfo = n->data;
146     hlinfo->status = T_UPTODATE;
147     hlinfo->checked_out = 1;
148 }
149
150 /* Return a List with all the files known to be linked to FILE in
151    the working directory.  Used by special_file_mismatch, to determine
152    whether it is safe to merge two files.
153
154    FIXME: What is the memory allocation for the return value?  We seem
155    to sometimes allocate a new list (getlist() call below) and sometimes
156    return an existing list (where we return n->data).  */
157 List *
158 list_linked_files_on_disk (char *file)
159 {
160     char *inodestr, *path;
161     struct stat sb;
162     Node *n;
163
164     /* If hardlist is NULL, we have not been doing an operation that
165        would permit us to know anything about the file's hardlinks
166        (cvs update, cvs commit, etc).  Return an empty list. */
167     if (hardlist == NULL)
168         return getlist();
169
170     /* Get the full pathname of file (assuming the working directory) */
171     if (file[0] == '/')
172         path = xstrdup (file);
173     else
174     {
175         char *dir = xgetwd();
176         path = (char *) xmalloc (strlen(dir) + strlen(file) + 2);
177         sprintf (path, "%s/%s", dir, file);
178         free (dir);
179     }
180
181     /* We do an extra lookup_file here just to make sure that there
182        is a node for `path' in the hardlist.  If that were not so,
183        comparing the working directory linkage against the repository
184        linkage for a file would always fail. */
185     (void) lookup_file_by_inode (path);
186
187     if( CVS_STAT( path, &sb ) < 0 )
188         error (1, errno, "cannot stat %s", file);
189     /* inodestr contains the hexadecimal representation of an
190        inode, so it requires two bytes of text to represent
191        each byte of the inode number. */
192     inodestr = (char *) xmalloc (2*sizeof(ino_t) + 1);
193     sprintf (inodestr, "%lx", (unsigned long) sb.st_ino);
194
195     /* Make sure the files linked to this inode are sorted. */
196     n = findnode (hardlist, inodestr);
197     sortlist (n->data, fsortcmp);
198
199     free (inodestr);
200     return n->data;
201 }
202
203 /* Compare the files in the `key' fields of two lists, returning 1 if
204    the lists are equivalent and 0 otherwise.
205
206    Only the basenames of each file are compared. This is an awful hack
207    that exists because list_linked_files_on_disk returns full paths
208    and the `hardlinks' structure of a RCSVers node contains only
209    basenames.  That in turn is a result of the awful hack that only
210    basenames are stored in the RCS file.  If anyone ever solves the
211    problem of correctly managing cross-directory hardlinks, this
212    function (along with most functions in this file) must be fixed. */
213                                                       
214 int
215 compare_linkage_lists (List *links1, List *links2)
216 {
217     Node *n1, *n2;
218     char *p1, *p2;
219
220     sortlist (links1, fsortcmp);
221     sortlist (links2, fsortcmp);
222
223     n1 = links1->list->next;
224     n2 = links2->list->next;
225
226     while (n1 != links1->list && n2 != links2->list)
227     {
228         /* Get the basenames of both files. */
229         p1 = strrchr (n1->key, '/');
230         if (p1 == NULL)
231             p1 = n1->key;
232         else
233             ++p1;
234
235         p2 = strrchr (n2->key, '/');
236         if (p2 == NULL)
237             p2 = n2->key;
238         else
239             ++p2;
240
241         /* Compare the files' basenames. */
242         if (strcmp (p1, p2) != 0)
243             return 0;
244
245         n1 = n1->next;
246         n2 = n2->next;
247     }
248
249     /* At this point we should be at the end of both lists; if not,
250        one file has more links than the other, and return 1. */
251     return (n1 == links1->list && n2 == links2->list);
252 }
253
254 /* Find a checked-out file in a list of filenames.  Used by RCS_checkout
255    when checking out a new hardlinked file, to decide whether this file
256    can be linked to any others that already exist.  The return value
257    is not currently used. */
258
259 int
260 find_checkedout_proc (Node *node, void *data)
261 {
262     Node **uptodate = (Node **) data;
263     Node *link;
264     char *dir = xgetwd();
265     char *path;
266     struct hardlink_info *hlinfo;
267
268     /* If we have already found a file, don't do anything. */
269     if (*uptodate != NULL)
270         return 0;
271
272     /* Look at this file in the hardlist and see whether the checked_out
273        field is 1, meaning that it has been checked out during this CVS run. */
274     path = (char *)
275         xmalloc (strlen (dir) + strlen (node->key) + 2);
276     sprintf (path, "%s/%s", dir, node->key);
277     link = lookup_file_by_inode (path);
278     free (path);
279     free (dir);
280
281     if (link == NULL)
282     {
283         /* We haven't seen this file -- maybe it hasn't been checked
284            out yet at all. */
285         return 0;
286     }
287
288     hlinfo = link->data;
289     if (hlinfo->checked_out)
290     {
291         /* This file has been checked out recently, so it's safe to
292            link to it. */
293         *uptodate = link;
294     }
295
296     return 0;
297 }
298