summaryrefslogtreecommitdiff
path: root/src/hardlink.c
blob: 04af09fe8546e14ea12a742c753eb488a69b88b4 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
/* This program is free software; you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation; either version 2, or (at your option)
   any later version.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.  */

/* Collect and manage hardlink info associated with a particular file.  */

#include "cvs.h"

#ifdef PRESERVE_PERMISSIONS_SUPPORT
# include "hardlink.h"

/* The structure currently used to manage hardlink info is a list.
   Therefore, most of the functions which manipulate hardlink data
   are walklist procedures.  This is not a very efficient implementation;
   if someone decides to use a real hash table (for instance), then
   much of this code can be rewritten to be a little less arcane.

   Each element of `hardlist' represents an inode.  It is keyed on the
   inode number, and points to a list of files.  This is to make it
   easy to find out what files are linked to a given file FOO: find
   FOO's inode, look it up in hardlist, and retrieve the list of files
   associated with that inode.

   Each file node, in turn, is represented by a `hardlink_info' struct,
   which includes `status' and `links' fields.  The `status' field should
   be used by a procedure like commit_fileproc or update_fileproc to
   record each file's status; that way, after all file links have been
   recorded, CVS can check the linkage of files which are in doubt
   (i.e. T_NEEDS_MERGE files).

   TODO: a diagram of an example hardlist would help here. */

/* TODO: change this to something with a marginal degree of
   efficiency, like maybe a hash table.  Yeah. */



static void
delhardlist (Node *p)
{
    if (p->data)
	dellist ((List **)&p->data);
}



List *hardlist;		/* Record hardlink information for working files */
char *working_dir;	/* The top-level working directory, used for
			   constructing full pathnames. */

/* Return a pointer to FILEPATH's node in the hardlist.  This means
   looking up its inode, retrieving the list of files linked to that
   inode, and then looking up FILE in that list.  If the file doesn't
   seem to exist, return NULL. */
Node *
lookup_file_by_inode (const char *filepath)
{
    char *inodestr;
    const char *file;
    struct stat sb;
    Node *hp, *p;

    /* Get file's basename, so that we can stat it. */
    file = strrchr (filepath, '/');
    if (file)
	++file;
    else
	file = filepath;

    if (stat (file, &sb) < 0)
    {
	if (existence_error (errno))
	{
	    /* The file doesn't exist; we may be doing an update on a
	       file that's been removed.  A nonexistent file has no
	       link information, so return without changing hardlist. */
	    free (inodestr);
	    return NULL;
	}
	error (1, errno, "cannot stat %s", file);
    }

    /* inodestr contains the hexadecimal representation of an
       inode. */
    inodestr = Xasprintf ("%lx", (unsigned long) sb.st_ino);

    /* Find out if this inode is already in the hardlist, adding
       a new entry to the list if not. */
    hp = findnode (hardlist, inodestr);
    if (hp == NULL)
    {
	hp = getnode ();
	hp->type = NT_UNKNOWN;
	hp->key = inodestr;
	hp->data = getlist ();
	hp->delproc = delhardlist;
	(void) addnode (hardlist, hp);
    }
    else
    {
	free (inodestr);
    }

    p = findnode (hp->data, filepath);
    if (p == NULL)
    {
	p = getnode ();
	p->type = NT_UNKNOWN;
	p->key = xstrdup (filepath);
	p->data = NULL;
	(void) addnode (hp->data, p);
    }

    return p;
}

/* After a file has been checked out, add a node for it to the hardlist
   (if necessary) and mark it as checked out. */
void
update_hardlink_info (const char *file)
{
    char *path;
    Node *n;
    struct hardlink_info *hlinfo;

    if (file[0] == '/')
    {
	path = xstrdup (file);
    }
    else
    {
	/* file is a relative pathname; assume it's from the current
	   working directory. */
	char *dir = xgetcwd ();
	path = Xasprintf ("%s/%s", dir, file);
	free (dir);
    }

    n = lookup_file_by_inode (path);
    if (n == NULL)
    {
	/* Something is *really* wrong if the file doesn't exist here;
	   update_hardlink_info should be called only when a file has
	   just been checked out to a working directory. */
	error (1, 0, "lost hardlink info for %s", file);
    }

    if (n->data == NULL)
	n->data = xmalloc (sizeof (struct hardlink_info));
    hlinfo = n->data;
    hlinfo->status = T_UPTODATE;
    hlinfo->checked_out = 1;
}

