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 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 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 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 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 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 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