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
46static void
47delhardlist (Node *p)
48{
49    if (p->data)
50	dellist ((List **)&p->data);
51}
52
53
54
55List *hardlist;		/* Record hardlink information for working files */
56char *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. */
63Node *
64lookup_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. */
127void
128update_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).  */
170List *
171list_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
224int
225compare_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
269int
270find_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