/* Return a List with all the files known to be linked to FILE in
   the working directory.  Used by special_file_mismatch, to determine
   whether it is safe to merge two files.

   FIXME: What is the memory allocation for the return value?  We seem
   to sometimes allocate a new list (getlist() call below) and sometimes
   return an existing list (where we return n->data).  */
List *
list_linked_files_on_disk (char *file)
{
    char *inodestr, *path;
    struct stat sb;
    Node *n;

    /* If hardlist is NULL, we have not been doing an operation that
       would permit us to know anything about the file's hardlinks
       (cvs update, cvs commit, etc).  Return an empty list. */
    if (hardlist == NULL)
	return getlist ();

    /* Get the full pathname of file (assuming the working directory) */
    if (file[0] == '/')
	path = xstrdup (file);
    else
    {
	char *dir = xgetcwd ();
	path = Xasprintf ("%s/%s", dir, file);
	free (dir);
    }

    /* We do an extra lookup_file here just to make sure that there
       is a node for `path' in the hardlist.  If that were not so,
       comparing the working directory linkage against the repository
       linkage for a file would always fail. */
    (void) lookup_file_by_inode (path);

    if (stat (path, &sb) < 0)
	error (1, errno, "cannot stat %s", file);
    /* inodestr contains the hexadecimal representation of an
       inode. */
    inodestr = Xasprintf ("%lx", (unsigned long) sb.st_ino);

    /* Make sure the files linked to this inode are sorted. */
    n = findnode (hardlist, inodestr);
    sortlist (n->data, fsortcmp);

    free (inodestr);
    return n->data;
}

/* Compare the files in the `key' fields of two lists, returning 1 if
   the lists are equivalent and 0 otherwise.

   Only the basenames of each file are compared. This is an awful hack
   that exists because list_linked_files_on_disk returns full paths
   and the `hardlinks' structure of a RCSVers node contains only
   basenames.  That in turn is a result of the awful hack that only
   basenames are stored in the RCS file.  If anyone ever solves the
   problem of correctly managing cross-directory hardlinks, this
   function (along with most functions in this file) must be fixed. */
						      
int
compare_linkage_lists (List *links1, List *links2)
{
    Node *n1, *n2;
    char *p1, *p2;

    sortlist (links1, fsortcmp);
    sortlist (links2, fsortcmp);

    n1 = links1->list->next;
    n2 = links2->list->next;

    while (n1 != links1->list && n2 != links2->list)
    {
	/* Get the basenames of both files. */
	p1 = strrchr (n1->key, '/');
	if (p1 == NULL)
	    p1 = n1->key;
	else
	    ++p1;

	p2 = strrchr (n2->key, '/');
	if (p2 == NULL)
	    p2 = n2->key;
	else
	    ++p2;

	/* Compare the files' basenames. */
	if (strcmp (p1, p2) != 0)
	    return 0;

	n1 = n1->next;
	n2 = n2->next;
    }

    /* At this point we should be at the end of both lists; if not,
       one file has more links than the other, and return 1. */
    return (n1 == links1->list && n2 == links2->list);
}

/* Find a checked-out file in a list of filenames.  Used by RCS_checkout
   when checking out a new hardlinked file, to decide whether this file
   can be linked to any others that already exist.  The return value
   is not currently used. */

int
find_checkedout_proc (Node *node, void *data)
{
    Node **uptodate = data;
    Node *link;
    char *dir = xgetcwd ();
    char *path;
    struct hardlink_info *hlinfo;

    /* If we have already found a file, don't do anything. */
    if (*uptodate != NULL)
	return 0;

    /* Look at this file in the hardlist and see whether the checked_out
       field is 1, meaning that it has been checked out during this CVS run. */
    path = Xasprintf ("%s/%s", dir, node->key);
    link = lookup_file_by_inode (path);
    free (path);
    free (dir);

    if (link == NULL)
    {
	/* We haven't seen this file -- maybe it hasn't been checked
	   out yet at all. */
	return 0;
    }

    hlinfo = link->data;
    if (hlinfo->checked_out)
    {
	/* This file has been checked out recently, so it's safe to
           link to it. */
	*uptodate = link;
    }

    return 0;
}
#endif /* PRESERVE_PERMISSIONS_SUPPORT */