1cf28ed85SJohn Marino /* Detect cycles in file tree walks.
2cf28ed85SJohn Marino
3*09d4459fSDaniel Fojt Copyright (C) 2003-2006, 2009-2020 Free Software Foundation, Inc.
4cf28ed85SJohn Marino
5cf28ed85SJohn Marino Written by Jim Meyering.
6cf28ed85SJohn Marino
7cf28ed85SJohn Marino This program is free software: you can redistribute it and/or modify
8cf28ed85SJohn Marino it under the terms of the GNU General Public License as published by
9cf28ed85SJohn Marino the Free Software Foundation; either version 3 of the License, or
10cf28ed85SJohn Marino (at your option) any later version.
11cf28ed85SJohn Marino
12cf28ed85SJohn Marino This program is distributed in the hope that it will be useful,
13cf28ed85SJohn Marino but WITHOUT ANY WARRANTY; without even the implied warranty of
14cf28ed85SJohn Marino MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15cf28ed85SJohn Marino GNU General Public License for more details.
16cf28ed85SJohn Marino
17cf28ed85SJohn Marino You should have received a copy of the GNU General Public License
18*09d4459fSDaniel Fojt along with this program. If not, see <https://www.gnu.org/licenses/>. */
19cf28ed85SJohn Marino
20cf28ed85SJohn Marino #include "cycle-check.h"
21cf28ed85SJohn Marino #include "hash.h"
22cf28ed85SJohn Marino
23cf28ed85SJohn Marino /* Use each of these to map a device/inode pair to an FTSENT. */
24cf28ed85SJohn Marino struct Active_dir
25cf28ed85SJohn Marino {
26cf28ed85SJohn Marino dev_t dev;
27cf28ed85SJohn Marino ino_t ino;
28cf28ed85SJohn Marino FTSENT *fts_ent;
29cf28ed85SJohn Marino };
30cf28ed85SJohn Marino
31cf28ed85SJohn Marino static bool
AD_compare(void const * x,void const * y)32cf28ed85SJohn Marino AD_compare (void const *x, void const *y)
33cf28ed85SJohn Marino {
34cf28ed85SJohn Marino struct Active_dir const *ax = x;
35cf28ed85SJohn Marino struct Active_dir const *ay = y;
36cf28ed85SJohn Marino return ax->ino == ay->ino
37cf28ed85SJohn Marino && ax->dev == ay->dev;
38cf28ed85SJohn Marino }
39cf28ed85SJohn Marino
40cf28ed85SJohn Marino static size_t
AD_hash(void const * x,size_t table_size)41cf28ed85SJohn Marino AD_hash (void const *x, size_t table_size)
42cf28ed85SJohn Marino {
43cf28ed85SJohn Marino struct Active_dir const *ax = x;
44cf28ed85SJohn Marino return (uintmax_t) ax->ino % table_size;
45cf28ed85SJohn Marino }
46cf28ed85SJohn Marino
47cf28ed85SJohn Marino /* Set up the cycle-detection machinery. */
48cf28ed85SJohn Marino
49cf28ed85SJohn Marino static bool
setup_dir(FTS * fts)50cf28ed85SJohn Marino setup_dir (FTS *fts)
51cf28ed85SJohn Marino {
52cf28ed85SJohn Marino if (fts->fts_options & (FTS_TIGHT_CYCLE_CHECK | FTS_LOGICAL))
53cf28ed85SJohn Marino {
54cf28ed85SJohn Marino enum { HT_INITIAL_SIZE = 31 };
55cf28ed85SJohn Marino fts->fts_cycle.ht = hash_initialize (HT_INITIAL_SIZE, NULL, AD_hash,
56cf28ed85SJohn Marino AD_compare, free);
57cf28ed85SJohn Marino if (! fts->fts_cycle.ht)
58cf28ed85SJohn Marino return false;
59cf28ed85SJohn Marino }
60cf28ed85SJohn Marino else
61cf28ed85SJohn Marino {
62cf28ed85SJohn Marino fts->fts_cycle.state = malloc (sizeof *fts->fts_cycle.state);
63cf28ed85SJohn Marino if (! fts->fts_cycle.state)
64cf28ed85SJohn Marino return false;
65cf28ed85SJohn Marino cycle_check_init (fts->fts_cycle.state);
66cf28ed85SJohn Marino }
67cf28ed85SJohn Marino
68cf28ed85SJohn Marino return true;
69cf28ed85SJohn Marino }
70cf28ed85SJohn Marino
71cf28ed85SJohn Marino /* Enter a directory during a file tree walk. */
72cf28ed85SJohn Marino
73cf28ed85SJohn Marino static bool
enter_dir(FTS * fts,FTSENT * ent)74cf28ed85SJohn Marino enter_dir (FTS *fts, FTSENT *ent)
75cf28ed85SJohn Marino {
76cf28ed85SJohn Marino if (fts->fts_options & (FTS_TIGHT_CYCLE_CHECK | FTS_LOGICAL))
77cf28ed85SJohn Marino {
78cf28ed85SJohn Marino struct stat const *st = ent->fts_statp;
79cf28ed85SJohn Marino struct Active_dir *ad = malloc (sizeof *ad);
80cf28ed85SJohn Marino struct Active_dir *ad_from_table;
81cf28ed85SJohn Marino
82cf28ed85SJohn Marino if (!ad)
83cf28ed85SJohn Marino return false;
84cf28ed85SJohn Marino
85cf28ed85SJohn Marino ad->dev = st->st_dev;
86cf28ed85SJohn Marino ad->ino = st->st_ino;
87cf28ed85SJohn Marino ad->fts_ent = ent;
88cf28ed85SJohn Marino
89cf28ed85SJohn Marino /* See if we've already encountered this directory.
90cf28ed85SJohn Marino This can happen when following symlinks as well as
91cf28ed85SJohn Marino with a corrupted directory hierarchy. */
92cf28ed85SJohn Marino ad_from_table = hash_insert (fts->fts_cycle.ht, ad);
93cf28ed85SJohn Marino
94cf28ed85SJohn Marino if (ad_from_table != ad)
95cf28ed85SJohn Marino {
96cf28ed85SJohn Marino free (ad);
97cf28ed85SJohn Marino if (!ad_from_table)
98cf28ed85SJohn Marino return false;
99cf28ed85SJohn Marino
100cf28ed85SJohn Marino /* There was an entry with matching dev/inode already in the table.
101cf28ed85SJohn Marino Record the fact that we've found a cycle. */
102cf28ed85SJohn Marino ent->fts_cycle = ad_from_table->fts_ent;
103cf28ed85SJohn Marino ent->fts_info = FTS_DC;
104cf28ed85SJohn Marino }
105cf28ed85SJohn Marino }
106cf28ed85SJohn Marino else
107cf28ed85SJohn Marino {
108cf28ed85SJohn Marino if (cycle_check (fts->fts_cycle.state, ent->fts_statp))
109cf28ed85SJohn Marino {
110cf28ed85SJohn Marino /* FIXME: setting fts_cycle like this isn't proper.
111cf28ed85SJohn Marino To do what the documentation requires, we'd have to
112cf28ed85SJohn Marino go around the cycle again and find the right entry.
113cf28ed85SJohn Marino But no callers in coreutils use the fts_cycle member. */
114cf28ed85SJohn Marino ent->fts_cycle = ent;
115cf28ed85SJohn Marino ent->fts_info = FTS_DC;
116cf28ed85SJohn Marino }
117cf28ed85SJohn Marino }
118cf28ed85SJohn Marino
119cf28ed85SJohn Marino return true;
120cf28ed85SJohn Marino }
121cf28ed85SJohn Marino
122cf28ed85SJohn Marino /* Leave a directory during a file tree walk. */
123cf28ed85SJohn Marino
124cf28ed85SJohn Marino static void
leave_dir(FTS * fts,FTSENT * ent)125cf28ed85SJohn Marino leave_dir (FTS *fts, FTSENT *ent)
126cf28ed85SJohn Marino {
127cf28ed85SJohn Marino struct stat const *st = ent->fts_statp;
128cf28ed85SJohn Marino if (fts->fts_options & (FTS_TIGHT_CYCLE_CHECK | FTS_LOGICAL))
129cf28ed85SJohn Marino {
130cf28ed85SJohn Marino struct Active_dir obj;
131cf28ed85SJohn Marino void *found;
132cf28ed85SJohn Marino obj.dev = st->st_dev;
133cf28ed85SJohn Marino obj.ino = st->st_ino;
134cf28ed85SJohn Marino found = hash_delete (fts->fts_cycle.ht, &obj);
135cf28ed85SJohn Marino if (!found)
136cf28ed85SJohn Marino abort ();
137cf28ed85SJohn Marino free (found);
138cf28ed85SJohn Marino }
139cf28ed85SJohn Marino else
140cf28ed85SJohn Marino {
141cf28ed85SJohn Marino FTSENT *parent = ent->fts_parent;
142cf28ed85SJohn Marino if (parent != NULL && 0 <= parent->fts_level)
143cf28ed85SJohn Marino CYCLE_CHECK_REFLECT_CHDIR_UP (fts->fts_cycle.state,
144cf28ed85SJohn Marino *(parent->fts_statp), *st);
145cf28ed85SJohn Marino }
146cf28ed85SJohn Marino }
147cf28ed85SJohn Marino
148cf28ed85SJohn Marino /* Free any memory used for cycle detection. */
149cf28ed85SJohn Marino
150cf28ed85SJohn Marino static void
free_dir(FTS * sp)151cf28ed85SJohn Marino free_dir (FTS *sp)
152cf28ed85SJohn Marino {
153cf28ed85SJohn Marino if (sp->fts_options & (FTS_TIGHT_CYCLE_CHECK | FTS_LOGICAL))
154cf28ed85SJohn Marino {
155cf28ed85SJohn Marino if (sp->fts_cycle.ht)
156cf28ed85SJohn Marino hash_free (sp->fts_cycle.ht);
157cf28ed85SJohn Marino }
158cf28ed85SJohn Marino else
159cf28ed85SJohn Marino free (sp->fts_cycle.state);
160cf28ed85SJohn Marino }
161